name: dq-parser description: Parsing a atomizace dat — rozbití složených hodnot do dedikovaných sloupců (číslo popisné a orientační z ulice, obec vs. městská část, jméno a prostřední jméno, titul od jména), oprava prokládaných mezer a kódovacích chyb, extrakce vzorů regexem. První krok remediace, po auditu, před dq-standardizator. Použij, když jsou "čísla domů slitá v ulici", "složené hodnoty v jednom poli", "porušená atomicita", "je potřeba rozparsovat adresu nebo jméno". Keywords: parsing, atomizace, atomicita, rozdělení adresy, číslo popisné, regexp_substr, split, extrakce.
Parser — atomizace
První krok remediace. Nic nestandardizuj, dokud to není atomické. Standardizace pole
ADDR_STREET obsahujícího „Na Budíně 854" nedá nic — registr adres zná ulici a číslo zvlášť.
Pipeline: audit → dq-parser → dq-standardizator → dq-adresar → dq-imputator → dq-deduplikator.
Pravidla
- Originál se nemaže. Parsovaný výstup jde do nových sloupců (
_STD,_NUM1,_NUM2). Original je evidence pro before/after a záchranná síť, když parser šlápne vedle. - Deterministicky, ne heuristikou. Parser, který v 3 % případů uhodne špatně, vyrobí tichá poškození. Když si pravidlem nejsi jistý, nech NULL a spočítej, kolik jich zbylo. Platí pro strukturované pole se známým vzorem — adresa, jméno, datum. U volného textu, kde vzor neexistuje, je statistické rozpoznávání entit (NER, spaCy) legitimní nástroj, ale jeho výstup je odhad: patří k němu skóre jistoty a příznak, a do produkčního sloupce nejde bez revize.
- Změř míru rozparsování — kolik řádků se povedlo rozložit. To je metrika do zprávy.
- Parsuj před čímkoli dalším: standardizace, imputace i deduplikace stojí na atomických polích.
Adresa
Nejčastější defekt: dedikované sloupce pro čísla domů jsou 100 % prázdné a číslo je slité do
názvu ulice. Ve zdroji vypadá jako Na Budíně 854, Nádražní 1211/25a, Bezručova 12/3.
Rozklad na tři složky — název ulice, číslo popisné, číslo orientační (s případným písmenem):
-- MySQL 8: REGEXP_SUBSTR / REGEXP_REPLACE
UPDATE PARTY_ADDRESS SET
ADDR_NUM1_STD = REGEXP_SUBSTR(ADDR_STREET, '[0-9]+(?=(/[0-9]+[a-zA-Z]?)?[[:space:]]*$)'),
ADDR_NUM2_STD = REGEXP_SUBSTR(ADDR_STREET, '(?<=/)[0-9]+[a-zA-Z]?[[:space:]]*$'),
ADDR_STREET_STD = TRIM(REGEXP_REPLACE(ADDR_STREET, '[0-9]+(/[0-9]+[a-zA-Z]?)?[[:space:]]*$', ''));
Python ekvivalent, když jde o pandas pipeline:
num = raw.str.extract(r'(\d+)(?:/\d+\w?)?\s*$', expand=False) # číslo popisné
street = raw.str.replace(r'\d+(/\d+\w?)?\s*$', '', regex=True).str.strip()
Pozor na pořadí čísel: v ČR se zapisuje popisné/orientační, ale konvence není vždy dodržená.
Když parser najde jen jedno číslo, ulož ho jako popisné a orientační nech NULL — nehádej.
Složená hodnota v poli obce
Praha 5 - Smíchov, České Budějovice - České Budějovice 5 — operátor napsal do jednoho pole
celou adresní hierarchii (obec + městská část + číslo obvodu). Znemožňuje párování na registr.
-- první segment před pomlčkou = obec, zbytek = městská část
SELECT ADDR_CITY,
TRIM(SUBSTRING_INDEX(ADDR_CITY, '-', 1)) obec,
NULLIF(TRIM(SUBSTRING(ADDR_CITY, LOCATE('-',ADDR_CITY)+1)),'') mcast
FROM PARTY_ADDRESS WHERE ADDR_CITY LIKE '%-%';
Detekce kandidátů, než začneš parsovat:
SELECT ADDR_CITY, COUNT(*) cnt FROM PARTY_ADDRESS
WHERE ADDR_CITY REGEXP '[0-9]' OR ADDR_CITY LIKE '%-%' OR LENGTH(ADDR_CITY) > 50
GROUP BY 1 ORDER BY cnt DESC LIMIT 20;
Pozor: pomlčka v názvu obce je někdy legitimní (Frýdek-Místek, Brandýs nad Labem-Stará Boleslav). Proto se rozklad ověřuje proti registru obcí, ne slepě — a když po rozkladu obec
v registru není, vrať původní hodnotu.
P.O. Box a jiné mimo-rozsahové formy
P.O. Box, poštovní přihrádka, „na doručovací adrese firmy" — formálně platné adresy, které registr budov nezná. Neparsuj je násilím, označ je vlastním příznakem a vyřaď z metriky match rate (jinak vykážeš jako chybu něco, co chyba není).
Jméno
Prokládané mezery
D A V I D, Ž A N N A — každé písmeno oddělené mezerou. Slepit lze deterministicky, když
jsou všechny tokeny jednopísmenné, nebo když je jednopísmenných většina (zbytek je typicky
pomlčka nebo iniciála):
toks = s.split()
if len(toks) > 1 and all(len(t) == 1 for t in toks):
s = "".join(toks)
elif len(toks) > 1 and sum(len(t) == 1 for t in toks) >= max(2, len(toks) - 1):
s = "".join(toks)
Nikdy neslepuj tokeny obecně — Anna Marie je dvě jména, ne AnnaMarie.
Vícesložkové jméno
Křestní + prostřední jméno v jednom poli. Rozděl podle mezery a druhý token ulož zvlášť; jednopísmenné tokeny (iniciály) zahazuj, jinak vyrobíš nesmyslné prostřední jméno:
UPDATE PART_PARTY
SET PARTY_FNAME_STD = <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)),
PARTY_MNAME_STD = NULLIF(<proper>(SUBSTRING_INDEX(SUBSTRING_INDEX(PARTY_FNAME,' ',2),' ',-1)),
<proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)))
WHERE PARTY_TYPE='P' AND LENGTH(SUBSTRING_INDEX(PARTY_FNAME,' ',2)) > 1;
Titul zapsaný v poli jména
Ing. Jan Novák v PARTY_NAME. Detekuj podle shody prvního tokenu se slovníkem titulů,
ne podle tečky — J. Novák je iniciála, ne titul.
Slovníkem je číselník titulů (REF_*_TITBEF pro tituly před jménem, TITAFT za ním) a shodu
dělej fuzzy, protože v datech vedle sebe stojí MuDr, MUDr. i Mudr. Parser titul jen
oddělí; převod na kanonický tvar je práce pro dq-standardizator.
Kódovací chyby
Dvě různé věci, dvě různá řešení:
| Chyba | Příklad | Řešení |
|---|---|---|
| systematická záměna znaku | ĄUDOVÍT (má být ĽUDOVÍT) | deterministický replace, pokud je vzor jednoznačný — např. Ą na začátku slova je vždy chybný přepis, protože v polštině tam nikdy nestojí |
| ztracené písmeno | BE0ŠOVÁ (má být BENEŠOVÁ) | neopravuj v parseru. Označ příznakem _CORRUPT a nech obnovu na dq-standardizator (registr jmen) nebo dq-deduplikator (čistý dvojník v klastru) |
s = re.sub(r"(^|(?<=\s))Ą", "Ľ", s) # jednoznačné, deterministické
corrupt = bool(re.search(r"[0-9]", s)) # číslice ve jménu = poškozeno, řeší se jinde
Rozdělení je záměrné: parser dělá jen to, co je jisté. Rekonstrukce chybějícího písmene je odhad a patří tam, kde je proti čemu ho ověřit.
Datum uložené jako text
Než parsuješ, ověř formát a den/měsíc:
SELECT SUBSTRING(<col>,1,2) prvni, SUBSTRING(<col>,4,2) druhy, COUNT(*)
FROM <t> GROUP BY 1,2 ORDER BY 3 DESC LIMIT 20;
Když první pozice nikdy nepřekročí 12 a druhá ano, je pořadí MM.DD; když obojí přesahuje 12,
je formát smíšený a je nutné rozhodnout per řádek podle druhého zdroje. Slepý STR_TO_DATE
tichý swap nezachytí a rozseje ho po celé tabulce.
Výstup parseru
- Nové
_STDsloupce s atomickými hodnotami, originály nedotčené. - Míra rozparsování na každou složku (
% řádků s neprázdným výsledkem). - Chybová tabulka řádků, které parser nerozložil, s důvodem u každého — auditní stopa o tom, proč záznam neprošel. Vstup pro ruční review nebo pro rozhodnutí „tohle je mimo rozsah".