@@ -1419,11 +1419,31 @@ def _card_control_labels(card_html: str) -> list[str]:
14191419 return []
14201420
14211421
1422- def find_target_record_card (html : str , target_file_id : str , institution : str | None = None ) -> dict | None :
1422+ def _card_matches_record_ref (card_el , record_ref : str | None ) -> bool :
1423+ """Kart, KAYIT NO'yu (record_ref) içeriyor mu — görünür metinde YA DA class/id niteliğinde
1424+ (UYAP 'incelenen-li {NO}'). PAYLAŞILAN-Esas'ta DOĞRU açık artırma kartını seçmek için."""
1425+ ref = re .sub (r"\D" , "" , str (record_ref or "" ))
1426+ if not ref :
1427+ return False
1428+ if ref in re .sub (r"\D" , "" , card_el .get_text (" " , strip = True )):
1429+ return True
1430+ for el in [card_el ] + card_el .find_all (True ):
1431+ cls = el .get ("class" ) or []
1432+ if isinstance (cls , str ):
1433+ cls = [cls ]
1434+ for token in list (cls ) + [el .get ("id" ) or "" ]:
1435+ if ref in re .sub (r"\D" , "" , str (token )):
1436+ return True
1437+ return False
1438+
1439+
1440+ def find_target_record_card (html : str , target_file_id : str , institution : str | None = None ,
1441+ target_record_ref : str | None = None ) -> dict | None :
14231442 """Listeleme sayfasında HEDEF kaydı resmî dosya kimliğiyle bulur (fiyat/P/Q/nth DEĞİL).
14241443
1425- Aynı dosya kimliğine sahip TEK kartı döndürür; birden çok distinkt dosya no içeren container
1426- (tüm-liste) satır/kart SAYILMAZ. İlk-global-kart ya da nth-kart seçilmez. OFFLINE testable.
1444+ ``target_record_ref`` (KAYIT NO) verilmişse aynı Esas'ı paylaşan kartlar arasından o KAYIT NO'lu
1445+ kartı seçer (PAYLAŞILAN-Esas'ta yanlış-kart edinimini önler; eşleşen kart yoksa dürüstçe None);
1446+ verilmemişse aynı dosya kimliğine sahip TEK kartı döndürür. Çok-kayıtlı container SAYILMAZ. OFFLINE testable.
14271447 """
14281448 try :
14291449 from bs4 import BeautifulSoup
@@ -1446,16 +1466,25 @@ def find_target_record_card(html: str, target_file_id: str, institution: str | N
14461466 inst_tok = _ascii_lower (institution ).split ()
14471467 if inst_tok and inst_tok [0 ] in _ascii_lower (text ):
14481468 match_fields .append ("institution" )
1449- matches .append ((c , re .sub (r"\s+" , " " , text ), match_fields ))
1450- if len (matches ) >= 1 :
1451- c , text , match_fields = matches [0 ]
1452- return {
1453- "html" : str (c ),
1454- "file_text" : text [:120 ],
1455- "match_fields" : match_fields ,
1456- "control_labels" : _card_control_labels (str (c )),
1457- "target_file" : tnum ,
1458- }
1469+ ref_match = _card_matches_record_ref (c , target_record_ref )
1470+ matches .append ((c , re .sub (r"\s+" , " " , text ), match_fields , ref_match ))
1471+ if not matches :
1472+ continue
1473+ if target_record_ref :
1474+ ref_hits = [m for m in matches if m [3 ]]
1475+ if not ref_hits :
1476+ continue # bu selector'da KAYIT NO eşleşen kart YOK → yanlış kartı ALMA, sonrakini dene
1477+ c , text , match_fields , _ = ref_hits [0 ]
1478+ match_fields = match_fields + ["record_ref" ]
1479+ else :
1480+ c , text , match_fields , _ = matches [0 ]
1481+ return {
1482+ "html" : str (c ),
1483+ "file_text" : text [:120 ],
1484+ "match_fields" : match_fields ,
1485+ "control_labels" : _card_control_labels (str (c )),
1486+ "target_file" : tnum ,
1487+ }
14591488 return None
14601489
14611490
@@ -1798,7 +1827,7 @@ def _select_target_page(self, context, target_file_id=None): # pragma: no cover
17981827 page = context .pages [idx ] if 0 <= idx < len (context .pages ) else context .pages [0 ]
17991828 return page , sel
18001829
1801- def _collect_documents (self , page , context , target_file_id = None , target_institution = None , native_only = False ) -> tuple : # pragma: no cover - canlı DOM/olay gerektirir
1830+ def _collect_documents (self , page , context , target_file_id = None , target_institution = None , native_only = False , target_record_ref = None ) -> tuple : # pragma: no cover - canlı DOM/olay gerektirir
18021831 """SAYFA-DURUMU FARKINDA, HEDEF-KAYIT-KAPSAMLI belge girişi (iki gerçek gözlenen yol).
18031832
18041833 ``search_listing`` → HEDEF kayıt kartı dosya kimliğiyle bulunur (fiyat/nth DEĞİL) →
@@ -1851,7 +1880,7 @@ def _collect_documents(self, page, context, target_file_id=None, target_institut
18511880 diag ["document_collection_attempts" ].append ({"stage" : "target_card" , "blocking_reason" : "no_target_file_id_provided" })
18521881 diag ["document_collection_failures" ] += 1
18531882 return documents , patterns , diag
1854- card = find_target_record_card (html , target_file_id , target_institution )
1883+ card = find_target_record_card (html , target_file_id , target_institution , target_record_ref )
18551884 if card is None :
18561885 diag ["document_collection_attempts" ].append ({"stage" : "target_card" , "blocking_reason" : "target_record_card_not_found_on_listing" })
18571886 diag ["document_collection_failures" ] += 1
@@ -1869,7 +1898,7 @@ def _collect_documents(self, page, context, target_file_id=None, target_institut
18691898 return documents , patterns , diag
18701899 # Kart-yerel kontrolü CANLI DOM'da HEDEF kart kapsamında bul (global text locator DEĞİL —
18711900 # üçüncü canlı hatanın kök nedeni buydu).
1872- control = self ._locate_card_control (page , target_file_id )
1901+ control = self ._locate_card_control (page , target_file_id , target_record_ref )
18731902 elif page_state == "record_detail" :
18741903 if target_file_id and not file_identity_matches (html , target_file_id ):
18751904 diag ["document_collection_attempts" ].append ({"stage" : "detail_identity" , "blocking_reason" : "detail_page_does_not_match_target_file_id" })
@@ -1982,32 +2011,72 @@ def _collect_documents(self, page, context, target_file_id=None, target_institut
19822011 patterns += p2
19832012 return documents , patterns , diag
19842013
1985- def _locate_card_control (self , page , target_file_id ): # pragma: no cover - canlı DOM
1986- """HEDEF kaydın CANLI kartını dosya kimliğiyle bulur; kart-yerel "İhale Evrak Listesi"
1987- kontrolünü döner (global metin locator DEĞİL — kart kapsamı zorunlu)."""
2014+ def _locate_card_control (self , page , target_file_id , target_record_ref = None ): # pragma: no cover - canlı DOM
2015+ """HEDEF kaydın CANLI kartını bulur; kart-yerel "İhale Evrak Listesi" kontrolünü döner.
2016+
2017+ ``target_record_ref`` (KAYIT NO) verilmişse PAYLAŞILAN-Esas'ta kart o KAYIT NO ile daraltılır
2018+ (yalnız Esas ile filtreleme çok-açık-artırmalı dosyada YANLIŞ kartı seçerdi → RECONCILIATION_FAILED).
2019+ KAYIT NO görünür metinde YA DA class ('incelenen-li {NO}') olabilir; eşleşen kart yoksa None (yanlış
2020+ kartı ALMA). Global metin locator DEĞİL — kart kapsamı zorunlu."""
19882021 import re as _re
19892022
19902023 tnum = normalize_file_identity (target_file_id )
19912024 key = _re .escape (tnum ).replace ("/" , r"\s*/\s*" )
1992- for csel in ("[class*=card]" , "[class*=sonuc]" , "[class*=result]" , "[class*=ilan]" , "li" , "tr" ):
1993- try :
1994- cards = page .locator (csel ).filter (has_text = _re .compile (key , _re .I ))
1995- if cards .count () == 0 :
1996- continue
1997- card = cards .first
1998- for name in ("button" , "link" ):
1999- try :
2000- act = card .get_by_role (name , name = _re .compile ("evrak listesi" , _re .I ))
2001- if act .count () > 0 :
2002- return act .first
2003- except Exception :
2004- continue
2025+ ref = _re .sub (r"\D" , "" , str (target_record_ref or "" ))
2026+ card_selectors = ("[class*=card]" , "[class*=sonuc]" , "[class*=result]" , "[class*=ilan]" , "li" , "tr" )
2027+
2028+ def _control_in (card ):
2029+ for name in ("button" , "link" ):
20052030 try :
2006- act = card .get_by_text ( _re .compile ("evrak listesi" , _re .I ))
2031+ act = card .get_by_role ( name , name = _re .compile ("evrak listesi" , _re .I ))
20072032 if act .count () > 0 :
20082033 return act .first
20092034 except Exception :
2010- pass
2035+ continue
2036+ try :
2037+ act = card .get_by_text (_re .compile ("evrak listesi" , _re .I ))
2038+ if act .count () > 0 :
2039+ return act .first
2040+ except Exception :
2041+ pass
2042+ return None
2043+
2044+ if ref :
2045+ # (a) KAYIT NO görünür metinde: Esas + KAYIT NO ile daralt
2046+ for csel in card_selectors :
2047+ try :
2048+ cc = page .locator (csel ).filter (has_text = _re .compile (key , _re .I )).filter (has_text = _re .compile (ref ))
2049+ if cc .count () > 0 :
2050+ ctrl = _control_in (cc .first )
2051+ if ctrl is not None :
2052+ return ctrl
2053+ except Exception :
2054+ continue
2055+ # (b) KAYIT NO class niteliğinde ('incelenen-li {NO}'): o elemanın evrak-kontrolü içeren kart atası
2056+ try :
2057+ holder = page .locator (f'[class*="{ ref } "]' )
2058+ if holder .count () > 0 :
2059+ for xp in ('xpath=ancestor-or-self::*[.//*[contains(@id,"detailButton")]][1]' ,
2060+ 'xpath=ancestor-or-self::*[.//a or .//button][1]' ):
2061+ try :
2062+ ctrl = _control_in (holder .first .locator (xp ))
2063+ if ctrl is not None :
2064+ return ctrl
2065+ except Exception :
2066+ continue
2067+ except Exception :
2068+ pass
2069+ return None # KAYIT NO verildi ama DOĞRU kart bulunamadı → yanlış kartı ALMA (dürüst)
2070+
2071+ # KAYIT NO yok → orijinal Esas-yalnız davranış (pilot geriye-uyumlu)
2072+ for csel in card_selectors :
2073+ try :
2074+ cards = page .locator (csel ).filter (has_text = _re .compile (key , _re .I ))
2075+ if cards .count () == 0 :
2076+ continue
2077+ ctrl = _control_in (cards .first )
2078+ if ctrl is not None :
2079+ return ctrl
20112080 except Exception :
20122081 continue
20132082 return None
0 commit comments