Coverage for backend/django/Economics/scheduling/events.py: 91%
39 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1from __future__ import annotations
3from collections.abc import Iterable
4import uuid
6from Economics.results.services.lifecycle.runs import mark_result_runs_stale_for_study
7from Economics.shared.choices import EconomicsScheduleMode
8from Economics.studies.models import EconomicsStudy
9from core.auxiliary.models.Scenario import Scenario
12def schedule_scenario_inputs_changed(*, scenario_ids: Iterable[int]) -> int:
13 """Mark economics runs stale when selected production schedules change."""
15 scenario_id_set = _scenario_id_set(scenario_ids)
16 _advance_schedule_revisions(scenario_id_set)
17 return _mark_schedule_scenario_studies_stale(
18 scenario_ids=scenario_id_set,
19 reason="economics_schedule_input_changed",
20 )
23def schedule_scenario_configuration_changed(*, scenario_ids: Iterable[int]) -> int:
24 """Mark economics runs stale when selected schedule scenario timing changes."""
26 scenario_id_set = _scenario_id_set(scenario_ids)
27 _advance_schedule_revisions(scenario_id_set)
28 return _mark_schedule_scenario_studies_stale(
29 scenario_ids=scenario_id_set,
30 reason="economics_schedule_configuration_changed",
31 )
34def schedule_scenario_solve_started(*, scenario_id: int) -> int:
35 """Advance solved-output identity and stale dependent runs before an MSS solve."""
37 scenario_ids = [scenario_id]
38 _advance_schedule_revisions(scenario_ids)
39 return _mark_schedule_scenario_studies_stale(
40 scenario_ids=scenario_ids,
41 reason="economics_schedule_solve_started",
42 )
45def schedule_scenario_solve_completed(*, scenario_id: int) -> int:
46 """Advance solved-output identity after all MSS child writes are terminal."""
48 return _advance_schedule_revisions([scenario_id])
51def _advance_schedule_revisions(scenario_ids: Iterable[int]) -> int:
52 """Assign a new O(1) schedule generation to every changed scenario."""
54 scenario_id_set = _scenario_id_set(scenario_ids)
55 if not scenario_id_set: 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true
56 return 0
57 return Scenario.objects.filter(pk__in=scenario_id_set).update(
58 economics_schedule_revision=uuid.uuid4()
59 )
62def _scenario_id_set(scenario_ids: Iterable[int]) -> set[int]:
63 """Materialize valid scenario identifiers once for revision and stale updates."""
65 return {scenario_id for scenario_id in scenario_ids if scenario_id is not None}
68def _mark_schedule_scenario_studies_stale(*, scenario_ids: Iterable[int], reason: str) -> int:
69 """Apply one stale event to all studies that depend on the changed scenarios."""
71 scenario_id_set = _scenario_id_set(scenario_ids)
72 if not scenario_id_set: 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true
73 return 0
75 stale_count = 0
76 studies = list(EconomicsStudy.objects.filter(
77 schedule_mode=EconomicsScheduleMode.SCENARIO,
78 schedule_scenario_id__in=scenario_id_set,
79 ))
80 composite_studies = EconomicsStudy.objects.filter(
81 schedule_mode=EconomicsScheduleMode.COMPOSITE,
82 schedule_plan__rules__source_scenario_id__in=scenario_id_set,
83 ).distinct()
84 studies.extend(composite_studies)
85 for study in studies:
86 stale_count += mark_result_runs_stale_for_study(
87 study=study,
88 reason=reason,
89 requires_solve=True,
90 )
91 return stale_count