perf(planning): copy only the debited balance when assigning a date
Assigning a date copied every balance and every earned activation. Balances and activations are only edited right after a state copies them, so the new state now shares them with its parent, except the balance it debits. Placement copying is unchanged. Part of #302. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9d9404c3db
commit
aa6a0c6f1b
2 files changed
+67
-28
No files matched your search
@@ -108,3 +108,26 @@ the sum. The core therefore scores them without revalidating;
|
||||
| loaded year | 48.12 | 44.03 |
|
||||
| two years | 1.04 | 0.96 |
|
||||
| three years | 3.35 | 3.03 |
|
||||
|
||||
### Copy only the debited balance when assigning a date
|
||||
|
||||
Assigning a date copied every balance and every earned activation. Balances and
|
||||
activations are only edited right after a state copies them, so the new state
|
||||
now shares them with its parent, except the balance it debits. Placement copying
|
||||
is unchanged. The benchmark scenarios have at most six balances and few earned
|
||||
activations, so the gain is within run-to-run noise.
|
||||
|
||||
| Scenario | Before (s) | After (s) |
|
||||
| --------------------------- | ---------: | --------: |
|
||||
| representative year | 0.29 | 0.28 |
|
||||
| typed obligations | 3.38 | 3.27 |
|
||||
| chained earned entitlements | 0.01 | 0.01 |
|
||||
| deliberate conflict | 0.00 | 0.00 |
|
||||
| loaded year | 43.93 | 44.05 |
|
||||
| two years | 0.95 | 0.92 |
|
||||
| three years | 3.00 | 2.97 |
|
||||
|
||||
Across the five exact steps, the same harness measured three years at
|
||||
7.45 seconds before the first step and 2.97 seconds after the last, two years at
|
||||
2.27 and 0.92 seconds, typed obligations at 5.11 and 3.27 seconds, and the loaded
|
||||
year at 58.26 and 44.05 seconds. These figures come from different sessions.
|
||||
@@ -696,22 +696,24 @@ function reason(code: string, message: string, ruleIds?: string[]): PlanningReas
|
||||
return ruleIds?.length ? { code, message, ruleIds } : { code, message };
|
||||
}
|
||||
|
||||
function cloneState(state: State, placements?: State['placements']): State {
|
||||
const clonedPlacements =
|
||||
placements ??
|
||||
new Map(
|
||||
[...state.placements.entries()].map(([date, placement]) => [
|
||||
date,
|
||||
{
|
||||
...placement,
|
||||
ruleIds: new Set(placement.ruleIds),
|
||||
},
|
||||
]),
|
||||
);
|
||||
derivePlacements(state.placements, clonedPlacements);
|
||||
function copyPlacements(state: State): State['placements'] {
|
||||
return new Map(
|
||||
[...state.placements.entries()].map(([date, placement]) => [
|
||||
date,
|
||||
{
|
||||
...placement,
|
||||
ruleIds: new Set(placement.ruleIds),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function cloneState(state: State): State {
|
||||
const placements = copyPlacements(state);
|
||||
derivePlacements(state.placements, placements);
|
||||
return {
|
||||
balances: state.balances.map((balance) => ({ ...balance })),
|
||||
placements: clonedPlacements,
|
||||
placements,
|
||||
earnedActivations: state.earnedActivations.map((activation) => ({
|
||||
...activation,
|
||||
dates: [...activation.dates],
|
||||
@@ -719,6 +721,16 @@ function cloneState(state: State, placements?: State['placements']): State {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Balances and activations are only edited right after a state copies them, so a
|
||||
* new placement shares them with its parent state, except the balance it debits.
|
||||
*/
|
||||
function debitBalance(state: State, balanceId: string): InternalBalance[] {
|
||||
return state.balances.map((balance) =>
|
||||
balance.id === balanceId ? { ...balance, remaining: balance.remaining - 1 } : balance,
|
||||
);
|
||||
}
|
||||
|
||||
function planningStateSnapshot(state: State, context: SearchContext): PlanningStateSnapshot {
|
||||
return {
|
||||
calculationDate: context.request.calculationDate,
|
||||
@@ -1458,18 +1470,20 @@ function assignDateVariants(
|
||||
return compareDate(a.expiryDate, b.expiryDate) || compareText(a.id, b.id);
|
||||
});
|
||||
return balances.map((balance) => {
|
||||
const result = cloneState(state);
|
||||
const target = result.balances.find((item) => item.id === balance.id)!;
|
||||
target.remaining--;
|
||||
notePlacementAdded(state.placements, result.placements, date);
|
||||
result.placements.set(date, {
|
||||
const placements = copyPlacements(state);
|
||||
notePlacementAdded(state.placements, placements, date);
|
||||
placements.set(date, {
|
||||
date,
|
||||
balanceId: target.id,
|
||||
type: target.type,
|
||||
balanceId: balance.id,
|
||||
type: balance.type,
|
||||
ruleIds: ruleId ? new Set([ruleId]) : new Set(),
|
||||
supporting: false,
|
||||
});
|
||||
return result;
|
||||
return {
|
||||
balances: debitBalance(state, balance.id),
|
||||
placements,
|
||||
earnedActivations: state.earnedActivations,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1497,18 +1511,20 @@ function assignDateToBalance(
|
||||
// Reject impossible mandatory moves before copying; validate complete patterns later.
|
||||
if (!mandatoryRelocationDate(date, balance, ruleIds, context)) return null;
|
||||
// Existing placements stay read-only; this assignment owns only its new placement.
|
||||
const result = cloneState(state, new Map(state.placements));
|
||||
const target = result.balances.find((item) => item.id === balanceId)!;
|
||||
target.remaining--;
|
||||
notePlacementAdded(state.placements, result.placements, date);
|
||||
result.placements.set(date, {
|
||||
const placements = new Map(state.placements);
|
||||
notePlacementAdded(state.placements, placements, date);
|
||||
placements.set(date, {
|
||||
date,
|
||||
balanceId,
|
||||
type: balance.type,
|
||||
ruleIds: new Set(ruleIds),
|
||||
supporting: false,
|
||||
});
|
||||
return result;
|
||||
return {
|
||||
balances: debitBalance(state, balanceId),
|
||||
placements,
|
||||
earnedActivations: state.earnedActivations,
|
||||
};
|
||||
}
|
||||
|
||||
function addEarnedBalance(
|
||||
|
||||
Reference in new issue
Block a user