Add verified data-access findings + MVP plan

This commit is contained in:
naps62
2026-06-22 13:33:53 +00:00
parent 7812019b29
commit a87da179c6
+84
View File
@@ -0,0 +1,84 @@
# radar-contratos — data access + MVP plan
> Living planning doc. The data-access question is **resolved** (verified June 2026).
## Data access — VERIFIED ✓
**Primary source: per-year XLSX bulk dumps on dados.gov.pt.**
Dataset: `contratos-publicos-portal-base-impic-contratos-de-2012-a-2026` (org: IMPIC).
- One **`.xlsx` per year, 2012 → 2026**, all populated (16 MB in 2012 → 67 MB in 2025;
2026 is partial/in-progress). Full set ≈ **600 MB**.
- Refresh cadence: **biweekly** (dataset `last_update` was within days of checking).
- **No auth, no scraping** — plain HTTPS file downloads.
- ⚠️ The dataset also ships a `.zip`/JSON mirror per year, but **many are empty/broken**
(20142018, 2025, 2026 zips were 0 bytes / `[]`). **Use the XLSX as source of truth.**
**Companion datasets (same IMPIC family, also bulk):**
- `...-entidades``entidades.xlsx` (~13 MB) — canonical entity list (buyers/suppliers).
- `...-anuncios-de-2012-a-2026` → announcements/tenders JSON (~15 MB/yr). Note: **only
*tendered* procedures announce** — ajuste direto usually has no announcement, so this is
a supplement, not the core.
- `...-modificacoes-contratuais-...` → contract amendments JSON (price/term changes).
**Fresher data later:** IMPIC offers an official **BASE API** (token via IMPIC Helpdesk,
daily updates) — defer; the biweekly XLSX is plenty for MVP.
### Contracts schema (39 cols) — the fields that matter
| Column | Use |
|---|---|
| `tipoprocedimento` | procedure type — `"Ajuste Direto Regime Geral"` etc. **(the core filter)** |
| `adjudicante` | buyer, as `"NIF - name"` → split on `" - "` for entity key |
| `adjudicatarios` | supplier(s), same `"NIF - name"` shape (can be multiple) |
| `precoContratual`, `PrecoTotalEfetivo`, `precoBaseProcedimento` | values |
| `dataCelebracaoContrato`, `dataPublicacao`, `dataDecisaoAdjudicacao`, `dataFechoContrato` | dates |
| `objectoContrato`, `CPV` | what was bought (CPV = EU procurement category code) |
| `fundamentAjusteDireto`, `fundamentacao` | legal basis cited for the direct award |
| `NUTs`, `LocalExecucao` | region |
| `concorrentes` | competitors (often empty for ajuste direto) |
| `Ano`, `prazoExecucao`, `regime`, `Lotes` | year, term, legal regime, lots |
## MVP scope — one lens: `ajuste direto` red flags
Keep it narrow. Ship **one analysis fully** before adding breadth.
1. **Contract-splitting** — same `adjudicante` → same `adjudicatario` (by NIF), same/near
`CPV`, clustered in a short window, each contract just under the legal ajuste-direto
value threshold, summing to *above* it. The law forbids splitting to dodge a tender.
⚠️ **Confirm current legal thresholds** (CCP, amended by Lei 30/2021 — values differ for
bens/serviços vs. empreitadas, and changed over time → threshold must be date-aware).
2. **Repeat winners** — supplier's share of a given buyer's ajuste-direto spend; flag high
concentration / suppliers that win disproportionately from one entity.
3. **Timing** — award bursts before elections or at year-end.
Each flag is a **question, not a verdict**: always deep-link the underlying contract, with
"isto não é uma acusação" framing.
## Architecture (proposed)
```
download yearly XLSX ──> parse ──> Postgres ──> SQL analyses ──> present
(biweekly job) (the red-flag logic)
```
- **The heavy lifting is SQL** (group-bys over a contracts table) — language-agnostic.
- Proposed stack: **Elixir/Phoenix + LiveView + Postgres**, consistent with
[`o-que-mudou`](../o-que-mudou); ingestion = download + xlsx parse + bulk upsert keyed on
`idcontrato`. Open to a standalone ETL step if Elixir xlsx parsing is painful at 600 MB.
- Entity table seeded from `entidades.xlsx`; NIF is the join key everywhere.
## Open questions
- **Legal thresholds, date-aware** — exact ajuste-direto / consulta-prévia ceilings per
period and contract type. Make-or-break for splitting detection accuracy.
- **`adjudicatarios` multiplicity** — contracts with multiple suppliers / lots; how to
attribute value.
- **Entity identity** — trust embedded NIF; reconcile name variants via `entidades.xlsx`.
- **Initial load volume** — ~600 MB XLSX → estimate row count (likely 1.52M contracts) and
confirm Postgres sizing (trivial) + parse time.
## Out of scope (later / other projects)
Cross-referencing winners against political donations, beneficial ownership, or officials'
declared interests — that's the separate "conflict-of-interest engine" idea, not this MVP.