mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
A target is a quota, not a flat allowance. The Plan column spread it evenly and kept asking for the same twelfth of a yearly figure no matter how far behind the year had fallen, so the one number operations actually needs — what must move per month for the rest of the year — was nowhere on the page. Plan now keeps its meaning and a Required column sits beside it. Plan is the committed spread and never moves, which is the whole reason it stays: Implement Rate is measured against it, so a month that missed still reads as a month that missed. Required is the same target read as a quota — at each bucket, whatever is still outstanding spread across the time still left. A 1,200 t year 20% met by June asks 140 t of June and 960 t of December, which is 1,200 less the 240 delivered. Over-delivery clamps to zero rather than going negative. Attainment is deliberately measured with the user's date bounds stripped (attainmentCtx) and every other filter left in place. Reusing the report's own filtered aggregate would make a July-only view read year-to-date as nothing delivered and demand the entire year's tonnage from one month — the failure would look like a plausible number, not an error. Granularity gains half-year, nine-month and 90-day. Postgres has no date_trunc for any of them, so PERIOD_UNITS entries became builders rather than fragments to interpolate, and all eight blocks anchor to the calendar year. Nine does not divide twelve and 90 does not divide 365: a nine-month year is Jan-Sep plus a short Oct-Dec, and the fourth 90-day block absorbs the remainder at 95 days. That last one is a choice — uncapped floor division opens a five-day stub bucket every December, which is noise rather than a period. Two consequences of the shared unit table, both handled here: - plannedRowsSql now generates a day at a time and groups, instead of stepping by the bucket width. The ragged blocks restart each January, so stepping 90 days from January 1st walks off the anchor in the second year. Day grain also gets partial-bucket overlap for free, at the same sub-day precision the old clipping had. - nextPeriodOrdinalExpr asks the unit for its next block start rather than adding its own step. revenue-by-period evaluates a regression there, and a ragged unit's final block is shorter than its nominal width, so + step would land past the next block and forecast at the wrong x. Verified against Postgres 16 with the entities synchronised into it: all 272 report/granularity combinations in the registry EXPLAIN clean, and the 1,200 t drill-down sums back to 1,200 at every one of the eight grains.