feat(web): „Подобни договори" - ценови ориентир по CPV кохорта на страницата на договора#210
feat(web): „Подобни договори" - ценови ориентир по CPV кохорта на страницата на договора#210B353N wants to merge 3 commits into
Conversation
|
Проверих PR #210 из основи локално — целия diff, SQL пътищата, заявките в РезюмеЧиста, добре обхваната функционалност: предизчислен rollup Сигурност / OWASP
Цялост на данните
Съответствие с описаниетоИмплементацията отговаря на PR описанието изцяло: rollup в двата пътя, четене на един ред вместо сканиране на дивизия (D1 таксува прочетени редове), праг 12, широки стъпала без фалшива точност, методология. Няма пряк issue — допълва посоката на #127/#188. Дребни бележки (незадължителни, не блокират)
ВердиктApprove на същество — сигурност и цялост на данните чисти, SQL параметризиран, покритието е силно; двете бележки са козметични и не блокират мърджа. |
Ревю на PR #210 — „Подобни договори“ (ценови ориентир по CPV кохорта)Благодаря за спретнатата и добре мотивирана промяна. Прегледах я обстойно с фокус върху сигурност, интегритет на данните и съответствие с OWASP; също прекарах дифа през локална проверка за SQL/XSS и злонамерени примеси. По-долу са наблюденията. Сигурност (OWASP)
Интегритет на данните
Тестове / качествоПокритие на нулевите пътища (suspect / NULL / нула / липсващ CPV / малка кохорта), гранична инклузивност на бандовете, DTO мапинг и парити между двата SQL пътя. Миграция Дребни бележки (не блокери)
Няма намерени уязвимости, изтичане на ресурси или следи от злонамерен код. Промяната е готова за merge. Вердикт: APPROVE (на същество) — сигурност и интегритет на данните чисти, OWASP-съвместимо; дребните бележки са по желание. |
|
Ре-проверих ( Неточност (medium): §4c на Low: „сред N договора" брои чистата кохорта, а линкът „Виж договорите в сектора →" води към Cross-PR: |
nedda76
left a comment
There was a problem hiding this comment.
Прегледах — PR-ът е внимателно направен: коректна nearest-rank статистика (буферът +0.9999999 безопасно поема IEEE-754 грешката), филтриране само по каноничен EUR (amount_eur IS NOT NULL AND > 0 AND value_flag='ok', което точно съвпада с rollup-а), параметризиран SQL, стабилен ROW_NUMBER() ... ORDER BY amount_eur, c.id, и неутрална формулировка („контекст на мащаба, не оценка за нередност"). Три бележки:
-
(потвърдено) Колапс на горните ленти при малка кохорта. Стълбицата стига до топ 1%/5%, но подът е
MIN_COHORT=12. При N≈12 nearest-rank даваp95 = p99 = max, тоест най-скъпият от 12 договора получава етикет „в най-горния 1%" — фалшива точност, точно обратното на целта на PR-а („никога фалшива точност"). Показвай фините ленти (топ 1%/5%) само когато N е достатъчно голямо, за да ги различи (anomaly-report.mjsсъщо отбелязва, че кохорти 12–20 са крехки в горния край). -
(вероятно) Кохортата е само по 2-цифрен CPV раздел. Раздел 45 „Строителство" слага ремонт за 5 хил. до автомагистрала за 50 млн. в една група — процентилната мрежа смесва структурно различна работа. Съзнателен компромис (цена на precompute/D1 rows-read) и UI-ят е внимателен, но „подобни договори" обещава по-тясна група, отколкото реално има. Пълен CPV код / CPV клас би бил по-тесен, но взривява кардиналността на rollup-а.
-
(вероятно) Излишно четене на договора на SSR hot path —
getContractCohortпречита реда на договора (amount_eur,value_flag, CPV), койтоgetContractвече е взел. ВPromise.allса, тоест латентността е скрита, но D1 таксува прочетени редове (192k страници + sitemap). Подай вече заредения договор на cohort helper-а вместо второ четене.
Координация: #210 и #212 добавят по един 0002_* migration — който се мерджне втори, трябва да се преномерира на 0003, иначе един от двата тихо няма да се приложи в прод D1.
nedda76
left a comment
There was a problem hiding this comment.
Допълнение към прегледа (дълбок пас). Няколко находки, по-сериозни от първоначалната ми бележка за „фалшива точност при N≈12" — те я включват и я заменят.
Критични — грешни етикети върху обичайни данни
-
Колапс при равни стойности → всичко е „в най-горния 1%". Каскадата в
cohortBandе с>=, затова при равни стойности на/над p99 order statistic всеки такъв договор получава „в най-горния 1% по стойност". Раздел от 200 стандартизирани договора на еднаква цена (100 000 €) етикетира и 200-те като „топ 1%". Стреля при всяко N — а еднакви цени при рамкови/типови поръчки са често срещани. По-драматично от познатия N≈12 колапс. -
Най-скъпият в кохортата винаги е „топ 1%" (self-inclusion) + фалшива точност при пода. Договорът е в собствената си кохорта (не leave-one-out), тоест максимумът е по дефиниция „топ 1%"; при N=12 това е ~топ 8%, показано като „топ 1%". Собственият
anomaly-report.mjsдокументира, че self-inclusion е ненадежден точно при 12–20 реда. Уговорката живее само в коментарите в кода — в UI-я я няма (обратно на твърдението в PR описанието), затова читателят не знае, че договорът се сравнява със себе си. -
Грешен етикет за медианата. Nearest-rank медианата е долно-средната стойност; с
>=договор в долната половина при четно N получава „над медианата", а показаната „медиана" е изместена надолу с една стъпка. Тестът го фиксира като очаквано поведение.
Висок приоритет
- Най-тежкото изчисление е в споделения
globalsrefresh batch. Пълният ~150k-редов window sort е добавен към@refresh-batch globals(който вече правиdata_freshness/home_totals/sector_totals/facet_counts), вместо в собствен@refresh-batch cohort-stats, както другите тежки rollup-и. Рискува да пробие CPU бюджета на едната D1 заявка и да провали целия globals step.
Средно
- Две несъвместими дефиниции на процентил за едни и същи CPV раздели: тук nearest-rank + self-inclusion; в
anomaly-report.mjsлинейна интерполация + leave-one-out. Една и съща „медиана"/„p95" дава различни числа на два продуктови екрана. PR описанието признава само разликата leave-one-out, не и nearest-rank vs интерполация.
Чисто (проверено, за фокус)
Параметризиран SQL; division идва от DB реда, не от заявката; предикатът за членство в кохортата е byte-еднакъв между precompute и read-side (показан договор винаги е реален член); ceil емулацията е коректна; refresh parity (INSERT) е фиксиран с тест; Promise.all — без латентностна регресия; stats четенето е O(1) по PK върху ≤46-редова таблица.
Вердикт: Request Changes
#1–#3 карат водещата функция да показва уверено грешни етикети върху обичайни данни — най-лошият режим за платформа за прозрачност, и нарушава собственото обещание „никога фалшива точност". Всички са евтини за поправка: показвай фините ленти (топ 1%/5%/медиана) само когато съответните персентили реално се различават и кохортата е достатъчно голяма; ползвай строго > / leave-one-out където е редно; премести recompute-а в собствен @refresh-batch. Основата (parity, сигурност, дизайн на precompute) е стабилна — това е поправка на логиката за етикети и на ETL разположението, не пренаписване.
(Заменя по-меката ми бележка „фалшива точност при N≈12" — тя е частен случай на #1/#2.)
Какво и защо
Добавя секция „Подобни договори" на страницата на договора - разбираем ценови контекст на всяка поръчка: „1 000 000 € е в топ 5% по стойност сред 214 договора в сектор „Строителство" (CPV 45); медианата за сектора е 112 000 €." Различно от рисковите флагове (#127) и health индекса (#188): те казват „нещо е съмнително", това отговаря на „голяма ли е тази сума за този вид поръчка".
Как работи:
cpv_division_stats(миграция0002): персентили на стойността (p25/медиана/p75/p90/p95/p99) по двуцифрена CPV дивизия, nearest-rank (k = ceil(q·n)), само върху „чистата" кохорта -value_flag = 'ok',amount_eur > 0, известен CPV. Преизгражда се изцяло и в двата пътя:scripts/precompute.sql(§4c) иscripts/refresh-slice.sql(в batchglobals, приsector_totals/facet_counts) - тест пази двете копия идентични.@sigma/db(queries/cohort.ts):getContractCohortвръща бенчмарк само когато сравнението е честно - чиста стойност, наличен CPV, кохорта от поне 12 договора (същия праг като anomaly report-а). Договори с непотвърдена стойност не получават сравнение.scripts/anomaly-report.mjs, който е праг за флагване, не дисплей). UI-ят изрично казва, че това е контекст за мащаба, не оценка за нередност.scripts/compare-served-sqlite.mjsиdocs/etl-pipeline-state.mdвключват новата таблица.Свързан issue
Няма пряк issue; допълва посоката на #127 (рискови флагове) и #188 (health индекс) с неутрален ценови контекст.
Вид промяна
feat— нова функционалностКак е тествано
pnpm typecheck- минава (всички пакети).pnpm test- минава. Нови тестове: поведенчески SQL тест (precompute-cohort.test.ts) прилага миграциите, зарежда корпус със шум (suspect/NULL/нулеви стойности, липсващ CPV) и проверява nearest-rank персентилите на реален sqlite3 + идемпотентност + парити между precompute и refresh-slice; unit тестове заcohortBand/getContractCohort(нулеви пътища: suspect, малка кохорта, липсващ CPV).pnpm lint- чисто.Чеклист
Co-Authored-By:trailermidt-bg/sigma:mainpnpm typecheckминаваpnpm test(поне за засегнатите пакети) минаваpnpm lintе чисто.env*или.dev.varsdocs/etl-pipeline-state.md)