Skip to content

Commit 6b8b6a5

Browse files
committed
fix(uyap): extract İhale Bedeli from UDF <data> section (values stored outside <content>)
Inspector confirmed the auction_result UDF stores filled values in a <data> section with semantic element names (<ihaleBedeli>4.654.000,00</ihaleBedeli>, <satisBedeliKalani>...</satisBedeliKalani>), NOT in the <content> CDATA (which holds only template labels). extract_udf_source_text read only <content>, so the İhale Bedeli value was never seen -> honest PENDING_REVIEW. 2026/263 worked because its amounts were inline in <content>. Fix: _extract_content_text now also reads the UDF <data> section and appends KNOWN FINANCIAL fields (exact tag-name match: ihaleBedeli/muhammenBedel/kiymeti/odenmesiGerekenBedel) as canonical labeled lines ('İhale Bedeli: <value>'). The existing label->value extractor (with the multi-occurrence change) then picks up the value. Deterministic and non-fabricating: the value comes from the explicit semantic element (tag name = field identity); the card 'Satış Tutarı' is still never used; compound/personal/unknown tags (e.g. borcluAdi, satisBedeliKalani) are NOT appended (no false match, no personal-data leak). content.xml without <data> is unchanged (no regression). Verified end-to-end: data-section UDF now yields P=1.234.567,89 via same_segment; personal <borcluAdi> excluded; inline UDF unchanged. +3 tests; 525 uyap/extract/reconcile/model/pilot tests pass.
1 parent 772f5a1 commit 6b8b6a5

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

src/sold/ingestion/uyap/udf.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ def _member_names_safe_summary(names: list) -> dict:
6363
}
6464

6565

66+
# UYAP UDF: doldurulmuş ALAN DEĞERLERİ ``<content>`` CDATA'sında DEĞİL, ayrı ``<data>`` bölümünde SEMANTİK
67+
# tag'lerle tutulabilir (ör. ``<ihaleBedeli>4.654.000,00</ihaleBedeli>``). YALNIZCA bilinen FİNANSAL tag'ler
68+
# (TAM ad eşleşmesi) kanonik Türkçe etiketle metne eklenir → alan-etiketi→değer çıkarımı bunları görür.
69+
# Bileşik/kişisel/bilinmeyen tag'ler EKLENMEZ (yanlış eşleşme + kişisel-veri sızıntısı önlenir).
70+
_UDF_DATA_FINANCIAL_LABELS = {
71+
"ihalebedeli": "İhale Bedeli",
72+
"muhammenbedel": "Muhammen Bedel",
73+
"muhammenbedeli": "Muhammen Bedel",
74+
"muhammenkiymet": "Muhammen Kıymet",
75+
"muhammenkiymeti": "Muhammen Kıymet",
76+
"kiymeti": "Kıymeti",
77+
"odenmesigerekenbedel": "Ödenmesi Gereken Bedel",
78+
}
79+
80+
81+
def _data_field_lines(root) -> list:
82+
"""UYAP UDF ``<data>`` bölümündeki BİLİNEN finansal alanları ``Kanonik Etiket: değer`` satırlarına çevirir.
83+
84+
Doldurulmuş değerler ``<content>`` CDATA'sında değil ``<data>``'da semantik tag'lerle tutulur. Yalnız
85+
bilinen finansal tag'ler (TAM ad eşleşmesi) kanonik etiketle eklenir; değer AÇIK semantik elemandan
86+
gelir (UYDURMA YOK, kart 'Satış Tutarı' KULLANILMAZ). Bileşik/kişisel/bilinmeyen alanlar EKLENMEZ.
87+
"""
88+
lines = []
89+
for data_el in root.iter():
90+
if str(data_el.tag).split("}")[-1].lower() != "data":
91+
continue
92+
for child in list(data_el):
93+
key = str(child.tag).split("}")[-1].lower().replace("_", "")
94+
label = _UDF_DATA_FINANCIAL_LABELS.get(key)
95+
val = (child.text or "").strip()
96+
if label and val:
97+
lines.append(f"{label}: {val}")
98+
break # ilk ``<data>`` bölümü yeterli
99+
return lines
100+
101+
66102
def _extract_content_text(xml_bytes: bytes, diag: dict) -> str | None:
67103
"""``content.xml`` baytlarını GÜVENLİ XML ayrıştırıp ``content`` elemanının CDATA/metnini aynen döndürür."""
68104
try:
@@ -96,6 +132,13 @@ def _extract_content_text(xml_bytes: bytes, diag: dict) -> str | None:
96132
if not text or not text.strip():
97133
diag["blocking_reason"] = "empty_content_text"
98134
return None
135+
# UYAP UDF: doldurulmuş finansal değerler <content> DIŞINDA <data> bölümünde semantik tag'lerde durur
136+
# (ör. <ihaleBedeli>). Bilinen finansal alanları kanonik etiketle EKLE ki İhale Bedeli/Muhammen çıkarımı
137+
# görebilsin (değer açık semantik elemandan; UYDURMA YOK). content.xml'de yoksa metin AYNEN kalır.
138+
data_lines = _data_field_lines(root)
139+
if data_lines:
140+
text = text + "\n" + "\n".join(data_lines)
141+
diag["udf_data_financial_field_count"] = len(data_lines)
99142
diag["source_text_available"] = True
100143
diag["text_extraction_supported"] = True
101144
return text

