a6c03a091e
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)
234 lines
7.2 KiB
PL/PgSQL
234 lines
7.2 KiB
PL/PgSQL
-- 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;
|