- Implemented PlanningDurationService to provide an indicative wait time before calculation based on user input and calendar settings. - Introduced duration estimation logic that considers horizon dates, balances, and rules without triggering a calculation. - Enhanced user input component to display estimated duration using the new service. - Updated translation files to include new duration estimate strings in multiple languages. - Added tests for PlanningDurationService to ensure accurate duration estimation and calibration. - Refactored leave planner service to remove hardcoded deadlines and improve worker responsiveness.
110 lines
6.1 KiB
Markdown
110 lines
6.1 KiB
Markdown
# 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 grounded in the
|
||
[browser reference measurements](benchmark/README.md): the 2026-10-03 annual
|
||
30-day scenario returned its outcome in about 0.6 seconds, and the 2026-10-05
|
||
two- and three-year calendar runs with 30 days per year took 5.08 and 10.86
|
||
seconds. These are throttled desktop measurements, not phone measurements or a
|
||
validated prediction model. The annual outcome timing does not include final
|
||
calendar rendering. The new rendered-calendar measurements below supplement
|
||
those references.
|
||
|
||
For a half-open horizon of `D` calendar days, the model uses:
|
||
|
||
```text
|
||
initial_ms = 300 + 600 × (D / 365.25)^1.2 × (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 powers and weights are implementation choices, not complexity guarantees
|
||
or fitted accuracy claims. With 30 available days per year, the model's search
|
||
term grows as the number of years to the power 2.7, rather than assuming a
|
||
linear increase. Horizons beyond the references 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.
|
||
|
||
## What is timed
|
||
|
||
Manual requests capture `performance.now()` inside
|
||
`UserInputService.requestComputation`, before updating the calculation trigger.
|
||
The existing initial calculation falls back to the start of `startCalculation`.
|
||
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, trace capture that delays the
|
||
outcome, 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 or trace promises;
|
||
later delivery, navigation and trace 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, a trace
|
||
that fails five minutes 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 original interrupted traces.
|
||
|
||
Rendered annual, two-year and three-year references, with the actual displayed
|
||
range and separate later trace wait, are recorded in
|
||
[the benchmark report](benchmark/README.md).
|