initial: split from gov-agreg — vreau.digital standalone platform

Moved from gov-agreg/src/pages/achizitii/* to root (drop prefix).
- 22 pages migrated, 127 files total
- All internal links: /achizitii/X → /X (176 occurrences fixed)
- AchizitiiLayout subnav rewritten: /X paths, top-right link to vreaudigital.ro hub
- BaseLayout new (vreau.digital branding, OG tags, site URL)
- astro.config.mjs: site https://vreau.digital, server output (was static)
- docker-compose: port 5096 (vreaudigital is 5095), container vreau-digital
- deploy.sh: paths /opt/vreau-digital, log /var/log/vreau-digital-deploy.log

Backend shared with gov-agreg:
- PostgreSQL satra (same schemas: seap, firms, anaf, anre, ...)
- Photon, Martin tiles
- Infisical /vreaudigital path (DATABASE_URL etc. shared)

build: PASS (npx astro check 0 errors, npm run build 5s vite + 10s server)
This commit is contained in:
Claude VM
2026-05-13 00:10:32 +03:00
commit a6c03a091e
352 changed files with 75295 additions and 0 deletions
@@ -0,0 +1,233 @@
-- Risk flags (red flags) for procurement transparency, based on OCP indicators.
-- Idempotent: safe to re-run.
BEGIN;
-- ── Column on announcements ──
ALTER TABLE seap.announcements
ADD COLUMN IF NOT EXISTS risk_flags JSONB;
CREATE INDEX IF NOT EXISTS idx_ann_risk_flags
ON seap.announcements USING gin(risk_flags)
WHERE risk_flags IS NOT NULL AND jsonb_array_length(risk_flags) > 0;
-- ── Materialized view: per-CPV-division median awarded value ──
DROP MATERIALIZED VIEW IF EXISTS seap.mv_cpv_median_value CASCADE;
CREATE MATERIALIZED VIEW seap.mv_cpv_median_value AS
SELECT
cpv_division,
count(*)::int AS contracts,
percentile_cont(0.5) WITHIN GROUP (ORDER BY awarded_value)::numeric(15,2) AS median_value,
avg(awarded_value)::numeric(15,2) AS avg_value,
percentile_cont(0.95) WITHIN GROUP (ORDER BY awarded_value)::numeric(15,2) AS p95_value
FROM seap.announcements
WHERE awarded_value IS NOT NULL
AND awarded_value > 0
AND cpv_division IS NOT NULL
GROUP BY cpv_division
HAVING count(*) >= 5;
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_cpv_median_pk
ON seap.mv_cpv_median_value(cpv_division);
-- ── Materialized view: authority supplier concentration (top supplier % of yearly value) ──
DROP MATERIALIZED VIEW IF EXISTS seap.mv_authority_concentration CASCADE;
CREATE MATERIALIZED VIEW seap.mv_authority_concentration AS
WITH yearly_pairs AS (
SELECT
a.authority_cui,
MIN(a.authority_name) AS authority_name,
EXTRACT(YEAR FROM a.publication_date)::int AS year,
a.supplier_cui,
MIN(a.supplier_name) AS supplier_name,
SUM(a.awarded_value)::numeric(15,2) AS total_value,
COUNT(*)::int AS contracts
FROM seap.announcements a
WHERE a.authority_cui IS NOT NULL
AND a.supplier_cui IS NOT NULL
AND a.awarded_value IS NOT NULL
AND a.awarded_value > 0
AND a.publication_date IS NOT NULL
AND a.publication_date >= now() - interval '36 months'
GROUP BY a.authority_cui, EXTRACT(YEAR FROM a.publication_date), a.supplier_cui
),
yearly_totals AS (
SELECT
authority_cui,
year,
SUM(total_value) AS year_total,
SUM(contracts) AS year_contracts
FROM yearly_pairs
GROUP BY authority_cui, year
),
ranked AS (
SELECT
p.authority_cui,
p.authority_name,
p.year,
p.supplier_cui,
p.supplier_name,
p.total_value,
p.contracts,
t.year_total,
t.year_contracts,
ROW_NUMBER() OVER (PARTITION BY p.authority_cui, p.year ORDER BY p.total_value DESC) AS rn,
(p.total_value / NULLIF(t.year_total, 0))::numeric(6,4) AS share
FROM yearly_pairs p
JOIN yearly_totals t USING (authority_cui, year)
)
SELECT
authority_cui,
authority_name,
year,
supplier_cui AS top_supplier_cui,
supplier_name AS top_supplier_name,
total_value AS top_supplier_value,
contracts AS top_supplier_contracts,
year_total,
year_contracts,
share AS top_supplier_share
FROM ranked
WHERE rn = 1
AND year_total >= 100000; -- skip tiny totals (noise)
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_auth_conc_pk
ON seap.mv_authority_concentration(authority_cui, year);
CREATE INDEX IF NOT EXISTS idx_mv_auth_conc_share
ON seap.mv_authority_concentration(top_supplier_share DESC NULLS LAST);
-- ── View: single-bidder contracts ──
DROP VIEW IF EXISTS seap.v_single_bidder CASCADE;
CREATE VIEW seap.v_single_bidder AS
SELECT a.*
FROM seap.announcements a
WHERE a.type = 'ca_notice'
AND (
a.num_offers = 1
OR (
a.details IS NOT NULL
AND jsonb_typeof(a.details->'all_winners') = 'array'
AND jsonb_array_length(a.details->'all_winners') = 1
)
);
-- ── Function: compute risk flags for a single announcement ──
-- Returns JSONB array of { code, severity, label, detail? }
CREATE OR REPLACE FUNCTION seap.compute_announcement_flags(
p_id BIGINT
) RETURNS JSONB
LANGUAGE plpgsql
AS $$
DECLARE
rec RECORD;
flags JSONB := '[]'::jsonb;
v_median NUMERIC;
BEGIN
SELECT a.id, a.type, a.publication_date, a.deadline_submission,
a.awarded_value, a.estimated_value, a.cpv_division,
a.num_offers, a.details
INTO rec
FROM seap.announcements a WHERE a.id = p_id;
IF NOT FOUND THEN RETURN NULL; END IF;
-- 1) Single bidder (only meaningful for ca_notice with winner data)
IF rec.type = 'ca_notice' THEN
IF rec.num_offers = 1 THEN
flags := flags || jsonb_build_object(
'code', 'single_bidder',
'severity', 'high',
'label', 'Un singur ofertant'
);
ELSIF rec.details IS NOT NULL
AND jsonb_typeof(rec.details->'all_winners') = 'array'
AND jsonb_array_length(rec.details->'all_winners') = 1 THEN
flags := flags || jsonb_build_object(
'code', 'single_bidder',
'severity', 'high',
'label', 'Un singur câștigător'
);
END IF;
END IF;
-- 2) Short deadline (only c_notice / rfq_invitation have submission deadlines)
IF rec.type IN ('c_notice','rfq_invitation')
AND rec.publication_date IS NOT NULL
AND rec.deadline_submission IS NOT NULL
AND (rec.deadline_submission - rec.publication_date) < interval '10 days' THEN
flags := flags || jsonb_build_object(
'code', 'short_deadline',
'severity', 'medium',
'label', 'Termen scurt',
'detail', EXTRACT(EPOCH FROM (rec.deadline_submission - rec.publication_date))/86400.0
);
END IF;
-- 3) Suspicious savings: awarded_value < 50% of estimated
IF rec.awarded_value IS NOT NULL
AND rec.estimated_value IS NOT NULL
AND rec.awarded_value > 0
AND rec.estimated_value > 0
AND rec.awarded_value < 0.5 * rec.estimated_value THEN
flags := flags || jsonb_build_object(
'code', 'suspicious_savings',
'severity', 'medium',
'label', 'Economii suspecte',
'detail', round(100.0 * (1 - rec.awarded_value / rec.estimated_value))::int
);
END IF;
-- 5) Overprice: awarded_value > 2 * median per CPV division
IF rec.awarded_value IS NOT NULL
AND rec.awarded_value > 0
AND rec.cpv_division IS NOT NULL THEN
SELECT median_value INTO v_median
FROM seap.mv_cpv_median_value
WHERE cpv_division = rec.cpv_division;
IF v_median IS NOT NULL AND v_median > 0
AND rec.awarded_value > 2 * v_median THEN
flags := flags || jsonb_build_object(
'code', 'overprice',
'severity', 'medium',
'label', 'Peste piață',
'detail', round((rec.awarded_value / v_median)::numeric, 1)
);
END IF;
END IF;
RETURN flags;
END;
$$;
-- ── Function: refresh all risk-related materialized views ──
CREATE OR REPLACE FUNCTION seap.refresh_risk_views()
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_cpv_median_value;
EXCEPTION WHEN OTHERS THEN
REFRESH MATERIALIZED VIEW seap.mv_cpv_median_value;
END;
$$;
CREATE OR REPLACE FUNCTION seap.refresh_concentration()
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_authority_concentration;
EXCEPTION WHEN OTHERS THEN
REFRESH MATERIALIZED VIEW seap.mv_authority_concentration;
END;
$$;
COMMIT;
-- Initial population (non-transactional)
REFRESH MATERIALIZED VIEW seap.mv_cpv_median_value;
REFRESH MATERIALIZED VIEW seap.mv_authority_concentration;