From 63774f74d3929bb76a2f29eb10e1c2eaf9e7e2d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 12:03:31 +0000 Subject: [PATCH] Add Polish license plate Anki generator with bidirectional cards Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- python_pkg/polish_license_plates/__init__.py | 7 + .../license_plate_data.py | 481 ++++++++++++++++++ .../polish_license_plates_anki.py | 242 +++++++++ .../polish_license_plates/tests/__init__.py | 1 + .../tests/test_polish_license_plates_anki.py | 231 +++++++++ 6 files changed, 963 insertions(+), 1 deletion(-) create mode 100644 python_pkg/polish_license_plates/__init__.py create mode 100644 python_pkg/polish_license_plates/license_plate_data.py create mode 100644 python_pkg/polish_license_plates/polish_license_plates_anki.py create mode 100644 python_pkg/polish_license_plates/tests/__init__.py create mode 100644 python_pkg/polish_license_plates/tests/test_polish_license_plates_anki.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c524d7e..8cead33 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -162,7 +162,7 @@ repos: - id: codespell args: - --skip=*.json,*.lock,*.min.js,*.min.css,.git,__pycache__,.venv,*.txt - - --ignore-words-list=ans,ect,nd,som,sur,te,nam,numer,lew,sie,wil,postion,clen,ther,folow,derrive + - --ignore-words-list=ans,ect,nd,som,sur,te,nam,numer,lew,sie,wil,postion,clen,ther,folow,derrive,ony,tje,noe exclude: ^(Bash/ffmpeg-build/|LaTeX/|CPP/) # =========================================================================== diff --git a/python_pkg/polish_license_plates/__init__.py b/python_pkg/polish_license_plates/__init__.py new file mode 100644 index 0000000..796216f --- /dev/null +++ b/python_pkg/polish_license_plates/__init__.py @@ -0,0 +1,7 @@ +"""Polish license plate Anki flashcard generator.""" + +from __future__ import annotations + +__all__ = ["LICENSE_PLATE_CODES"] + +from python_pkg.polish_license_plates.license_plate_data import LICENSE_PLATE_CODES diff --git a/python_pkg/polish_license_plates/license_plate_data.py b/python_pkg/polish_license_plates/license_plate_data.py new file mode 100644 index 0000000..01f446b --- /dev/null +++ b/python_pkg/polish_license_plates/license_plate_data.py @@ -0,0 +1,481 @@ +"""Database of Polish car license plate registration codes. + +This module contains a comprehensive mapping of Polish vehicle registration +plate codes to their corresponding locations (cities, powiats, voivodeships). + +Polish license plates use a system where: +- First letter indicates the voivodeship (province) +- Following 1-2 letters indicate the specific city or powiat (county) + +The database is organized by voivodeships in alphabetical order: +- B: Podlaskie +- C: Kujawsko-Pomorskie +- D: Dolnośląskie +- E: Łódzkie +- F: Lubuskie +- G: Pomorskie +- K: Małopolskie +- L: Lubelskie +- N: Warmińsko-Mazurskie +- O: Opolskie +- P: Wielkopolskie +- R: Podkarpackie +- S: Śląskie +- T: Świętokrzyskie +- W: Mazowieckie +- Z: Zachodniopomorskie + +Examples: + WA = Warszawa (Warsaw) + KR = Kraków + GD = Gdańsk +""" + +from __future__ import annotations + +LICENSE_PLATE_CODES: dict[str, str] = { + "DA": "Wrocław Fabryczna", + "DB": "Wałbrzych", + "DC": "Wrocław Śródmieście", + "DD": "Dzierżoniów", + "DE": "Wrocław Psie Pole", + "DF": "Wrocław Krzyki", + "DG": "Głogów", + "DH": "Wrocław Stare Miasto", + "DJ": "Jelenia Góra", + "DK": "Kłodzko", + "DL": "Legnica", + "DLB": "Lubań", + "DLE": "Legnica powiat", + "DMI": "Milicz", + "DN": "Wrocław Nowy Dwór", + "DO": "Oława", + "DP": "Polkowice", + "DR": "Wrocław Krzyki", + "DS": "Świdnica", + "DSR": "Środa Śląska", + "DSW": "Świebodzice", + "DT": "Twardogóra", + "DTR": "Trzebnica", + "DW": "Wałbrzych powiat", + "DWL": "Wołów", + "DWR": "Wrocław", + "DZ": "Zgorzelec", + "DZA": "Ząbkowice Śląskie", + "DZG": "Zgorzelec powiat", + "CB": "Bydgoszcz", + "CBR": "Brodnica", + "CC": "Chełmno", + "CD": "Świecie", + "CE": "Inowrocław", + "CG": "Grudziądz", + "CH": "Chojnice", + "CI": "Inowrocław powiat", + "CL": "Lipno", + "CMG": "Mogilno", + "CN": "Nakło nad Notecią", + "CR": "Radziejów", + "CT": "Toruń", + "CTR": "Toruń powiat", + "CTU": "Tuchola", + "CW": "Włocławek", + "CWA": "Wąbrzeźno", + "CWL": "Włocławek powiat", + "CZ": "Żnin", + "LB": "Biała Podlaska", + "LBI": "Biłgoraj", + "LC": "Chełm", + "LCH": "Chełm powiat", + "LHR": "Hrubieszów", + "LI": "Janów Lubelski", + "LKR": "Kraśnik", + "LKS": "Krasnystaw", + "LL": "Lublin", + "LLE": "Łęczna", + "LLU": "Łuków", + "LM": "Biała Podlaska powiat", + "LOP": "Opole Lubelskie", + "LPA": "Parczew", + "LPU": "Puławy", + "LRA": "Radzyń Podlaski", + "LRY": "Ryki", + "LSI": "Świdnik", + "LT": "Tomaszów Lubelski", + "LU": "Lublin powiat", + "LWL": "Włodawa", + "LZ": "Zamość", + "LZA": "Zamość powiat", + "FG": "Gorzów Wielkopolski", + "FKR": "Krosno Odrzańskie", + "FMI": "Międzyrzecz", + "FNW": "Nowa Sól", + "FSD": "Strzelce-Drezdenko", + "FSL": "Słubice", + "FSU": "Sulęcin", + "FSW": "Świebodzin", + "FWS": "Wschowa", + "FZ": "Zielona Góra", + "FZG": "Zielona Góra powiat", + "FZI": "Żagań", + "FZY": "Żary", + "EA": "Bełchatów", + "EB": "Łódź Bałuty", + "EBE": "Bełchatów powiat", + "EBR": "Brzeziny", + "EC": "Łęczyca", + "ED": "Łódź Śródmieście", + "EE": "Łódź Górna", + "EG": "Głowno", + "EK": "Kutno", + "EKU": "Kutno powiat", + "EL": "Łask", + "ELA": "Łowicz", + "ELE": "Łęczyca powiat", + "ELW": "Łowicz powiat", + "EM": "Opoczno", + "EO": "Opoczno powiat", + "EP": "Piotrków Trybunalski", + "EPA": "Pajęczno", + "EPD": "Poddębice", + "EPI": "Piotrków Trybunalski powiat", + "ER": "Rawa Mazowiecka", + "ERA": "Radomsko", + "ERW": "Rawa Mazowiecka powiat", + "ES": "Sieradz", + "ESI": "Sieradz powiat", + "ESK": "Skierniewice", + "ESR": "Skierniewice powiat", + "ET": "Tomaszów Mazowiecki", + "EW": "Wieluń", + "EWI": "Wieluń powiat", + "EZ": "Zduńska Wola", + "EZD": "Zgierz", + "KA": "Kraków Krowodrza", + "KB": "Bochnia", + "KBC": "Brzesko", + "KC": "Chrzanów", + "KCH": "Chrzanów powiat", + "KD": "Kraków Nowa Huta", + "KDA": "Dąbrowa Tarnowska", + "KE": "Kraków Śródmieście", + "KG": "Gorlice", + "KH": "Kraków Podgórze", + "KI": "Miechów", + "KK": "Kraków Śródmieście", + "KL": "Limanowa", + "KLI": "Limanowa powiat", + "KM": "Myślenice", + "KN": "Nowy Sącz", + "KNS": "Nowy Sącz powiat", + "KNT": "Nowy Targ", + "KO": "Olkusz", + "KOL": "Olkusz powiat", + "KOS": "Oświęcim", + "KP": "Proszowice", + "KR": "Kraków", + "KRA": "Kraków powiat", + "KS": "Sucha Beskidzka", + "KT": "Tarnów", + "KTA": "Tarnów powiat", + "KTT": "Tatry", + "KW": "Wadowice", + "KWA": "Wadowice powiat", + "WA": "Warszawa", + "WB": "Warszawa Bemowo", + "WBR": "Białobrzegi", + "WC": "Ciechanów", + "WCI": "Ciechanów powiat", + "WD": "Warszawa Praga Południe", + "WE": "Warszawa Praga Północ", + "WF": "Garwolin", + "WG": "Grodzisk Mazowiecki", + "WGM": "Grójec", + "WGO": "Gostynin", + "WGR": "Garwolin powiat", + "WH": "Warszawa Mokotów", + "WI": "Pruszków", + "WJ": "Józefów", + "WK": "Kozienice", + "WL": "Legionowo", + "WLI": "Lipsko", + "WLS": "Łosice", + "WM": "Mińsk Mazowiecki", + "WMA": "Maków Mazowiecki", + "WML": "Mława", + "WN": "Warszawa Białołęka", + "WND": "Nowy Dwór Mazowiecki", + "WO": "Otwock", + "WOR": "Ostrołęka", + "WOS": "Ostrów Mazowiecka", + "WOT": "Otwock powiat", + "WP": "Piaseczno", + "WPI": "Płońsk", + "WPL": "Płock", + "WPN": "Przasnysz", + "WPR": "Przysucha", + "WPU": "Pułtusk", + "WPY": "Płońsk powiat", + "WPZ": "Przasnysz powiat", + "WR": "Radom", + "WRA": "Radom powiat", + "WS": "Siedlce", + "WSC": "Sokołów Podlaski", + "WSE": "Siedlce powiat", + "WSI": "Sierpc", + "WSK": "Sochaczew", + "WSZ": "Szydłowiec", + "WT": "Warszawa Wola", + "WU": "Warszawa Ursus", + "WV": "Ostrołęka powiat", + "WW": "Warszawa Ochota", + "WWL": "Wołomin", + "WWY": "Wyszkó", + "WX": "Warszawa Ursynów", + "WY": "Warszawa Wola", + "WZ": "Żyrardów", + "WZW": "Zwoleń", + "OA": "Brzeg", + "OB": "Namysłów", + "OGL": "Głubczyce", + "OK": "Kędzierzyn-Koźle", + "OKL": "Kluczbork", + "OKR": "Krapkowice", + "OL": "Nysa", + "ONA": "Namysłów powiat", + "ONY": "Nysa powiat", + "OP": "Opole", + "OO": "Opole powiat", + "OOL": "Olesno", + "OPO": "Prudnik", + "OST": "Strzelce Opolskie", + "RB": "Brzozów", + "RBI": "Biłgoraj", + "RC": "Rzeszów Centrum", + "RD": "Dębica", + "RDE": "Dębica powiat", + "RJ": "Jarosław", + "RJA": "Jarosław powiat", + "RJS": "Jasło", + "RK": "Krosno", + "RKL": "Kolbuszowa", + "RKR": "Krosno powiat", + "RL": "Leżajsk", + "RLE": "Lesko", + "RLS": "Lubaczów", + "RLU": "Łańcut", + "RM": "Mielec", + "RMI": "Mielec powiat", + "RN": "Nisko", + "RP": "Przemyśl", + "RPR": "Przemyśl powiat", + "RPZ": "Przeworsk", + "RR": "Rzeszów", + "RRS": "Ropczyce-Sędziszów", + "RRZ": "Rzeszów powiat", + "RSA": "Sanok", + "RSN": "Sanok powiat", + "RSR": "Stalowa Wola", + "RST": "Strzyżów", + "RTA": "Tarnobrzeg", + "RZ": "Rzeszów", + "BA": "Augustów", + "BBI": "Białystok", + "BC": "Hajnówka", + "BD": "Bielsk Podlaski", + "BE": "Wysokie Mazowieckie", + "BG": "Grajewo", + "BGR": "Grajewo powiat", + "BH": "Hajnówka powiat", + "BHA": "Hajnówka", + "BI": "Białystok", + "BIA": "Białystok powiat", + "BJ": "Kolno", + "BK": "Kolno powiat", + "BKL": "Kolno", + "BL": "Łomża", + "BLM": "Łomża powiat", + "BLS": "Łomża", + "BM": "Mońki", + "BMN": "Mońki powiat", + "BO": "Sokółka", + "BP": "Zambrów", + "BPI": "Piątnica", + "BR": "Siemiatycze", + "BS": "Sokółka powiat", + "BSE": "Sejny", + "BSI": "Siemiatycze powiat", + "BSK": "Sokółka", + "BSU": "Suwałki", + "BT": "Suwałki powiat", + "BWM": "Wysokie Mazowieckie powiat", + "BZA": "Zambrów powiat", + "GA": "Gdańsk", + "GB": "Bytów", + "GBY": "Bytów powiat", + "GC": "Chojnice", + "GCH": "Chojnice powiat", + "GCZ": "Człuchów", + "GD": "Gdańsk", + "GDA": "Gdańsk powiat", + "GDY": "Gdynia", + "GI": "Kościerzyna", + "GKA": "Kartuzy", + "GKS": "Kościerzyna powiat", + "GKW": "Kwidzyn", + "GL": "Lębork", + "GLE": "Lębork powiat", + "GMB": "Malbork", + "GND": "Nowy Dwór Gdański", + "GP": "Puck", + "GPU": "Puck powiat", + "GS": "Słupsk", + "GSL": "Słupsk powiat", + "GSP": "Starogard Gdański", + "GST": "Sztum", + "GT": "Tczew", + "GTB": "Tczew powiat", + "GW": "Wejherowo", + "GWE": "Wejherowo powiat", + "SA": "Sosnowiec", + "SB": "Bielsko-Biała", + "SBB": "Bielsko-Biała powiat", + "SBE": "Będzin", + "SBI": "Bieruń-Lędziny", + "SC": "Chorzów", + "SCH": "Cieszyn", + "SCI": "Cieszyn powiat", + "SD": "Dąbrowa Górnicza", + "SF": "Racibórz", + "SG": "Gliwice", + "SGI": "Gliwice powiat", + "SH": "Chorzów", + "SI": "Siemianowice Śląskie", + "SJ": "Jastrzębie-Zdrój", + "SJZ": "Jastrzębie-Zdrój", + "SK": "Katowice", + "SKA": "Katowice powiat", + "SKL": "Kłobuck", + "SKT": "Lubliniec", + "SL": "Rybnik", + "SLU": "Lubliniec powiat", + "SM": "Mysłowice", + "SMI": "Mikołów", + "SML": "Myszków", + "SN": "Nowy Targ", + "SO": "Sosnowiec powiat", + "SP": "Piekary Śląskie", + "SPI": "Pszczyna", + "SPS": "Pszczyna powiat", + "SR": "Rybnik powiat", + "SRC": "Racibórz powiat", + "SRY": "Rybnik", + "SS": "Świętochłowice", + "ST": "Tychy", + "STA": "Tarnowskie Góry", + "STG": "Tarnowskie Góry powiat", + "SW": "Wodzisław Śląski", + "SWD": "Wodzisław Śląski powiat", + "SY": "Ruda Śląska", + "SZ": "Zabrze", + "SZA": "Zawiercie", + "SZO": "Żory", + "SZY": "Żywiec", + "TB": "Busko-Zdrój", + "TBU": "Busko-Zdrój powiat", + "TJE": "Jędrzejów", + "TK": "Kielce", + "TKA": "Kazimierza Wielka", + "TKI": "Kielce powiat", + "TKN": "Końskie", + "TKO": "Końskie powiat", + "TOS": "Ostrołęka", + "TPI": "Pińczów", + "TSA": "Sandomierz", + "TSK": "Skarżysko-Kamienna", + "TST": "Starachowice", + "TWL": "Włoszczowa", + "NBA": "Bartoszyce", + "NBR": "Braniewo", + "NDZ": "Działdowo", + "NE": "Elbląg", + "NEL": "Elbląg powiat", + "NEB": "Ełk", + "NEK": "Ełk powiat", + "NG": "Giżycko", + "NGI": "Giżycko powiat", + "NGO": "Gołdap", + "NI": "Iława", + "NKE": "Kętrzyn", + "NL": "Lidzbark Warmiński", + "NMR": "Mrągowo", + "NNI": "Nidzica", + "NO": "Olsztyn", + "NOE": "Olecko", + "NOL": "Olsztyn powiat", + "NOS": "Ostróda", + "NPI": "Pisz", + "NSZ": "Szczytno", + "NW": "Węgorzewo", + "PCD": "Czarnków-Trzcianka", + "PCH": "Chodzież", + "PGN": "Gniezno", + "PGO": "Gostyń", + "PGR": "Grodzisk Wielkopolski", + "PIA": "Piła", + "PJ": "Jarocin", + "PJA": "Jarocin powiat", + "PK": "Kępno", + "PKA": "Kalisz", + "PKL": "Kalisz powiat", + "PKN": "Koło", + "PKO": "Konin", + "PKS": "Kościan", + "PL": "Leszno", + "PLE": "Leszno powiat", + "PMI": "Międzychód", + "PNT": "Nowy Tomyśl", + "PO": "Poznań", + "POB": "Oborniki", + "POL": "Ostrów Wielkopolski", + "POP": "Opole", + "POS": "Ostrzeszów", + "POT": "Ostrów Wielkopolski powiat", + "PP": "Pleszew", + "PPI": "Piła powiat", + "PPL": "Pleszew powiat", + "PRA": "Poznań powiat", + "PRS": "Rawicz", + "PSE": "Śrem", + "PSL": "Słupca", + "PSR": "Środa Wielkopolska", + "PSZ": "Szamotuły", + "PT": "Turek", + "PTU": "Turek powiat", + "PW": "Wągrowiec", + "PWA": "Wągrowiec powiat", + "PWL": "Wolsztyn", + "PWR": "Września", + "PZ": "Poznań", + "PZL": "Złotów", + "ZBI": "Białogard", + "ZCH": "Choszczno", + "ZG": "Gryfice", + "ZGR": "Gryfino", + "ZI": "Stargard", + "ZK": "Kołobrzeg", + "ZKA": "Kamień Pomorski", + "ZKL": "Kołobrzeg powiat", + "ZKO": "Koszalin", + "ZKS": "Koszalin powiat", + "ZL": "Łobez", + "ZM": "Myślibórz", + "ZPL": "Pyrzyce", + "ZPO": "Police", + "ZS": "Szczecin", + "ZSL": "Sławno", + "ZST": "Stargard powiat", + "ZSW": "Świnoujście", + "ZSZ": "Szczecinek", + "ZW": "Wałcz", + "ZZ": "Szczecin powiat", +} diff --git a/python_pkg/polish_license_plates/polish_license_plates_anki.py b/python_pkg/polish_license_plates/polish_license_plates_anki.py new file mode 100644 index 0000000..d120d00 --- /dev/null +++ b/python_pkg/polish_license_plates/polish_license_plates_anki.py @@ -0,0 +1,242 @@ +"""Anki flashcard generator for Polish car license plates. + +Generates Anki-compatible flashcard decks with bidirectional cards for Polish +vehicle registration plate codes and their corresponding locations. + +Creates two types of cards: +1. Code → Location (e.g., WY → Warszawa Wola) +2. Location → Code (e.g., Warszawa Wola → WY) + +Usage: + # Generate Anki cards for all Polish license plates + python -m python_pkg.polish_license_plates.polish_license_plates_anki + + # Specify custom output file + python -m python_pkg.polish_license_plates.polish_license_plates_anki \ + --output plates.apkg + +Output: + Creates a self-contained .apkg file that can be directly imported into Anki. +""" + +from __future__ import annotations + +import argparse +import hashlib +from pathlib import Path +import random +import sys +from typing import TYPE_CHECKING + +import genanki + +from python_pkg.polish_license_plates.license_plate_data import LICENSE_PLATE_CODES + +if TYPE_CHECKING: + from collections.abc import Sequence + + +def generate_anki_package( + deck_name: str = "Polish License Plates", +) -> genanki.Package: + """Generate Anki package (.apkg) for Polish license plates. + + Creates two cards for each license plate code: + 1. Code → Location + 2. Location → Code + + Args: + deck_name: Name for the Anki deck. + + Returns: + genanki.Package object ready to be written to file. + """ + # Create unique model ID based on deck name + model_id_hash = hashlib.md5( + f"polish_license_plates_{deck_name}".encode(), + usedforsecurity=False, + ) + model_id = int(model_id_hash.hexdigest()[:8], 16) + + # Define the note model with centered styling and bidirectional templates + card_css = """ +.card { + font-family: Arial, sans-serif; + font-size: 28px; + text-align: center; + color: #333; + background-color: #fff; +} +.card.night_mode { + color: #eee; + background-color: #2f2f2f; +} +.question { + display: flex; + justify-content: center; + align-items: center; + min-height: 60vh; + font-size: 48px; + font-weight: bold; + color: #2C3E50; +} +.card.night_mode .question { + color: #ECF0F1; +} +.answer { + font-size: 36px; + font-weight: bold; + margin-top: 20px; + color: #27AE60; +} +.card.night_mode .answer { + color: #2ECC71; +} +.plate-code { + font-family: 'Courier New', monospace; + background-color: #FFD700; + color: #000; + padding: 15px 30px; + border: 3px solid #000; + border-radius: 8px; + display: inline-block; + letter-spacing: 5px; +} +.card.night_mode .plate-code { + background-color: #FFA500; +} +""" + + my_model = genanki.Model( + model_id, + "Polish License Plate Model", + fields=[ + {"name": "Code"}, + {"name": "Location"}, + ], + templates=[ + { + "name": "Code → Location", + "qfmt": '