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

1from __future__ import annotations 

2 

3from collections.abc import Iterable 

4import uuid 

5 

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 

10 

11 

12def schedule_scenario_inputs_changed(*, scenario_ids: Iterable[int]) -> int: 

13 """Mark economics runs stale when selected production schedules change.""" 

14 

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 ) 

21 

22 

23def schedule_scenario_configuration_changed(*, scenario_ids: Iterable[int]) -> int: 

24 """Mark economics runs stale when selected schedule scenario timing changes.""" 

25 

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 ) 

32 

33 

34def schedule_scenario_solve_started(*, scenario_id: int) -> int: 

35 """Advance solved-output identity and stale dependent runs before an MSS solve.""" 

36 

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 ) 

43 

44 

45def schedule_scenario_solve_completed(*, scenario_id: int) -> int: 

46 """Advance solved-output identity after all MSS child writes are terminal.""" 

47 

48 return _advance_schedule_revisions([scenario_id]) 

49 

50 

51def _advance_schedule_revisions(scenario_ids: Iterable[int]) -> int: 

52 """Assign a new O(1) schedule generation to every changed scenario.""" 

53 

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 ) 

60 

61 

62def _scenario_id_set(scenario_ids: Iterable[int]) -> set[int]: 

63 """Materialize valid scenario identifiers once for revision and stale updates.""" 

64 

65 return {scenario_id for scenario_id in scenario_ids if scenario_id is not None} 

66 

67 

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.""" 

70 

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 

74 

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