docs(web): методологията описва точно таблата — формули, обхват, изключения - #193
StanislavBG wants to merge 52 commits into
Conversation
…ns, overrun index)
The popover often renders inside a thead th whose white-space: nowrap is inherited by every line of the card, so long summaries and the mono readout ran past the right edge. Reset wrapping on .metric-info-pop (white-space: normal + overflow-wrap: anywhere), widen the card to 320px clamped to the viewport (min(320px, 100vw - 16px)), and add the JS shift-into-viewport + coarse-pointer 44px hit area so all copies of the component behave identically.
The обзор cross lens now re-runs the combo chart, year cards and totals server-side for the ticked CPV groups (repeatable ?cpv, validated 5-digit codes, deduped, capped at 10), and the chart stretches to fill its card against the CPV list. One aggregate scan via an OR of half-open cpv_code index ranges; ?cpv stays keyed in the edge cache (CWE-349) with UI-canonicalized ordering, and selection changes are announced via an sr-only status line.
The by-sector aggregate grouped on substr(t.cpv_code, 1, 2) and then ran cpvDivision() over the already-truncated prefix, so a dirty code like ' 45000000' resolved to division '4' there while the leaderboard (cpvDivision over the full code) resolved it to 45 — the same contract landed in different CPV sectors on the two surfaces. Select the full t.cpv_code, GROUP BY it in SQL, and run cpvDivision() on the full code in JS exactly like the leaderboard mapping; the existing merge folds all codes onto their canonical division and re-applies secLimit post-merge. Test covers a dirty leading char flowing through both surfaces to the same division.
…he full cpv_code Grouping by the full cpv_code fixed the dirty-code divergence but returned one row per distinct 8-digit code — 582 rows on the local corpus (thousands at scale) shipped out of D1 for a 15-row table. SECTOR_KEY_SQL strips the separator characters real codes carry and takes substr(clean, 1, 2) only when the cleaned code provably starts with two digits (in which case it IS cpvDivision(code)); anything else falls through as the full raw code for the JS re-key to fold in. Exact cpvDivision semantics, division-sized result set, still no pre-merge LIMIT truncation. Adds a sqlite3-CLI integration test pinning SECTOR_KEY_SQL ≡ cpvDivision across the dirt corpus, and a unit test pinning the GROUP BY key against both traps (naive substr, full-code blowup).
Every column header in „Кои институции раздуват най-много" and „Раздуване по сектори" now carries the same ⓘ MetricInfo popover the headline KPIs use: what the metric is, how it is computed (grounded in the SQL — growth is SUM(delta)/SUM(signing), €-weighted, not an average of percents), and the honest inclusion caveat (annex_count > 0, current > signing, signing ≥ €1 000). Table header cells get white-space: nowrap so the ⓘ never wraps the label. Also: the „Договори по мащаб" leaderboard rows gain right-side breathing room (row padding-right var(--s-3)) so value text and truncated titles no longer touch the card edge; the inset lives on the row, so the selected highlight still reads full-width and the proportional bar-track math is untouched in normal and fullscreen mode.
…compare lens (review)
…ючения Добавя раздел „Аналитичните изгледи" в /methodology с описание на всяко табло (Анализи, Обзор, Конкуренция, Раздуване, Потоци, Карта, Мрежа, Договори): какво показва визуалът, формулата с думи, обхватът и изключенията, и честните уговорки — сверено ред по ред срещу кода. Само документация; без промяна в поведението.
f5f8fb5 to
7413f87
Compare
|
@todorkolev готов за ревю 🙏 — rebase-нат на main, CI зелен, prettier-чист, CSS промените в styles/* (app.css само @import). Резолвнати нишки. Approve-ни когато ти е удобно. |
|
Проверих PR #193 внимателно — целият собствен diff, всяко фактическо твърдение в текста срещу кода на loader-ите и заявките, плюс сигурност и цялост на данните. Обхват на промянатаВъпреки че diff-ът срещу Сигурност и цялост на данните
Сверяване на твърденията срещу кода (цялост)Тъй като PR-ът обещава „сверено ред по ред срещу кода", проверих локално всяко число и формула. Всички съвпадат точно:
Не открих нито едно разминаване между текста и поведението на кода. Уговорките („ориентир, не заключение", „ръстът не е нередност", „типичната цена е само ориентир") са честни и точно поставени — точно това, което искаме от методологична страница пред граждани. Бележки (незадължителни, не блокиращи)
CI е зелен, prettier/typecheck минават, 377 теста зелени (документационна промяна не добавя тестова повърхност — приемливо тук). Благодаря за изрядно свършената работа — рядко се вижда методология, сверена буквално срещу заявките. ВЕРДИКТ: Approve на същество — да се слее само след стека #169–#172 (draft, blocked по дизайн). Няма проблеми със сигурността или целостта на данните. |
|
Одобрявам Ревю на PR #193 — „методологията описва точно таблата"Прегледах промяната строго, с акцент върху сигурност, цялост на данните и точност спрямо кода. Разграничих собствената промяна на PR-а от подлежащия стек. Обхват — какво реално въвежда този PRДифът показва ~9,6 хил. реда, но собствената промяна е един-единствен комит ( Промяната е само документация: нов раздел „7. Аналитичните изгледи" в Сигурност / OWASPЧисто — тук няма атакуема повърхност:
Цялост на данните — сверих всяко твърдение ред по ред срещу кодаТова е същината при документация, която обещава „сверено срещу кода". Всички числени твърдения съвпадат точно:
Не открих нито едно разминаване между текста и поведението на кода. Съответствие с описанието на PR-аОписанието твърди „единственият променен файл е Дребни, незадължителни бележки (не блокират)
Отлична, честна и точна работа — тонът „ориентир, не заключение" е спазен навсякъде. |
chooseXTicks took the lowest 5 stops from the in-range tick ladder, so a wide log range (e.g. 25%–4800%) left the upper ~40% of the axis without labels even though points were plotted there. Sample evenly across the in-range ladder instead, always keeping the first and last stops.
The card's hint claimed avgYoy is "the same value as on /трендs", but /trends never displays this figure (methodology.tsx documents /trends as forecast-free) — the metric is analytics-only, computed from the same underlying monthly series. Reword the hint to state that accurately.
The file's own header states the palette lives in exactly one place (OKLch); --paper-raised: #ffffff was the one raw-hex exception.
…ly CSRF postcss 8.5.15 (GHSA-r28c-9q8g-f849) and valibot 1.4.0 (GHSA-5qjj-4xww-7phc) are patch-level, non-breaking bumps to 8.5.18+ and 1.4.2+ via pnpm-workspace overrides. react-router 7.18.0 is flagged by GHSA-qwww-vcr4-c8h2, a CSRF issue confined to the unstable RSC APIs (verified via repo-wide grep: zero RSC usage). No fix exists in the 7.x line; the minimum fixed version is 8.3.0, a major breaking bump out of scope here. Suppressed via osv-scanner.toml, following the same time-boxed pattern as the existing sharp (GHSA-f88m-g3jw-g9cj) entry, reviewable by 2026-10-01.
…nflicts - osv-scanner.toml: kept the react-router RSC suppression (this branch); dropped main's sharp suppression since this branch already bumped sharp to ^0.35.0 (5f94c94), which resolves GHSA-f88m-g3jw-g9cj outright - packages/db/migrations: renumbered this branch's overrun-index migration from 0002 to 0003 to avoid colliding with main's already-merged 0002_current_value_currency.sql; updated migrations.test.ts to apply both in sequence - apps/web/app/routes/analytics.tsx, trends.tsx: kept this branch's redesigned loaders/queries (analytics hub, obzor three-lens view), applied main's getDb() read-only D1 chokepoint (midt-bg#199) instead of raw env.DB - apps/web/app/routes/overruns.tsx: not itself conflicted, but also routed through getDb() to satisfy the chokepoint guard test added by main (readonly-db-chokepoint.test.ts) - pnpm-lock.yaml: regenerated via pnpm install
|
Този клон е в конфликт с |
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщен преглед на PR — „docs(web): методологията описва точно таблата — формули, обхват, изключения"
Какво прави PR-ът
Въпреки заглавието docs(web): …, това е значителна функционална промяна, обхваната в 9 партиди. PR-ът:
- добавя ново табло
/overruns(за „раздуване" на договори) и пренаписва страниците/trendsи/analyticsс леки „заглавни четива" вместо тежки lens-preview изгледи (edge-кеширани за 1800 s, паралелни заявки); - въвежда нова бизнес-логика и заявки (
overruns.ts,flows.ts,competition.ts,analytics-stats.ts,cpvGroupSelection,getOverrunsAnalytics,getOpaqueShareByYearи др.) с изчерпателна тестова покривка; - добавя нови презентационни компоненти (
ComboTrendChart,FullscreenButton,MetricInfo), CSS за таблото (изнесен от inlinestyle=за CSP-чистота) и премахва неизползваните.lens-*стилове; - разширява страницата
methodology.tsxс нова секция 7 „Аналитичните изгледи" (само проза, коректно преномериране и препратки); - обновява зависимости за сигурност (
sharp ^0.35.0,postcss ^8.5.18,valibot ^1.4.2) и подменя потискане вosv-scanner.toml.
Сигурност (Фаза 0)
Чисто във всички партиди с достъпен diff. Няма твърдо кодирани тайни, нови външни URL адреси, злонамерени шаблони или необосновани зависимости. Входът е валидиран и ограничен навсякъде (cpvGroupSelection, whitelist на query параметри, JSX escaping — защита срещу CWE-349 и XSS). Обновленията на зависимости са коректно обосновани с CVE/GHSA номера и последователно отразени в lock файла.
Качество
Кодът е с високо качество: чисти, SSR-безопасни функции, споделени SQL фрагменти, които предотвратяват разминаване между изгледите, детерминистични tie-breaker-и, коректно чункиране спрямо D1 лимита от 100 параметъра и осмислена, нетривиална тестова покривка (гранични случаи, NaN/null, YoY през липсваща година).
Основни находки (не блокиращи)
- Несъответствие заглавие ↔ обхват. Заглавието е
docs(web): …, но PR-ът добавя ~хиляди редове нова логика, компоненти и CSS (напр.overruns.ts+582,pages.css+3080). Препоръчва се типът на commit-а да отрази реалния обхват (напр.feat(web): …) заради changelog/семантичното версиониране, и да се потвърди, че няма scope creep. - Липса на горна граница за CPV групи (
cpvGroupsClauseвgetSpendingTrend/listOverviewContracts). За разлика отgetOverrunAnnexes, тук няма чункиране — при ~49+ избрани групи заявката ще надхвърли лимита от 100 SQL променливи на D1 и ще се провали. Желателна е явна серверна граница/чункиране. Аналогично,getCpvGroupMediansправи паралелен fan-out без таван. estimateYoyGrowth/ гранулярност. Гранулярността наgetSpendingTrendе сменена от'year'на'month'; функцията предполага месечна серия и при друга гранулярност мълчаливо връща „плосък" фактор — струва си потвърждение.- Достъпност на scatter точките в
/overruns:<circle onClick>не са фокусируеми от клавиатура и няматrole/tabIndex/onKeyDown. Има достъпна алтернатива (списък с бутони +aria-label), затова е дребна забележка. - Кръстосани зависимости за потвърждение: че
sharpнаистина е вдигнат до>= 0.35.0(иначе премахнатото потискане чупи скенера — потвърдено в партида 9); че нито един жив шаблон не реферира премахнатите.lens-*класове. - Дребни/CSS забележки: твърдо кодирано „24" вместо
CONTRACT_CARD_LIMIT; непоследователен fallback за--font-serif; възможно z-index наслагване на.metric-info-popвътре в изгледа на цял екран; повтарящ сеfontshorthand.
Ограничения на прегледа
Diff-ът на pages.css (+3080/-3) не беше наличен за инспекция — препоръчва се ръчна проверка за дублиране/мъртви стилове и Фаза 0 върху пълния файл преди сливане. Желателно е и потвърждение, че pnpm install --frozen-lockfile минава в CI.
Заключение
COMMENT — няма блокиращи проблеми. Промяната е атомарна, добре документирана и тествана. Преди сливане си струва да се адресират: несъответствието заглавие/обхват, потенциалният отказ при много CPV групи (D1 лимит), потвърждението за гранулярността на estimateYoyGrowth и ръчният преглед на pages.css. Останалите забележки са с нисък приоритет.
…d-rolled casts
Removes the last 11 `as unknown as D1Database` casts left behind after the
main-branch merge, so packages/db/src/queries/{competition,flows,overruns,regions,trend}.test.ts
clear the midt-bg#325 fake-D1 gate (scripts/check-fake-d1.mjs) that started failing CI on this
branch after it landed on main.
cpvBucket fell through to 'goods' for any catalogued division not in CPV_BUCKET_WORKS/SERVICES. A future services division added to CPV_SECTORS but forgotten in CPV_BUCKET_SERVICES would silently misclassify as goods, with no test able to catch it (the existing partition test only asserted !== 'other'). CPV_BUCKET_GOODS is now an explicit set; an omitted division now falls to 'other', and a new test asserts WORKS ∪ SERVICES ∪ GOODS === CPV_SECTORS exactly. Same change is required byte-identical on the sibling PRD feedback-193-methodology branch, which flags the same line — coordinate before either merges. Addresses ydimitrof's review on PR midt-bg#171 (also flagged on midt-bg#193).
…only adjacent years in YoY Addresses ydimitrof's review round on PR midt-bg#193: - trend.ts: validCpvGroups caps at 40 groups (2 binds each) so cpvGroupsClause can never exceed D1's 100-bound-parameter limit; test proves 60 groups stay bindable - analytics-stats.ts: estimateYoyGrowth skips non-adjacent year pairs so a gap year is never read as a one-year ratio; months===12 documented as an invariant - trends.tsx: interpolate CONTRACT_CARD_LIMIT into the truncation subtitle - config: goods is now an explicit CPV bucket set; unassigned catalogued divisions fall to other and trip the partition tests - overruns-sql.test.ts: global tab replace in sqlLiteral - competition.ts: note the GLOB 4-digit invariant guarding the lexicographic year compare
… review) The 14 subject-risk columns were added by editing 0000_init.sql. The served D1 is persistent and `wrangler d1 migrations apply` is filename-tracked, so an edit to the already-applied 0000_init reaches the work DB (green local CI) but never prod — precompute's UPDATE would then fail on missing columns (the Extract the columns into 0006_subject_risk_columns.sql (ALTER TABLE ADD COLUMN) and remove them from 0000_init (SQLite has no ADD COLUMN IF NOT EXISTS, so a fresh DB can't carry both). 0006 clears the 0002 claimants (midt-bg#226/midt-bg#193/midt-bg#172) to avoid a duplicate-version at apply. The precompute CREATE TABLE IF NOT EXISTS mirror stays a no-op on the existing tables. The four risk tests now apply 0006 after 0000_init, mirroring the served-D1 chain. Reword the 0000_init header to distinguish the rebuilt-every-import work DB from the persistent served D1, so the next person adds objects via a new migration instead of editing 0000_init. Also document that value_flag='ok' is the exact complement of the hidden suspect set.
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщение на прегледа
Какво прави този PR
Въпреки заглавието docs(web), това е значителна функционална промяна, разпределена в 8 партиди. PR-ът обновява методологичната документация (нов раздел 7, пренномериране на разделите, обновен TOC и вътрешни препратки) така, че тя да описва точно таблата — формули, обхват и изключения. Успоредно с това добавя:
- Нов продукционен код за анализи: помощни функции (
analytics-stats,overruns-chart,overruns-inspector,filters.cpvGroupSelection), нови query функции (overruns.ts,regions.ts,trend.ts), CPV дял (cpvBucket/cpvDivision),getOpaqueShareByYear. - Пренаписани маршрути:
analytics.tsx(начална страница),overruns.tsx,trends.tsx(пълно функционално пренаписване, +619/−108 — нов маршрутен модел с три „ъгъла", нови графики, премахнати филтри за сектор и финансиране). - Нова SQL миграция (0011, forward-only, с частичен индекс), нови tie-breakers в
ORDER BYза детерминизъм,RESERVED_CACHE_PARAMSдисциплина. - CSS: пренасяне на inline
style=към класове (CSP-чисто), премахване на неизползвани.lens-*правила, голямо добавяне вpages.css. - Изчерпателни тестове за render, loader, миграции и заявки.
Обща оценка
Кодът е с високо качество: презентационен и само за четене, edge-кеширан, с ограничени заявки. Няма проблеми със сигурността в нито една партида — потребителските входове минават през стриктна валидация (/^\d{5}$/ за CPV групи, pick/allow-list за query-параметри, clampLimit), подават се като bound параметри (без SQL инжекция), а изходът се екранира от React (без XSS). Няма нови мрежови извиквания, зависимости, промени в auth/CI, тайни или следи от prompt injection. Целостта на данните е спазена — помощниците не измислят стойности при оскъдни данни (връщат —, непотвърден), изключват непълните периоди/години и клампират аномалии. Методологичният текст съответства на реалната семантика на изгледите (праг ≥ 1000 €, медиана vs. средно, дял с една оферта само при ≥20 поръчки, растеж претеглен по €).
Ключови забележки (не блокиращи)
-
Несъответствие заглавие/обхват (повтарящо се във всички партиди): заглавието е
docs(web), но PR-ът съдържа съществен продукционен код — SQL миграция, нови query функции, пренаписани маршрути и масивен CSS. Молба да се потвърди, че етикетът/обхватът са умишлени и че методологичните текстове са синхронизирани с новия изглед наtrends.tsx(сектор/финансиране са премахнати). -
getCpvGroupMediansняма таван върху броя групи (партида 8) — за разлика отvalidCpvGroups, който сряза доMAX_CPV_GROUPS, и стартира по една конкурентна D1 заявка на уникална група. Предложение за устойчивост. -
Неизползван импорт
singleSelectFiltersвtrends.tsx(партида 4) — може да счупи билда приnoUnusedLocals/строг ESLint. -
pages.css(+3080/−3) не беше наличен за diff (маркиран като бинарен/твърде голям) — препоръка за ръчен преглед за неочакваниurl(...),@importили вградени base64 данни. -
Съгласуваност между партидите: изтритите
.lens-*CSS правила предполагат, че съответният маркъп също се премахва — да се потвърди. Стабилността на „stretched link" +MetricInfoбутона зависи от CSS stacking context — струва си визуална проверка.
Заключение
COMMENT — няма дефекти, които да блокират сливането или да се проявят в продукция. Основното действие преди merge е сверяване на заглавието/обхвата на PR-а с реалното съдържание и потвърждаване, че документацията е синхронизирана с пренаписаните изгледи. Останалите забележки са незадължителни подобрения.
At the minimum 2-point series, width/n overshot point spacing and the edge bar spilled outside the viewBox. Also clamp each bar's half-width to the room actually available on either side of its center so edge bars never overflow regardless of n.
getCpvGroupMedians issues one concurrent D1 query per unique group via Promise.all with no cap, unlike validCpvGroups which already truncates to MAX_CPV_GROUPS. Reuse that helper so untrusted input cannot fan out unbounded concurrent D1 queries.
The new loader reads only cpvGroupSelection; the old single-select loader call is gone but the import was left behind.
GROWTH_TRAILING_YEARS = 3 keeps only 2022-2024, so the 2020-2021 jump is excluded by the sliding window itself, not by a median of four ratios — the window only ever produces two.
ydimitrof
left a comment
There was a problem hiding this comment.
Обобщение на прегледа
Какво прави PR-ът: Въпреки заглавието „docs(web)…", промяната далеч надхвърля документацията. Тя изгражда цялостно табло за анализи в apps/web: пренаписана начална страница на „Анализи", нов раздел „Методология" с формули/обхват/изключения, нови функционални route-ове (/trends, /overruns) от порядъка на стотици редове, разширения на API-контракта (нови типове и заявки getOpaqueShareByYear, getFlowsHeadline), помощни функции cpvDivision/cpvBucket в @sigma/config, частичен индекс (миграция 0011), нови дизайн-токени, обширни CSS стилове и богато тестово покритие.
Обща оценка: Кодът е чист, добре изолиран, добре документиран и щателно тестван. Логиката е изнесена в pure функции, компонентите са SSR-безопасни, а граничните случаи (деление на нула, non-finite вход, частични периоди, липсваща предходна година при YoY) са коректно обработени с честен em-dash вместо измислени стойности.
Сигурност и интегритет на данните — без блокиращи дефекти:
- Няма злонамерен код, нови мрежови повиквания, тайни,
eval/обфускация, промени по auth/permissions/CI или подозрителни зависимости. Не са открити опити за prompt injection. - Входовете са валидирани и whitelist-нати:
yearпрез regex,byкато union тип,cur='1', а CPV групите презcpvGroupSelection(/^\d{5}$/, дедупликация, запазен ред, таванMAX_CPV_GROUPS) — защита срещу CWE-349 и неограничено разрастване на кеш ключове/SQL обхват. - SQL използва константи и параметризирани плейсхолдъри; спазен е лимитът на D1 от 100 свързани параметъра (IN-листата е чънкована). Целият изход минава през екраниран JSX — без XSS.
- Миграция 0011 е
IF NOT EXISTS, forward-only, с частичен предикат, съответстващ наOVERRUN_WHERE. FX нормализацията към EUR (пег за BGN, идентичност за EUR, NULL за непозната валута) и подът от €1000 са покрити с тестове. Добавени tie-breaker-и вORDER BYпремахват недетерминизъм приLIMIT.
Незадължителни забележки (не блокиращи):
- Разминаване в обхвата спрямо заглавието. Няколко партиди отбелязват, че commit-ът е обозначен
docs(web), но реалното съдържание е предимно нов функционален код, route-ове и голям CSS. Особено внимание заслужаваpages.css(+3080/−3), чийто diff не беше достъпен за преглед — препоръчва се ревюиращ с достъп до пълния diff да потвърди, че разрастването е свързано и оправдано, без нежелано включен генериран/минифициран код. - Дублирана логика. Изчислението на
yearStart('-Q1'/'-01'/null) е копирано дословно междуTrendChart.tsxиComboTrendChart.tsx— струва си да се изнесе в общ helper. - Форма на моковете в тестове. В
analytics.test.tsнякои мокове не съвпадат с типизираните контракти (напр.getOpaqueShareByYear), тъй че деривацията на „opaque" не се упражнява реално — тестът минава, но не пази маппинга в loader-а (loader-ът е в друга партида). - Крос-партидни зависимости за проследяване:
RESERVED_CACHE_PARAMSизискваgда е добавен вCANONICAL_QUERY_PARAMS(вquery-params.ts, друга партида), иначе тестът „keeps RESERVED_CACHE_PARAMS honest" ще падне; премахнатите стилове.analytics-lenses/.lens-*предполагат съответно премахване на JSX разметката в друга партида. corpus.countвgetOverrunsAnalyticsидва от единичен проход без JOIN, докато лидербордът/byAuthorityползват INNER JOIN — договор без свързан участник/възложител би влязъл в корпусния брой, но не в разбивката. Документирано е като умишлено; отбелязва се за пълнота.
Заключение: Няма блокиращи концерни по сигурност, коректност или интегритет на данните. Основната препоръка преди сливане е да се потвърди голямата, непрегледана CSS промяна (pages.css) и да се изясни/коригира разминаването между типа docs и реалния функционален обхват на PR-а.
lyubomir-bozhinov
left a comment
There was a problem hiding this comment.
Прегледах #193 при HEAD f39ab996. Delta-та е review-fix код (не самият doc) — cross-check-нах кода срещу methodology дока, няма drift. estimateYoyGrowth adjacent-year fix (analytics-stats.ts:189): if (curYear - prevYear !== 1) continue коректно спира gap-година (2021→2023 без 2022) да се чете като едногодишен скок — поправя публикуван growth rate, fail-safe към flat, съвпада с „3-year trailing median of complete-year ratios" (analytics.tsx). getCpvGroupMedians fan-out cap (trend.ts) преизползва validCpvGroups (MAX_CPV_GROUPS=40), консистентно с getSpendingTrend. Вердикт: чисто.
# Conflicts: # apps/web/app/components/TrendChart.tsx # apps/web/app/lib/query-params.ts # apps/web/app/routes/analytics.tsx # apps/web/app/routes/trends.tsx # apps/web/app/styles/pages.css # apps/web/workers/cache-key.test.ts # packages/db/src/queries/competition.test.ts # packages/db/src/queries/flows.test.ts # packages/db/src/queries/regions.test.ts # packages/db/src/queries/trend.test.ts
…cInfo, fullscreen hook, growth stats and sparse cpv stats Exports CPV_DIVISION_SET (matches pr/overruns) so the bucket fallthrough is testable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…а на opaque мока; общ yearAxisTicks; поправена номерация в методологията Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Какво
Разширява
/methodologyс нов раздел „7. Аналитичните изгледи: какво показват и как се смятат" — по едно описание за всяко аналитично табло, сверено ред по ред срещу кода на loader-ите и заявките, а не преразказано по памет.За всяко табло: какво показва визуалът, формулата с думи, обхватът и изключенията, и честните уговорки — в съществуващия тон (неутрален, описателен, „ориентир, не заключение"), на български, с препратки към речника и раздела за стойността.
Обхванати табла: Анализи (индекс), Договори — обзор (три ъгъла + типични цени по CPV), Конкуренция (една оферта + HHI), Раздуване след анекси, Потоци, Карта, Мрежа, Договори.
Ключови сверявания срещу кода:
bids_received = 1, само с известен брой оферти, ≥ 20 договора); HHI = сбор от квадратите на дяловете (0–1, ≥ 0,25 висока концентрация, ≥ 2 доставчика).Обхват
Основно документация —
methodology.tsxе единственият файл, променен от първоначалния commit.Ревюто по-долу добави осем дребни code fixes в извън-documentation файлове, открити при
сверката с кода (виж „Ревю"); никой от тях не променя поведение извън описаните поправки.
Проверки
pnpm --filter @sigma/web typecheck— зеленоprettier --check .— зеленоpnpm --filter @sigma/web test— 377 теста зелениРевю (the reviewer, 2026-07-20)
12 коментара — всички разгледани и затворени:
38847f9(isomorphic layout-effect вMetricInfo, коректнотретиране на partial точка на индекс 0 в
ComboTrendChart, консистентен||fallback за ЕИКв
overruns.tsx, защита от деление на нула и dedup на CPV групите вtrends.tsx, поправениCSS маркер/дублиран селектор, премахнат неизползван import в
cache-key.ts) иaeb950b(именуван
authorityEikalias наauthoritySlugвidentity.tsза яснота — стойността оставанепроменена).
analytics-stats.ts,агрегацията година↔месец в
estimateYoyGrowth, анкорите#source/#moneyвmethodology.tsx) — обяснени в отговор на съответните нишки.Всичките 12 нишки са resolved.
Зависимости
Draft: зависи от стека #169–#172, защото описва таблата, които те въвеждат (петте карти на
/analytics, обзорът на/trends,/overruns). Ще се извади от draft, след като стекът се слее.Вид промяна
fix— поправка на бъгfeat— нова функционалностdocs— документацияrefactor/perf/style— без промяна в поведениетоtest/ci/build/chore— поддръжкаЧеклист
Co-Authored-By:, вече публикувани, не се пренаписват) — виж „Бележка при merge“ по-долуmidt-bg/sigma:mainpnpm typecheckминава (CI: стъпка Typecheck е зелена)pnpm testминава (CI: стъпка Test е зелена)pnpm lintе чисто (CI: стъпка Lint е зелена).env*или.dev.vars(gitleaks + semgrep са зелени)docs/е обновена — не е приложимо за тази промянаБележка при merge
2 комита в този branch носят
Co-Authored-By:trailer към агент (Claude). AGENTS.md го забранява наmain, а squash-merge запазва trailer-ите от комитите. Историята не се пренаписва (без rebase/force-push), затова при merge моля заменете автоматично генерирания commit message със заглавието и тялото по-долу. Няма trailer-и към хора в този branch (всички комити са от автора на PR-а), така че не се губи credit.Заглавие (subject):
Тяло (body):