Skip to content

test(db): golden regression dataset for traceable totals - #220

Open
ydimitrof wants to merge 4 commits into
midt-bg:mainfrom
ydimitrof:test/golden-dataset
Open

test(db): golden regression dataset for traceable totals#220
ydimitrof wants to merge 4 commits into
midt-bg:mainfrom
ydimitrof:test/golden-dataset

Conversation

@ydimitrof

Copy link
Copy Markdown
Contributor

Какво

Closes #99.

Синтетичен golden корпус (2 възложители × 3 изпълнители × 8 договора), прекаран end-to-end през реалния production derive ред (derive-amendments.sqlload-nuts.sqlseed-state-owned.sqlnormalize-raw.sqlpromote-amendments.sqlprecompute.sql, огледално на import.mjs runFullDerive) срещу sqlite3, с абсолютни, ръчно сметнати очаквани стойности на всяко зърно.

Покритие на fixture-а

  • Всичките 5 value_flag изхода: ok, value_suspect (300×估 → repair към估имацията), value_low (signing 0), annex_suspect (анекс 100× signing), review (15×).
  • Трите валутни пътя: BGN (÷1.95583), EUR (as-is), USD (× fx_rate от fx_rates, 10-дневния carry-forward прозорец).
  • Анекси през derive-amendmentspromote-amendments (един suspicious 100×, един легитимен 1.5×, чийто current_value влиза в amount_eur).
  • Реален state-owned ЕИК (121396123) през реалния seed-state-owned.sql; name-keyed изпълнител без валиден ЕИК (eik_valid=0); NUTS/settlement обогатяване през OCDS parties + реалния load-nuts.sql.

Асърти (9 теста)

