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:
@@ -0,0 +1,396 @@
|
||||
// Utility/regulator profile helpers — ANRE (energy), ANCOM (telco), CNSC (procurement
|
||||
// contestations). Each returns null when the CUI has no presence in the source so the
|
||||
// UI can skip the badge/section entirely. Mirrors the style of the existing helpers
|
||||
// in profile-queries.ts (RegAS / AEP / ANAF). Always uses query() from './db.js' and
|
||||
// PostgreSQL parameter binding ($1) with the standard normCui regex normalization.
|
||||
import { query } from './db.js';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// ANRE — Autoritatea Națională de Reglementare în domeniul Energiei.
|
||||
// Source: anre.licente (corporate licenses, 3 sub-registries) and the
|
||||
// per-CUI rollup anre.mv_licente_per_cui.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AnreLicentaPreview {
|
||||
license_source: string; // 'electricitate' | 'gaze' | 'atestat'
|
||||
license_no: string;
|
||||
license_type: string | null; // 'Licenta' | 'Autorizatie de Infiintare' | 'Atestat' | …
|
||||
license_subtype: string | null; // 'Furnizare' | 'Producere' | 'Distributie' | …
|
||||
stare: string | null; // 'Acordata' | 'Expirata' | 'Retrasa' | …
|
||||
data_emitere: string | null;
|
||||
data_expirare: string | null;
|
||||
}
|
||||
|
||||
export interface AnreStatus {
|
||||
nr_licente_total: number;
|
||||
nr_active: number;
|
||||
nr_expirate: number;
|
||||
nr_retrase: number;
|
||||
nr_electricitate: number;
|
||||
nr_gaze: number;
|
||||
nr_atestate: number;
|
||||
surse: string[];
|
||||
subtipuri: string[];
|
||||
prima_emitere: string | null;
|
||||
ultima_emitere: string | null;
|
||||
ultima_expirare: string | null;
|
||||
recent: AnreLicentaPreview[];
|
||||
}
|
||||
|
||||
export async function getAnreStatus(cui: string): Promise<AnreStatus | null> {
|
||||
const normCui = cui.replace(/^RO\s*/i, '').trim();
|
||||
const aggR = await query<any>(
|
||||
`SELECT nr_licente_total::int,
|
||||
nr_active::int,
|
||||
nr_expirate::int,
|
||||
nr_retrase::int,
|
||||
nr_electricitate::int,
|
||||
nr_gaze::int,
|
||||
nr_atestate::int,
|
||||
surse,
|
||||
subtipuri,
|
||||
to_char(prima_emitere, 'YYYY-MM-DD') AS prima_emitere,
|
||||
to_char(ultima_emitere, 'YYYY-MM-DD') AS ultima_emitere,
|
||||
to_char(ultima_expirare, 'YYYY-MM-DD') AS ultima_expirare
|
||||
FROM anre.mv_licente_per_cui
|
||||
WHERE cui = $1`,
|
||||
[normCui],
|
||||
);
|
||||
const a = aggR.rows[0];
|
||||
if (!a || Number(a.nr_licente_total) === 0) return null;
|
||||
|
||||
const recentR = await query<AnreLicentaPreview>(
|
||||
`SELECT license_source,
|
||||
license_no,
|
||||
license_type,
|
||||
license_subtype,
|
||||
stare,
|
||||
to_char(data_emitere, 'YYYY-MM-DD') AS data_emitere,
|
||||
to_char(data_expirare, 'YYYY-MM-DD') AS data_expirare
|
||||
FROM anre.licente
|
||||
WHERE titular_cui = $1
|
||||
ORDER BY (CASE WHEN stare ILIKE 'Acord%' OR stare ILIKE 'Activ%' THEN 0 ELSE 1 END),
|
||||
data_emitere DESC NULLS LAST
|
||||
LIMIT 10`,
|
||||
[normCui],
|
||||
);
|
||||
|
||||
return {
|
||||
nr_licente_total: Number(a.nr_licente_total),
|
||||
nr_active: Number(a.nr_active) || 0,
|
||||
nr_expirate: Number(a.nr_expirate) || 0,
|
||||
nr_retrase: Number(a.nr_retrase) || 0,
|
||||
nr_electricitate: Number(a.nr_electricitate) || 0,
|
||||
nr_gaze: Number(a.nr_gaze) || 0,
|
||||
nr_atestate: Number(a.nr_atestate) || 0,
|
||||
surse: a.surse || [],
|
||||
subtipuri: a.subtipuri || [],
|
||||
prima_emitere: a.prima_emitere,
|
||||
ultima_emitere: a.ultima_emitere,
|
||||
ultima_expirare: a.ultima_expirare,
|
||||
recent: recentR.rows,
|
||||
};
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// ANCOM — Autoritatea Națională pentru Administrare și Reglementare
|
||||
// în Comunicații. CUI is direct on the registry detail page.
|
||||
// Source: ancom.operatori + ancom.mv_operatori_per_cui (rollup).
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AncomOperatorPreview {
|
||||
ancom_id: number;
|
||||
titular_name: string;
|
||||
status: string;
|
||||
judet: string | null;
|
||||
detail_url: string;
|
||||
}
|
||||
|
||||
export interface AncomStatus {
|
||||
nr_autorizatii: number;
|
||||
retele: string[]; // ['R1','R3', …] (codes)
|
||||
servicii: string[]; // ['S1','S2', …] (codes)
|
||||
are_internet_fix: boolean;
|
||||
are_mobil: boolean;
|
||||
are_fibra: boolean;
|
||||
are_status_activ: boolean;
|
||||
prima_autorizare: string | null;
|
||||
ultima_autorizare: string | null;
|
||||
operatori: AncomOperatorPreview[];
|
||||
}
|
||||
|
||||
export async function getAncomStatus(cui: string): Promise<AncomStatus | null> {
|
||||
const normCui = cui.replace(/^RO\s*/i, '').trim();
|
||||
const aggR = await query<any>(
|
||||
`SELECT nr_autorizatii::int,
|
||||
retele,
|
||||
servicii,
|
||||
are_internet_fix,
|
||||
are_mobil,
|
||||
are_fibra,
|
||||
are_status_activ,
|
||||
to_char(prima_autorizare, 'YYYY-MM-DD') AS prima_autorizare,
|
||||
to_char(ultima_autorizare, 'YYYY-MM-DD') AS ultima_autorizare
|
||||
FROM ancom.mv_operatori_per_cui
|
||||
WHERE cui = $1`,
|
||||
[normCui],
|
||||
);
|
||||
const a = aggR.rows[0];
|
||||
if (!a || Number(a.nr_autorizatii) === 0) return null;
|
||||
|
||||
const opR = await query<AncomOperatorPreview>(
|
||||
`SELECT ancom_id,
|
||||
titular_name,
|
||||
status,
|
||||
judet,
|
||||
detail_url
|
||||
FROM ancom.operatori
|
||||
WHERE titular_cui = $1
|
||||
ORDER BY (CASE WHEN status = 'autorizat' THEN 0 ELSE 1 END), ancom_id
|
||||
LIMIT 5`,
|
||||
[normCui],
|
||||
);
|
||||
|
||||
return {
|
||||
nr_autorizatii: Number(a.nr_autorizatii),
|
||||
retele: a.retele || [],
|
||||
servicii: a.servicii || [],
|
||||
are_internet_fix: !!a.are_internet_fix,
|
||||
are_mobil: !!a.are_mobil,
|
||||
are_fibra: !!a.are_fibra,
|
||||
are_status_activ: !!a.are_status_activ,
|
||||
prima_autorizare: a.prima_autorizare,
|
||||
ultima_autorizare: a.ultima_autorizare,
|
||||
operatori: opR.rows,
|
||||
};
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// CNSC — Consiliul Național de Soluționare a Contestațiilor.
|
||||
// A CUI can appear as either authority (the public buyer being contested)
|
||||
// or contestator (the bidder filing the complaint). We expose both sides;
|
||||
// return null only when neither side has any rows.
|
||||
// Stage 2 PDF parse hasn't run yet → decision_type is mostly NULL; UI hides
|
||||
// fields that are NULL.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CnscDeciziePreview {
|
||||
decision_no: number;
|
||||
decision_year: number;
|
||||
registration_no_cnsc: string | null;
|
||||
registration_date: string | null;
|
||||
contestator_names: string[];
|
||||
contestator_cuis: string[];
|
||||
authority_name: string | null;
|
||||
authority_cuis: string[];
|
||||
decision_type: string | null;
|
||||
pdf_url: string | null;
|
||||
}
|
||||
|
||||
export interface CnscAuthoritySide {
|
||||
contestation_count: number;
|
||||
admis_count: number;
|
||||
admis_in_parte_count: number;
|
||||
respins_count: number;
|
||||
resolved_count: number;
|
||||
first_contestation_date: string | null;
|
||||
last_contestation_date: string | null;
|
||||
recent: CnscDeciziePreview[];
|
||||
}
|
||||
|
||||
export interface CnscContestatorSide {
|
||||
contestations_filed: number;
|
||||
won_admis: number;
|
||||
won_partial: number;
|
||||
lost_respins: number;
|
||||
resolved_count: number;
|
||||
first_contestation_date: string | null;
|
||||
last_contestation_date: string | null;
|
||||
recent: CnscDeciziePreview[];
|
||||
}
|
||||
|
||||
export interface CnscStatus {
|
||||
asAuthority: CnscAuthoritySide | null;
|
||||
asContestator: CnscContestatorSide | null;
|
||||
}
|
||||
|
||||
export async function getCnscStatus(cui: string): Promise<CnscStatus | null> {
|
||||
const normCui = cui.replace(/^RO\s*/i, '').trim();
|
||||
|
||||
const authAggR = await query<any>(
|
||||
`SELECT contestation_count::int,
|
||||
admis_count::int,
|
||||
admis_in_parte_count::int,
|
||||
respins_count::int,
|
||||
resolved_count::int,
|
||||
to_char(first_contestation_date, 'YYYY-MM-DD') AS first_contestation_date,
|
||||
to_char(last_contestation_date, 'YYYY-MM-DD') AS last_contestation_date
|
||||
FROM cnsc.mv_per_authority_cui
|
||||
WHERE cui = $1`,
|
||||
[normCui],
|
||||
);
|
||||
|
||||
const contAggR = await query<any>(
|
||||
`SELECT contestations_filed::int,
|
||||
won_admis::int,
|
||||
won_partial::int,
|
||||
lost_respins::int,
|
||||
resolved_count::int,
|
||||
to_char(first_contestation_date, 'YYYY-MM-DD') AS first_contestation_date,
|
||||
to_char(last_contestation_date, 'YYYY-MM-DD') AS last_contestation_date
|
||||
FROM cnsc.mv_per_contestator_cui
|
||||
WHERE cui = $1`,
|
||||
[normCui],
|
||||
);
|
||||
|
||||
const authAgg = authAggR.rows[0];
|
||||
const contAgg = contAggR.rows[0];
|
||||
|
||||
const hasAuth = !!authAgg && Number(authAgg.contestation_count) > 0;
|
||||
const hasCont = !!contAgg && Number(contAgg.contestations_filed) > 0;
|
||||
if (!hasAuth && !hasCont) return null;
|
||||
|
||||
let asAuthority: CnscAuthoritySide | null = null;
|
||||
if (hasAuth) {
|
||||
const recentR = await query<any>(
|
||||
`SELECT decision_no,
|
||||
decision_year,
|
||||
registration_no_cnsc,
|
||||
to_char(registration_date, 'YYYY-MM-DD') AS registration_date,
|
||||
contestator_names,
|
||||
contestator_cuis,
|
||||
authority_name,
|
||||
authority_cuis,
|
||||
decision_type,
|
||||
pdf_url
|
||||
FROM cnsc.decizii
|
||||
WHERE $1 = ANY(authority_cuis)
|
||||
ORDER BY decision_year DESC, decision_no DESC
|
||||
LIMIT 10`,
|
||||
[normCui],
|
||||
);
|
||||
asAuthority = {
|
||||
contestation_count: Number(authAgg.contestation_count),
|
||||
admis_count: Number(authAgg.admis_count) || 0,
|
||||
admis_in_parte_count: Number(authAgg.admis_in_parte_count) || 0,
|
||||
respins_count: Number(authAgg.respins_count) || 0,
|
||||
resolved_count: Number(authAgg.resolved_count) || 0,
|
||||
first_contestation_date: authAgg.first_contestation_date,
|
||||
last_contestation_date: authAgg.last_contestation_date,
|
||||
recent: recentR.rows.map(r => ({
|
||||
decision_no: Number(r.decision_no),
|
||||
decision_year: Number(r.decision_year),
|
||||
registration_no_cnsc: r.registration_no_cnsc,
|
||||
registration_date: r.registration_date,
|
||||
contestator_names: r.contestator_names || [],
|
||||
contestator_cuis: r.contestator_cuis || [],
|
||||
authority_name: r.authority_name,
|
||||
authority_cuis: r.authority_cuis || [],
|
||||
decision_type: r.decision_type,
|
||||
pdf_url: r.pdf_url,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
let asContestator: CnscContestatorSide | null = null;
|
||||
if (hasCont) {
|
||||
const recentR = await query<any>(
|
||||
`SELECT decision_no,
|
||||
decision_year,
|
||||
registration_no_cnsc,
|
||||
to_char(registration_date, 'YYYY-MM-DD') AS registration_date,
|
||||
contestator_names,
|
||||
contestator_cuis,
|
||||
authority_name,
|
||||
authority_cuis,
|
||||
decision_type,
|
||||
pdf_url
|
||||
FROM cnsc.decizii
|
||||
WHERE $1 = ANY(contestator_cuis)
|
||||
ORDER BY decision_year DESC, decision_no DESC
|
||||
LIMIT 10`,
|
||||
[normCui],
|
||||
);
|
||||
asContestator = {
|
||||
contestations_filed: Number(contAgg.contestations_filed),
|
||||
won_admis: Number(contAgg.won_admis) || 0,
|
||||
won_partial: Number(contAgg.won_partial) || 0,
|
||||
lost_respins: Number(contAgg.lost_respins) || 0,
|
||||
resolved_count: Number(contAgg.resolved_count) || 0,
|
||||
first_contestation_date: contAgg.first_contestation_date,
|
||||
last_contestation_date: contAgg.last_contestation_date,
|
||||
recent: recentR.rows.map(r => ({
|
||||
decision_no: Number(r.decision_no),
|
||||
decision_year: Number(r.decision_year),
|
||||
registration_no_cnsc: r.registration_no_cnsc,
|
||||
registration_date: r.registration_date,
|
||||
contestator_names: r.contestator_names || [],
|
||||
contestator_cuis: r.contestator_cuis || [],
|
||||
authority_name: r.authority_name,
|
||||
authority_cuis: r.authority_cuis || [],
|
||||
decision_type: r.decision_type,
|
||||
pdf_url: r.pdf_url,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return { asAuthority, asContestator };
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Bugetar — Transparență Bugetară MFP. Phase 1 ingest covers the
|
||||
// universe of reporting public entities (18,822 rows) but not
|
||||
// actual execuții data (Phase 2 needs 2captcha). Surfacing
|
||||
// registry membership on autoritate profile is still useful:
|
||||
// confirms this is a "real" public budget entity + links to
|
||||
// the MFP search UI for manual lookup.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
const BUGETAR_SECTOR_LABELS: Record<string, string> = {
|
||||
'01': 'buget de stat',
|
||||
'02': 'bugete locale',
|
||||
'03': 'asigurări sociale',
|
||||
'04': 'șomaj',
|
||||
'05': 'sănătate (FNUASS)',
|
||||
};
|
||||
|
||||
export interface BugetarStatus {
|
||||
sector_bugetar: string;
|
||||
sector_label: string;
|
||||
judet: string | null;
|
||||
entity_name: string;
|
||||
is_ordonator_principal: boolean;
|
||||
cui_match_method: string | null;
|
||||
cui_match_score: number | null;
|
||||
}
|
||||
|
||||
export async function getBugetarStatus(cui: string): Promise<BugetarStatus | null> {
|
||||
const norm = cui.replace(/^RO\s*/i, '').trim();
|
||||
const r = await query<{
|
||||
sector_bugetar: string;
|
||||
judet: string;
|
||||
entity_name: string;
|
||||
is_ordonator_principal: boolean;
|
||||
cui_match_method: string | null;
|
||||
cui_match_score: number | null;
|
||||
}>(
|
||||
`SELECT sector_bugetar, judet, entity_name, is_ordonator_principal,
|
||||
cui_match_method, cui_match_score
|
||||
FROM bugetar.entitate
|
||||
WHERE cui = $1
|
||||
ORDER BY is_ordonator_principal DESC, sector_bugetar
|
||||
LIMIT 1`,
|
||||
[norm],
|
||||
);
|
||||
const row = r.rows[0];
|
||||
if (!row) return null;
|
||||
return {
|
||||
sector_bugetar: row.sector_bugetar,
|
||||
sector_label: BUGETAR_SECTOR_LABELS[row.sector_bugetar] || row.sector_bugetar,
|
||||
judet: row.judet,
|
||||
entity_name: row.entity_name,
|
||||
is_ordonator_principal: row.is_ordonator_principal,
|
||||
cui_match_method: row.cui_match_method,
|
||||
cui_match_score: row.cui_match_score == null ? null : Number(row.cui_match_score),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user