Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions data/szif/README.md
Original file line number Diff line number Diff line change
@@ -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
```
188 changes: 132 additions & 56 deletions data/szif/main.py
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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)
30 changes: 9 additions & 21 deletions data/szif/schema.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
Expand Down
Loading