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:
1 parent
209bf05f36
commit
6811142299
45 files changed
+8795
-3075
No files matched your search
@@ -3,6 +3,16 @@ import { Injectable, inject, Injector, computed } from '@angular/core';
|
||||
import { storedSignal } from '../shared/utils/stored-signal';
|
||||
import type { PlanOutcome, RemainingBalance } from './leave-planner.service';
|
||||
import { addPlanningDays } from './planning-date';
|
||||
import {
|
||||
optimizationUnit,
|
||||
validateOptimizationConfiguration,
|
||||
type OptimizationConfiguration,
|
||||
type OptimizationElements,
|
||||
type OptimizationCondition,
|
||||
type OptimizationMeasure,
|
||||
type OptimizationAggregation,
|
||||
type OptimizationDirection,
|
||||
} from './planning-objectives';
|
||||
|
||||
export interface AppTranslations {
|
||||
locale: string;
|
||||
@@ -77,6 +87,25 @@ export interface AppTranslations {
|
||||
invalidHorizon: string;
|
||||
};
|
||||
rules: {
|
||||
optimization: {
|
||||
newRule: string;
|
||||
defaultName: string;
|
||||
elementsLabel: string;
|
||||
conditionsLabel: string;
|
||||
measureLabel: string;
|
||||
aggregationLabel: string;
|
||||
directionLabel: string;
|
||||
thresholdLabel: string;
|
||||
thresholdSummary: string;
|
||||
summary: string;
|
||||
invalidConfiguration: string;
|
||||
nameRequired: string;
|
||||
elements: Record<OptimizationElements, string>;
|
||||
conditions: Record<OptimizationCondition, string>;
|
||||
measures: Record<OptimizationMeasure, string>;
|
||||
aggregations: Record<OptimizationAggregation, string>;
|
||||
directions: Record<OptimizationDirection, string>;
|
||||
};
|
||||
title: string;
|
||||
subtitle: string;
|
||||
active: string;
|
||||
@@ -390,6 +419,29 @@ export class TranslationService {
|
||||
}
|
||||
}
|
||||
|
||||
public optimizationSummary(configuration: OptimizationConfiguration): string {
|
||||
const tr = this.tr().rules.optimization;
|
||||
if (!validateOptimizationConfiguration(configuration).valid) return tr.invalidConfiguration;
|
||||
const unit = optimizationUnit(configuration);
|
||||
const unitLabel =
|
||||
unit === 'count'
|
||||
? tr.aggregations.count
|
||||
: this.tr().planning.statistics[
|
||||
unit === 'working-days' ? 'workingDays' : 'calendarDays'
|
||||
];
|
||||
const conditions = configuration.conditions.map((condition) => tr.conditions[condition]);
|
||||
if (configuration.measure === 'work-sequence-deficit')
|
||||
conditions.push(
|
||||
tr.thresholdSummary.replace('{count}', String(configuration.thresholdDays)),
|
||||
);
|
||||
return tr.summary
|
||||
.replace('{direction}', tr.directions[configuration.direction])
|
||||
.replace('{aggregation}', tr.aggregations[configuration.aggregation])
|
||||
.replace('{measure}', `${tr.measures[configuration.measure]} (${unitLabel})`)
|
||||
.replace('{elements}', tr.elements[configuration.elements])
|
||||
.replace('{conditions}', conditions.length ? ` (${conditions.join(', ')})` : '');
|
||||
}
|
||||
|
||||
public getLanguageName(langCode: string): string {
|
||||
try {
|
||||
// Using native Intl.DisplayNames to get name of language in its own language
|
||||
@@ -421,7 +473,8 @@ export class TranslationService {
|
||||
const reasons = this.tr().planning.details.remainingReasons;
|
||||
if (!plan) return reasons['not-established'];
|
||||
const code =
|
||||
incomplete && ['preserved', 'not-established'].includes(balance.reason.code)
|
||||
incomplete &&
|
||||
['preserved', 'active-priorities', 'not-established'].includes(balance.reason.code)
|
||||
? 'calculation-incomplete'
|
||||
: balance.reason.code;
|
||||
return (reasons[code] ?? reasons['not-established'])
|
||||
|
||||
Reference in new issue
Block a user