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 |
|
| loaded year | 48.12 | 44.03 |
|
||||||
| two years | 1.04 | 0.96 |
|
| two years | 1.04 | 0.96 |
|
||||||
| three years | 3.35 | 3.03 |
|
| 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 };
|
return ruleIds?.length ? { code, message, ruleIds } : { code, message };
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneState(state: State, placements?: State['placements']): State {
|
function copyPlacements(state: State): State['placements'] {
|
||||||
const clonedPlacements =
|
return new Map(
|
||||||
placements ??
|
[...state.placements.entries()].map(([date, placement]) => [
|
||||||
new Map(
|
date,
|
||||||
[...state.placements.entries()].map(([date, placement]) => [
|
{
|
||||||
date,
|
...placement,
|
||||||
{
|
ruleIds: new Set(placement.ruleIds),
|
||||||
...placement,
|
},
|
||||||
ruleIds: new Set(placement.ruleIds),
|
]),
|
||||||
},
|
);
|
||||||
]),
|
}
|
||||||
);
|
|
||||||
derivePlacements(state.placements, clonedPlacements);
|
function cloneState(state: State): State {
|
||||||
|
const placements = copyPlacements(state);
|
||||||
|
derivePlacements(state.placements, placements);
|
||||||
return {
|
return {
|
||||||
balances: state.balances.map((balance) => ({ ...balance })),
|
balances: state.balances.map((balance) => ({ ...balance })),
|
||||||
placements: clonedPlacements,
|
placements,
|
||||||
earnedActivations: state.earnedActivations.map((activation) => ({
|
earnedActivations: state.earnedActivations.map((activation) => ({
|
||||||
...activation,
|
...activation,
|
||||||
dates: [...activation.dates],
|
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 {
|
function planningStateSnapshot(state: State, context: SearchContext): PlanningStateSnapshot {
|
||||||
return {
|
return {
|
||||||
calculationDate: context.request.calculationDate,
|
calculationDate: context.request.calculationDate,
|
||||||
@@ -1458,18 +1470,20 @@ function assignDateVariants(
|
|||||||
return compareDate(a.expiryDate, b.expiryDate) || compareText(a.id, b.id);
|
return compareDate(a.expiryDate, b.expiryDate) || compareText(a.id, b.id);
|
||||||
});
|
});
|
||||||
return balances.map((balance) => {
|
return balances.map((balance) => {
|
||||||
const result = cloneState(state);
|
const placements = copyPlacements(state);
|
||||||
const target = result.balances.find((item) => item.id === balance.id)!;
|
notePlacementAdded(state.placements, placements, date);
|
||||||
target.remaining--;
|
placements.set(date, {
|
||||||
notePlacementAdded(state.placements, result.placements, date);
|
|
||||||
result.placements.set(date, {
|
|
||||||
date,
|
date,
|
||||||
balanceId: target.id,
|
balanceId: balance.id,
|
||||||
type: target.type,
|
type: balance.type,
|
||||||
ruleIds: ruleId ? new Set([ruleId]) : new Set(),
|
ruleIds: ruleId ? new Set([ruleId]) : new Set(),
|
||||||
supporting: false,
|
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.
|
// Reject impossible mandatory moves before copying; validate complete patterns later.
|
||||||
if (!mandatoryRelocationDate(date, balance, ruleIds, context)) return null;
|
if (!mandatoryRelocationDate(date, balance, ruleIds, context)) return null;
|
||||||
// Existing placements stay read-only; this assignment owns only its new placement.
|
// Existing placements stay read-only; this assignment owns only its new placement.
|
||||||
const result = cloneState(state, new Map(state.placements));
|
const placements = new Map(state.placements);
|
||||||
const target = result.balances.find((item) => item.id === balanceId)!;
|
notePlacementAdded(state.placements, placements, date);
|
||||||
target.remaining--;
|
placements.set(date, {
|
||||||
notePlacementAdded(state.placements, result.placements, date);
|
|
||||||
result.placements.set(date, {
|
|
||||||
date,
|
date,
|
||||||
balanceId,
|
balanceId,
|
||||||
type: balance.type,
|
type: balance.type,
|
||||||
ruleIds: new Set(ruleIds),
|
ruleIds: new Set(ruleIds),
|
||||||
supporting: false,
|
supporting: false,
|
||||||
});
|
});
|
||||||
return result;
|
return {
|
||||||
|
balances: debitBalance(state, balanceId),
|
||||||
|
placements,
|
||||||
|
earnedActivations: state.earnedActivations,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEarnedBalance(
|
function addEarnedBalance(
|
||||||
|
|||||||
Reference in new issue
Block a user