feat(planning): make optimization rules composable

Evaluate explicit, ordered optimization configurations through one shared
planner model. Add six editable presets, durable one-time legacy migration,
and consistent completed and cancelled plan provenance.

Include functional regressions and the agreed trace-removal and benchmark
follow-up specs. Performance acceptance remains in #78 under unchanged gates.

Refs: #70, #71, #72, #73, #74, #75, #76, #77, #78
This commit is contained in:
nessar committed 2026-10-10 16:07:34 +02:00
1 parent 209bf05f36
commit 6811142299
45 files changed
+8795 -3075

No files matched your search

@@ -1536,6 +1536,24 @@ export class CalendarYear {
}
protected objectiveCopy(id: string) {
if (id.startsWith('optimization:')) {
const ruleId = id.slice('optimization:'.length);
const rule = this.activeCalculation?.snapshot.rules.find(
(candidate) => candidate.id === ruleId,
);
const copy = this.tr().planning.explanation.objectives['optimization'];
return {
...copy,
label: copy.label
.replace('{rule}', this.ruleLabel(ruleId))
.replace(
'{summary}',
rule && 'configuration' in rule
? this.translationService.optimizationSummary(rule.configuration)
: '',
),
};
}
const key = id.startsWith('preference:') ? 'preference' : id;
const copy = this.tr().planning.explanation.objectives[key];
const rule =
@@ -1645,7 +1663,7 @@ export class CalendarYear {
if (stage === 'validation') return 5;
if (stage === 'obligations') return 20;
if (stage === 'at-risk') return 35;
if (stage.startsWith('preference:')) return 50;
if (stage.startsWith('preference:') || stage.startsWith('optimization:')) return 50;
if (stage === 'holiday-week-value') return 58;
if (stage === 'non-working-day-gain') return 65;
if (stage === 'longest-work-sequence') return 72;