refactor(planning): remove the original decision trace
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 1m15s
CI / test (push) Successful in 3m18s
CI / benchmark (push) Failing after 5m53s
CI / build (push) Successful in 1m23s
CI / security (push) Successful in 7m6s
CI / policy (pull_request) Successful in 7s
CI / validate (pull_request) Successful in 1m9s
CI / build (pull_request) Successful in 43s
CI / test (pull_request) Successful in 3m19s
CI / images (push) Successful in 3m7s
CI / required (push) Successful in 2s
CI / benchmark (pull_request) Failing after 5m28s
CI / security (pull_request) Successful in 6m37s
CI / images (pull_request) Successful in 3m18s
CI / required (pull_request) Successful in 1s

This commit is contained in:
nessar committed 2026-10-15 20:42:33 +02:00
1 parent ea4256107f
commit d9d9befa12
43 files changed
+729 -8880

No files matched your search

@@ -533,476 +533,8 @@
{{ tr().planning.explanationError }} {{ explanationError()!.message }}
</p>
}
@if (planningOutcome() && planningOutcome()?.kind !== 'InvalidInput') {
<details class="trace-explorer" [open]="traceOpen()" (toggle)="toggleTrace($event)">
<summary>
<mat-icon aria-hidden="true">account_tree</mat-icon>
<span
>{{ tr().planning.traceTitle
}}<small>{{ tr().planning.traceHelp }}</small></span
>
<mat-icon class="disclosure-chevron" aria-hidden="true">expand_more</mat-icon>
</summary>
@if (traceLoading()) {
<p role="status">{{ tr().planning.traceLoading }}</p>
} @else if (planningTrace(); as trace) {
@if (trace.error) {
<p class="inline-error" role="alert">{{ trace.error.message }}</p>
}
<div class="trace-layout">
<nav class="trace-phases" [attr.aria-label]="tr().planning.phases">
<div class="trace-phase-stepper">
<button
type="button"
data-phase-step="first"
[attr.aria-disabled]="tracePhaseIndex() === 0"
[attr.aria-label]="tr().planning.firstPhase"
(click)="stepTracePhase('first')"
>
<mat-icon aria-hidden="true">first_page</mat-icon>
</button>
<button
type="button"
data-phase-step="previous"
[attr.aria-disabled]="tracePhaseIndex() === 0"
[attr.aria-label]="tr().planning.previousPhase"
(click)="stepTracePhase('previous')"
>
<mat-icon aria-hidden="true">chevron_left</mat-icon>
</button>
<output
aria-current="step"
[attr.aria-label]="tracePhaseAnnouncement()"
>{{ tracePhaseIndex() + 1 }} {{ tr().planning.of }}
{{ trace.phases.length }}</output
>
<button
type="button"
data-phase-step="next"
[attr.aria-disabled]="
tracePhaseIndex() === trace.phases.length - 1
"
[attr.aria-label]="tr().planning.nextPhase"
(click)="stepTracePhase('next')"
>
<mat-icon aria-hidden="true">chevron_right</mat-icon>
</button>
<button
type="button"
data-phase-step="last"
[attr.aria-disabled]="
tracePhaseIndex() === trace.phases.length - 1
"
[attr.aria-label]="tr().planning.lastPhase"
(click)="stepTracePhase('last')"
>
<mat-icon aria-hidden="true">last_page</mat-icon>
</button>
</div>
@for (phase of trace.phases; track phase.id; let index = $index) {
<button
type="button"
[class.active]="index === tracePhaseIndex()"
[attr.aria-pressed]="index === tracePhaseIndex()"
(click)="selectTracePhase(index)"
>
<span>{{ phaseLabel(phase.name) }}</span>
<small>{{ tracePhaseStatusLabel(phase.status) }}</small>
</button>
}
</nav>
@if (selectedTracePhase(); as phase) {
<section class="trace-decisions" aria-labelledby="trace-phase-title">
<h3 id="trace-phase-title">{{ phaseLabel(phase.name) }}</h3>
@if (phase.decisions.length) {
<nav [attr.aria-label]="tr().planning.decisions">
<button
type="button"
[attr.aria-disabled]="traceDecisionIndex() === 0"
[attr.aria-label]="tr().planning.firstDecision"
(click)="stepTraceDecision('first')"
>
<mat-icon aria-hidden="true">first_page</mat-icon>
</button>
<button
type="button"
[attr.aria-disabled]="traceDecisionIndex() === 0"
[attr.aria-label]="tr().planning.previousDecision"
(click)="stepTraceDecision('previous')"
>
<mat-icon aria-hidden="true">chevron_left</mat-icon>
</button>
<output
aria-current="step"
[attr.aria-label]="traceDecisionAnnouncement()"
>{{ tr().planning.decision }}
{{ traceDecisionIndex() + 1 }} {{ tr().planning.of }}
{{ phase.decisions.length }}</output
>
<button
type="button"
[attr.aria-disabled]="
traceDecisionIndex() === phase.decisions.length - 1
"
[attr.aria-label]="tr().planning.nextDecision"
(click)="stepTraceDecision('next')"
>
<mat-icon aria-hidden="true">chevron_right</mat-icon>
</button>
<button
type="button"
[attr.aria-disabled]="
traceDecisionIndex() === phase.decisions.length - 1
"
[attr.aria-label]="tr().planning.lastDecision"
(click)="stepTraceDecision('last')"
>
<mat-icon aria-hidden="true">last_page</mat-icon>
</button>
</nav>
@if (selectedTraceDecision(); as decision) {
<div
class="trace-toolbar"
role="group"
[attr.aria-label]="tr().planning.replay"
>
<button
type="button"
[attr.aria-pressed]="
traceReplay() && traceMoment() === 'before'
"
(click)="showTraceMoment('before')"
>
{{ tr().planning.before }}
</button>
<button
type="button"
[attr.aria-pressed]="
traceReplay() && traceMoment() === 'after'
"
(click)="showTraceMoment('after')"
>
{{ tr().planning.after }}
</button>
<button
type="button"
[attr.aria-pressed]="!traceReplay()"
(click)="showFinalPlan()"
>
{{ tr().planning.finalPlan }}
</button>
</div>
@if (historicalEligibilityLoading()) {
<p role="status">
{{ tr().planning.historicalEligibilityLoading }}
</p>
}
@if (historicalEligibilityError(); as error) {
<p role="alert">
{{ tr().planning.historicalEligibilityError }}
{{ error }}
<button
type="button"
(click)="retryHistoricalEligibility()"
>
{{ tr().planning.retryHistoricalEligibility }}
</button>
</p>
}
<p>
<strong>{{ tr().planning.dates }}:</strong>
{{
decision.dates.length
? decision.dates.join(', ')
: tr().planning.none
}}
</p>
@if (decision.reason) {
<p>
<strong>{{ tr().planning.reason }}:</strong>
{{ decision.reason.message }}
</p>
}
<h4>{{ tr().planning.candidates }}</h4>
@if (decision.candidates.length > 50) {
<nav
class="candidate-pagination"
[attr.aria-label]="tr().planning.candidates"
>
<button
type="button"
[attr.aria-disabled]="
traceCandidatePage() === 0
"
[attr.aria-label]="
tr().planning.previousCandidates
"
(click)="stepTraceCandidatePage(-1)"
>
<mat-icon aria-hidden="true"
>chevron_left</mat-icon
>
</button>
<output
>{{ tr().planning.page }}
{{ traceCandidatePage() + 1 }}
{{ tr().planning.of }}
{{ traceCandidatePages() }}</output
>
<button
type="button"
[attr.aria-disabled]="
traceCandidatePage() ===
traceCandidatePages() - 1
"
[attr.aria-label]="tr().planning.nextCandidates"
(click)="stepTraceCandidatePage(1)"
>
<mat-icon aria-hidden="true"
>chevron_right</mat-icon
>
</button>
</nav>
}
<div class="candidate-list">
@for (
candidate of visibleTraceCandidates();
track $index
) {
<details>
<summary>
<span
class="status-symbol"
aria-hidden="true"
>{{
candidate.status === 'chosen'
? '✓'
: candidate.status ===
'ineligible'
? '×'
: '•'
}}</span
>
{{ traceStatusLabel(candidate.status) }} ·
@if (
candidate.sourceDate &&
candidate.sourceDate !== candidate.date
) {
{{ candidate.sourceDate }} →
}
{{
candidate.dates?.join(', ') ||
candidate.date ||
tr().planning.none
}}
</summary>
@if (candidate.reason) {
<p>{{ candidate.reason.message }}</p>
}
@if (candidate.technicalComparison?.length) {
<section class="technical-comparison">
<h5>
{{
tr().planning.details
.traceStageTitle
}}
</h5>
<p class="calculation-intro">
{{
tr().planning.details
.traceStageHelp
}}
</p>
<table class="impact-table">
<thead>
<tr>
<th scope="col">
{{
tr().planning
.explanation
.criterion
}}
</th>
<th scope="col">
{{
tr().planning.before
}}
</th>
<th scope="col">
{{
tr().planning.after
}}
</th>
</tr>
</thead>
<tbody>
@for (
comparator of candidate.technicalComparison;
track $index
) {
<tr
[class.decisive-row]="
comparator.name ===
candidate.firstDecisiveComparator
"
>
<th scope="row">
<code>{{
comparator.name
}}</code>
<small>{{
comparator.direction ===
'maximize'
? tr()
.planning
.details
.higher
: tr()
.planning
.details
.lower
}}</small>
@if (
comparator.name ===
candidate.firstDecisiveComparator
) {
<span
class="decisive-label"
>{{
tr()
.planning
.details
.firstDifference
}}</span
>
}
</th>
<td>
{{
comparator.before
}}
</td>
<td>
{{
comparator.after
}}
</td>
</tr>
}
</tbody>
</table>
</section>
}
@if (candidate.comparison; as comparison) {
<div class="calculation-content">
<ng-container
[ngTemplateOutlet]="
calculationTable
"
[ngTemplateOutletContext]="{
comparison,
beforeLabel:
tr().planning.before,
afterLabel: tr().planning.after,
audit: comparison,
traceEffect:
!!candidate
.technicalComparison
?.length,
}"
/>
</div>
}
@if (candidate.placement) {
<button
type="button"
(click)="
analyzeTraceCandidate(candidate)
"
>
{{ tr().planning.compareAlternatives }}
</button>
}
</details>
}
</div>
<h4>{{ tr().planning.deltas }}</h4>
<ul>
@for (delta of decision.deltas; track $index) {
<li>
<strong>{{
formatIdentifier(delta.kind)
}}</strong>
@if (delta.fromDate || delta.date) {
· {{ delta.fromDate || delta.date }}
}
@if (delta.toDate) {
→ {{ delta.toDate }}
}
@if (delta.balanceId) {
· {{ delta.balanceId }}
}
@if (delta.remainingAfter !== undefined) {
· {{ delta.remainingBefore }} →
{{ delta.remainingAfter }}
}
</li>
}
</ul>
@if (traceState(); as state) {
<p class="trace-state-summary" role="status">
{{
traceMoment() === 'before'
? tr().planning.before
: tr().planning.after
}}: {{ state.placements.length }}
{{ tr().planning.placements }},
{{ state.earnedActivations.length }}
{{ tr().planning.activations }}
@for (balance of state.balances; track balance.id) {
<span class="trace-state-balance"
>{{ balance.id }}: {{ balance.remaining }}
{{ tr().planning.balanceRemaining }}
<small
>{{ tr().planning.details.available }}
{{ displayDate(balance.availableFrom) }}
·
{{ tr().planning.details.expires }}
{{
displayDate(balance.expiryDate)
}}</small
></span
>
}
</p>
}
}
} @else {
<p>{{ tr().planning.noDecisions }}</p>
}
</section>
}
</div>
} @else if (traceResolved()) {
<p role="status">{{ tr().planning.traceUnavailable }}</p>
}
</details>
}
</div>
@if (traceReplay()) {
<div class="replay-notice" role="status">
<span>{{ tr().planning.replayNotice }}</span>
<button type="button" class="quiet-button" (click)="showFinalPlan()">
{{ tr().planning.finalPlan }}
</button>
</div>
}
<p class="calendar-help">
<mat-icon aria-hidden="true">touch_app</mat-icon
>{{ declarationEntry() ? tr().bookedDays.help : tr().planning.calendarHelp }}
@@ -1037,7 +569,7 @@
<span class="legend-swatch today" aria-hidden="true"></span
>{{ tr().dayTypes.today }}
</li>
@if (planDetailsOpen() || traceReplay()) {
@if (planDetailsOpen()) {
<li>
<span class="legend-swatch current" aria-hidden="true"></span
>{{ tr().planning.currentPlacement }}
@@ -1049,62 +581,6 @@
>{{ tr().planning.explainedPeriod }}
</li>
}
@if (traceReplay() && counterfactual()) {
<li>
<span class="legend-symbol hypothetical-symbol" aria-hidden="true">~✓</span
>{{ tr().planning.eligible }}
</li>
<li>
<span class="legend-symbol hypothetical-symbol" aria-hidden="true">~×</span
>{{ tr().planning.ineligible }}
</li>
}
@if (traceReplay()) {
<li>
<span class="legend-symbol" aria-hidden="true">✓</span
>{{ traceStatusLabel('chosen') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">−</span
>{{ traceStatusLabel('evaluated-rejected') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">⌁</span
>{{ traceStatusLabel('pruned') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">?</span
>{{ traceStatusLabel('not-examined') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">×</span
>{{ traceStatusLabel('ineligible') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">+</span
>{{ traceChangeLabel('added') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">←</span
>{{ traceChangeLabel('moved-from') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">→</span
>{{ traceChangeLabel('moved-to') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">−</span
>{{ traceChangeLabel('removed') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">≈</span
>{{ traceChangeLabel('shared') }}
</li>
<li>
<span class="legend-symbol" aria-hidden="true">=</span
>{{ traceChangeLabel('unchanged') }}
</li>
}
</ul>
</div>
@@ -1129,7 +605,6 @@
[declarationPreview]="declarationPreview()"
[currentDate]="declarationEntry() ? null : currentPlacementDate()"
[explainedPeriod]="declarationEntry() ? undefined : explainedPeriod()"
[dateStatuses]="!declarationEntry() && traceReplay() ? dateStatuses() : {}"
(dateSelected)="!declarationEntry() && selectCalendarDate($event)"
></app-calendar-month>
</mat-card-content>
@@ -1144,22 +619,13 @@
let-beforeLabel="beforeLabel"
let-afterLabel="afterLabel"
let-audit="audit"
let-traceEffect="traceEffect"
>
<section class="criteria-detail">
<h4>
{{
traceEffect
? tr().planning.details.traceImpactTitle
: tr().planning.details.criteria
}}
{{ tr().planning.details.criteria }}
</h4>
<p class="calculation-intro">
{{
traceEffect
? tr().planning.details.traceImpactHelp
: tr().planning.details.criteriaHelp
}}
{{ tr().planning.details.criteriaHelp }}
</p>
<table class="impact-table criteria-table">
<thead>
@@ -1178,7 +644,7 @@
@if (objective.numeric) {
<tr
[class.decisive-row]="
!traceEffect && objective.id === comparison.firstDecisiveObjective
objective.id === comparison.firstDecisiveObjective
"
>
<th scope="row">
@@ -1191,10 +657,7 @@
? tr().planning.details.higher
: tr().planning.details.lower
}}</small>
@if (
!traceEffect &&
objective.id === comparison.firstDecisiveObjective
) {
@if (objective.id === comparison.firstDecisiveObjective) {
<span class="decisive-label">{{
tr().planning.details.firstDifference
}}</span>
@@ -1218,10 +681,7 @@
@if (!objective.numeric) {
<li>
{{ objective.label }}
@if (
!traceEffect &&
objective.id === comparison.firstDecisiveObjective
) {
@if (objective.id === comparison.firstDecisiveObjective) {
<span class="decisive-label">{{
tr().planning.details.firstDifference
}}</span>
@@ -1347,11 +807,7 @@
{{ tr().planning.comparison.title }}
</h2>
<p id="comparison-intro">
{{
traceReplay()
? tr().planning.explanation.traceHelp
: tr().planning.explanation.moveHelp
}}
{{ tr().planning.explanation.moveHelp }}
</p>
</div>
<button
@@ -1376,11 +832,7 @@
} @else if (counterfactual(); as analysis) {
<div class="comparison-date-pair">
<div class="original-date">
<span>{{
traceReplay()
? tr().planning.comparison.candidate
: tr().planning.explanation.plannedDate
}}</span>
<span>{{ tr().planning.explanation.plannedDate }}</span>
<strong>{{ displayDate(analysis.placement.date) }}</strong>
</div>
<mat-icon aria-hidden="true">arrow_forward</mat-icon>
@@ -1469,7 +921,7 @@
}
</div>
</div>
} @else if (!traceReplay() && result.date === analysis.placement.date) {
} @else if (result.date === analysis.placement.date) {
<p class="comparison-empty">{{ tr().planning.explanation.sameDate }}</p>
} @else {
<div class="comparison-result-heading">
@@ -1488,20 +940,10 @@
{{ tr().planning.explanation.criterion }}
</th>
<th scope="col">
{{
traceReplay()
? tr().planning.explanation
.beforeDecision
: tr().planning.explanation.currentPlan
}}
{{ tr().planning.explanation.currentPlan }}
</th>
<th scope="col">
{{
traceReplay()
? tr().planning.explanation
.withCandidate
: tr().planning.explanation.alternative
}}
{{ tr().planning.explanation.alternative }}
</th>
</tr>
</thead>
@@ -1529,34 +971,14 @@
<details class="technical-details date-calculation-details">
<summary>{{ tr().planning.technicalDetails }}</summary>
<div class="calculation-content">
<p>
<strong>{{ tr().planning.historical }}:</strong>
{{
dateStatuses()[result.date]?.label ??
traceStatusLabel(
result.historicalStatus ?? 'not-examined'
)
}}
</p>
@if (dateStatuses()[result.date]?.reason; as historicalReason) {
<p>
<strong>{{ tr().planning.reason }}:</strong>
{{ historicalReason }}
</p>
}
@if (dateComparison(); as comparison) {
<ng-container
[ngTemplateOutlet]="calculationTable"
[ngTemplateOutletContext]="{
comparison,
beforeLabel: traceReplay()
? tr().planning.explanation.beforeDecision
: tr().planning.explanation.currentPlan,
afterLabel: traceReplay()
? tr().planning.explanation.withCandidate
: tr().planning.explanation.alternative,
beforeLabel: tr().planning.explanation.currentPlan,
afterLabel: tr().planning.explanation.alternative,
audit: result,
traceEffect: traceReplay(),
}"
/>
} @else {
@@ -1583,9 +1005,7 @@
{{
counterfactualError()
? tr().planning.retryCounterfactual
: traceReplay()
? tr().planning.comparison.backToTrace
: tr().planning.comparison.backToPlan
: tr().planning.comparison.backToPlan
}}
</button>
</footer>