Analiza json fajlova sa podacima o BM.
  • HTML 53%
  • Python 46.1%
  • Makefile 0.9%
Find a file
Petar Komordzic b3f82032c2
Some checks failed
Provera spajanja / verify (push) Has been cancelled
feat: Initial Commit
2026-08-01 15:14:45 +02:00
.github/workflows feat: Initial Commit 2026-08-01 15:14:45 +02:00
data feat: Initial Commit 2026-08-01 15:14:45 +02:00
docs feat: Initial Commit 2026-08-01 15:14:45 +02:00
scripts feat: Initial Commit 2026-08-01 15:14:45 +02:00
.gitignore feat: Initial Commit 2026-08-01 15:14:45 +02:00
Makefile feat: Initial Commit 2026-08-01 15:14:45 +02:00
README.md feat: Initial Commit 2026-08-01 15:14:45 +02:00

Migracija podataka o biračkim mestima

Prelazak sa starog, normalizovanog izvora (ps_regions.json) na noviji i detaljniji (polling_stations.json) — sa dokazom da je spajanje ispravno, tabelama preslikavanja i interaktivnim pregledom za ručnu proveru.

Repozitorijum je napisan kao primer kako se radi migracija između dva šifarnika koji nemaju zajedničke ID vrednosti na svim nivoima, a ne samo kao rešenje za ove konkretne fajlove.


Problem u jednoj slici

STARO (normalizovano)                    NOVO (denormalizovano)

polling_stations                         results
  id: "4246"                 ─── isto ───► id: "4246"
  name: "Precizni Liv"                     name_lat: "Precizni Liv - Ada, Molski put 4"
  address: "Ada, Molski put 4"             locality:
  municipality: 21  ──┐                      id: "1"        ◄── isti grad, drugi broj
                      │                      county: "severnobanatski"
municipalities        │                      region: "vojvodina"
  21: "Ada"  ◄────────┘

Biračko mesto 4246 ima isti id u oba fajla. Ali grad Ada je u starom fajlu vođen pod brojem 21, a u novom pod "1". Brojevi opština, okruga i regiona nemaju nikakve veze jedni sa drugima, a ni nazivi nisu uvek isti (ZvezdaraBeograd-Zvezdara).

Rešenje u jednoj rečenici

Migracija se radi na nivou biračkog mesta, preko polja id, a preslikavanje šifarnika se izvodi iz samih biračkih mesta — svako mesto već nosi i staru opštinu i novi lokalitet, pa veza ispada sama.


Brzi start

Bez ijedne spoljne biblioteke — samo Python 3.9+ iz standardne instalacije.

# 1. stavi ulazne fajlove u data/
cp ps_regions.json polling_stations.json data/

# 2. dokaži da je ključ ispravan (izlazni kod 0/1, pogodno za CI)
python3 scripts/verify_join.py

# 3. generiši tabele preslikavanja
python3 scripts/build_crosswalk.py      # -> out/crosswalk.json

# 4. prebaci podatke u ciljnu šemu
python3 scripts/migrate.py              # -> out/polling_stations_migrated.json

# 5. napravi interaktivni pregled
python3 scripts/build_dashboard.py      # -> out/dashboard.html

Ili sve odjednom:

make all

Otvori out/dashboard.html u pregledaču — radi bez interneta, podaci su ugrađeni u fajl.


Šta se nalazi u repozitorijumu

scripts/
  common.py                  učitavanje, normalizacija teksta, otisak, geo
  verify_join.py             5 provera da `id` označava isto biračko mesto
  build_crosswalk.py         izvodi tabele preslikavanja iz uparenih mesta
  migrate.py                 prebacuje podatke u ciljnu šemu
  build_dashboard.py         pravi samostalni HTML pregled
  dashboard_template.html    šablon za HTML (podaci se ubacuju na mesto __PAYLOAD__)

docs/
  struktura-podataka.md      oblik oba izvora, poređenje obima
  dokaz-kljuca.md            zašto se sme verovati polju `id`
  mapiranje-polja.md         tabela: koje staro polje ide u koje novo
  id-prostori.md             pet ID prostora i zamka sa brojevima
  hijerarhija.md             opština → lokalitet, okrug → county, regioni
  crosswalk.md               kako je nastao crosswalk.json i šta je u njemu
  rucna-provera.md           kako proveriti rezultat rukom
  plan-migracije.md          kontrolna lista i ciljna šema
  anomalije.md               rizici, zamke, stvari koje pucaju tiho