tests/test_uyap_live_fix12.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,49 @@ def test_root_content_xml_found_and_extracted():
8686
assert native_udf_supported(diag) is True
8787

8888

89+
# --- <data> bölümü finansal alanları (gerçek Ankara 2026/23: değer <content> DIŞINDA) ------- #
90+
_DATA_SECTION_XML = (
91+
'<?xml version="1.0" encoding="UTF-8"?>\n'
92+
'<template format_id="1.8">\n'
93+
' <content><![CDATA[Artırma Sonuç Tutanağı İhale Bedeli Ödenmesi Gereken Bedel]]></content>\n'
94+
' <data>\n'
95+
' <ihaleBedeli>1.234.567,89 TL</ihaleBedeli>\n'
96+
' <satisBedeliKalani>111.111,11 TL</satisBedeliKalani>\n'
97+
' <borcluAdi>SENTETİK KİŞİ</borcluAdi>\n'
98+
' </data>\n'
99+
'</template>\n'
100+
)
101+
102+
103+
def test_data_section_known_financial_field_appended_personal_excluded():
104+
# Doldurulmuş İhale Bedeli <content> CDATA'sında DEĞİL, <data><ihaleBedeli>'de; kanonik etiketle EKLENİR.
105+
text, diag = extract_udf_source_text(_make_udf(_DATA_SECTION_XML))
106+
assert text is not None
107+
assert "İhale Bedeli: 1.234.567,89 TL" in text # bilinen finansal alan kanonik etiketle
108+
assert diag.get("udf_data_financial_field_count") == 1 # yalnız ihaleBedeli finansal-haritada
109+
assert "SENTETİK KİŞİ" not in text # kişisel alan EKLENMEZ (sızıntı yok)
110+
assert "111.111,11" not in text # harita-dışı finansal-olmayan alan EKLENMEZ
111+
112+
113+
def test_data_section_ihale_bedeli_feeds_auction_price_extraction():
114+
# Uçtan uca: <data><ihaleBedeli> → metne eklenir → İhale Bedeli(P) çıkarımı bunu görür (uydurma yok).
115+
text, _ = extract_udf_source_text(_make_udf(_DATA_SECTION_XML))
116+
ev = extract_evidence(
117+
[{"artifact_type": "auction_result", "text": text},
118+
{"artifact_type": "status_card", "text": "Satıldı Satış İşlemleri Tamamlandı"}],
119+
institution="Ankara", file_id="2026/23",
120+
)
121+
assert ev.ihale_bedeli == 1_234_567.89
122+
assert ev.auction_price_field_label_found is True
123+
124+
125+
def test_inline_content_amounts_unchanged_when_no_data_section():
126+
# <data> yoksa metin AYNEN kalır (2026/263 satır-içi düzen; regresyon yok).
127+
text, diag = extract_udf_source_text(_make_udf())
128+
assert diag.get("udf_data_financial_field_count") == 0
129+
assert "İhale Bedeli : 1.234.567,89" in text
130+
131+
89132
def test_no_extractall_used_in_reader():
90133
src = inspect.getsource(_udf)
91134
assert "extractall" not in src # keyfi üye diske AÇILMAZ

0 commit comments

Comments
 (0)