mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 07:08:18 +00:00
feat(operations-targets): plan at any of the eight report grains
Targets could only be committed weekly, monthly, quarterly or yearly, so a figure the business quotes per half-year or per 90 days had to be split by hand into buckets it was never expressed in. The reports already re-gather a target into whatever grain the viewer asks for; this just lets the plan be entered at the grain it was agreed in. Adds day, half-year, nine-month and 90-day, matching the report units added alongside. normalisePeriodStart snaps each to its block start with the same calendar-year anchoring the SQL uses — Jan/Jul for half-years, Jan/Oct for nine-months, days 1/91/181/271 for 90-day blocks, including the same cap on the fourth block so late December does not snap into a stub of its own. That agreement is the load-bearing part. The unique index is keyed on period_start, and a target snapped to a boundary the report does not bucket on is a plan measured against a period that does not exist. The two halves live in different files and different languages, so the spec pins the boundaries rather than trusting them to stay in step. Adds operations-targets.service.spec.ts, which the module had none of: every period type, idempotency, the leap year, and the block-four cap.
This commit is contained in:
@@ -26,10 +26,19 @@ import {
|
||||
CONTAINER_CLASSES,
|
||||
} from '../reports/operations-classification';
|
||||
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
|
||||
/**
|
||||
* Normalises any date inside a bucket to the bucket's first day, matching
|
||||
* Postgres `date_trunc` — which is what the reports group by. Week starts
|
||||
* Monday, the same as `date_trunc('week', …)` and ISO week numbering.
|
||||
* Normalises any date inside a bucket to the bucket's first day, matching the
|
||||
* bucket expression the reports group by (`PERIOD_UNITS` in
|
||||
* `reports/revenue-classification.ts`). Week starts Monday, the same as
|
||||
* `date_trunc('week', …)` and ISO week numbering.
|
||||
*
|
||||
* The four units Postgres has no `date_trunc` for are anchored to the calendar
|
||||
* year, exactly as their SQL twins are: half-years at Jan/Jul, nine-months at
|
||||
* Jan/Oct, ninety-days at day 1/91/181/271. **This function and
|
||||
* `PERIOD_UNITS[...].truncOn` must agree** — a target whose `period_start` is
|
||||
* not a real block start plans against a bucket boundary that does not exist.
|
||||
*
|
||||
* Done in UTC throughout: the stored column is a bare `date`, and running the
|
||||
* arithmetic in local time would shift a 1st-of-month target into the previous
|
||||
@@ -38,6 +47,8 @@ import {
|
||||
export function normalisePeriodStart(periodType: TargetPeriodType, value: string): string {
|
||||
const d = new Date(`${value.slice(0, 10)}T00:00:00Z`);
|
||||
switch (periodType) {
|
||||
case 'day':
|
||||
break;
|
||||
case 'week': {
|
||||
// getUTCDay(): 0 = Sunday. Monday-based offset puts Sunday six days in.
|
||||
const offset = (d.getUTCDay() + 6) % 7;
|
||||
@@ -50,6 +61,22 @@ export function normalisePeriodStart(periodType: TargetPeriodType, value: string
|
||||
case 'quarter':
|
||||
d.setUTCMonth(Math.floor(d.getUTCMonth() / 3) * 3, 1);
|
||||
break;
|
||||
case 'half_year':
|
||||
d.setUTCMonth(Math.floor(d.getUTCMonth() / 6) * 6, 1);
|
||||
break;
|
||||
case 'nine_month':
|
||||
// Two blocks a year, not 1.33: Jan–Sep, then a short Oct–Dec.
|
||||
d.setUTCMonth(Math.floor(d.getUTCMonth() / 9) * 9, 1);
|
||||
break;
|
||||
case 'ninety_day': {
|
||||
// Day-of-year, zero-based, so this matches SQL's 1-based `(doy - 1) / 90`.
|
||||
// Capped at block 3 for the same reason the SQL caps it: uncapped, the
|
||||
// last days of December become a 5-day stub block of their own.
|
||||
const yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
|
||||
const dayIndex = Math.floor((d.getTime() - yearStart) / MS_PER_DAY);
|
||||
d.setTime(yearStart + Math.min(Math.floor(dayIndex / 90), 3) * 90 * MS_PER_DAY);
|
||||
break;
|
||||
}
|
||||
case 'year':
|
||||
d.setUTCMonth(0, 1);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user