Files
vac-optimizer/docs/planning-duration.md
T
nessarandClaude Opus 5.5 c2745ecbd6
CI / policy (push) Successful in 22s
CI / validate (push) Successful in 35s
CI / build (push) Successful in 21s
CI / policy (pull_request) Successful in 8s
CI / validate (pull_request) Successful in 24s
CI / benchmark (push) Successful in 1m28s
CI / security (push) Successful in 1m4s
CI / build (pull_request) Successful in 24s
CI / test (push) Failing after 2m8s
CI / images (push) Skipped
CI / required (push) Failing after 1s
CI / security (pull_request) Successful in 56s
CI / benchmark (pull_request) Successful in 1m32s
CI / test (pull_request) Failing after 1m50s
CI / images (pull_request) Skipped
CI / required (pull_request) Failing after 2s
feat(planning): follow the indicative wait in calculation progress
The overlay percentage now rises linearly to 95% at the expected wait
captured at Calculate, holds there, and shows 100% only when the planner
completes. Beneath the status, one line names the current planning stage
(with the rule name for rule stages) and another shows the approximate
remaining time, or "Taking longer than expected" once the wait is
exceeded, announced once through the live status. Cancel freezes the
percentage and hides both lines.

PlanningCalculationService reports the stage and submitted request
instead of fixed per-stage percentages; PlanningDurationService owns the
wait progress on a 250 ms tick.

Closes #300

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 01:04:47 +02:00

