diff --git a/data/szif/README.md b/data/szif/README.md index a5934ca0..d4cc1d36 100644 --- a/data/szif/README.md +++ b/data/szif/README.md @@ -1,24 +1,10 @@ # Státní zemědělský intervenční fond -Státní zemědělský intervenční fond (SZIF) dlouhodobě vydává seznam příjemců dotací a některá jejich metadata. Roky tato data byla pouze ve formě nahlédnutí [na jejich webu](https://www.szif.cz/irj/portal/szif/seznam-prijemcu-dotaci), nově začali vydávat data i jako XML exporty. +Státní zemědělský intervenční fond (SZIF) dlouhodobě vydává seznam příjemců dotací a některá jejich metadata. Roky tato data byla pouze ve formě nahlédnutí [na jejich webu](https://www.szif.cz/irj/portal/szif/seznam-prijemcu-dotaci), nově začali vydávat data i jako XML exporty. Ještě nověji začli vydávat CSV exporty (ale jen pro novější roky). Pro jejich zpracování do relační formy stačí pustit tento jeden Python skript. Poznámky: -- v datech není žádný unikátní identifikátor přijemce, vytváříme si tedy jeden syntetický identifikátor -- v datech chybí IČO, takže pro další propojení s daty je třeba udělat vazbu přes název a okres, takto jdou propojit jen vyšší desítky procent příjemců -- jde tu pouze o data za rok 2018 a 2017, byť SZIF funguje o dost déle +- ve starších datech není žádný unikátní identifikátor přijemce, vytváříme si tedy jeden syntetický identifikátor +- ve starších datech chybí IČO, takže pro další propojení s daty je třeba udělat vazbu přes název a okres, takto jdou propojit jen vyšší desítky procent příjemců - pozor na přechodné vnitrostátní podpory, které jsou hrazené z národních zdrojů, ty nejsou v přehledu žadatelů a nejsou ani v platbách ve sloupci `zdroje_cr`, více vizte web SZIF -- jde o první nástřel, mohou zde být jisté nedostatky - -## Příklad užití - -Jde o velmi volně relační konverzi, takže je třeba propojit data přes rok a identifikátor příjemce - -```sql -SELECT - * -FROM - szif.zadatele inner join szif.platby using(rok, id_prijemce) -LIMIT 1000 -``` diff --git a/data/szif/main.py b/data/szif/main.py index 55e9ec5b..50c840e4 100644 --- a/data/szif/main.py +++ b/data/szif/main.py @@ -1,32 +1,133 @@ import csv +import io import os from urllib.request import urlopen from lxml.etree import iterparse -BASE_URL = ( +HTTP_TIMEOUT = 10 +XML_BASE_URL = ( "https://www.szif.cz/cs/CmDocument?rid=%2Fapa_anon%2Fcs%2F" "dokumenty_ke_stazeni%2Fpkp%2Fspd%2Fopendata%2Fspd{year}.xml" ) -years = [2017, 2018, 2019, 2020, 2021] +CSV_BASE_URL_22 = ( + "https://szif.gov.cz/cs/CmDocument?rid=%2Fapa_anon%2Fcs%2F" + "dokumenty_ke_stazeni%2Fpkp%2Fspd%2Fopendata%2Fspd2022.csv" +) +CSV_BASE_URL = ( + "https://szif.gov.cz/cs/CmDocument?rid=%2Fapa_anon%2Fcs%2F" + "dokumenty_ke_stazeni%2Fpkp%2Fspd%2Fopendata%2Fspd{year}czk.csv" +) + +URLS = { + 2017: XML_BASE_URL.format(year=2017), + 2018: XML_BASE_URL.format(year=2018), + 2019: XML_BASE_URL.format(year=2019), + 2020: XML_BASE_URL.format(year=2020), + 2021: XML_BASE_URL.format(year=2021), + 2022: CSV_BASE_URL_22, + 2023: CSV_BASE_URL.format(year=2023), + 2024: CSV_BASE_URL.format(year=2024), +} + + +def parse_xml(r, partial): + et = iterparse(r) + elyear = None + for num, (action, element) in enumerate(et): + if partial and num > 1e3: + break + assert action == "end" + + if element.tag != "zadatel": + continue + + platba = {"rok": elyear} + for key in ["jmeno_nazev", "obec", "okres"]: + platba[key] = element.find(key).text + + for elplatba in element.findall("platby/platba") + element.findall( + "platby_pvp/platba_pvp" + ): + for key in [ + "fond_typ_podpory", + "opatreni", + "zdroje_cr", + "zdroje_eu", + "celkem_czk", + ]: + platba[key] = getattr(elplatba.find(key), "text", None) + + yield platba + + element.clear() + + +def parse_csv(r, partial): + tr = io.TextIOWrapper(r, encoding="utf8") + cr = csv.DictReader(tr) + for num, row in enumerate(cr): + if partial and num > 1e3: + break + yield { + "datum": row["Datum nabytí právní moci"], + "jmeno_nazev": row["Jméno/Název"], + "obec": row["Obec"], + "okres": row["Okres"], + "fond_typ_podpory": row["Typ fondu"], + "opatreni": row["Opatření"], + "zdroje_cr": row["Zdroje ČR"], + "zdroje_eu": row["Zdroje EU"], + "celkem_czk": row["Celkem CZK"], + } + + +def parse_csv_czk(r, partial): + tr = io.TextIOWrapper(r, encoding="utf8") + cr = csv.DictReader(tr) + for num, row in enumerate(cr): + if partial and num > 1e3: + break + platba = { + "datum": row["Datum nabytí právní moci rozhodnutí"], + "jmeno_nazev": row["Název příjemce (právnická osoba)"] + or row["Příjmení a jméno příjemce"], + "ico": int(row["IČ Příjemce"]) if row["IČ Příjemce"].isdigit() else None, + "obec": row["Obec"], + "okres": row["Okres (NUTS 4)"], + "fond_typ_podpory": row["Fond"], + "opatreni": row["Název opatření"], + "zdroje_cr": ( + float( + row["Celková částka spolufinancovaná pro tohoto příjemce EZZF"] or 0 + ) + + float( + row["Celková částka spolufinancovaná pro tohoto příjemce EZFRV"] + or 0 + ) + # obcas je tam tenhle sloupec misto toho predchoziho + + float(row["Částka podle operace v rámci spolufinancování EZFRV"] or 0) + ), + "zdroje_eu": float(row["Celková částka z EU pro tohoto příjemce"] or 0), + } + if platba["zdroje_eu"] == 0: + platba["zdroje_eu"] = float(row["Částka podle operací v rámci EZZF"] or 0) + platba["zdroje_eu"] += float(row["Částka podle operací v rámci EZFRV"] or 0) + platba["celkem_czk"] = platba["zdroje_cr"] + platba["zdroje_eu"] + yield platba def main(outdir: str, partial: bool = False): - id_prijemce = 1 - - with open(os.path.join(outdir, "zadatele.csv"), "w", encoding="utf8") as fz, open( - os.path.join(outdir, "platby.csv"), "w", encoding="utf8" - ) as fp: - cz = csv.DictWriter( - fz, - ["id_prijemce", "rok", "jmeno_nazev", "obec", "okres", "castka_bez_pvp"], - lineterminator="\n", - ) + with open(os.path.join(outdir, "platby.csv"), "w", encoding="utf8") as fp: cp = csv.DictWriter( fp, [ - "id_prijemce", "rok", + "datum", + "jmeno_nazev", + "ico", + "obec", + "okres", "fond_typ_podpory", "opatreni", "zdroje_cr", @@ -36,48 +137,23 @@ def main(outdir: str, partial: bool = False): lineterminator="\n", ) - cz.writeheader() cp.writeheader() - for year in years: - url = BASE_URL.format(year=year) - - with urlopen(url) as r: - et = iterparse(r) - - elyear = None - for num, (action, element) in enumerate(et): - if partial and num > 1e3: - break - assert action == "end" - if element.tag == "rok": - elyear = int(element.text) - assert elyear == year, "Necekany rok v datech" - - if element.tag != "zadatel": - continue - - zadatel = {"id_prijemce": id_prijemce, "rok": elyear} - - for key in ["jmeno_nazev", "obec", "okres", "castka_bez_pvp"]: - zadatel[key] = element.find(key).text - - for elplatba in element.findall("platby/platba") + element.findall( - "platby_pvp/platba_pvp" - ): - platba = {"id_prijemce": id_prijemce, "rok": elyear} - for key in [ - "fond_typ_podpory", - "opatreni", - "zdroje_cr", - "zdroje_eu", - "celkem_czk", - ]: - platba[key] = getattr(elplatba.find(key), "text", None) - - cp.writerow(platba) - - cz.writerow(zadatel) - id_prijemce += 1 - - element.clear() + for year, url in URLS.items(): + print(f"Downloading {year} data from {url}") + with urlopen(url, timeout=HTTP_TIMEOUT) as r: + if url.endswith(".xml"): + parser = parse_xml + elif url.endswith("czk.csv"): + parser = parse_csv_czk + elif url.endswith(".csv"): + parser = parse_csv + else: + raise ValueError(f"Unknown file type: {url}") + + for platba in parser(r, partial): + platba["rok"] = year + for c in ["zdroje_cr", "zdroje_eu", "celkem_czk"]: + if not platba.get(c): + platba[c] = 0.0 + cp.writerow(platba) diff --git a/data/szif/schema.py b/data/szif/schema.py index 5127804e..c66b8e8a 100644 --- a/data/szif/schema.py +++ b/data/szif/schema.py @@ -1,34 +1,22 @@ from sqlalchemy import Column, MetaData, Table -from sqlalchemy.sql.sqltypes import BigInteger, Integer, Numeric, Text +from sqlalchemy.sql.sqltypes import Date, Integer, Numeric, Text meta = MetaData() schema = [ Table( - "zadatele", + "platby", meta, - Column( - "id_prijemce", - BigInteger, - nullable=False, - primary_key=True, - autoincrement=False, - ), - Column("rok", Integer, nullable=False, primary_key=True, autoincrement=False), + Column("rok", Integer, nullable=False), + Column("datum", Date, nullable=True), Column("jmeno_nazev", Text, nullable=False), + Column("ico", Integer, nullable=True), Column("obec", Text, nullable=True), Column("okres", Text, nullable=True), - Column("castka_bez_pvp", Numeric(12, 2), nullable=False), - ), - Table( - "platby", - meta, - Column("id_prijemce", BigInteger, nullable=False), - Column("rok", Integer, nullable=False), - Column("fond_typ_podpory", Text, nullable=False), - Column("opatreni", Text, nullable=False), - Column("zdroje_cr", Numeric(12, 2), nullable=True), - Column("zdroje_eu", Numeric(12, 2), nullable=True), + Column("fond_typ_podpory", Text, nullable=True), + Column("opatreni", Text, nullable=True), + Column("zdroje_cr", Numeric(12, 2), nullable=False), + Column("zdroje_eu", Numeric(12, 2), nullable=False), Column("celkem_czk", Numeric(12, 2), nullable=False), ), ]