Skip to content

Commit 5bbf5ee

Browse files
committed
test(uyap): viewer download-required + same-row download fallback + honesty (Live Fix 6.1, offline)
22 offline tests (no network/live): accessible viewer content and dom_text => content_available; the real Turkish 'Evrak Goruntulenemedi, Evragi indirerek Goruntuleyebilirsiniz.', its folded form, and constrained mojibake (latin-1/cp1252) => download_required; a bare 'indir', a generic program-download instruction, a viewer-failure message without the download instruction (=> viewer_error), and the download instruction without the failure are all NOT download_required; resolve_row_view_action exposes the row's positively resolved single download action from the SAME row (never global/Nth/other-row), a download-only row has no resolved view (so no viewer/fallback), two downloads leave the download action unresolved, a view-only row has no resolved download, and unknown/second/Nth actions are never auto-inferred as download; extraction_supported_for is true only for .txt/.html/.htm (raw .udf/.pdf and a mimeType=Udf hint alone are unsupported); a downloaded unsupported UDF (no text) yields no injected Ihale Bedeli and is not admissible; result-card Satis Tutari not substituted; run_pilot is non-mutating for both the unsupported-UDF and supported-download paths (count 7, uyap.json unchanged, no 8th observation, never admits); structural freeze (4 SMM moments, no uyap_sale_prob, TOKI external 0, conditional_on_trade). Full suite: 414 passed (392 baseline + 22).
1 parent 466a2ec commit 5bbf5ee

1 file changed

Lines changed: 254 additions & 0 deletions

File tree

