infosoud 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
infosoud-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pravnikovo.info
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: infosoud
3
+ Version: 0.1.0
4
+ Summary: Czech court case lookup tool (infosoud.gov.cz)
5
+ Author: pravnikovo.info
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://infosoud.pravnikovo.info
8
+ Project-URL: Repository, https://github.com/pravnikovo/infosoud
9
+ Keywords: czech,court,justice,infosoud,právní,soud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Office/Business
13
+ Classifier: Natural Language :: Czech
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.20
18
+ Provides-Extra: web
19
+ Requires-Dist: flask>=2.0; extra == "web"
20
+ Dynamic: license-file
21
+
22
+ # InfoSoud
23
+
24
+ Neoficiální klient pro [InfoSoud](https://infosoud.gov.cz) — vyhledávání v soudních řízeních České republiky.
25
+
26
+ Umožňuje vyhledat průběh soudního řízení, nařízená jednání a události podle spisové značky. Data pocházejí z veřejného API Ministerstva spravedlnosti ČR.
27
+
28
+ **[infosoud.pravnikovo.info](https://infosoud.pravnikovo.info)** — webové rozhraní
29
+
30
+ ## Instalace
31
+
32
+ ```bash
33
+ pip install infosoud
34
+ ```
35
+
36
+ ## Použití
37
+
38
+ ### Příkazový řádek
39
+
40
+ ```bash
41
+ # Vyhledat řízení
42
+ infosoud "1 T 64/2024" OSSCEDC
43
+ infosoud "1T64_2024 OSSCEDC" # kompaktní formát
44
+ infosoud "4 T 21/2025 melnik" # název soudu místo kódu
45
+
46
+ # Zobrazit jednání
47
+ infosoud --hearings "1 T 64/2024" OSSCEDC
48
+
49
+ # JSON výstup
50
+ infosoud --json "1 T 64/2024" OSSCEDC
51
+
52
+ # CSV výstup (události)
53
+ infosoud --csv "1 T 64/2024" OSSCEDC
54
+
55
+ # Vypsat soudy
56
+ infosoud --courts
57
+ infosoud --courts --csv
58
+ ```
59
+
60
+ ### Python
61
+
62
+ ```python
63
+ from infosoud import InfoSoudClient, parse_spis_zn
64
+
65
+ client = InfoSoudClient()
66
+
67
+ # Vyhledat řízení
68
+ case = client.search_case("1 T 64/2024", "OSSCEDC")
69
+ print(case["stav"]) # "nevyřízená věc"
70
+ print(case["organizace"]) # "Okresní soud Děčín"
71
+
72
+ for event in case["udalosti"]:
73
+ print(f"{event['datum']} {event['udalost']}")
74
+
75
+ # Vyhledat jednání
76
+ hearings = client.search_hearings("1 T 64/2024", "OSSCEDC")
77
+ for h in hearings["udalosti"]:
78
+ print(f"{h['datum']} {h['cas']} {h['druhJednani']} {h['resitel']}")
79
+
80
+ # Detail události (čas, jednací síň)
81
+ detail = client.get_event_detail("1 T 64/2024", "OSSCEDC", "NAR_JED", 48)
82
+ for attr in detail["atributy"]:
83
+ print(f"{attr['typ']}: {attr['hodnota']}")
84
+
85
+ # Spisová značka — tolerantní parser
86
+ spis = parse_spis_zn("1T64_2024")
87
+ print(spis.canonical()) # "1 T 64/2024"
88
+ print(spis.compact()) # "1T64_2024"
89
+
90
+ # Fuzzy vyhledání soudu
91
+ code = client.resolve_court_name("melnik") # → "OSSTCME"
92
+ code = client.resolve_court_name("praha 9") # → "OSPHA09"
93
+
94
+ # Seznam soudů
95
+ courts = client.build_court_map() # {kód: název} pro všech 104 soudů
96
+ ```
97
+
98
+ ### Formáty spisové značky
99
+
100
+ Parser je maximálně tolerantní — všechny tyto formáty jsou ekvivalentní:
101
+
102
+ ```
103
+ 1 T 64/2024 1T64/2024 1T 64/2024
104
+ 1 T64/2024 1T64_2024 1 T 64 / 2024
105
+ ```
106
+
107
+ Kód soudu lze přidat za sp. zn.: `1T64_2024 OSSCEDC`
108
+
109
+ Místo kódu soudu lze zadat název: `4 T 21/2025 melnik` (bez diakritiky)
110
+
111
+ ## API endpointy
112
+
113
+ | Endpoint | Metoda | Popis |
114
+ |----------|--------|-------|
115
+ | `/api/v1/rizeni/vyhledej` | POST | Vyhledání řízení |
116
+ | `/api/v1/jednani/vyhledej` | POST | Vyhledání jednání |
117
+ | `/api/v1/udalost/vyhledej` | POST | Detail události |
118
+ | `/api/v1/organizace/lov` | GET | Seznam krajských/vrchních soudů |
119
+ | `/api/v1/organizace/podrizene/lov` | GET | Seznam okresních soudů |
120
+
121
+ Base URL: `https://infosoud.gov.cz/api/v1`
122
+
123
+ ## Kódy soudů
124
+
125
+ Kompletní seznam 104 soudů (96 z API + 8 poboček):
126
+
127
+ ```bash
128
+ infosoud --courts
129
+ ```
130
+
131
+ Příklady: `OSSCEDC` (OS Děčín), `OSPHA09` (OS Praha 9), `KSJIMBM` (KS Brno), `MSPHAAB` (MS Praha)
132
+
133
+ ## Webové rozhraní
134
+
135
+ ```bash
136
+ pip install infosoud[web]
137
+ python -m infosoud.web
138
+ ```
139
+
140
+ Otevře webserver na `http://localhost:8060` s vyhledávacím formulářem, timeline událostí, ICS exportem jednání a CSV exportem.
141
+
142
+ ## Licence
143
+
144
+ MIT — viz [LICENSE](LICENSE)
145
+
146
+ ---
147
+
148
+ *Neoficiální nástroj. Data pocházejí z [infosoud.gov.cz](https://infosoud.gov.cz). Projekt [pravnikovo.info](https://pravnikovo.info) — open source nástroje pro právníky.*
@@ -0,0 +1,127 @@
1
+ # InfoSoud
2
+
3
+ Neoficiální klient pro [InfoSoud](https://infosoud.gov.cz) — vyhledávání v soudních řízeních České republiky.
4
+
5
+ Umožňuje vyhledat průběh soudního řízení, nařízená jednání a události podle spisové značky. Data pocházejí z veřejného API Ministerstva spravedlnosti ČR.
6
+
7
+ **[infosoud.pravnikovo.info](https://infosoud.pravnikovo.info)** — webové rozhraní
8
+
9
+ ## Instalace
10
+
11
+ ```bash
12
+ pip install infosoud
13
+ ```
14
+
15
+ ## Použití
16
+
17
+ ### Příkazový řádek
18
+
19
+ ```bash
20
+ # Vyhledat řízení
21
+ infosoud "1 T 64/2024" OSSCEDC
22
+ infosoud "1T64_2024 OSSCEDC" # kompaktní formát
23
+ infosoud "4 T 21/2025 melnik" # název soudu místo kódu
24
+
25
+ # Zobrazit jednání
26
+ infosoud --hearings "1 T 64/2024" OSSCEDC
27
+
28
+ # JSON výstup
29
+ infosoud --json "1 T 64/2024" OSSCEDC
30
+
31
+ # CSV výstup (události)
32
+ infosoud --csv "1 T 64/2024" OSSCEDC
33
+
34
+ # Vypsat soudy
35
+ infosoud --courts
36
+ infosoud --courts --csv
37
+ ```
38
+
39
+ ### Python
40
+
41
+ ```python
42
+ from infosoud import InfoSoudClient, parse_spis_zn
43
+
44
+ client = InfoSoudClient()
45
+
46
+ # Vyhledat řízení
47
+ case = client.search_case("1 T 64/2024", "OSSCEDC")
48
+ print(case["stav"]) # "nevyřízená věc"
49
+ print(case["organizace"]) # "Okresní soud Děčín"
50
+
51
+ for event in case["udalosti"]:
52
+ print(f"{event['datum']} {event['udalost']}")
53
+
54
+ # Vyhledat jednání
55
+ hearings = client.search_hearings("1 T 64/2024", "OSSCEDC")
56
+ for h in hearings["udalosti"]:
57
+ print(f"{h['datum']} {h['cas']} {h['druhJednani']} {h['resitel']}")
58
+
59
+ # Detail události (čas, jednací síň)
60
+ detail = client.get_event_detail("1 T 64/2024", "OSSCEDC", "NAR_JED", 48)
61
+ for attr in detail["atributy"]:
62
+ print(f"{attr['typ']}: {attr['hodnota']}")
63
+
64
+ # Spisová značka — tolerantní parser
65
+ spis = parse_spis_zn("1T64_2024")
66
+ print(spis.canonical()) # "1 T 64/2024"
67
+ print(spis.compact()) # "1T64_2024"
68
+
69
+ # Fuzzy vyhledání soudu
70
+ code = client.resolve_court_name("melnik") # → "OSSTCME"
71
+ code = client.resolve_court_name("praha 9") # → "OSPHA09"
72
+
73
+ # Seznam soudů
74
+ courts = client.build_court_map() # {kód: název} pro všech 104 soudů
75
+ ```
76
+
77
+ ### Formáty spisové značky
78
+
79
+ Parser je maximálně tolerantní — všechny tyto formáty jsou ekvivalentní:
80
+
81
+ ```
82
+ 1 T 64/2024 1T64/2024 1T 64/2024
83
+ 1 T64/2024 1T64_2024 1 T 64 / 2024
84
+ ```
85
+
86
+ Kód soudu lze přidat za sp. zn.: `1T64_2024 OSSCEDC`
87
+
88
+ Místo kódu soudu lze zadat název: `4 T 21/2025 melnik` (bez diakritiky)
89
+
90
+ ## API endpointy
91
+
92
+ | Endpoint | Metoda | Popis |
93
+ |----------|--------|-------|
94
+ | `/api/v1/rizeni/vyhledej` | POST | Vyhledání řízení |
95
+ | `/api/v1/jednani/vyhledej` | POST | Vyhledání jednání |
96
+ | `/api/v1/udalost/vyhledej` | POST | Detail události |
97
+ | `/api/v1/organizace/lov` | GET | Seznam krajských/vrchních soudů |
98
+ | `/api/v1/organizace/podrizene/lov` | GET | Seznam okresních soudů |
99
+
100
+ Base URL: `https://infosoud.gov.cz/api/v1`
101
+
102
+ ## Kódy soudů
103
+
104
+ Kompletní seznam 104 soudů (96 z API + 8 poboček):
105
+
106
+ ```bash
107
+ infosoud --courts
108
+ ```
109
+
110
+ Příklady: `OSSCEDC` (OS Děčín), `OSPHA09` (OS Praha 9), `KSJIMBM` (KS Brno), `MSPHAAB` (MS Praha)
111
+
112
+ ## Webové rozhraní
113
+
114
+ ```bash
115
+ pip install infosoud[web]
116
+ python -m infosoud.web
117
+ ```
118
+
119
+ Otevře webserver na `http://localhost:8060` s vyhledávacím formulářem, timeline událostí, ICS exportem jednání a CSV exportem.
120
+
121
+ ## Licence
122
+
123
+ MIT — viz [LICENSE](LICENSE)
124
+
125
+ ---
126
+
127
+ *Neoficiální nástroj. Data pocházejí z [infosoud.gov.cz](https://infosoud.gov.cz). Projekt [pravnikovo.info](https://pravnikovo.info) — open source nástroje pro právníky.*
@@ -0,0 +1,3 @@
1
+ """InfoSoud — Czech court case lookup tool (infosoud.gov.cz)."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,165 @@
1
+ """CLI for InfoSoud — Czech court case lookup.
2
+
3
+ Usage:
4
+ python -m infosoud "1 T 64/2024" OSSCEDC
5
+ python -m infosoud "1T64_2024 OSSCEDC"
6
+ python -m infosoud "4 T 21/2025 melnik"
7
+ python -m infosoud --hearings "1 T 64/2024" OSSCEDC
8
+ python -m infosoud --courts
9
+ python -m infosoud --json "1 T 64/2024" OSSCEDC
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import sys
17
+
18
+ from .client import InfoSoudClient, parse_spis_zn, EVENT_LABELS
19
+
20
+
21
+ def format_case_summary(data: dict) -> str:
22
+ """Format case data into human-readable summary."""
23
+ lines = []
24
+ org = data.get("organizace", "?")
25
+ nadr = data.get("nadrizenaOrganizace", "")
26
+ stav = data.get("stav") or "?"
27
+ stav_datum = data.get("stavDatum", "")
28
+
29
+ spzn = (f"{data.get('cislo', '') or ''} {data.get('druh', '') or ''} "
30
+ f"{data.get('bcVec', '') or ''}/{data.get('rocnik', '') or ''}")
31
+ lines.append(f"Sp. zn.: {spzn}")
32
+ lines.append(f"Soud: {org}" + (f" ({nadr})" if nadr else ""))
33
+ lines.append(f"Stav: {stav}" + (f" (od {stav_datum})" if stav_datum else ""))
34
+
35
+ events = data.get("udalosti") or []
36
+ if events:
37
+ lines.append(f"\nUdálosti ({len(events)}):")
38
+ for ev in events:
39
+ code = ev.get("udalost", "?")
40
+ label = EVENT_LABELS.get(code, code)
41
+ datum = ev.get("datum", "?")
42
+ zruseno = " [ZRUŠENO]" if ev.get("zruseno") else ""
43
+ lines.append(f" {datum} {label}{zruseno}")
44
+
45
+ return "\n".join(lines)
46
+
47
+
48
+ def format_hearings_summary(data: dict) -> str:
49
+ """Format hearings data into human-readable summary."""
50
+ lines = [f"Jednání — {data.get('organizace', '?')}"]
51
+ events = data.get("udalosti") or []
52
+ if not events:
53
+ lines.append(" Žádná jednání nařízena.")
54
+ return "\n".join(lines)
55
+ for ev in events:
56
+ zruseno = " [ZRUŠENO]" if ev.get("jednaniZruseno") else ""
57
+ lines.append(f" {ev.get('datum', '?')} {ev.get('cas', '?')} "
58
+ f"{ev.get('druhJednani', '?')}{zruseno}")
59
+ lines.append(f" Soudce: {ev.get('resitel', '?')}")
60
+ lines.append(f" Síň: {ev.get('jednaciSin', '?')}")
61
+ return "\n".join(lines)
62
+
63
+
64
+ def main():
65
+ parser = argparse.ArgumentParser(
66
+ prog="infosoud",
67
+ description="InfoSoud — vyhledávání v soudních řízeních (infosoud.gov.cz)",
68
+ )
69
+ parser.add_argument("spis_zn", nargs="?",
70
+ help="Spisová značka (např. '1 T 64/2024', '1T64_2024 OSSCEDC', '4 T 21/2025 melnik')")
71
+ parser.add_argument("court_code", nargs="?",
72
+ help="Kód soudu (nepovinné pokud je v sp. zn.)")
73
+ parser.add_argument("--hearings", action="store_true",
74
+ help="Zobrazit nařízená jednání")
75
+ parser.add_argument("--courts", action="store_true",
76
+ help="Vypsat seznam soudů")
77
+ parser.add_argument("--json", dest="json_out", action="store_true",
78
+ help="Výstup v JSON")
79
+ parser.add_argument("--csv", dest="csv_out", action="store_true",
80
+ help="Výstup v CSV")
81
+
82
+ args = parser.parse_args()
83
+ client = InfoSoudClient()
84
+
85
+ if args.courts:
86
+ courts = client.build_court_map()
87
+ if args.json_out:
88
+ print(json.dumps(courts, ensure_ascii=False, indent=2))
89
+ elif args.csv_out:
90
+ print("kod,nazev")
91
+ for code, name in sorted(courts.items(), key=lambda x: x[1]):
92
+ print(f"{code},{name}")
93
+ else:
94
+ for code, name in sorted(courts.items(), key=lambda x: x[1]):
95
+ print(f" {code} {name}")
96
+ print(f"\nCelkem: {len(courts)} soudů")
97
+ return
98
+
99
+ if not args.spis_zn:
100
+ parser.print_help()
101
+ sys.exit(1)
102
+
103
+ # Try to resolve court name from input
104
+ spis_zn_raw = args.spis_zn
105
+ court_code = args.court_code or ""
106
+
107
+ if not court_code:
108
+ # Check if trailing text in spis_zn is a court name
109
+ import re
110
+ m = re.match(
111
+ r"((?:\d+\s*[A-Za-z]+\s*\d+\s*[/_]\s*\d{4}))\s+(.+)$",
112
+ spis_zn_raw.strip(),
113
+ )
114
+ if m:
115
+ spzn_part = m.group(1).strip()
116
+ tail = m.group(2).strip()
117
+ if re.match(r"^[A-Z][A-Z0-9]{4,9}$", tail):
118
+ spis_zn_raw, court_code = spzn_part, tail
119
+ else:
120
+ resolved = client.resolve_court_name(tail)
121
+ if resolved:
122
+ spis_zn_raw, court_code = spzn_part, resolved
123
+
124
+ spis_zn = parse_spis_zn(spis_zn_raw)
125
+ code = court_code or spis_zn.court_code
126
+
127
+ if not code:
128
+ print("Chybí kód soudu. Zadejte jako druhý argument nebo součást sp. zn.",
129
+ file=sys.stderr)
130
+ print("Příklad: python -m infosoud '1 T 64/2024' OSSCEDC", file=sys.stderr)
131
+ print(" python -m infosoud '4 T 21/2025 melnik'", file=sys.stderr)
132
+ sys.exit(1)
133
+
134
+ try:
135
+ if args.hearings:
136
+ data = client.search_hearings(spis_zn, code)
137
+ if args.json_out:
138
+ print(json.dumps(data, ensure_ascii=False, indent=2))
139
+ else:
140
+ print(format_hearings_summary(data))
141
+ else:
142
+ data = client.search_case(spis_zn, code)
143
+ if args.json_out:
144
+ print(json.dumps(data, ensure_ascii=False, indent=2))
145
+ elif args.csv_out:
146
+ events = data.get("udalosti") or []
147
+ print("datum;udalost;zruseno;souvisejici_spzn;soud")
148
+ for ev in events:
149
+ code_ev = ev.get("udalost", "")
150
+ label = EVENT_LABELS.get(code_ev, code_ev)
151
+ zr = "Ano" if ev.get("zruseno") else ""
152
+ zid = ev.get("znackaId", {})
153
+ rel = (f"{zid.get('cisloSenatu', '')} {zid.get('druhVeci', '')} "
154
+ f"{zid.get('bcVec', '')}/{zid.get('rocnik', '')} "
155
+ f"{zid.get('organizace', '')}").strip()
156
+ print(f"{ev.get('datum', '')};{label};{zr};{rel};{data.get('organizace', '')}")
157
+ else:
158
+ print(format_case_summary(data))
159
+ except Exception as e:
160
+ print(f"Chyba: {e}", file=sys.stderr)
161
+ sys.exit(1)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,329 @@
1
+ """InfoSoud API client — Czech court case tracking (infosoud.gov.cz).
2
+
3
+ Public REST API of the Czech Ministry of Justice for looking up
4
+ court case status, events timeline, and scheduled hearings.
5
+
6
+ No authentication required. Be polite — use reasonable delays between requests.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import re
14
+ import time
15
+ import unicodedata
16
+ from dataclasses import dataclass
17
+
18
+ import requests
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ BASE_URL = "https://infosoud.gov.cz/api/v1"
23
+
24
+ # Universal regex — tolerant to any whitespace/separator combo
25
+ _SPZN_UNIVERSAL = re.compile(
26
+ r"(\d+)\s*([A-Za-z]+)\s*(\d+)\s*[/_]\s*(\d{4})"
27
+ r"(?:\s+([A-Z][A-Z0-9]{4,9}))?$"
28
+ )
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class SpisZn:
33
+ """Parsed spisová značka (Czech court case file number)."""
34
+ cislo_senatu: int
35
+ druh_veci: str
36
+ bc_vec: int
37
+ rocnik: int
38
+ court_code: str | None = None
39
+
40
+ def to_api_params(self, court_code: str | None = None) -> dict:
41
+ """Convert to InfoSoud API request body."""
42
+ code = court_code or self.court_code
43
+ params = {
44
+ "cisloSenatu": str(self.cislo_senatu),
45
+ "druhVeci": self.druh_veci,
46
+ "bcVec": str(self.bc_vec),
47
+ "rocnik": str(self.rocnik),
48
+ }
49
+ if code:
50
+ code = resolve_court_code(code)
51
+ ct = classify_court_code(code)
52
+ if ct == "NS":
53
+ params["typOrganizace"] = "NEJVYSSI"
54
+ elif ct in ("KS", "VS", "MS"):
55
+ params["druhOrganizace"] = code
56
+ else:
57
+ params["okresniSoud"] = code
58
+ return params
59
+
60
+ def canonical(self) -> str:
61
+ """Canonical form: '1 T 64/2024'."""
62
+ return f"{self.cislo_senatu} {self.druh_veci} {self.bc_vec}/{self.rocnik}"
63
+
64
+ def compact(self) -> str:
65
+ """Compact form: '1T64_2024'."""
66
+ return f"{self.cislo_senatu}{self.druh_veci}{self.bc_vec}_{self.rocnik}"
67
+
68
+ def __str__(self) -> str:
69
+ s = self.canonical()
70
+ if self.court_code:
71
+ s += f" {self.court_code}"
72
+ return s
73
+
74
+
75
+ def parse_spis_zn(text: str) -> SpisZn:
76
+ """Parse spisová značka in any common format.
77
+
78
+ Extremely tolerant — whitespace and separators are ignored.
79
+ All of these parse identically:
80
+ "1 T 64/2024" "1T64/2024" "1T 64/2024"
81
+ "1 T64/2024" "1T64_2024" "1 T 64 / 2024"
82
+ "43T191_2024" "43 T 191/2024 OSPHA09"
83
+ "11 C 233/2022" "2 To 29/2023" "6To436/2025"
84
+ """
85
+ text = text.strip()
86
+ m = _SPZN_UNIVERSAL.match(text)
87
+ if m:
88
+ return SpisZn(
89
+ cislo_senatu=int(m.group(1)),
90
+ druh_veci=m.group(2),
91
+ bc_vec=int(m.group(3)),
92
+ rocnik=int(m.group(4)),
93
+ court_code=m.group(5) if m.group(5) else None,
94
+ )
95
+ raise ValueError(f"Cannot parse spisová značka: {text!r}")
96
+
97
+
98
+ # --- Court codes ---
99
+
100
+ KS_CODES = {
101
+ "KSJIMBM": "Krajský soud Brno",
102
+ "KSJICCB": "Krajský soud České Budějovice",
103
+ "KSVYCHK": "Krajský soud Hradec Králové",
104
+ "KSSEMOS": "Krajský soud Ostrava",
105
+ "KSZPCPM": "Krajský soud Plzeň",
106
+ "KSSTCAB": "Krajský soud Praha",
107
+ "KSSCEUL": "Krajský soud Ústí nad Labem",
108
+ "MSPHAAB": "Městský soud Praha",
109
+ "VSSEMOL": "Vrchní soud Olomouc",
110
+ "VSPHAAB": "Vrchní soud Praha",
111
+ }
112
+
113
+ EXTRA_COURTS = {
114
+ "KSVYCPA": "Krajský soud Hradec Králové — pobočka Pardubice",
115
+ "KSSEMOC": "Krajský soud Ostrava — pobočka Olomouc",
116
+ "KSSCELB": "Krajský soud Ústí nad Labem — pobočka Liberec",
117
+ "VSSTCAB": "Vrchní soud Praha",
118
+ "VSSEMOC": "Vrchní soud Olomouc",
119
+ "OSSEMHA": "Okresní soud Karviná — pobočka Havířov",
120
+ "OSSEMKR": "Okresní soud Bruntál — pobočka Krnov",
121
+ "OSSEMVM": "Okresní soud Vsetín — pobočka Valašské Meziříčí",
122
+ }
123
+
124
+ COURT_CODE_ALIASES = {
125
+ "KSSEMOC": "KSSEMOS",
126
+ "VSSTCAB": "VSPHAAB",
127
+ }
128
+
129
+ PRAGUE_DISTRICTS = [f"OSPHA{i:02d}" for i in range(1, 11)]
130
+
131
+ EVENT_LABELS = {
132
+ "ZAHAJ_RIZ": "Zahájení řízení",
133
+ "NAR_JED": "Nařízení jednání",
134
+ "ZRUS_JED": "Zrušení jednání",
135
+ "VYD_ROZH": "Vydání rozhodnutí",
136
+ "ODES_SPIS": "Odeslání spisu",
137
+ "VRAC_SPIS": "Vrácení spisu",
138
+ "POD_OP_PR": "Podání opravného prostředku",
139
+ "VYR_OP_PR": "Vyřízení opravného prostředku",
140
+ "ODVOLANI": "Řízení o odvolání",
141
+ "DOVOL_RIZ": "Řízení o dovolání",
142
+ "NAD_RIZENI": "Řízení u nadřízeného soudu",
143
+ "ST_VEC_ODS": "Skončení věci",
144
+ "ST_VEC_OBZ": "Obživnutí věci",
145
+ "ST_VEC_PRE": "Přerušení řízení",
146
+ "ST_VEC_UPR": "Ukončení přerušení",
147
+ "ST_VEC_VYR": "Vyřízení věci",
148
+ "ST_VEC_PUK": "Pravomocné ukončení",
149
+ "SPIS_K_SC": "Spis odeslán soudci",
150
+ "SPIS_K_SO": "Spis odeslán soudnímu komisaři",
151
+ "SPIS_OD_SC": "Soudce předal spis",
152
+ "SPIS_OD_SO": "Soudní komisař předal spis",
153
+ "PREVD_SPIS": "Převedeno pod jinou sp. zn.",
154
+ "PR_VEC_NS": "Přerušení věci",
155
+ "VR_SP_NS": "Vrácení spisu odvolacímu soudu",
156
+ }
157
+
158
+
159
+ def classify_court_code(code: str) -> str:
160
+ """Classify court code: NS, KS, VS, MS, or OS."""
161
+ if not code:
162
+ return "OS"
163
+ upper = code.upper()
164
+ for prefix in ("NS", "VS", "MS", "KS"):
165
+ if upper.startswith(prefix):
166
+ return prefix
167
+ return "OS"
168
+
169
+
170
+ def resolve_court_code(code: str) -> str:
171
+ """Resolve alternative court codes to InfoSoud codes."""
172
+ return COURT_CODE_ALIASES.get(code.upper(), code)
173
+
174
+
175
+ def strip_diacritics(text: str) -> str:
176
+ """Remove Czech diacritics for fuzzy matching."""
177
+ return "".join(
178
+ c for c in unicodedata.normalize("NFD", text)
179
+ if unicodedata.category(c) != "Mn"
180
+ )
181
+
182
+
183
+ class InfoSoudClient:
184
+ """Client for the InfoSoud REST API.
185
+
186
+ Usage::
187
+
188
+ client = InfoSoudClient()
189
+ case = client.search_case("1 T 64/2024", "OSSCEDC")
190
+ hearings = client.search_hearings("1 T 64/2024", "OSSCEDC")
191
+ courts = client.build_court_map()
192
+ """
193
+
194
+ def __init__(self, delay: float = 0.5, timeout: int = 15,
195
+ base_url: str = BASE_URL):
196
+ self.base_url = base_url
197
+ self.delay = delay
198
+ self.timeout = timeout
199
+ self.session = requests.Session()
200
+ self.session.headers.update({
201
+ "User-Agent": "infosoud-oss/0.1 (https://github.com/pravnikovo/infosoud)",
202
+ "Accept": "application/json",
203
+ "Content-Type": "application/json",
204
+ })
205
+ self._last_request = 0.0
206
+
207
+ def _throttle(self) -> None:
208
+ elapsed = time.monotonic() - self._last_request
209
+ if elapsed < self.delay:
210
+ time.sleep(self.delay - elapsed)
211
+
212
+ def _get(self, path: str, params: dict | None = None) -> dict | list:
213
+ self._throttle()
214
+ url = f"{self.base_url}{path}"
215
+ t0 = time.monotonic()
216
+ resp = self.session.get(url, params=params, timeout=self.timeout)
217
+ self._last_request = time.monotonic()
218
+ elapsed_ms = int((self._last_request - t0) * 1000)
219
+ logger.debug("GET %s → %d (%dms)", path, resp.status_code, elapsed_ms)
220
+ resp.raise_for_status()
221
+ return resp.json()
222
+
223
+ def _post(self, path: str, body: dict) -> dict:
224
+ self._throttle()
225
+ url = f"{self.base_url}{path}"
226
+ t0 = time.monotonic()
227
+ resp = self.session.post(url, json=body, timeout=self.timeout)
228
+ self._last_request = time.monotonic()
229
+ elapsed_ms = int((self._last_request - t0) * 1000)
230
+ logger.debug("POST %s → %d (%dms)", path, resp.status_code, elapsed_ms)
231
+ resp.raise_for_status()
232
+ return resp.json()
233
+
234
+ def search_case(self, spis_zn: str | SpisZn,
235
+ court_code: str | None = None) -> dict:
236
+ """Search case by spisová značka. Returns events timeline."""
237
+ if isinstance(spis_zn, str):
238
+ spis_zn = parse_spis_zn(spis_zn)
239
+ code = court_code or spis_zn.court_code
240
+
241
+ if code and code.upper() in ("OSPHA", "OSPHA0"):
242
+ return self._search_prague(spis_zn, "/rizeni/vyhledej")
243
+
244
+ return self._post("/rizeni/vyhledej", spis_zn.to_api_params(court_code))
245
+
246
+ def search_hearings(self, spis_zn: str | SpisZn,
247
+ court_code: str | None = None) -> dict:
248
+ """Search hearings for a case."""
249
+ if isinstance(spis_zn, str):
250
+ spis_zn = parse_spis_zn(spis_zn)
251
+ code = court_code or spis_zn.court_code
252
+
253
+ if code and code.upper() in ("OSPHA", "OSPHA0"):
254
+ return self._search_prague(spis_zn, "/jednani/vyhledej",
255
+ extra={"typHledani": "SPZN"})
256
+
257
+ params = spis_zn.to_api_params(court_code)
258
+ params["typHledani"] = "SPZN"
259
+ return self._post("/jednani/vyhledej", params)
260
+
261
+ def get_event_detail(self, spis_zn: str | SpisZn,
262
+ court_code: str | None,
263
+ event_type: str, event_order: int) -> dict:
264
+ """Fetch detail for a single event (room, time, result)."""
265
+ if isinstance(spis_zn, str):
266
+ spis_zn = parse_spis_zn(spis_zn)
267
+ params = spis_zn.to_api_params(court_code)
268
+ params["druhUdalosti"] = event_type
269
+ params["poradiUdalosti"] = str(event_order)
270
+ return self._post("/udalost/vyhledej", params)
271
+
272
+ def get_courts(self) -> list[dict]:
273
+ """Get list of krajské/vrchní soudy."""
274
+ return self._get("/organizace/lov")
275
+
276
+ def get_district_courts(self, ks_code: str | None = None) -> list[dict]:
277
+ """Get list of okresní soudy, optionally filtered by KS."""
278
+ params = {"kod": ks_code} if ks_code else None
279
+ return self._get("/organizace/podrizene/lov", params=params)
280
+
281
+ def build_court_map(self) -> dict[str, str]:
282
+ """Fetch and build complete court code → name mapping."""
283
+ result = dict(EXTRA_COURTS)
284
+ for court in self.get_courts():
285
+ result[court["kod"]] = court["nazev"]
286
+ for court in self.get_district_courts():
287
+ result[court["kod"]] = court["nazev"]
288
+ return result
289
+
290
+ def resolve_court_name(self, name: str,
291
+ court_map: dict | None = None) -> str | None:
292
+ """Fuzzy-match a court name (diacritics-insensitive) to a court code."""
293
+ if court_map is None:
294
+ court_map = self.build_court_map()
295
+ name_lower = strip_diacritics(name.lower())
296
+ for prefix in ("os ", "okresni soud ", "krajsky soud ", "ks ", "ms "):
297
+ if name_lower.startswith(prefix):
298
+ name_lower = name_lower[len(prefix):]
299
+ break
300
+ best_code, best_score = None, 0
301
+ for code, full_name in court_map.items():
302
+ full_lower = strip_diacritics(full_name.lower())
303
+ if name_lower in full_lower:
304
+ score = len(name_lower) / len(full_lower)
305
+ if score > best_score:
306
+ best_score = score
307
+ best_code = code
308
+ return best_code
309
+
310
+ def _search_prague(self, spis_zn: SpisZn, path: str,
311
+ extra: dict | None = None) -> dict:
312
+ """Try all Prague districts for an ambiguous OSPHA code."""
313
+ for district in PRAGUE_DISTRICTS:
314
+ params = spis_zn.to_api_params(district)
315
+ if extra:
316
+ params.update(extra)
317
+ try:
318
+ result = self._post(path, params)
319
+ if result.get("udalosti") or result.get("stav"):
320
+ logger.info("Resolved OSPHA → %s for %s",
321
+ district, spis_zn.canonical())
322
+ return result
323
+ except requests.HTTPError as e:
324
+ if e.response is not None and e.response.status_code == 400:
325
+ continue
326
+ raise
327
+ raise ValueError(
328
+ f"Cannot find {spis_zn.canonical()} in any Prague district court"
329
+ )
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: infosoud
3
+ Version: 0.1.0
4
+ Summary: Czech court case lookup tool (infosoud.gov.cz)
5
+ Author: pravnikovo.info
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://infosoud.pravnikovo.info
8
+ Project-URL: Repository, https://github.com/pravnikovo/infosoud
9
+ Keywords: czech,court,justice,infosoud,právní,soud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Office/Business
13
+ Classifier: Natural Language :: Czech
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: requests>=2.20
18
+ Provides-Extra: web
19
+ Requires-Dist: flask>=2.0; extra == "web"
20
+ Dynamic: license-file
21
+
22
+ # InfoSoud
23
+
24
+ Neoficiální klient pro [InfoSoud](https://infosoud.gov.cz) — vyhledávání v soudních řízeních České republiky.
25
+
26
+ Umožňuje vyhledat průběh soudního řízení, nařízená jednání a události podle spisové značky. Data pocházejí z veřejného API Ministerstva spravedlnosti ČR.
27
+
28
+ **[infosoud.pravnikovo.info](https://infosoud.pravnikovo.info)** — webové rozhraní
29
+
30
+ ## Instalace
31
+
32
+ ```bash
33
+ pip install infosoud
34
+ ```
35
+
36
+ ## Použití
37
+
38
+ ### Příkazový řádek
39
+
40
+ ```bash
41
+ # Vyhledat řízení
42
+ infosoud "1 T 64/2024" OSSCEDC
43
+ infosoud "1T64_2024 OSSCEDC" # kompaktní formát
44
+ infosoud "4 T 21/2025 melnik" # název soudu místo kódu
45
+
46
+ # Zobrazit jednání
47
+ infosoud --hearings "1 T 64/2024" OSSCEDC
48
+
49
+ # JSON výstup
50
+ infosoud --json "1 T 64/2024" OSSCEDC
51
+
52
+ # CSV výstup (události)
53
+ infosoud --csv "1 T 64/2024" OSSCEDC
54
+
55
+ # Vypsat soudy
56
+ infosoud --courts
57
+ infosoud --courts --csv
58
+ ```
59
+
60
+ ### Python
61
+
62
+ ```python
63
+ from infosoud import InfoSoudClient, parse_spis_zn
64
+
65
+ client = InfoSoudClient()
66
+
67
+ # Vyhledat řízení
68
+ case = client.search_case("1 T 64/2024", "OSSCEDC")
69
+ print(case["stav"]) # "nevyřízená věc"
70
+ print(case["organizace"]) # "Okresní soud Děčín"
71
+
72
+ for event in case["udalosti"]:
73
+ print(f"{event['datum']} {event['udalost']}")
74
+
75
+ # Vyhledat jednání
76
+ hearings = client.search_hearings("1 T 64/2024", "OSSCEDC")
77
+ for h in hearings["udalosti"]:
78
+ print(f"{h['datum']} {h['cas']} {h['druhJednani']} {h['resitel']}")
79
+
80
+ # Detail události (čas, jednací síň)
81
+ detail = client.get_event_detail("1 T 64/2024", "OSSCEDC", "NAR_JED", 48)
82
+ for attr in detail["atributy"]:
83
+ print(f"{attr['typ']}: {attr['hodnota']}")
84
+
85
+ # Spisová značka — tolerantní parser
86
+ spis = parse_spis_zn("1T64_2024")
87
+ print(spis.canonical()) # "1 T 64/2024"
88
+ print(spis.compact()) # "1T64_2024"
89
+
90
+ # Fuzzy vyhledání soudu
91
+ code = client.resolve_court_name("melnik") # → "OSSTCME"
92
+ code = client.resolve_court_name("praha 9") # → "OSPHA09"
93
+
94
+ # Seznam soudů
95
+ courts = client.build_court_map() # {kód: název} pro všech 104 soudů
96
+ ```
97
+
98
+ ### Formáty spisové značky
99
+
100
+ Parser je maximálně tolerantní — všechny tyto formáty jsou ekvivalentní:
101
+
102
+ ```
103
+ 1 T 64/2024 1T64/2024 1T 64/2024
104
+ 1 T64/2024 1T64_2024 1 T 64 / 2024
105
+ ```
106
+
107
+ Kód soudu lze přidat za sp. zn.: `1T64_2024 OSSCEDC`
108
+
109
+ Místo kódu soudu lze zadat název: `4 T 21/2025 melnik` (bez diakritiky)
110
+
111
+ ## API endpointy
112
+
113
+ | Endpoint | Metoda | Popis |
114
+ |----------|--------|-------|
115
+ | `/api/v1/rizeni/vyhledej` | POST | Vyhledání řízení |
116
+ | `/api/v1/jednani/vyhledej` | POST | Vyhledání jednání |
117
+ | `/api/v1/udalost/vyhledej` | POST | Detail události |
118
+ | `/api/v1/organizace/lov` | GET | Seznam krajských/vrchních soudů |
119
+ | `/api/v1/organizace/podrizene/lov` | GET | Seznam okresních soudů |
120
+
121
+ Base URL: `https://infosoud.gov.cz/api/v1`
122
+
123
+ ## Kódy soudů
124
+
125
+ Kompletní seznam 104 soudů (96 z API + 8 poboček):
126
+
127
+ ```bash
128
+ infosoud --courts
129
+ ```
130
+
131
+ Příklady: `OSSCEDC` (OS Děčín), `OSPHA09` (OS Praha 9), `KSJIMBM` (KS Brno), `MSPHAAB` (MS Praha)
132
+
133
+ ## Webové rozhraní
134
+
135
+ ```bash
136
+ pip install infosoud[web]
137
+ python -m infosoud.web
138
+ ```
139
+
140
+ Otevře webserver na `http://localhost:8060` s vyhledávacím formulářem, timeline událostí, ICS exportem jednání a CSV exportem.
141
+
142
+ ## Licence
143
+
144
+ MIT — viz [LICENSE](LICENSE)
145
+
146
+ ---
147
+
148
+ *Neoficiální nástroj. Data pocházejí z [infosoud.gov.cz](https://infosoud.gov.cz). Projekt [pravnikovo.info](https://pravnikovo.info) — open source nástroje pro právníky.*
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ infosoud/__init__.py
5
+ infosoud/__main__.py
6
+ infosoud/client.py
7
+ infosoud.egg-info/PKG-INFO
8
+ infosoud.egg-info/SOURCES.txt
9
+ infosoud.egg-info/dependency_links.txt
10
+ infosoud.egg-info/entry_points.txt
11
+ infosoud.egg-info/requires.txt
12
+ infosoud.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ infosoud = infosoud.__main__:main
@@ -0,0 +1,4 @@
1
+ requests>=2.20
2
+
3
+ [web]
4
+ flask>=2.0
@@ -0,0 +1 @@
1
+ infosoud
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "infosoud"
7
+ version = "0.1.0"
8
+ description = "Czech court case lookup tool (infosoud.gov.cz)"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [{name = "pravnikovo.info"}]
13
+ keywords = ["czech", "court", "justice", "infosoud", "právní", "soud"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Office/Business",
18
+ "Natural Language :: Czech",
19
+ ]
20
+ dependencies = ["requests>=2.20"]
21
+
22
+ [project.optional-dependencies]
23
+ web = ["flask>=2.0"]
24
+
25
+ [project.scripts]
26
+ infosoud = "infosoud.__main__:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://infosoud.pravnikovo.info"
30
+ Repository = "https://github.com/pravnikovo/infosoud"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+