data/                        ulazni fajlovi (nisu u gitu — vidi .gitignore)
out/                         sve generisano (nije u gitu)

Ključni rezultati

Provera Rezultat
Biračkih mesta spojeno preko id 8.309 / 8.309 (0 izgubljenih, 1 dodato)
voter_count identičan 8.309 / 8.309 — 100,00%
name + " - " + address = name_lat 8.307 / 8.309 — 99,98%
Medijana pomeraja koordinata 70 m
Lokaliteta koji mešaju više starih opština 0 od 193
Nezavisna provera otiskom (bez id) 8.307 / 8.307 — 100,00% slaganja

Poređenja radi, kod nasumičnog uparivanja istih zapisa voter_count se poklapa u 0,02% slučajeva, a medijana rastojanja je 137 km.


Šta je najvažnije razumeti

1. Samo id se prenosi direktno. Sve ostale ID vrednosti su različiti brojevni prostori. Detaljno u docs/id-prostori.md.

2. Preslikavanje se izvodi, ne pogađa. Ne poredimo opštine po nazivu ni po broju — prolazimo kroz biračka mesta i brojimo glasove. Detaljno u docs/crosswalk.md.

3. Dokaz nije kružan. Postoji druga, potpuno nezavisna provera koja ne čita id ni sa jedne strane. Detaljno u docs/dokaz-kljuca.md.

4. Jedan podatak se gubi. control_region ne postoji u novom izvoru i moramo ga sami održavati.

5. Adresa se ne gubi, ali se ne sme izvoditi deljenjem. Novi fajl ima naziv i adresu spojene; deljenje po " - " promašuje na oko 10% zapisa jer i sami nazivi objekata sadrže crticu.


Ulazni podaci

Fajlovi u data/ nisu deo repozitorijuma (vidi .gitignore) jer su oko 10 MB i predstavljaju izvorne podatke. Očekivani oblik:

data/ps_regions.json

{
  "regions":          [{ "id": 1, "name": "Vojvodina" }],
  "control_regions":  [{ "id": 1, "name": "Univerzitet u Novom Pazaru" }],
  "districts":        [{ "id": 1, "name": "Južnobanatski okrug", "region": 1 }],
  "municipalities":   [{ "id": 1, "name": "Alibunar", "district": 1, "control_region": 4 }],
  "polling_stations": [{ "id": "4246", "name": "Precizni Liv", "station_number": 1,
                         "address": "Ada, Molski put 4", "geo": { "lat": 45.78, "lon": 20.13 },
                         "voter_count": 747, "municipality": 21 }]
}

data/polling_stations.json

{
  "count": 8310,
  "results": [{
    "id": "4246", "number": 1,
    "name_cyr": "…", "name_lat": "Precizni Liv - Ada, Molski put 4",
    "locality": { "id": "1", "name_lat": "Ada",
                  "region": { "id": "vojvodina", "name_lat": "Region Vojvodine" },
                  "county": { "id": "severnobanatski", "name_lat": "Severnobanatski okrug" } },
    "geo": { "lat": 45.78524, "lon": 20.1355 },
    "geo_approx": false, "geo_source": "volunteer",
    "voter_count": 747, "turnout": 376,
    "network": { "mts": 2, "yettel": 2, "a1": 2 }, "image": null
  }]
}

Primena na druge podatke

Postupak nije vezan za ove fajlove. Da bi se primenio na drugi par izvora, dovoljno je promeniti:

  • common.py — funkcije fingerprint_old / fingerprint_new. Otisak treba da bude sastavljen od polja koja postoje u oba izvora, a nemaju veze sa ključem koji proveravamo. Ovde je to naziv, adresa i broj birača.
  • build_crosswalk.py — koji parovi šifarnika se izvlače u koraku 3.

Ostalo radi bez izmena: provere u koracima 12, brojanje glasova, otkrivanje podela i dvosmislenosti.


Licenca

Kod je slobodan za korišćenje. Ulazni podaci nisu deo repozitorijuma i podležu uslovima svog izvora.