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,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Load EU CPV nomenclature (RO names) into seap.cpv_codes + backfill announcements."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import execute_values
|
||||
|
||||
CPV_URL = 'https://raw.githubusercontent.com/samhallskod/cpv-eu/main/data/cpv.json'
|
||||
|
||||
|
||||
def main():
|
||||
db = os.environ.get('DATABASE_URL')
|
||||
if not db:
|
||||
print('DATABASE_URL not set', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
cache = Path('/tmp/cpv.json')
|
||||
if not cache.exists():
|
||||
print(f'Downloading {CPV_URL}...')
|
||||
urllib.request.urlretrieve(CPV_URL, cache)
|
||||
data = json.loads(cache.read_text())
|
||||
print(f'Loaded {len(data)} CPV codes from JSON')
|
||||
|
||||
rows = []
|
||||
for entry in data:
|
||||
code = entry['code'] # 8-digit (no dash)
|
||||
labels = entry.get('labels', {})
|
||||
name_ro = labels.get('ro') or labels.get('en') or code
|
||||
name_en = labels.get('en')
|
||||
level = entry.get('level', 0)
|
||||
emoji = entry.get('emoji')
|
||||
|
||||
# Division = first 2 digits + 6 zeros
|
||||
division = code[:2] + '000000'
|
||||
|
||||
# Parent = level - 1 — easiest: trim trailing zero pairs based on level
|
||||
# Level mapping: 1=XX000000, 2=XXX00000, 3=XXXX0000, 4=XXXXX000, 5=XXXXXX00, 6=XXXXXXX0, 7=XXXXXXXX (full)
|
||||
# For our purposes, parent is one level up — replace last non-zero digit with 0
|
||||
parent = None
|
||||
if level > 1:
|
||||
# find rightmost non-zero, zero it out
|
||||
digits = list(code)
|
||||
# Find position of last meaningful digit
|
||||
for i in range(len(digits) - 1, -1, -1):
|
||||
if digits[i] != '0':
|
||||
digits[i] = '0'
|
||||
break
|
||||
parent_code = ''.join(digits)
|
||||
if parent_code != code:
|
||||
parent = parent_code
|
||||
|
||||
# Build full code with check digit (synthesize a placeholder dash + 0)
|
||||
# The real check digit is per-code — we don't compute it; use the 8-digit form everywhere.
|
||||
rows.append((code, code, name_ro, name_en, level, division, parent, emoji))
|
||||
|
||||
print(f'Inserting {len(rows)} rows into seap.cpv_codes...')
|
||||
conn = psycopg2.connect(db)
|
||||
cur = conn.cursor()
|
||||
cur.execute('TRUNCATE seap.cpv_codes')
|
||||
execute_values(cur, """
|
||||
INSERT INTO seap.cpv_codes (code, code_full, name_ro, name_en, level,
|
||||
division_code, parent_code, emoji)
|
||||
VALUES %s
|
||||
""", rows, page_size=500)
|
||||
conn.commit()
|
||||
cur.execute('SELECT count(*), count(*) FILTER (WHERE level=1) FROM seap.cpv_codes')
|
||||
total, divisions = cur.fetchone()
|
||||
print(f' → {total} codes loaded, {divisions} top-level divisions')
|
||||
|
||||
# Backfill cpv_division + cpv_name_ro on announcements
|
||||
print('Backfilling cpv_division + cpv_name_ro on announcements...')
|
||||
cur.execute("""
|
||||
UPDATE seap.announcements
|
||||
SET cpv_division = seap.cpv_division(cpv_code),
|
||||
cpv_name_ro = seap.cpv_name(cpv_code)
|
||||
WHERE cpv_code IS NOT NULL
|
||||
AND (cpv_division IS NULL OR cpv_name_ro IS NULL)
|
||||
""")
|
||||
affected = cur.rowcount
|
||||
conn.commit()
|
||||
print(f' → {affected:,} announcement rows enriched')
|
||||
|
||||
# Show top divisions
|
||||
cur.execute("""
|
||||
SELECT a.cpv_division, c.name_ro, c.emoji,
|
||||
count(*) as n,
|
||||
sum(awarded_value)::numeric(15,0) as total_value
|
||||
FROM seap.announcements a
|
||||
LEFT JOIN seap.cpv_codes c ON c.code = a.cpv_division
|
||||
WHERE a.cpv_division IS NOT NULL AND a.source LIKE 'wsp_%'
|
||||
GROUP BY 1, 2, 3
|
||||
ORDER BY 4 DESC
|
||||
LIMIT 15
|
||||
""")
|
||||
print('\nTop 15 CPV divisions in WSP data:')
|
||||
for r in cur.fetchall():
|
||||
print(f' {r[2] or " "} {r[0]:<10} {(r[1] or "?")[:45]:<45} {r[3]:>5} contracte {r[4] or 0:>13,} RON')
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user