sud-cli 0.1.1__py3-none-any.whl
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.
- sud_cli/__init__.py +3 -0
- sud_cli/__main__.py +4 -0
- sud_cli/_wm.py +118 -0
- sud_cli/agenda.py +90 -0
- sud_cli/autofill.py +192 -0
- sud_cli/autofill_markers.json +56 -0
- sud_cli/backup.py +256 -0
- sud_cli/calendar_ics.py +191 -0
- sud_cli/case_diff.py +165 -0
- sud_cli/case_search_bridge.py +1322 -0
- sud_cli/chain.py +226 -0
- sud_cli/cli.py +3721 -0
- sud_cli/completeness.py +105 -0
- sud_cli/config.py +20 -0
- sud_cli/conflict.py +210 -0
- sud_cli/conflicts.py +207 -0
- sud_cli/court_alerts.py +156 -0
- sud_cli/court_source.py +108 -0
- sud_cli/data/completeness_rules.json +18 -0
- sud_cli/data/key_rates.json +59 -0
- sud_cli/data/npa_historical_editions.json +19 -0
- sud_cli/db.py +2072 -0
- sud_cli/deadline_matrix.py +99 -0
- sud_cli/deadline_state.py +25 -0
- sud_cli/deadlines.py +8 -0
- sud_cli/deadlines_state.py +1 -0
- sud_cli/deferral.py +5 -0
- sud_cli/efrsb_manual.py +85 -0
- sud_cli/efrsb_public.py +210 -0
- sud_cli/egrul_nalog.py +150 -0
- sud_cli/explain.py +190 -0
- sud_cli/fees_extra.py +104 -0
- sud_cli/forms.py +204 -0
- sud_cli/fssp_manual.py +91 -0
- sud_cli/fssp_semi.py +285 -0
- sud_cli/fssp_stats.py +105 -0
- sud_cli/government_transfers.json +8 -0
- sud_cli/holidays_rf.json +62 -0
- sud_cli/idempotency.py +78 -0
- sud_cli/importing.py +575 -0
- sud_cli/interest.py +144 -0
- sud_cli/jurisdiction.py +177 -0
- sud_cli/jurisdiction_rules.json +265 -0
- sud_cli/legal_deadlines.py +278 -0
- sud_cli/legal_engine.py +558 -0
- sud_cli/legal_rules.json +1058 -0
- sud_cli/limitation.py +70 -0
- sud_cli/mcp_server.py +1594 -0
- sud_cli/metrics.py +142 -0
- sud_cli/npa_as_of.py +21 -0
- sud_cli/output.py +89 -0
- sud_cli/payment.py +24 -0
- sud_cli/pdn.py +259 -0
- sud_cli/plenum_corpus.py +134 -0
- sud_cli/plenums_seed.json +45 -0
- sud_cli/practice_status.py +136 -0
- sud_cli/pravo_publication.py +361 -0
- sud_cli/pretrial.py +151 -0
- sud_cli/production_calendar.py +233 -0
- sud_cli/providers.py +95 -0
- sud_cli/readiness.py +234 -0
- sud_cli/regional_holidays.py +37 -0
- sud_cli/reminders.py +92 -0
- sud_cli/reports.py +349 -0
- sud_cli/sud_netguard.py +943 -0
- sud_cli/timeline_audit.py +80 -0
- sud_cli/verify.py +201 -0
- sud_cli-0.1.1.dist-info/METADATA +119 -0
- sud_cli-0.1.1.dist-info/RECORD +74 -0
- sud_cli-0.1.1.dist-info/WHEEL +4 -0
- sud_cli-0.1.1.dist-info/entry_points.txt +2 -0
- sud_cli-0.1.1.dist-info/licenses/AUTHORS +5 -0
- sud_cli-0.1.1.dist-info/licenses/LICENSE.md +661 -0
- sud_cli-0.1.1.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Deterministic checks for procedural case timelines."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _time(event):
|
|
8
|
+
return datetime.fromisoformat(str(event["timestamp"]).replace("Z", "+00:00"))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _anomaly(code, severity, title, detail, basis, ids):
|
|
12
|
+
return {"code": code, "severity": severity, "title": title, "detail": detail,
|
|
13
|
+
"legal_basis": basis, "event_ids": list(dict.fromkeys(ids))}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def audit_timeline(events):
|
|
17
|
+
events = list(events)
|
|
18
|
+
ordered = sorted(events, key=_time)
|
|
19
|
+
anomalies = []
|
|
20
|
+
initiation = next((e for e in ordered if e.get("action", "").lower() in {
|
|
21
|
+
"initiation", "case_initiated", "возбуждение уголовного дела", "вуд"
|
|
22
|
+
} or e.get("doc_type", "").lower() in {"vud", "initiation"}), None)
|
|
23
|
+
if initiation:
|
|
24
|
+
for event in ordered:
|
|
25
|
+
action = f"{event.get('action', '')} {event.get('doc_type', '')}".lower()
|
|
26
|
+
if _time(event) < _time(initiation) and any(word in action for word in
|
|
27
|
+
("search", "обыск", "interrogation", "допрос", "seizure", "выемка", "экспертиз")):
|
|
28
|
+
if not any(word in action for word in ("inspection", "осмотр места происшествия")):
|
|
29
|
+
anomalies.append(_anomaly("PRE_INITIATION_ACTION", "CRITICAL",
|
|
30
|
+
"Следственное действие до возбуждения дела",
|
|
31
|
+
f"{event.get('action', event.get('doc_type', ''))} проведено до ВУД",
|
|
32
|
+
"ст. 144, 146 УПК РФ", [event.get("id", ""), initiation.get("id", "")]))
|
|
33
|
+
|
|
34
|
+
delivered = {}
|
|
35
|
+
for event in ordered:
|
|
36
|
+
action = f"{event.get('action', '')} {event.get('doc_type', '')}".lower()
|
|
37
|
+
if any(word in action for word in ("доставлен", "delivered", "фактическое задержание")):
|
|
38
|
+
delivered[event.get("participant", event.get("suspect", ""))] = event
|
|
39
|
+
if "протокол задержания" in action or "detention_protocol" in action:
|
|
40
|
+
key = event.get("participant", event.get("suspect", ""))
|
|
41
|
+
prior = delivered.get(key)
|
|
42
|
+
if prior and (_time(event) - _time(prior)).total_seconds() > 3 * 3600:
|
|
43
|
+
anomalies.append(_anomaly("DETENTION_PROTOCOL_DELAY", "HIGH",
|
|
44
|
+
"Протокол задержания составлен с опозданием",
|
|
45
|
+
"между доставлением и протоколом прошло более 3 часов",
|
|
46
|
+
"ч. 1 ст. 92 УПК РФ", [prior.get("id", ""), event.get("id", "")]))
|
|
47
|
+
|
|
48
|
+
witness_events = defaultdict(list)
|
|
49
|
+
for event in events:
|
|
50
|
+
for witness in event.get("witnesses", []) or []:
|
|
51
|
+
witness_events[str(witness)].append(event)
|
|
52
|
+
address = event.get("location") or event.get("object_address")
|
|
53
|
+
for witness in event.get("witness_details", []) or []:
|
|
54
|
+
if address and witness.get("address") == address:
|
|
55
|
+
anomalies.append(_anomaly("REPEATED_OR_BIASED_WITNESS", "HIGH",
|
|
56
|
+
"Понятой может быть заинтересованным",
|
|
57
|
+
"адрес понятого совпадает с адресом объекта",
|
|
58
|
+
"ст. 60 УПК РФ", [event.get("id", "")]))
|
|
59
|
+
for witness, rows in witness_events.items():
|
|
60
|
+
if len(rows) > 1:
|
|
61
|
+
anomalies.append(_anomaly("REPEATED_OR_BIASED_WITNESS", "MEDIUM",
|
|
62
|
+
"Повторное участие понятого",
|
|
63
|
+
f"лицо {witness} указано в {len(rows)} процессуальных событиях",
|
|
64
|
+
"ст. 60 УПК РФ", [row.get("id", "") for row in rows]))
|
|
65
|
+
|
|
66
|
+
custody = defaultdict(list)
|
|
67
|
+
for event in ordered:
|
|
68
|
+
evidence = event.get("evidence_id")
|
|
69
|
+
if evidence:
|
|
70
|
+
custody[evidence].append(event)
|
|
71
|
+
for evidence, rows in custody.items():
|
|
72
|
+
for previous, current in zip(rows, rows[1:]):
|
|
73
|
+
if previous.get("custodian") and current.get("custodian") and previous["custodian"] != current["custodian"]:
|
|
74
|
+
documented = current.get("transfer_document") or current.get("transfer_doc_id") or current.get("document_id")
|
|
75
|
+
if not documented:
|
|
76
|
+
anomalies.append(_anomaly("CHAIN_OF_CUSTODY_BROKEN", "CRITICAL",
|
|
77
|
+
"Разрыв цепочки хранения вещдока",
|
|
78
|
+
f"{evidence}: {previous['custodian']} передал без документа {current['custodian']}",
|
|
79
|
+
"ст. 81, 82 УПК РФ", [previous.get("id", ""), current.get("id", "")]))
|
|
80
|
+
return {"status": "ok", "anomalies_count": len(anomalies), "anomalies": anomalies}
|
sud_cli/verify.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Quote verification (S-52, v2.9.0) — правило «тексты только из реестра»: «никогда не ok на глаз».
|
|
2
|
+
|
|
3
|
+
Pure offline functions: a quoted fragment is checked against an explicit local
|
|
4
|
+
source — a bundled historical NPA edition (``sud_cli.npa_as_of``), the events
|
|
5
|
+
table of a case, or a plain-text file. The verdict is one of:
|
|
6
|
+
|
|
7
|
+
exact the normalized fragment occurs literally in the source;
|
|
8
|
+
fuzzy no literal match, but a difflib similarity above the
|
|
9
|
+
``1 - max_fuzzy`` threshold (only when max_fuzzy > 0);
|
|
10
|
+
not_found neither of the above — the honest default;
|
|
11
|
+
edition_unavailable the requested bundle edition/article/date is absent
|
|
12
|
+
(an NPA source NEVER degrades to not_found: absence of
|
|
13
|
+
the bundle is not absence of the norm).
|
|
14
|
+
|
|
15
|
+
Normalization is minimal and lossless for legal text: NBSP -> space, whitespace
|
|
16
|
+
runs -> single space, ё -> е, case-folded. Punctuation is deliberately NOT
|
|
17
|
+
stripped — dropping it is how a "looks the same" citation quietly becomes ok.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import difflib
|
|
21
|
+
import re
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
SNIPPET_WINDOW = 80
|
|
25
|
+
DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
26
|
+
|
|
27
|
+
_WS_RE = re.compile(r"\s+")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VerifyInputError(ValueError):
|
|
31
|
+
"""Bad arguments for the verification itself (empty text, unknown source)."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def normalize(text: str) -> str:
|
|
35
|
+
"""Whitespace-tolerant, ё/е-insensitive, lowercased; punctuation kept."""
|
|
36
|
+
out = text.replace("\u00a0", " ").replace("\u202f", " ")
|
|
37
|
+
out = _WS_RE.sub(" ", out).strip()
|
|
38
|
+
return out.replace("\u0401", "\u0415").replace("\u0451", "\u0435").lower()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _snippet(body: str, start: int, end: int) -> str:
|
|
42
|
+
lo = max(0, start - SNIPPET_WINDOW)
|
|
43
|
+
hi = min(len(body), end + SNIPPET_WINDOW)
|
|
44
|
+
prefix = "..." if lo > 0 else ""
|
|
45
|
+
suffix = "..." if hi < len(body) else ""
|
|
46
|
+
return prefix + body[lo:hi] + suffix
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _best_window(query: str, body: str):
|
|
50
|
+
"""Best SequenceMatcher ratio over same-length-ish sliding windows."""
|
|
51
|
+
if not query or not body:
|
|
52
|
+
return 0.0, 0, 0
|
|
53
|
+
length = len(query)
|
|
54
|
+
lengths = sorted({w for w in range(max(1, length - 3), length + 4) if w <= len(body)})
|
|
55
|
+
if not lengths:
|
|
56
|
+
lengths = [len(body)]
|
|
57
|
+
step = 1 if len(body) <= 20000 else 4
|
|
58
|
+
best_ratio, best_start, best_len = 0.0, 0, lengths[0]
|
|
59
|
+
for wlen in lengths:
|
|
60
|
+
for i in range(0, len(body) - wlen + 1, step):
|
|
61
|
+
ratio = difflib.SequenceMatcher(None, query, body[i:i + wlen]).ratio()
|
|
62
|
+
if ratio > best_ratio:
|
|
63
|
+
best_ratio, best_start, best_len = ratio, i, wlen
|
|
64
|
+
if ratio >= 1.0:
|
|
65
|
+
return best_ratio, best_start, best_len
|
|
66
|
+
return best_ratio, best_start, best_len
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _match(norm_query: str, norm_body: str, max_fuzzy: float, location: str,
|
|
70
|
+
extra=None):
|
|
71
|
+
"""Verdict for one normalized body. Exact only on a literal substring."""
|
|
72
|
+
base = {"location": location, "offset": None, "matched_fragment": None,
|
|
73
|
+
"ratio": None, "threshold": None, "snippet": None, "detail": None}
|
|
74
|
+
if extra:
|
|
75
|
+
base.update(extra)
|
|
76
|
+
idx = norm_body.find(norm_query)
|
|
77
|
+
if idx >= 0:
|
|
78
|
+
return {**base, "verdict": "exact", "offset": idx,
|
|
79
|
+
"matched_fragment": norm_body[idx:idx + len(norm_query)]}
|
|
80
|
+
if max_fuzzy and max_fuzzy > 0:
|
|
81
|
+
threshold = 1.0 - max_fuzzy
|
|
82
|
+
ratio, start, wlen = _best_window(norm_query, norm_body)
|
|
83
|
+
if ratio >= threshold:
|
|
84
|
+
return {**base, "verdict": "fuzzy", "offset": start, "ratio": round(ratio, 4),
|
|
85
|
+
"threshold": round(threshold, 4),
|
|
86
|
+
"matched_fragment": norm_body[start:start + wlen] or None,
|
|
87
|
+
"snippet": _snippet(norm_body, start, start + wlen)}
|
|
88
|
+
return {**base, "verdict": "not_found",
|
|
89
|
+
"detail": f"best fuzzy ratio {round(ratio, 4)} below threshold {round(threshold, 4)}"}
|
|
90
|
+
return {**base, "verdict": "not_found", "detail": "fuzzy matching disabled (max_fuzzy=0)"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------- sources
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _source_npa(act: str, article: str, date: str, query: str, max_fuzzy: float):
|
|
97
|
+
from .npa_as_of import lookup_as_of
|
|
98
|
+
|
|
99
|
+
location = f"{act}/{article}/{date}"
|
|
100
|
+
result = lookup_as_of(act, article, date)
|
|
101
|
+
if result.get("status") != "ok":
|
|
102
|
+
# Absent bundle/edition is a coverage gap, never a "not found" claim.
|
|
103
|
+
return {"verdict": "edition_unavailable", "location": location, "offset": None,
|
|
104
|
+
"matched_fragment": None, "ratio": None, "threshold": None,
|
|
105
|
+
"snippet": None, "detail": result.get("detail", "edition not in offline bundle")}
|
|
106
|
+
return _match(query, normalize(result["text"]), max_fuzzy, location)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _source_case(case_id: str, state_dir, query: str, max_fuzzy: float):
|
|
110
|
+
from .db import get_case_events, get_connection, init_db, list_case_documents
|
|
111
|
+
|
|
112
|
+
init_db(Path(state_dir))
|
|
113
|
+
conn = get_connection(Path(state_dir))
|
|
114
|
+
try:
|
|
115
|
+
events = get_case_events(conn, case_id)
|
|
116
|
+
docs = list_case_documents(conn, case_id)
|
|
117
|
+
finally:
|
|
118
|
+
conn.close()
|
|
119
|
+
# правило «тексты только из реестра»: покрытие прогона честное — приложенные документы дела
|
|
120
|
+
# сканируются наравне с событиями; нечитаемые (бинарь/нет файла) — явно
|
|
121
|
+
# в docs_skipped, не молча.
|
|
122
|
+
bodies = [(f"event:{e['event_id']}", normalize(e.get("doc_text") or "")) for e in events]
|
|
123
|
+
skipped = 0
|
|
124
|
+
for d in docs:
|
|
125
|
+
try:
|
|
126
|
+
raw = Path(d["path"]).read_text(encoding="utf-8")
|
|
127
|
+
except (OSError, UnicodeDecodeError):
|
|
128
|
+
skipped += 1
|
|
129
|
+
continue
|
|
130
|
+
bodies.append((f"doc:{d['id']}@{d['sha256'][:12]}", normalize(raw)))
|
|
131
|
+
scope = {"events_scanned": len(events), "docs_scanned": len(docs) - skipped,
|
|
132
|
+
"docs_skipped_unreadable": skipped}
|
|
133
|
+
best = {"verdict": "not_found", "location": None, "offset": None,
|
|
134
|
+
"matched_fragment": None, "ratio": None, "threshold": None,
|
|
135
|
+
"snippet": None,
|
|
136
|
+
"detail": (f"case {case_id}: " + ", ".join(f"{k}={v}" for k, v in scope.items())
|
|
137
|
+
+ (" (нечитаемые документы НЕ проверялись — сверь формат)" if skipped else "")),
|
|
138
|
+
**scope}
|
|
139
|
+
for location, body in bodies:
|
|
140
|
+
verdict = _match(query, body, max_fuzzy, location, dict(scope))
|
|
141
|
+
if verdict["verdict"] == "exact":
|
|
142
|
+
return verdict
|
|
143
|
+
if verdict["verdict"] == "fuzzy" and best["verdict"] != "fuzzy":
|
|
144
|
+
best = verdict
|
|
145
|
+
return best
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _source_file(path, query: str, max_fuzzy: float):
|
|
149
|
+
p = Path(path)
|
|
150
|
+
raw = p.read_text(encoding="utf-8") # OSError propagates to the CLI layer
|
|
151
|
+
body = normalize(raw)
|
|
152
|
+
location = f"file:{p}"
|
|
153
|
+
found = _match(query, body, max_fuzzy, location)
|
|
154
|
+
if found["offset"] is not None:
|
|
155
|
+
found["location"] = f"{location}:{found['offset']}"
|
|
156
|
+
return found
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------- entry
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def verify_quote(text, source, *, act=None, article=None, date=None,
|
|
163
|
+
case_id=None, path=None, state_dir=None, max_fuzzy=0.0):
|
|
164
|
+
"""Verify ``text`` against one local source. Raises VerifyInputError on
|
|
165
|
+
bad arguments; never touches the network."""
|
|
166
|
+
if not isinstance(text, str) or not text.strip():
|
|
167
|
+
raise VerifyInputError("text must be a non-empty string")
|
|
168
|
+
try:
|
|
169
|
+
max_fuzzy = float(max_fuzzy or 0.0)
|
|
170
|
+
except (TypeError, ValueError):
|
|
171
|
+
raise VerifyInputError("max-fuzzy must be a number")
|
|
172
|
+
if not 0.0 <= max_fuzzy <= 1.0:
|
|
173
|
+
raise VerifyInputError("max-fuzzy must be within 0..1")
|
|
174
|
+
query = normalize(text)
|
|
175
|
+
if not query:
|
|
176
|
+
raise VerifyInputError("text normalizes to empty string")
|
|
177
|
+
|
|
178
|
+
if source == "npa":
|
|
179
|
+
if not (act and article and date):
|
|
180
|
+
raise VerifyInputError("source npa requires --act, --article and --date")
|
|
181
|
+
if not DATE_RE.match(date):
|
|
182
|
+
raise VerifyInputError("date must be YYYY-MM-DD")
|
|
183
|
+
payload = _source_npa(act, article, date, query, max_fuzzy)
|
|
184
|
+
elif source == "case":
|
|
185
|
+
if not case_id:
|
|
186
|
+
raise VerifyInputError("source case requires --case-id")
|
|
187
|
+
if state_dir is None:
|
|
188
|
+
raise VerifyInputError("source case requires a state dir")
|
|
189
|
+
payload = _source_case(case_id, state_dir, query, max_fuzzy)
|
|
190
|
+
elif source == "file":
|
|
191
|
+
if not path:
|
|
192
|
+
raise VerifyInputError("source file requires --path")
|
|
193
|
+
if not Path(path).is_file():
|
|
194
|
+
raise VerifyInputError(f"file not found: {path}")
|
|
195
|
+
payload = _source_file(path, query, max_fuzzy)
|
|
196
|
+
else:
|
|
197
|
+
raise VerifyInputError(f"unknown source: {source!r}")
|
|
198
|
+
|
|
199
|
+
return {"source": source, "status": payload["verdict"],
|
|
200
|
+
"requested_text": text, "normalized_text": query,
|
|
201
|
+
"max_fuzzy": max_fuzzy, **payload}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sud-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Offline Phase 1 foundation for the sud-cli court-document toolkit.
|
|
5
|
+
Author-email: lnfnt <lnfnt@proton.me>
|
|
6
|
+
License-Expression: AGPL-3.0-only
|
|
7
|
+
License-File: AUTHORS
|
|
8
|
+
License-File: LICENSE.md
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Keywords: cli,court,npa,offline
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Legal Industry
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# sud-cli
|
|
21
|
+
|
|
22
|
+
    
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
<p align="center"><img src="assets/poster.jpg" width="820" alt="sud-cli poster"></p>
|
|
26
|
+
|
|
27
|
+
CLI-инструмент для юридической работы с российскими судами: поиск практики и карточек дел,
|
|
28
|
+
процессуальные сроки, госпошлины, реестр доказательств, НПА. Offline-first: ядро работает без
|
|
29
|
+
сети, live-адаптеры ограничены документированными источниками. Встроенный MCP-сервер — 65
|
|
30
|
+
инструментов.
|
|
31
|
+
|
|
32
|
+
**Русский** · [<img src="assets/flags/gb.svg" height="16" align="top" alt="EN"> English](README.en.md)
|
|
33
|
+
|
|
34
|
+
## Установка
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install sud-cli
|
|
38
|
+
sud-cli --help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Из репозитория: `pip install git+https://github.com/lnfnt/sud-cli.git`.
|
|
42
|
+
Локальный checkout: `python3 -m venv .venv && . .venv/bin/activate && pip install .`
|
|
43
|
+
Python >= 3.10, рантайм-зависимостей нет.
|
|
44
|
+
|
|
45
|
+
## Возможности
|
|
46
|
+
|
|
47
|
+
- **Поиск практики и дел** — единый запрос по официальным источникам (sudact,
|
|
48
|
+
publication.pravo.gov.ru, картотека арбитражных дел), карточки и таймлайны (КАД), подбор
|
|
49
|
+
практики по номеру дела и предмету спора.
|
|
50
|
+
- **Карточка контрагента (ЕГРЮЛ/ЕГРИП)** — открытые реестры ФНС (ст. 6 129-ФЗ), без капчи и
|
|
51
|
+
защищённых эндпоинтов.
|
|
52
|
+
- **Проверка банкротства** — основной путь через картотеку арбитражных дел; перепроверка ЕФРСБ
|
|
53
|
+
ручным контуром с фиксацией provenance (`efrsb link` / `efrsb record`).
|
|
54
|
+
- **ФССП, полу-автомат** — `fssp assist`: инструмент открывает банк данных, заполняет форму и
|
|
55
|
+
отправляет запрос; капчу решает человек (скриншот → код). Результат с provenance попадает в
|
|
56
|
+
реестр доказательств. Ручной контур `fssp check` доступен всегда.
|
|
57
|
+
- **Сроки и пошлины** — расчёт по ГПК/АПК/КАС РФ с производственным календарём; тарифы
|
|
58
|
+
госпошлин в проверяемых JSON-файлах правил.
|
|
59
|
+
- **Досье дела** — документы и версии, представители, доказательства, сроки, черновики;
|
|
60
|
+
экспорт; идемпотентный purge строго внутри state-директории.
|
|
61
|
+
- **Корпус постановлений ВС/ВАС** — `plenum list | plenum search`.
|
|
62
|
+
- **MCP-сервер** — `sud-cli mcp`, 65 инструментов для агентских рантаймов.
|
|
63
|
+
- **§13 AGPL** — `sud-cli license`: лицензия, provenance сборки, способ получить исходники.
|
|
64
|
+
|
|
65
|
+
## Команды (Chain · Reps · Conflicts · Reports · Backup · Metrics · Purge)
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
sud-cli deadlines chain А40-11111/25 # цепочка процессуальных сроков (Chain)
|
|
69
|
+
sud-cli case rep add | case rep list # представители по делу (Reps)
|
|
70
|
+
sud-cli calendar conflicts # конфликты слушаний (Conflicts)
|
|
71
|
+
sud-cli report portfolio / report case # отчёты (Reports)
|
|
72
|
+
sud-cli backup create | backup verify | backup restore # бэкап состояния (Backup)
|
|
73
|
+
sud-cli case export / case purge / metrics # экспорт, purge, метрики (Metrics, Purge)
|
|
74
|
+
sud-cli fssp assist --kind ur --inn 7704361225 # ФССП, полу-автомат
|
|
75
|
+
sud-cli efrsb link --kind ul --inn 7704361225 # ЕФРСБ, ручной контур
|
|
76
|
+
sud-cli plenum list | plenum search ТЕКСТ # корпус ВС/ВАС
|
|
77
|
+
sud-cli mcp # MCP-сервер (65 tools)
|
|
78
|
+
sud-cli license # §13 AGPL
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Разделы реестра дела
|
|
82
|
+
|
|
83
|
+
| Раздел | Содержание |
|
|
84
|
+
|---|---|
|
|
85
|
+
| **Evidence** | доказательства с provenance: кто, когда, метод, приложенный файл |
|
|
86
|
+
| **DocHistory** | версии процессуальных документов дела |
|
|
87
|
+
| **Checklist** | чек-листы из файлов правил |
|
|
88
|
+
| **Forms** | шаблоны и заполнение процессуальных форм |
|
|
89
|
+
|
|
90
|
+
## Ограничения
|
|
91
|
+
|
|
92
|
+
- Автоподача не реализована и не планируется: документ в внешние системы отправляет человек.
|
|
93
|
+
- Практика ищётся по формальным признакам; оценку перспектив даёт юрист, не инструмент.
|
|
94
|
+
- Это не юридическая консультация: расчёт сроков, форматирование, поиск.
|
|
95
|
+
|
|
96
|
+
## Сетевая политика
|
|
97
|
+
|
|
98
|
+
Live-адаптеры работают через ваш прокси (`SUD_EGRESS_PROXY`) с fail-closed DNS-pinning;
|
|
99
|
+
хост с ответом «лимит подключений» уходит в cooldown автоматически. Обход капчи автоматикой
|
|
100
|
+
запрещён конструкцией (см. ФССП/ЕФРСБ).
|
|
101
|
+
|
|
102
|
+
## Проверка релиза
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary>Контрольные суммы и GPG</summary>
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
sha256sum -c SHA256SUMS
|
|
109
|
+
gpg --import release_pub.asc
|
|
110
|
+
gpg --verify sud_cli-0.1.1.tar.gz.asc # fingerprint F4492D47B75F12D025400C19291E4ACACA58902A
|
|
111
|
+
```
|
|
112
|
+
</details>
|
|
113
|
+
|
|
114
|
+
## Вклад и лицензия
|
|
115
|
+
|
|
116
|
+
AGPL-3.0-only; вклад требует DCO sign-off и права перелицензирования владельцем
|
|
117
|
+
(CONTRIBUTING.md). Уязвимости — приватно (SECURITY.md).
|
|
118
|
+
|
|
119
|
+
Автор и правообладатель: [lnfnt](mailto:lnfnt@proton.me).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
sud_cli/__init__.py,sha256=tNwG1ZR3oqdfxrlWoK8lhKPFz0DoLubkNKc-8oHpjwo,88
|
|
2
|
+
sud_cli/__main__.py,sha256=qt9jVV7hfl6Ijw9sqqiLe1I7JJtdII4gFcJSJ5hf12I,86
|
|
3
|
+
sud_cli/_wm.py,sha256=HrGGkIaAiydK071v9ns1f1_5eWXzwquFtO5-Dloksc4,6664
|
|
4
|
+
sud_cli/agenda.py,sha256=bJu4e7KNNMxNwjP-IrktaVvScuHLrcUQytr5vQXh9gg,3244
|
|
5
|
+
sud_cli/autofill.py,sha256=lZhrkt_rZS7YvyaRycTEEFDsonuDqwkw4nkSoRPOMyc,8681
|
|
6
|
+
sud_cli/autofill_markers.json,sha256=KTVZFYttvTybrqKgofaZbVFQoWaI35A70Ym7gkRvjQM,1836
|
|
7
|
+
sud_cli/backup.py,sha256=aTSX7cBTQ_-_Ba8Gxd2xK6hhlvW0_pkICjqF7S5rPuo,9584
|
|
8
|
+
sud_cli/calendar_ics.py,sha256=C9J0Lo7XCw9NcYQd0TYuWEab9Wd4l_CRzy1vr7JDu8k,7617
|
|
9
|
+
sud_cli/case_diff.py,sha256=9ZgPnn8Ny_nN-0Tts0k5k5QQmy2A4DQK1QjTulE76dw,5847
|
|
10
|
+
sud_cli/case_search_bridge.py,sha256=WErvMM3ekPKUpugpv1LxN9C05ixW71TXSwAOVSogMJo,55190
|
|
11
|
+
sud_cli/chain.py,sha256=SRnNzsxoy7UjQnk1MoZmX0DyJZVD8siSf7jXwu8rbd0,11641
|
|
12
|
+
sud_cli/cli.py,sha256=9xCT9huU_hCgN3HX9dP5cH1YGpsc9ujg1gJhZcnId_M,163019
|
|
13
|
+
sud_cli/completeness.py,sha256=i3QsMFu6-yYEp6YbcyXeN7jJVjWxfBNfIu7IDkJf9NA,4883
|
|
14
|
+
sud_cli/config.py,sha256=vVVRXSYt5qEZYx9CbPC8joy80_hEdJnVLxC5KymNdoI,489
|
|
15
|
+
sud_cli/conflict.py,sha256=ISzD-pENTRC-2Zs50TVoF42dMLslaJNzoxjhc42hOAY,7658
|
|
16
|
+
sud_cli/conflicts.py,sha256=I3-Hp8gUgfga8bO4kMW7vXaltJ7dS9UXG0ZyBK8On4E,9308
|
|
17
|
+
sud_cli/court_alerts.py,sha256=xDP3_mX6w-fx15kXeeEyTElrkw4lMdZDQ_Kv7fBp198,6670
|
|
18
|
+
sud_cli/court_source.py,sha256=eGb_LlpUk9K93FT98MOQWKVcCu7NyEjr2bppkVVyEos,3119
|
|
19
|
+
sud_cli/db.py,sha256=cNUTYpmh5qZz4VdUICW3kb82O3inzjYsK8L3-D0pPGw,78654
|
|
20
|
+
sud_cli/deadline_matrix.py,sha256=_9DmXWvty_NSqKxeay_AfcWRBg6z4n1Z6zZ-BXHTHK4,3463
|
|
21
|
+
sud_cli/deadline_state.py,sha256=iL61Z_O-Q72z9fNucUlXf-DH8U79NrNH49Rikv5Pij4,611
|
|
22
|
+
sud_cli/deadlines.py,sha256=WxR1R01rk19H8SKTWEkl6hewj9Lr_DubIRUahbMJfKM,182
|
|
23
|
+
sud_cli/deadlines_state.py,sha256=e__eem7RI_G2NLYQXQ6Yr7AxQn_iLIbgVm-hjqrFOmM,30
|
|
24
|
+
sud_cli/deferral.py,sha256=kB_Xupmai4sOpMaA3uoW_u_fNU6JiGaWVyUz0vcYBRc,1303
|
|
25
|
+
sud_cli/efrsb_manual.py,sha256=kPz7sA6_oF9vc_u8Grg26PweVqX4OKgTM1KlDvc5Q8A,4379
|
|
26
|
+
sud_cli/efrsb_public.py,sha256=VVzWoRoduen0CPqxLvGXWquQOb3O1eQ0wdWCQhpbPqU,9807
|
|
27
|
+
sud_cli/egrul_nalog.py,sha256=JZ5574Yym0T5lUZ0X7E35g8ScMF6KmiPKnyeEG8jaDg,6416
|
|
28
|
+
sud_cli/explain.py,sha256=Axu77XKmC82scVSUwUKHhdZxFlJWpicxFz0yBsB09js,10342
|
|
29
|
+
sud_cli/fees_extra.py,sha256=zStzt31792hOS4bnKeEZ7_PL2GWOAeMJeldczvqOWqw,6170
|
|
30
|
+
sud_cli/forms.py,sha256=1tCpoiQdUosSydjGMqI_l_mLdSrGY47Cpqfh1Exu2jc,8666
|
|
31
|
+
sud_cli/fssp_manual.py,sha256=bQN9iuhk6UKma7wRgHCCj4R9Wjx_10RH_95r7ge3KFA,5656
|
|
32
|
+
sud_cli/fssp_semi.py,sha256=y5EkZKL3v-gRYHyWDW5H-nqM2a06uKayCyffCbfKqQQ,13763
|
|
33
|
+
sud_cli/fssp_stats.py,sha256=0egjMri8zZYgVIYt6raP6nk_DiH8r6L9Npev0wqvABU,4540
|
|
34
|
+
sud_cli/government_transfers.json,sha256=7vFwIw4ursx5k0DeJzN160swOxpBlFGt7VA_4g_mFFE,310
|
|
35
|
+
sud_cli/holidays_rf.json,sha256=Qbm3y614jbWT2ujnTI1GkUxOvwmdcFOmfU9MutpV0FI,6462
|
|
36
|
+
sud_cli/idempotency.py,sha256=E4gDPATm8GpXY3ICdCmTtQU5jrkTjRBS4_WazzsHv74,3292
|
|
37
|
+
sud_cli/importing.py,sha256=xaZVhry91bP6h7TxUqaWLzdnINGNo2B1Rw2svnAF1PY,20785
|
|
38
|
+
sud_cli/interest.py,sha256=fdT-YDI1eTcmH9UafXXAIZxCiTESlnySBB_AWCaRwEQ,6624
|
|
39
|
+
sud_cli/jurisdiction.py,sha256=853_iWTOnWnvlTy6kehRUlLve-YYJoV43TB7o4uLdm8,6822
|
|
40
|
+
sud_cli/jurisdiction_rules.json,sha256=GiHlZVP7mtgYX1KlA_oaMQVrRj5F1JTVdbnvKvZWIb0,14109
|
|
41
|
+
sud_cli/legal_deadlines.py,sha256=KGRsSCgUWGJHBdQx2ocd9U7glhh7mAijvu6I3hYXS2g,12168
|
|
42
|
+
sud_cli/legal_engine.py,sha256=tTJGzLyELd2OWqEnBpUMIPUHh9tS2GwXFKP_rkFpoik,24346
|
|
43
|
+
sud_cli/legal_rules.json,sha256=FZiedx4Jr-LXnp9J79IE7gL3igVqPBpAth9PpK7qL7s,49182
|
|
44
|
+
sud_cli/limitation.py,sha256=KJZeVclgQeiB7MqMO0Zdcvqi0veuLuvz5uKNvrRGbOI,2864
|
|
45
|
+
sud_cli/mcp_server.py,sha256=UQvhDm7xKv53d41L8MV8BnjK-t3nzlzrNbvzZE6r-0w,92404
|
|
46
|
+
sud_cli/metrics.py,sha256=K7F564wAzp97jAnQOvV83e9lQJhNAvdiz7ecthuUhjc,5506
|
|
47
|
+
sud_cli/npa_as_of.py,sha256=NUldTJHz-NLT4NZrpKFeLPtROPlqLYdj9Jc1rX_v580,998
|
|
48
|
+
sud_cli/output.py,sha256=8nLr-gr3qfJyd6hMNaHGrsIj6E-1qWZRTp8_-hxT31s,2832
|
|
49
|
+
sud_cli/payment.py,sha256=nv-gLA7QAHIh5Bb-DpOGwm-HSy8Nnp6SFB3UfdH73dE,1001
|
|
50
|
+
sud_cli/pdn.py,sha256=8TKMSn6CkQaRMcgiZAWCZ41vbvQ7ViagmNSF6z5mqkU,11140
|
|
51
|
+
sud_cli/plenum_corpus.py,sha256=9QCow-cqc5KkJoWUnDwb43VA8S0IaQkrjKac6iAweRk,5816
|
|
52
|
+
sud_cli/plenums_seed.json,sha256=quDHljO3wEMoOXnaga0XiucaXcaMNtlKSAxXzyh4StQ,2584
|
|
53
|
+
sud_cli/practice_status.py,sha256=jphTOvy8JIpomglQfJ3HRaTI3ktL2zAXWKK5J2pH47c,5788
|
|
54
|
+
sud_cli/pravo_publication.py,sha256=ccpn5T_qXIHA1ZGWLpOydKYeac8UNfkqPR5SkEK_fZ0,13947
|
|
55
|
+
sud_cli/pretrial.py,sha256=2Fi229SHVYAvfCgnekZzN-WVJqN2B4M-ZhKdlFYzII4,5659
|
|
56
|
+
sud_cli/production_calendar.py,sha256=gXfvyE4ecPqz_-E2cCdJtvPUTiMsoXrlJGVsqh74zYc,11010
|
|
57
|
+
sud_cli/providers.py,sha256=ro36-dVXj8eeV3pB9pmEK7Xd8oN_jtYzIBB-1HcYXfo,2768
|
|
58
|
+
sud_cli/readiness.py,sha256=SFmgIpMM0yfDi_v4CCMUMzr9tf7vKzJIjNwO3CnPMkU,9089
|
|
59
|
+
sud_cli/regional_holidays.py,sha256=H-iFgfVW8_79VYXm0lh2_6gdmHmQvtKlel0wfOYE2xQ,1899
|
|
60
|
+
sud_cli/reminders.py,sha256=Bf3uqtZEp5MQreFAVAfOEM59HVJTVaXvyyDxNSAdvm4,4006
|
|
61
|
+
sud_cli/reports.py,sha256=WtvM6G16V3ZAygtfjZslk3cxYeGdOqp8BrtgIuDTyXQ,15789
|
|
62
|
+
sud_cli/sud_netguard.py,sha256=blaf32ygNkZUmOhCIbeZR08uHIjbkXZS5rfZsxVyLlk,36378
|
|
63
|
+
sud_cli/timeline_audit.py,sha256=UfxDGbsAZ523pAlX3STvnnHttIZkl6E00-pSKMze2gw,4870
|
|
64
|
+
sud_cli/verify.py,sha256=Rk9VCZ4EeDt3FHxgBrWIjG0x9-U9zXXXoz2I1Ykdcok,9069
|
|
65
|
+
sud_cli/data/completeness_rules.json,sha256=kEM5Xj08Eb23RhjO_1ga9bNA0zON03k6nNFDVoXLNEM,1576
|
|
66
|
+
sud_cli/data/key_rates.json,sha256=cgCi8NHaec3g0TyvOje0UMBCAFfY6BbZidzYP1PKdNg,1012
|
|
67
|
+
sud_cli/data/npa_historical_editions.json,sha256=I4RFn8Elxj-Uz4FyZaZKnleosEfMWOySsKOx2Dx4HOc,3209
|
|
68
|
+
sud_cli-0.1.1.dist-info/METADATA,sha256=-TNG5Iej6bV8g1nXn6PMwYqEjcIjWUFwwM24-cgWt74,7162
|
|
69
|
+
sud_cli-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
70
|
+
sud_cli-0.1.1.dist-info/entry_points.txt,sha256=9sWJVvYr_EJHDw-pt5CcNddE2ksAhIsSSc8EzT6SW7s,45
|
|
71
|
+
sud_cli-0.1.1.dist-info/licenses/AUTHORS,sha256=Cj1riO_7PbZaUXoJPEDsRFcA2ZbuE3WmQ8FiCW3zYEY,99
|
|
72
|
+
sud_cli-0.1.1.dist-info/licenses/LICENSE.md,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
73
|
+
sud_cli-0.1.1.dist-info/licenses/NOTICE,sha256=1bN6U6PK344YOidAt7PT_GhpKLWkMQkxLJfy1vPqjk0,272
|
|
74
|
+
sud_cli-0.1.1.dist-info/RECORD,,
|