Per-contract recount (id/amount_eur/value_flag/EUR timeline/fx одит) — зърното, което reconciliation gate-ът (#97) структурно не може да провери; company_totals, authority_totals, sector_totals, facet_counts, flow_pairs, home_totals, разпределение на value_flag, search surface, data_freshness, плюс самия integrity gate (двете мрежи са допълващи се).

Всяко golden число носи деривацията си в коментар (напр. 195583 ÷ 1.95583 = 100000). Умишлено няма --update скрипт — регенериране от pipeline-а би благословило точно бъга, който тестът пази. Процедурата за умишлена промяна е документирана в docs/review-testing.md („Golden dataset"); docs/integrity-gate.md бележките за blind spot-а вече сочат към теста.

Доказана чувствителност (mutation checks, dev-time, reverted)

  • Промяна на peg-а 1.95583 → 1.95584 в normalize-raw.sql: 5/9 теста червени.
  • Разхлабване на annex_suspect прага ≥100× → ≥1000×: 6/9 червени.

Проверки

  • packages/db: 192 теста / 28 файла зелени (вкл. новите 9); pnpm test зелен и в 6-те workspace-а.
  • pnpm typecheck зелен; node scripts/check-docs.mjs зелен.

Свързано

#97 (gate, чиито граници този тест допълва), #98 (при промяна на стойностната база golden числата се преизчисляват ръчно по документираната процедура), #217 (derive слоят се покрива поведенчески — този PR е този крак).

@lyubomir-bozhinov

Copy link
Copy Markdown
Collaborator

Проверих отново (c98e4ef) — истински дълбок regression тест, не cheater:

  • Независим oracle: GOLDEN са ръчно сметнати литерали (аритметиката е в коментарите), всяко assert е toEqual(GOLDEN.x) — нищо не се смята с кода под тест.
  • Върти реалния derive pipeline (normalize-raw → promote-amendments → precompute), 9/9 зелени, и е mutation-sensitive: peg 1.95583→1.95584 → 5/9 падат; анекс праг ≥100×→≥1000× → 6/9 (точно както твърди PR-ът). Per-grain absolutes хващат misattribution, който запазва grand total — точно каквото gate-ът на Данни: твърди проверки за цялост в pipeline-а (reconciliation gate) #97 структурно не може да види.
  • Покрива ok / value_suspect / annex_suspect / value_low / review. Покрива Данни: регресионен golden-dataset за проследими тотали #99.

Should-fix (не блокер): инвариантът amount_eur IS NOT NULL е asserted-adjacent, но не се упражнява — няма нито един ред с NULL amount_eur. Доказано: махането на предиката и в шестте rollup-а е no-op (9/9 пак минават). Регресия, при която чуждовалутен ред без FX курс изтече в сумите (или се маркира като value_suspect), няма да се хване. Добави един договор в чужда валута без fx_rates ред (amount_eur NULL, изключен от всички rollup-и, suspect KPI непроменен) — това упражнява и 10-дневния FX carry-forward на границата на отхвърляне.

Nit: readScript няма -bail (за разлика от сестринските тестове) — SQL грешка по средата на .read скрипт не вдига beforeAll (sqlite3 излиза с 0).

Одобрявам; should-fix-ът е малък и си струва преди merge.

@lyubomir-bozhinov lyubomir-bozhinov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Издържано — точно това, което #99 иска. Датасетът е реален: ръчно изведени очаквани стойности, прекарани през истинския production SQL pipeline, всяка value_flag пътека ударена по веднъж. Кръстосах homeTotals.value_eur = 241100 (150100 + 91000, hand-derived) — бие. assertIntegrity върви срещу същата база → golden + reconciliation мрежите са комплементарни. Липсата на --update скрипт е правилният избор — авто-регенерация би благословила точно бъга, който датасетът лови.

Дребно: assertIntegrity се внася по суров relative path извън пакета (../../../scripts/integrity-checks.mjs) — при разместване на scripts/ тестът ще пада с module-not-found вместо assertion; workspace alias/entry point би ги развързал.

Synthetic 8-contract corpus (all 5 value_flag outcomes, BGN/EUR/USD
paths, amendments, state-owned EIK) run through the real production
derive order (derive-amendments -> normalize-raw -> promote-amendments
-> precompute) against sqlite3, asserting hand-computed absolute
numbers at every grain: per-contract amount_eur/value_flag/EUR
timeline, company_totals, authority_totals, sector_totals,
facet_counts, flow_pairs, home_totals, value_flag distribution, search
surface, freshness boundary, plus the reconciliation gate.

Sensitivity proven by throwaway mutations: changing the BGN peg fails
5/9 tests; loosening the annex_suspect threshold fails 6/9.
…bg#99)

review-testing.md gains the golden-dataset description and the manual
update procedure (recompute by hand from the fixture, never copy
pipeline output; deliberately no --update script). integrity-gate.md's
two blind-spot notes now point at the implemented test.
The file was committed with data rows exceeding printWidth 100; CI gates on
`prettier --check .` (midt-bg#88), so format it before the substantive change lands.
Pure reformatting, no semantic change.
Add c9 — a USD contract signed one day past the 10-day fx carry-forward
window (only USD rate is dated 06-01, signing 06-12), so it derives with
amount_eur NULL. It probes two paths the prior fixture left un-exercised:

- the 10-day carry-forward at its rejection boundary (widen the window and
  the row would convert), and
- the amount_eur IS NOT NULL predicate on all six value rollups: c9 counts
  in the corpus COUNT(*) tallies (home/facet/search/freshness) but is
  excluded from every value SUM and its paired count, and is 'ok' not
  value_suspect so the suspect KPI is untouched. Dropping the predicate now
  leaks a NULL into a sum instead of being a no-op.

Also pass -bail to readScript so a SQL error inside a .read derive step fails
beforeAll instead of exiting 0 (matches the sibling db tests).

GOLDEN constants hand-derived and reconciled against the real pipeline
(clean_total 241100, 9 contracts, integrity gate green).
@ydimitrof
ydimitrof force-pushed the test/golden-dataset branch from c98e4ef to c137871 Compare July 13, 2026 08:07
@lyubomir-bozhinov

Copy link
Copy Markdown
Collaborator

Аритметиката на golden set-а е коректна (проверих c1/c6/c8, rollup-ите и NULL c9 срещу пега).

Пропуск: euro-annex double-conversion капанът (#245 — 2026 EUR анекс върху BGN договор, current_value_eur ÷1.95583 повторно → наполовина) не се упражнява. И двата анекса във fixture-а (c6, c8) са BGN; c2/c4/c5 са EUR, но без анекс. Регресия, връщаща #245, би минала и 9-те golden проверки.

Добави 10-ти договор: BGN-подписан с EUR анекс, и assert-ни current_value_eur == суровата EUR сума (не ÷1.95583). Това е дискриминиращият случай, който документира прод-бъга.

@nedda76 nedda76 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Прегледах fixture-а, golden числата и асъртите. Чист, чисто адитивен PR (нов тест + docs, нула продукционен код) с висока стойност като регресионна мрежа — прекарва синтетичния корпус през реалния derive ред и сравнява с ръчно сметнати абсолютни числа, тъй че мисатрибуция, която пази грандтотала, тук пада (точно каквото integrity gate-ът структурно не хваща).

Проверих на ръка (съвпадат)

  • Per-contract: c1 195583÷1.95583=100000, c3 50000×0.9=45000, c4 300×оценка→value_suspect→repair до 1000, c5 15×→review, c6 100×→annex_suspect→връщане към 100, c8 анекс 1.5×→30000. Всичко излиза.
  • c9 (граничният случай) е най-хубавото зърно: USD, подписан на 06-12, единствен курс на 06-01; date(06-12,'-10d')=06-02 > 06-01 → извън 10-дневния carry-forward → amount_eur NULL. И проверява точно правилното следствие: c9 влиза в корпусните COUNT(*) (home/facet/search/freshness), но пада от всеки value SUM и сдвоената му бройка; as_of=06-12. Заковава amount_eur IS NOT NULL предиката на шестте rollup-а.
  • Агрегатите се затварят: company_totals (B1 160000/4/2 авт.), authority_totals (A1 150100, A2 91000, avg-ите), flow_pairs (A2→B1 60000/2 без c9), home_totals 241100, facet_counts eu '0' 8/191100. Всичко се сумира.

Бележка (дребно)

  • Описанието на PR-а казва „3 изпълнители × 8 договора", а fixture-ът има 9 (деветият е именно c9 — граничният fx случай). Струва си да се оправи текстът, за да не подведе ревюъра, че липсва ред.

Отличен тест. Само преглед — самият мърдж не е мой.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Данни: регресионен golden-dataset за проследими тотали

3 participants