mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 14:43:01 +02:00
Add Polish license plate Anki generator with bidirectional cards
Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
This commit is contained in:
parent
99d967cc56
commit
63774f74d3
@ -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/)
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
7
python_pkg/polish_license_plates/__init__.py
Normal file
7
python_pkg/polish_license_plates/__init__.py
Normal file
@ -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
|
||||
481
python_pkg/polish_license_plates/license_plate_data.py
Normal file
481
python_pkg/polish_license_plates/license_plate_data.py
Normal file
@ -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",
|
||||
}
|
||||
242
python_pkg/polish_license_plates/polish_license_plates_anki.py
Normal file
242
python_pkg/polish_license_plates/polish_license_plates_anki.py
Normal file
@ -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": '<div class="question">'
|
||||
'<span class="plate-code">{{Code}}</span>'
|
||||
"</div>",
|
||||
"afmt": '<div class="question">'
|
||||
'<span class="plate-code">{{Code}}</span>'
|
||||
"</div>"
|
||||
'<hr id="answer">'
|
||||
'<div class="answer">{{Location}}</div>',
|
||||
},
|
||||
{
|
||||
"name": "Location → Code",
|
||||
"qfmt": '<div class="question">{{Location}}</div>',
|
||||
"afmt": '<div class="question">{{Location}}</div>'
|
||||
'<hr id="answer">'
|
||||
'<div class="answer">'
|
||||
'<span class="plate-code">{{Code}}</span>'
|
||||
"</div>",
|
||||
},
|
||||
],
|
||||
css=card_css,
|
||||
)
|
||||
|
||||
# Create unique deck ID
|
||||
deck_id = random.randrange(1 << 30, 1 << 31) # noqa: S311
|
||||
|
||||
# Create the deck
|
||||
my_deck = genanki.Deck(deck_id, deck_name)
|
||||
|
||||
# Generate notes for each license plate code
|
||||
for code, location in sorted(LICENSE_PLATE_CODES.items()):
|
||||
note = genanki.Note(
|
||||
model=my_model,
|
||||
fields=[code, location],
|
||||
tags=["geography", "poland", "license-plates", "transportation"],
|
||||
)
|
||||
my_deck.add_note(note)
|
||||
|
||||
# Create package
|
||||
return genanki.Package(my_deck)
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
"""Main entry point.
|
||||
|
||||
Args:
|
||||
argv: Command line arguments.
|
||||
|
||||
Returns:
|
||||
Exit code.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate Anki flashcards for Polish license plates.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
"-o",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Output file path (default: polish_license_plates.apkg)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--deck-name",
|
||||
"-d",
|
||||
type=str,
|
||||
default="Polish License Plates",
|
||||
help="Name for the Anki deck (default: 'Polish License Plates')",
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# Determine output path
|
||||
output_path = (
|
||||
Path(args.output) if args.output else Path("polish_license_plates.apkg")
|
||||
)
|
||||
|
||||
try:
|
||||
num_codes = len(LICENSE_PLATE_CODES)
|
||||
num_cards = num_codes * 2 # Two cards per code (bidirectional)
|
||||
|
||||
sys.stdout.write(
|
||||
f"Generating flashcards for {num_codes} Polish license plate codes...\n"
|
||||
)
|
||||
sys.stdout.write(
|
||||
"Each code will have 2 cards: Code → Location and Location → Code\n"
|
||||
)
|
||||
|
||||
# Generate the package
|
||||
package = generate_anki_package(args.deck_name)
|
||||
|
||||
# Write to file
|
||||
package.write_to_file(str(output_path))
|
||||
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.write("=" * 70 + "\n")
|
||||
sys.stdout.write("FLASHCARD GENERATION COMPLETE\n")
|
||||
sys.stdout.write("=" * 70 + "\n")
|
||||
sys.stdout.write(f"License plate codes: {num_codes}\n")
|
||||
sys.stdout.write(f"Total flashcards: {num_cards} (bidirectional)\n")
|
||||
sys.stdout.write(f"Output file: {output_path.absolute()}\n")
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.write("Card types:\n")
|
||||
sys.stdout.write(" 1. Code → Location (e.g., WY → Warszawa Wola)\n")
|
||||
sys.stdout.write(" 2. Location → Code (e.g., Warszawa Wola → WY)\n")
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.write("To import into Anki:\n")
|
||||
sys.stdout.write(" 1. Open Anki\n")
|
||||
sys.stdout.write(" 2. File → Import\n")
|
||||
sys.stdout.write(f" 3. Select: {output_path.absolute()}\n")
|
||||
sys.stdout.write(" 4. Click Import\n")
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.write("You can now learn Polish license plates both ways!\n")
|
||||
except (OSError, ValueError) as e:
|
||||
sys.stderr.write(f"Error: {e}\n")
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
1
python_pkg/polish_license_plates/tests/__init__.py
Normal file
1
python_pkg/polish_license_plates/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests init file."""
|
||||
@ -0,0 +1,231 @@
|
||||
"""Tests for the Polish license plates Anki generator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
from python_pkg.polish_license_plates.license_plate_data import (
|
||||
LICENSE_PLATE_CODES,
|
||||
)
|
||||
from python_pkg.polish_license_plates.polish_license_plates_anki import (
|
||||
generate_anki_package,
|
||||
main,
|
||||
)
|
||||
except ImportError:
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||
from python_pkg.polish_license_plates.license_plate_data import (
|
||||
LICENSE_PLATE_CODES,
|
||||
)
|
||||
from python_pkg.polish_license_plates.polish_license_plates_anki import (
|
||||
generate_anki_package,
|
||||
main,
|
||||
)
|
||||
|
||||
|
||||
class TestLicensePlateData:
|
||||
"""Tests for license plate data."""
|
||||
|
||||
def test_has_codes(self) -> None:
|
||||
"""Test that we have license plate codes."""
|
||||
assert len(LICENSE_PLATE_CODES) > 0
|
||||
|
||||
def test_all_codes_are_uppercase(self) -> None:
|
||||
"""Test that all codes are uppercase strings."""
|
||||
for code in LICENSE_PLATE_CODES:
|
||||
assert isinstance(code, str)
|
||||
assert code.isupper()
|
||||
assert len(code) >= 2
|
||||
|
||||
def test_all_locations_are_strings(self) -> None:
|
||||
"""Test that all locations are non-empty strings."""
|
||||
for location in LICENSE_PLATE_CODES.values():
|
||||
assert isinstance(location, str)
|
||||
assert len(location) > 0
|
||||
|
||||
def test_no_duplicate_codes(self) -> None:
|
||||
"""Test that all codes are unique."""
|
||||
codes = list(LICENSE_PLATE_CODES.keys())
|
||||
assert len(codes) == len(set(codes))
|
||||
|
||||
def test_warsaw_codes_present(self) -> None:
|
||||
"""Test that Warsaw codes are in the database."""
|
||||
warsaw_codes = [
|
||||
"WA",
|
||||
"WB",
|
||||
"WC",
|
||||
"WD",
|
||||
"WE",
|
||||
"WF",
|
||||
"WG",
|
||||
"WH",
|
||||
"WI",
|
||||
"WJ",
|
||||
"WK",
|
||||
"WL",
|
||||
"WM",
|
||||
"WN",
|
||||
"WO",
|
||||
"WP",
|
||||
"WR",
|
||||
"WS",
|
||||
"WT",
|
||||
"WU",
|
||||
"WW",
|
||||
"WX",
|
||||
"WY",
|
||||
"WZ",
|
||||
]
|
||||
for code in warsaw_codes:
|
||||
assert code in LICENSE_PLATE_CODES
|
||||
|
||||
def test_major_cities_present(self) -> None:
|
||||
"""Test that major Polish cities have codes."""
|
||||
major_cities = {
|
||||
"WA": "Warszawa",
|
||||
"KR": "Kraków",
|
||||
"GD": "Gdańsk",
|
||||
"PO": "Poznań",
|
||||
"WR": "Radom",
|
||||
"BI": "Białystok",
|
||||
}
|
||||
for code, city_part in major_cities.items():
|
||||
assert code in LICENSE_PLATE_CODES
|
||||
assert city_part.lower() in LICENSE_PLATE_CODES[code].lower()
|
||||
|
||||
def test_voivodeship_prefixes_present(self) -> None:
|
||||
"""Test that all 16 voivodeship prefixes are represented."""
|
||||
voivodeship_prefixes = {
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"K",
|
||||
"L",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"W",
|
||||
"Z",
|
||||
}
|
||||
found_prefixes = {code[0] for code in LICENSE_PLATE_CODES}
|
||||
assert voivodeship_prefixes.issubset(found_prefixes)
|
||||
|
||||
|
||||
class TestGenerateAnkiPackage:
|
||||
"""Tests for generating Anki package."""
|
||||
|
||||
def test_generates_package(self) -> None:
|
||||
"""Test that output is a genanki Package."""
|
||||
package = generate_anki_package("Test Deck")
|
||||
assert package is not None
|
||||
assert len(package.decks) == 1
|
||||
|
||||
def test_generates_notes_for_all_codes(self) -> None:
|
||||
"""Test that package contains notes for all license plate codes."""
|
||||
package = generate_anki_package()
|
||||
deck = package.decks[0]
|
||||
# Each code generates one note with two card templates
|
||||
assert len(deck.notes) == len(LICENSE_PLATE_CODES)
|
||||
|
||||
def test_custom_deck_name(self) -> None:
|
||||
"""Test that custom deck name is used."""
|
||||
package = generate_anki_package("Custom Deck")
|
||||
deck = package.decks[0]
|
||||
assert deck.name == "Custom Deck"
|
||||
|
||||
def test_notes_have_correct_fields(self) -> None:
|
||||
"""Test that notes have Code and Location fields."""
|
||||
package = generate_anki_package()
|
||||
deck = package.decks[0]
|
||||
note = deck.notes[0]
|
||||
# Note should have 2 fields: Code and Location
|
||||
assert len(note.fields) == 2
|
||||
# Fields should be non-empty strings
|
||||
assert len(note.fields[0]) > 0
|
||||
assert len(note.fields[1]) > 0
|
||||
|
||||
def test_notes_have_tags(self) -> None:
|
||||
"""Test that notes have appropriate tags."""
|
||||
package = generate_anki_package()
|
||||
deck = package.decks[0]
|
||||
note = deck.notes[0]
|
||||
assert "geography" in note.tags
|
||||
assert "poland" in note.tags
|
||||
assert "license-plates" in note.tags
|
||||
|
||||
def test_model_has_bidirectional_templates(self) -> None:
|
||||
"""Test that the model has two card templates (bidirectional)."""
|
||||
package = generate_anki_package()
|
||||
deck = package.decks[0]
|
||||
model = deck.notes[0].model
|
||||
# Should have 2 templates: Code → Location and Location → Code
|
||||
assert len(model.templates) == 2
|
||||
|
||||
|
||||
class TestMain:
|
||||
"""Tests for the main CLI function."""
|
||||
|
||||
def test_creates_output_file(self, tmp_path: Path) -> None:
|
||||
"""Test that main creates the output file."""
|
||||
output_file = tmp_path / "test_output.apkg"
|
||||
|
||||
result = main(
|
||||
[
|
||||
"--output",
|
||||
str(output_file),
|
||||
]
|
||||
)
|
||||
|
||||
assert result == 0
|
||||
assert output_file.exists()
|
||||
|
||||
def test_custom_deck_name(self, tmp_path: Path) -> None:
|
||||
"""Test that custom deck name is used."""
|
||||
output_file = tmp_path / "test_output.apkg"
|
||||
|
||||
result = main(
|
||||
[
|
||||
"--output",
|
||||
str(output_file),
|
||||
"--deck-name",
|
||||
"Custom Deck",
|
||||
]
|
||||
)
|
||||
|
||||
assert result == 0
|
||||
assert output_file.exists()
|
||||
|
||||
def test_default_output_path(self) -> None:
|
||||
"""Test that default output path is used when not specified."""
|
||||
# Clean up any existing file
|
||||
default_path = Path("polish_license_plates.apkg")
|
||||
if default_path.exists():
|
||||
default_path.unlink()
|
||||
|
||||
result = main([])
|
||||
|
||||
assert result == 0
|
||||
assert default_path.exists()
|
||||
|
||||
# Clean up
|
||||
default_path.unlink()
|
||||
|
||||
def test_help_flag(self) -> None:
|
||||
"""Test that --help works."""
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main(["--help"])
|
||||
assert exc_info.value.code == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Loading…
Reference in New Issue
Block a user