From 061887f870afb248174b17c3c1f48fb53739e6af Mon Sep 17 00:00:00 2001 From: saidsurucu Date: Wed, 27 May 2026 14:06:09 +0300 Subject: [PATCH] fix(anayasa): force correct host for AYM document URLs by path get_anayasa_document_unified 404'd when a /ND/ (Norm Denetimi) path was supplied on the bireysel host (kararlarbilgibankasi) instead of the norm host. Detection was netloc-first and passed the wrong-domain URL through unchanged. Add normalize_anayasa_document_url(): classify by path (/ND/ vs /BB/) and re-key the host to the canonical domain, preserving query/fragment. Route get_document_unified() off the normalized result. Verified end-to-end: the previously-404 URL now returns the decision markdown. Co-Authored-By: Claude Opus 4.7 (1M context) --- anayasa_mcp_module/unified_client.py | 80 ++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/anayasa_mcp_module/unified_client.py b/anayasa_mcp_module/unified_client.py index 1319b85..26543e1 100644 --- a/anayasa_mcp_module/unified_client.py +++ b/anayasa_mcp_module/unified_client.py @@ -2,8 +2,8 @@ # Unified client for both Norm Denetimi and Bireysel Başvuru import logging -from typing import Optional -from urllib.parse import urlparse +from typing import Optional, Tuple +from urllib.parse import urlparse, urlunparse from .models import ( AnayasaUnifiedSearchRequest, @@ -18,6 +18,51 @@ from .bireysel_client import AnayasaBireyselBasvuruApiClient logger = logging.getLogger(__name__) +# Canonical hosts per decision type. Norm Denetimi (/ND/) documents live on the +# "norm" subdomain; Bireysel Başvuru (/BB/) documents on the plain subdomain. +# Callers (or upstream search links) sometimes supply the wrong host for a given +# path, which makes the AYM server return 404. We re-key the host off the path. +_NORM_HOST = "normkararlarbilgibankasi.anayasa.gov.tr" +_BIREYSEL_HOST = "kararlarbilgibankasi.anayasa.gov.tr" + + +def normalize_anayasa_document_url(document_url: str) -> Tuple[Optional[str], str]: + """Detect the AYM decision type from the URL path and force the correct host. + + Detection is path-based (``/ND/`` vs ``/BB/``) because the path is + unambiguous, whereas the supplied host may be wrong. Query params and + fragment are preserved (they are harmless for document fetches). + + Returns ``(decision_type, normalized_url)`` where ``decision_type`` is + ``"norm_denetimi"``, ``"bireysel_basvuru"``, or ``None`` if it cannot be + determined (URL returned unchanged in that case). + """ + parsed = urlparse(document_url) + path = parsed.path or "" + + if "/ND/" in path: + decision_type, host = "norm_denetimi", _NORM_HOST + elif "/BB/" in path: + decision_type, host = "bireysel_basvuru", _BIREYSEL_HOST + else: + # Fall back to host-based detection when the path is uninformative. + if "normkararlarbilgibankasi" in parsed.netloc: + return "norm_denetimi", document_url + if "kararlarbilgibankasi" in parsed.netloc: + return "bireysel_basvuru", document_url + return None, document_url + + normalized = urlunparse(( + parsed.scheme or "https", + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + )) + return decision_type, normalized + + class AnayasaUnifiedClient: """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches.""" @@ -80,13 +125,19 @@ class AnayasaUnifiedClient: async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown: """Unified document retrieval that auto-detects the appropriate client.""" - # Auto-detect decision type based on URL - parsed_url = urlparse(document_url) - - if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url: - # Norm Denetimi document - result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number) - + # Auto-detect decision type from the path and force the correct host. + # This repairs malformed URLs (e.g. a /ND/ path on the bireysel host), + # which otherwise 404 against the AYM server. + decision_type, normalized_url = normalize_anayasa_document_url(document_url) + if normalized_url != document_url: + logger.info( + f"AnayasaUnifiedClient: Normalized document URL " + f"'{document_url}' -> '{normalized_url}'" + ) + + if decision_type == "norm_denetimi": + result = await self.norm_client.get_decision_document_as_markdown(normalized_url, page_number) + return AnayasaUnifiedDocumentMarkdown( decision_type="norm_denetimi", source_url=result.source_url, @@ -96,11 +147,10 @@ class AnayasaUnifiedClient: total_pages=result.total_pages, is_paginated=result.is_paginated ) - - elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url: - # Bireysel Başvuru document - result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number) - + + elif decision_type == "bireysel_basvuru": + result = await self.bireysel_client.get_decision_document_as_markdown(normalized_url, page_number) + return AnayasaUnifiedDocumentMarkdown( decision_type="bireysel_basvuru", source_url=result.source_url, @@ -110,7 +160,7 @@ class AnayasaUnifiedClient: total_pages=result.total_pages, is_paginated=result.is_paginated ) - + else: raise ValueError(f"Cannot determine document type from URL: {document_url}")