tests/test_uyap_live_fix6_1.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
"""UYAP Live Browser Pilot 1 — Live Interoperability Fix 6.1 testleri (OFFLINE; ağ/canlı YOK).
2+
3+
Ek GERÇEK operatör gözlemi: satır-yerel eye/view eylemi bir UYAP UDF görüntüleyici sekmesi açtı, ama
4+
belge RENDER OLMADI; görüntüleyici görünürde şunu yazdı:
5+
"Evrak Görüntülenemedi, Evrağı indirerek Görüntüleyebilirsiniz." Dolayısıyla görüntüleyiciye ulaşmak
6+
belge içeriğinin erişilebilir olduğunu GARANTİ ETMEZ.
7+
8+
Fix 6.1: görüntüleyici-SONUÇ sınıflandırması (content_available / download_required / ...) + AYNI-SATIR
9+
download geri-dönüşü (Fix-6 POZİTİF download semantiğini yeniden kullanır; global/Nth/başka-satır YOK;
10+
ham UDF çıkarımı DESTEKLENMİYORSA dürüstçe raporlanır; UYDURMA/OCR/known-truth enjeksiyonu YOK).
11+
Bu testler yalnız saf/çevrimdışı davranışı doğrular; CANLI PASS DEĞİL; post-Fix-6.1 operatör rerun gerekir.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import json
17+
import shutil
18+
19+
from bs4 import BeautifulSoup
20+
21+
from sold.ingestion.uyap import (
22+
audit_candidate,
23+
classify_viewer_outcome,
24+
extract_evidence,
25+
extraction_supported_for,
26+
genuine_fingerprint,
27+
reconcile,
28+
resolve_row_view_action,
29+
run_pilot,
30+
viewer_download_instruction_detected,
31+
)
32+
from sold.ingestion.uyap.collect import _row_action_specs
33+
34+
TARGET = "2026/263 Esas"
35+
REAL_MSG = "Evrak Görüntülenemedi, Evrağı indirerek Görüntüleyebilirsiniz."
36+
37+
38+
def _acts(inner_html: str):
39+
return _row_action_specs(BeautifulSoup(f"<div class=row>{inner_html}</div>", "html.parser"))
40+
41+
42+
_ROW_DL_EYE = (
43+
'<a href="#"><svg><use href="#icon-download"></use></svg></a>'
44+
'<a href="#"><svg><use href="#icon-eye"></use></svg></a>'
45+
)
46+
47+
48+
# --- A. Görüntüleyici sonucu: içerik erişilebilir --------------------------------------- #
49+
def test_accessible_viewer_content_is_content_available():
50+
txt = "ARTIRMA SONUÇ TUTANAĞI İhale Bedeli: 5.715.000,00 ALACAĞA MAHSUBEN"
51+
assert classify_viewer_outcome(txt) == "content_available"
52+
assert classify_viewer_outcome("herhangi", "dom_text") == "content_available"
53+
54+
55+
# --- B. download_required: gerçek Türkçe / fold / mojibake ------------------------------ #
56+
def test_real_turkish_download_instruction_is_download_required():
57+
assert viewer_download_instruction_detected(REAL_MSG) is True
58+
assert classify_viewer_outcome(REAL_MSG) == "download_required"
59+
60+
61+
def test_folded_turkish_download_instruction():
62+
folded = "evrak goruntulenemedi, evragi indirerek goruntuleyebilirsiniz."
63+
assert classify_viewer_outcome(folded) == "download_required"
64+
65+
66+
def test_mojibake_download_instruction():
67+
assert classify_viewer_outcome(REAL_MSG.encode("utf-8").decode("latin-1")) == "download_required"
68+
assert classify_viewer_outcome(REAL_MSG.encode("utf-8").decode("cp1252")) == "download_required"
69+
70+
71+
# --- C. Katı: yalın indir / program indirme / eksik bileşen download_required DEĞİL ------ #
72+
def test_generic_indir_alone_is_not_download_required():
73+
assert classify_viewer_outcome("Dosyayı indir") != "download_required"
74+
assert viewer_download_instruction_detected("Dosyayı indir") is False
75+
76+
77+
def test_generic_program_download_instruction_is_not_download_required():
78+
assert classify_viewer_outcome("Programı indirmek için tıklayınız") != "download_required"
79+
80+
81+
def test_viewer_failure_without_instruction_does_not_trigger_fallback():
82+
# 'Evrak Görüntülenemedi' TEK BAŞINA download_required değil (fallback tetiklemez) → viewer_error
83+
assert classify_viewer_outcome("Evrak Görüntülenemedi") == "viewer_error"
84+
assert viewer_download_instruction_detected("Evrak Görüntülenemedi") is False
85+
86+
87+
def test_download_instruction_without_failure_does_not_trigger_fallback():
88+
assert viewer_download_instruction_detected("Evrağı indirerek Görüntüleyebilirsiniz") is False
89+
90+
91+
# --- D. Fix-6 POZİTİF download eylemi AYNI SATIRDAN çözülür ------------------------------ #
92+
def test_same_row_download_action_is_positively_resolved():
93+
acts = _acts(_ROW_DL_EYE)
94+
res = resolve_row_view_action(acts)
95+
assert res["resolved"] is True and res["view_action"]["semantic"] == "view"
96+
assert res["download_action_resolved"] is True
97+
assert res["download_action"]["semantic"] == "download"
98+
# AYNI satırın eylemlerinden biri (global/Nth/başka-satır DEĞİL)
99+
assert res["download_action"] in acts and res["view_action"] in acts
100+
101+
102+
def test_download_only_row_has_no_resolved_view_so_no_viewer_opened():
103+
# view çözülmediğinde görüntüleyici HİÇ açılmaz → outcome/fallback devreye girmez
104+
res = resolve_row_view_action(_acts('<a class="fa-download"></a>'))
105+
assert res["resolved"] is False and res["view_action"] is None
106+
107+
108+
def test_two_downloads_download_action_unresolved():
109+
res = resolve_row_view_action(_acts('<a class="fa-download"></a><a class="fa-download"></a>'))
110+
assert res["download_action_resolved"] is False and res["download_action"] is None
111+
112+
113+
def test_view_only_row_download_action_unresolved():
114+
res = resolve_row_view_action(_acts('<a title="Goruntule"></a>'))
115+
assert res["resolved"] is True
116+
assert res["download_action_resolved"] is False and res["download_action"] is None
117+
118+
119+
def test_non_view_unknown_not_auto_download():
120+
# [view, unknown] → download çıkarılmaz (unknown download DEĞİL)
121+
res = resolve_row_view_action(_acts('<a title="Goruntule"></a><a class="icon-foo"></a>'))
122+
assert res["resolved"] is True
123+
assert res["download_action_resolved"] is False and res["download_action_detected"] is False
124+
125+
126+
def test_second_or_nth_action_not_auto_download():
127+
res = resolve_row_view_action(_acts('<a class="x"></a><a class="y"></a>'))
128+
assert res["download_action_resolved"] is False # konum/Nth ile download SEÇİLMEZ
129+
130+
131+
# --- E. Çıkarım-desteği dürüstlüğü (ham UDF/PDF DESTEKLENMEZ) ---------------------------- #
132+
def test_extraction_supported_only_for_text_html():
133+
assert extraction_supported_for(".txt") is True
134+
assert extraction_supported_for(".html") is True and extraction_supported_for(".htm") is True
135+
assert extraction_supported_for("txt") is True # nokta olmadan da
136+
assert extraction_supported_for(".udf") is False
137+
assert extraction_supported_for(".pdf") is False
138+
assert extraction_supported_for(".zip") is False
139+
assert extraction_supported_for(None) is False
140+
141+
142+
def test_mimetype_udf_hint_alone_does_not_imply_support():
143+
# viewer URL mimeType=Udf tek başına destek ANLAMINA GELMEZ
144+
assert extraction_supported_for(".udf", "udf") is False
145+
assert extraction_supported_for(None, "udf") is False
146+
147+
148+
# --- F. OUTCOME C dürüstlüğü: indirilen ham UDF çıkarılamaz, known-truth ENJEKTE EDİLMEZ -- #
149+
def test_downloaded_unsupported_udf_yields_no_injected_price():
150+
# İndirilmiş ama çıkarımı desteklenmeyen resmî auction_result (metin YOK) → İhale Bedeli None
151+
downloaded = {"artifact_type": "auction_result", "source_ref": "download:.udf", "extraction_supported": False}
152+
ev = extract_evidence([downloaded], institution="Ankara", file_id=TARGET)
153+
assert ev.ihale_bedeli is None # 5715000 ENJEKTE EDİLMEZ
154+
au = audit_candidate(ev, reconcile([downloaded], "Ankara", TARGET))
155+
assert au.auction_price is None and au.decision != "ADMISSIBLE_COMPLETED_SALE"
156+
157+
158+
def test_result_card_satis_tutari_not_substituted():
159+
card = {"artifact_type": "status_card",
160+
"text": "Satış Durumu Satıldı Satış Tutarı 5.715.000,00 TL 50984 Ada 1 Parsel"}
161+
ev = extract_evidence([card], institution="Ankara", file_id=TARGET)
162+
au = audit_candidate(ev, reconcile([card], "Ankara", TARGET))
163+
assert ev.ihale_bedeli is None and au.decision != "ADMISSIBLE_COMPLETED_SALE"
164+
165+
166+
# --- G. Non-mutation: indirilen ham artifact uyap.json'ı DEĞİŞTİRMEZ / ADMİT ETMEZ ------- #
167+
def test_run_pilot_download_required_unsupported_non_mutating(tmp_path):
168+
from sold.structural.datasets import GENUINE_DIR
169+
170+
gdir = tmp_path / "genuine"
171+
gdir.mkdir()
172+
for f in ("uyap.json", "kap.json", "toki.json"):
173+
shutil.copyfile(GENUINE_DIR / f, gdir / f)
174+
gp = gdir / "uyap.json"
175+
before = genuine_fingerprint(gp)
176+
# OUTCOME C: görüntüleyici download_required → aynı-satır indirme → ham UDF çıkarımı DESTEKLENMEZ
177+
artifacts = [
178+
{"artifact_type": "status_card",
179+
"text": "Muhammen Bedel 6.800.000,00 TL KDV Oranı : %20 Satış Durumu Satıldı 50984 Ada 1 Parsel",
180+
"source_ref": "live://index"},
181+
{"artifact_type": "auction_result", "source_ref": "download:.udf", "extraction_supported": False},
182+
]
183+
r = run_pilot(offline_artifacts=artifacts, genuine_path=gp, store_dir=tmp_path, report_path=tmp_path / "r.json")
184+
assert r["pilot_outcome"] in ("NOT_RUN", "FAIL", "PARTIAL") # ham UDF çıkarılamadı → PASS DEĞİL
185+
mg = r["mutation_guard"]
186+
assert mg["uyap_json_unchanged"] and mg["genuine_uyap_count_unchanged"] and mg["smm_moments_unchanged"]
187+
assert mg["uyap_sale_prob_absent"] is True
188+
after = genuine_fingerprint(gp)
189+
assert after["genuine_uyap_count"] == 7 and after["sha256"] == before["sha256"]
190+
recs = json.loads(gp.read_text(encoding="utf-8"))
191+
assert sum(1 for x in recs if str(x.get("public_record_id")) == TARGET) == 1 # 8. gözlem YOK
192+
193+
194+
def test_run_pilot_fix6_1_offline_supported_path_non_mutating(tmp_path):
195+
from sold.structural.datasets import GENUINE_DIR
196+
197+
gdir = tmp_path / "genuine"
198+
gdir.mkdir()
199+
for f in ("uyap.json", "kap.json", "toki.json"):
200+
shutil.copyfile(GENUINE_DIR / f, gdir / f)
201+
gp = gdir / "uyap.json"
202+
before = genuine_fingerprint(gp)
203+
artifacts = [
204+
{"artifact_type": "status_card",
205+
"text": "Muhammen Bedel 6.800.000,00 TL KDV Oranı : %20 Satış Durumu Satıldı 50984 Ada 1 Parsel 60 Nolu B.B.",
206+
"source_ref": "live://index"},
207+
{"artifact_type": "auction_result",
208+
"text": ("ARTIRMA SONUÇ TUTANAĞI 50984 Ada 1 Parsel İhale Bedeli: 5.715.000,00 "
209+
"Ödenmesi Gereken Bedel: ALACAĞA MAHSUBEN Satıldı Satış İşlemleri Tamamlandı"),
210+
"source_ref": "download:.html"}, # desteklenen indirilmiş format
211+
{"artifact_type": "appraisal_report",
212+
"text": "BİLİRKİŞİ RAPORU 6.800.000,00 TL 50984 Ada 1 Parsel 60 Nolu Bağımsız Bölüm",
213+
"source_ref": "viewer:udf_viewer"},
214+
]
215+
r = run_pilot(offline_artifacts=artifacts, genuine_path=gp, store_dir=tmp_path, report_path=tmp_path / "r.json")
216+
assert r["verification_layer_result"] == "PASS"
217+
assert r["pilot_outcome"] == "NOT_RUN" # offline → CANLI PASS DEĞİL
218+
mg = r["mutation_guard"]
219+
assert mg["uyap_json_unchanged"] and mg["genuine_uyap_count_unchanged"]
220+
after = genuine_fingerprint(gp)
221+
assert after["genuine_uyap_count"] == 7 and after["sha256"] == before["sha256"]
222+
223+
224+
# --- H. Yapısal freeze -------------------------------------------------------------------- #
225+
def test_structural_freeze_four_moments_no_sale_prob():
226+
from sold.structural import build_observed_moments, load_genuine_datasets
227+
228+
g = load_genuine_datasets()
229+
built = build_observed_moments(g["uyap"], g["kap"], g["toki_result"])
230+
smm = {k for k in built["moments"] if k.startswith(("uyap_win", "kap_log"))}
231+
assert smm == {
232+
"uyap_win_over_appraisal_mean", "uyap_win_over_appraisal_sd",
233+
"kap_log_ratio_mean", "kap_log_ratio_sd",
234+
}
235+
assert "uyap_sale_prob" not in built["moments"]
236+
237+
238+
def test_toki_external_zero_moments_and_conditional_on_trade():
239+
from sold.structural import (
240+
DEFAULT_FREE,
241+
PRICE_ESTIMATE_CONDITION,
242+
StructuralParams,
243+
build_observed_moments,
244+
context_from_datasets,
245+
load_genuine_datasets,
246+
source_jacobian_ranks,
247+
)
248+
249+
assert PRICE_ESTIMATE_CONDITION == "conditional_on_trade"
250+
g = load_genuine_datasets()
251+
built = build_observed_moments(g["uyap"], g["kap"], g["toki_result"])
252+
ctx = context_from_datasets(g["uyap"], g["kap"])
253+
sj = source_jacobian_ranks(StructuralParams(), ctx, DEFAULT_FREE, built["moments"], built["provenance"])
254+
assert sj["J_TOKI"]["rank"] == 0 and sj["J_TOKI"]["n_moments"] == 0

0 commit comments

Comments
 (0)