138 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Indicative wait before Calculate
The small range beside **Calculate** describes the configuration currently in
the form. Editing dates, balances or rules updates it without requesting a plan
or changing the previously displayed calendar. It is an approximation, with no
accuracy guarantee or automatic stopping deadline.
## Initial estimate
`PlanningDurationService` shares the date-only request with `CalendarYear` so
the estimate and calculation use the same horizon, balances and rules. Invalid
horizon dates hide the estimate and retain the existing disabled Calculate
control. Other malformed inputs keep a rough fallback estimate; the planner
still performs its own validation.
The initial model is a deliberately rough engineering prior fitted to the
rendered waits recorded in the
[2026-10-18 rendered-wait recalibration](benchmark/README.md): six
configurations, from a five-day request to a three-year horizon with 30 days
per year, each timed from the Calculate click to the rendered final calendar.
They were taken after the engine speedups of
[issue #302](https://gitea.nessar.fr/nessar/vac-optimizer/issues/302), which
made the earlier 2026-10-03 and 2026-10-05 references obsolete as grounding.
These are desktop emulation measurements: 4× CPU throttling
slows the main thread but not the dedicated planning worker, so they are not
phone measurements, and a slower device can wait longer than the prior says.
The prior is not a validated prediction model; its rounded constants stay
within about 1.2× of every measured mean.
For a half-open horizon of `D` calendar days, the model uses:
```text
initial_ms = 400 + 250 × (D / 365.25)^0.5 × (max(1, P) / 30)^1.5 × R
```
`P` approximates placement load. Each non-negative finite balance contributes
at most five sevenths of its calendar-day overlap with the horizon, availability
and inclusive expiry. An at-risk balance has weight 1; a deferred balance has
weight 0.25 to account for possible supporting placements. Already expired and
not-yet-available balances contribute nothing. Each enabled IF_THEN rule adds
its non-negative earned quantity times `D / 365.25` as a rough allowance.
`R` starts at 1. Each enabled rule adds `max(1, placeDays / 3)` times 0.5 for an
obligation or 0.15 for a preference. TIME_WINDOW rules wholly outside the
horizon contribute nothing. These weights approximate extra search, rather than
solving constraints or counting holiday occurrences in the form. Typed
obligations, date distribution, interacting rules, earned entitlements, device
load and browser differences can all change the actual wait substantially.
The constants are fitted to a handful of runs, not derived. The fixed 400 ms
approximates request scheduling, worker startup and rendering (a one-year
request for 5 CP waited 465 ms). The one-to-three-year runs grew roughly as the
number of years squared above such an overhead (730, 1,909 and 4,477 ms).
Horizon length and placements grew together in them, so the 0.5 horizon power
is a fit, not a finding about horizon length alone. Five years with 30
available days per year (20 CP plus 10 RTT and the six default optimization
presets) now estimates about 12 seconds, displayed as 6–37 seconds; the
previous constants gave 44–265 seconds. Horizons beyond three years are
unvalidated extrapolation. The estimate performs one pass over balances and
rules and runs no planner.
## Local refinement and precision
After each successfully rendered `Plan`, the service retains the ratio of its
observed elapsed time to the initial model for that calculation's immutable
submitted snapshot. The median of the most recent eight ratios scales the
initial estimate for the current form. A changed form therefore does not
relabel the completed measurement as a calculation of the new configuration.
The median limits the effect of isolated unusually slow runs; it cannot remove
systematic model error or predict arbitrary obligation searches.
Only eight numeric ratios are retained in memory for the current application
session. Reloading restores the initial estimate. Nothing is persisted or sent
to a remote service, so there is no saved-measurement restoration dependency.
Storage failure does not affect this service.
The displayed lower bound is half the estimated duration, rounded down; the
upper bound is three times that duration, rounded up. Values are whole seconds,
or whole minutes when the lower bound is at least one minute. The minimum
displayed value is 1 and the upper bound is at least one unit higher. Internal
duration rounding to whole milliseconds prevents floating-point noise at unit
boundaries. Very fast runs can finish before the displayed lower bound. The
range is explicitly indicative, not a confidence interval or a promise.
## During a calculation
The loading overlay's percentage follows the indicative wait, not the planning
stages completed. At Calculate, `PlanningDurationService` freezes the expected
duration `e` for the submitted snapshot, including the local refinement. On a
250 ms tick, it measures elapsed time from the same timestamp the calibration
uses. Progress rises linearly to 95% at `e`, holds there and shows 100% only
when the planner reports completion. Per-stage weights are not used, because
the costly stage depends on the configuration. Form edits and repeated stages
cannot move it backwards. On Cancel, the tick stops and the percentage freezes.
Below the status, one line names the current planning stage. For an
optimization or preference stage, it shows the rule's name. When `e` is at least
3 seconds, a second line shows "About N s left". This is in whole seconds under
10 seconds, 5-second steps under a minute and whole minutes after that. Once
the elapsed time reaches `e`, the line reads "Taking longer than expected" for
any `e`, and the live status announces it once. Both lines are visible text,
not live regions, and are hidden after Cancel.
## What is timed
Every request, manual or initial, captures `performance.now()` inside
`PlanningCalculationService.start`.
The endpoint is Angular's `afterNextRender` callback after the complete plan has
been projected into the calendar, its months and placements have been rendered,
and the loading overlay has been removed. This measures request scheduling,
calendar preparation, worker startup, search, outcome transport, projection and Angular rendering. It is a DOM
readiness endpoint, rather than a claim to measure a physical screen's paint.
Neither core `planningWorkMs` nor artifact timings supply the calibration
measurement. The callback does not wait for the explanation promise;
later delivery, navigation and explanation errors never add a second measurement.
Cancellation (including a retained incumbent), incomplete outcomes, invalid
inputs, conflicts and calculation errors do not calibrate successful-plan
waiting time. Superseded calculations and callbacks for destroyed calendars
are ignored. Zero, negative and non-finite elapsed values are discarded.
Exceeding the range has no side effects. The estimate supplies no deadline,
timer, date adjustment or new calculation. **Cancel** remains explicit.
## Verification
Controlled-clock tests at the existing calendar and input seams cover initial
estimation, unsubmitted edits, minute formatting, local refinement, request
scheduling, a twenty-second calculation exceeding its initial range, an explanation
that arrives after calendar readiness, cancellation with and without
an incumbent, incomplete/error outcomes and superseded calculations. No timing
assertion depends on CI hardware speed. Existing settings, planner and worker
tests cover saved horizons, long calculations and interrupted-plan explanations.
Rendered annual, two-year and three-year references, with the actual displayed
range and historical artifact-delivery wait, are recorded in
[the benchmark report](benchmark/README.md).