diff --git a/apps/web/app/components/RiskIndicators.tsx b/apps/web/app/components/RiskIndicators.tsx
index 59e1568a..28cecba3 100644
--- a/apps/web/app/components/RiskIndicators.tsx
+++ b/apps/web/app/components/RiskIndicators.tsx
@@ -1,7 +1,13 @@
import type { ContractDetail } from '@sigma/api-contract';
+import { Link } from 'react-router';
import { pct } from '@sigma/shared';
import { evaluateRiskIndicators } from '../lib/riskLogic';
+// The per-contract risk signals. Copy is deliberately non-accusatory (#219): each item states the
+// structural fact and frames it as a signal that warrants a look, never as proven wrongdoing. The
+// note below repeats the disclaimer next to the flags and links to the full methodology, so the
+// caveat travels with the flags wherever they appear (issue #219 co-requisite of the risk numbers).
+// Labels mirror methodology §10 and the homepage FLAG_LABELS for a consistent vocabulary.
export function RiskIndicators({ contract }: { contract: ContractDetail }) {
const flags = evaluateRiskIndicators(contract);
@@ -27,22 +33,23 @@ export function RiskIndicators({ contract }: { contract: ContractDetail }) {
+ Сигналите са ориентири за преглед, не присъда — отбелязват договор, който заслужава + внимание, а не доказано нарушение.{' '} + Как четем сигналите → + {' · '} + Смятате сигнал за грешен? → +
); } diff --git a/apps/web/app/routes/contract.tsx b/apps/web/app/routes/contract.tsx index f3e393c5..8eb1175b 100644 --- a/apps/web/app/routes/contract.tsx +++ b/apps/web/app/routes/contract.tsx @@ -1,5 +1,14 @@ import { Link } from 'react-router'; -import { count, longDate, money, moneyBare, plural, signedMoney, signedPct } from '@sigma/shared'; +import { + count, + isNaturalPersonProfileName, + longDate, + money, + moneyBare, + plural, + signedMoney, + signedPct, +} from '@sigma/shared'; import { contractIdFromSlug, contractSlug, getContract, getDb } from '@sigma/db'; import type { CohortBand, ContractDetail } from '@sigma/api-contract'; import type { Route } from './+types/contract'; @@ -71,7 +80,7 @@ function AnnexDescription({ text }: { text: string | null }) { export function meta({ data, params, matches }: Route.MetaArgs) { const c = data?.contract; - return seoMeta({ + const tags = seoMeta({ matches, path: `/contracts/${contractSlug(contractIdFromSlug(params.id))}`, title: `${c?.subject ?? 'Договор'} — СИГМА`, @@ -79,6 +88,15 @@ export function meta({ data, params, matches }: Route.MetaArgs) { ? `Договор по УНП ${c.unp} между ${c.authority.name} и ${c.bidder.displayName}.` : '', }); + // GDPR/ЗЗЛД (#219 review): when the bidder is a sole-trader (ЕТ) / natural person, keep this contract + // page — which carries the „Сигнали за риск" box — out of search indexes, mirroring the noindex on + // sole-trader company profiles (company.tsx) and their exclusion from the sitemap. The contract stays + // fully public on the site; only search-engine amplification of a named individual + risk label is + // avoided. + if (c && isNaturalPersonProfileName(c.bidder.displayName)) { + tags.push({ name: 'robots', content: 'noindex' }); + } + return tags; } export function headers() { diff --git a/apps/web/app/routes/methodology.tsx b/apps/web/app/routes/methodology.tsx index 2b4aa857..7d5cd791 100644 --- a/apps/web/app/routes/methodology.tsx +++ b/apps/web/app/routes/methodology.tsx @@ -37,6 +37,7 @@ const TOC = [ ['identity', 'Имена, ЕИК, УНП'], ['gaps', 'Известни празнини в полетата'], ['export', 'Сваляне и достъп до данните'], + ['flagged', 'Сигнали за риск'], ['contact', 'Поправки и обратна връзка'], ]; @@ -143,8 +144,10 @@ export default function Methodology({ loaderData }: Route.ComponentProps) {- СИГМА е изцяло само за четене: не въвежда нови данни, не оценява процедурите и не - маркира фирми като рискови. + СИГМА е изцяло само за четене: не въвежда нови данни и не оценява процедурите. Тя + отбелязва договори със структурни сигнали за преглед (виж{' '} + раздел 10), но не обявява фирми или възложители за нарушители + — сигналът е ориентир, не присъда.
@@ -530,8 +533,62 @@ export default function Methodology({ loaderData }: Route.ComponentProps) {
Засега не предлагаме публично API в реално време.
++ На страницата на всеки договор СИГМА отбелязва{' '} + структурни сигнали за риск, когато поне един признак заслужава + преглед. Сигналите са ориентири, не присъда: те не твърдят нарушение, а + насочват към договори, които си струва да се погледнат по-внимателно. +
+Отбелязваме четири признака:
++ Допусната е една-единствена оферта (приети оферти = 1), без финансиране от ЕС. +
+ → приети оферти = 1 ++ Същият признак, но при финансиране от ЕС, където правилата за конкуренция са + по-строги — затова го отделяме. +
+ → приети оферти = 1 · финансиране от ЕС ++ Текущата стойност е нараснала с над 20% спрямо стойността при подписване — + индикатор за оскъпяване след сключването. +
+ → текуща стойност > 1.2 × стойност при подписване ++ Стойност с непотвърдена достоверност (виж речника) или договор, подписан повече + от два дни след датата си на публикуване. Двудневният допуск поема обичайното + забавяне при въвеждане; отвъд него редът на датите остава необяснен от + източника. +
+ → value_flag / date_flag ++ Ограничения. Сигналите стъпват на публични данни и структурни + признаци, не на разследване. Възможни са грешки или празнини в източника (виж{' '} + раздел 8), затова един сигнал може да е фалшив — например + законна процедура с една оферта по обективни причини. Сигнал не е констатация за + нарушение и не оценява поведението на конкретно лице. Ако смятате конкретен сигнал + за неточен, пишете ни (раздел 11). +
+Грешките поправяме ръчно при сигнал — двойни записи за институция/компания (изпратете двата ЕИК/линка) или сума, която не отговаря на оригиналния документ diff --git a/apps/web/app/styles/pages.css b/apps/web/app/styles/pages.css index fd6341d3..b317a413 100644 --- a/apps/web/app/styles/pages.css +++ b/apps/web/app/styles/pages.css @@ -102,6 +102,15 @@ .risk-list li:last-child { margin-bottom: 0; } +/* Non-accusatory disclaimer that travels with the per-contract flags (#219). */ +.risk-note { + margin: var(--s-4) 0 0; + padding-top: var(--s-3); + border-top: 1px solid var(--warning); +} +.risk-note a { + color: var(--warning-strong); +} /* Value-history strip + current lot row */ .value-history {