add sayistay module
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://www.star-history.com/#saidsurucu/yargi-mcp&Date)
|
[](https://www.star-history.com/#saidsurucu/yargi-mcp&Date)
|
||||||
|
|
||||||
Bu proje, çeşitli Türk hukuk kaynaklarına (Yargıtay, Danıştay, Emsal Kararlar, Uyuşmazlık Mahkemesi, Anayasa Mahkemesi - Norm Denetimi ile Bireysel Başvuru Kararları, Kamu İhale Kurulu Kararları ve Rekabet Kurumu Kararları) erişimi kolaylaştıran bir [FastMCP](https://gofastmcp.com/) sunucusu oluşturur. Bu sayede, bu kaynaklardan veri arama ve belge getirme işlemleri, Model Context Protocol (MCP) destekleyen LLM (Büyük Dil Modeli) uygulamaları (örneğin Claude Desktop veya [5ire](https://5ire.app)) ve diğer istemciler tarafından araç (tool) olarak kullanılabilir hale gelir.
|
Bu proje, çeşitli Türk hukuk kaynaklarına (Yargıtay, Danıştay, Emsal Kararlar, Uyuşmazlık Mahkemesi, Anayasa Mahkemesi - Norm Denetimi ile Bireysel Başvuru Kararları, Kamu İhale Kurulu Kararları, Rekabet Kurumu Kararları ve Sayıştay Kararları) erişimi kolaylaştıran bir [FastMCP](https://gofastmcp.com/) sunucusu oluşturur. Bu sayede, bu kaynaklardan veri arama ve belge getirme işlemleri, Model Context Protocol (MCP) destekleyen LLM (Büyük Dil Modeli) uygulamaları (örneğin Claude Desktop veya [5ire](https://5ire.app)) ve diğer istemciler tarafından araç (tool) olarak kullanılabilir hale gelir.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ Bu proje, çeşitli Türk hukuk kaynaklarına (Yargıtay, Danıştay, Emsal Kara
|
|||||||
* **Anayasa Mahkemesi (Bireysel Başvuru):** Kapsamlı kriterlerle bireysel başvuru "Karar Arama Raporu" oluşturma ve listedeki kararların metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme.
|
* **Anayasa Mahkemesi (Bireysel Başvuru):** Kapsamlı kriterlerle bireysel başvuru "Karar Arama Raporu" oluşturma ve listedeki kararların metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme.
|
||||||
* **KİK (Kamu İhale Kurulu):** Çeşitli kriterlerle Kurul kararlarını arama; uzun karar metinlerini (varsayılan 5.000 karakterlik) sayfalanmış Markdown formatında getirme.
|
* **KİK (Kamu İhale Kurulu):** Çeşitli kriterlerle Kurul kararlarını arama; uzun karar metinlerini (varsayılan 5.000 karakterlik) sayfalanmış Markdown formatında getirme.
|
||||||
* **Rekabet Kurumu:** Çeşitli kriterlerle Kurul kararlarını arama; karar metinlerini Markdown formatında getirme.
|
* **Rekabet Kurumu:** Çeşitli kriterlerle Kurul kararlarını arama; karar metinlerini Markdown formatında getirme.
|
||||||
|
* **Sayıştay:** 3 karar türü ile kapsamlı denetim kararlarına erişim + **8 Daire Filtreleme** + **Tarih Aralığı & İçerik Arama** (Genel Kurul yorumlayıcı kararları, Temyiz Kurulu itiraz kararları, Daire ilk derece denetim kararları)
|
||||||
|
|
||||||
* Karar metinlerinin daha kolay işlenebilmesi için Markdown formatına çevrilmesi.
|
* Karar metinlerinin daha kolay işlenebilmesi için Markdown formatına çevrilmesi.
|
||||||
* Claude Desktop uygulaması ile `fastmcp install` komutu kullanılarak kolay entegrasyon.
|
* Claude Desktop uygulaması ile `fastmcp install` komutu kullanılarak kolay entegrasyon.
|
||||||
@@ -178,12 +179,23 @@ Bu FastMCP sunucusu aşağıdaki temel araçları sunar:
|
|||||||
* `get_rekabet_kurumu_document(karar_id: str, page_number: Optional[int] = 1) -> RekabetDocument`: Belirli bir Rekabet Kurumu kararını `karar_id` ile alır. Kararın PDF formatındaki orijinalinden istenen sayfayı ayıklar ve Markdown formatında döndürür.
|
* `get_rekabet_kurumu_document(karar_id: str, page_number: Optional[int] = 1) -> RekabetDocument`: Belirli bir Rekabet Kurumu kararını `karar_id` ile alır. Kararın PDF formatındaki orijinalinden istenen sayfayı ayıklar ve Markdown formatında döndürür.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* **Sayıştay Araçları (3 Karar Türü + 8 Daire Filtreleme):**
|
||||||
|
* `search_sayistay_genel_kurul(karar_no, karar_tarih_baslangic, karar_tamami, ...)`: Sayıştay Genel Kurul (yorumlayıcı) kararlarını arar. **Tarih aralığı** (2006-2024) + **İçerik arama** (400 karakter)
|
||||||
|
* `search_sayistay_temyiz_kurulu(ilam_dairesi, kamu_idaresi_turu, temyiz_karar, ...)`: Temyiz Kurulu (itiraz) kararlarını arar. **8 Daire filtreleme** + **Kurum türü** + **Konu sınıflandırması**
|
||||||
|
* `search_sayistay_daire(yargilama_dairesi, web_karar_metni, hesap_yili, ...)`: Daire (ilk derece denetim) kararlarını arar. **8 Daire filtreleme** + **Hesap yılı** + **İçerik arama**
|
||||||
|
* `get_sayistay_genel_kurul_document_markdown(decision_id: str)`: Genel Kurul kararının tam metnini Markdown formatında getirir
|
||||||
|
* `get_sayistay_temyiz_kurulu_document_markdown(decision_id: str)`: Temyiz Kurulu kararının tam metnini Markdown formatında getirir
|
||||||
|
* `get_sayistay_daire_document_markdown(decision_id: str)`: Daire kararının tam metnini Markdown formatında getirir
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### **📊 Kapsamlı İstatistikler**
|
### **📊 Kapsamlı İstatistikler**
|
||||||
- **Toplam Mahkeme/Kurum:** 11 farklı hukuki kurum
|
- **Toplam Mahkeme/Kurum:** 12 farklı hukuki kurum
|
||||||
- **Toplam MCP Tool:** 30+ arama ve belge getirme aracı
|
- **Toplam MCP Tool:** 36+ arama ve belge getirme aracı
|
||||||
- **Daire/Kurul Filtreleme:** 79 farklı seçenek (52 Yargıtay + 27 Danıştay)
|
- **Daire/Kurul Filtreleme:** 87 farklı seçenek (52 Yargıtay + 27 Danıştay + 8 Sayıştay)
|
||||||
- **Tarih Filtreleme:** 5 Bedesten API aracında ISO 8601 formatında tam tarih aralığı desteği
|
- **Tarih Filtreleme:** 5 Bedesten API aracında ISO 8601 formatında tam tarih aralığı desteği
|
||||||
- **Kesin Cümle Arama:** 5 Bedesten API aracında çift tırnak ile tam cümle arama (`"\"mülkiyet kararı\""` formatı)
|
- **Kesin Cümle Arama:** 5 Bedesten API aracında çift tırnak ile tam cümle arama (`"\"mülkiyet kararı\""` formatı)
|
||||||
- **API Kaynağı:** Dual/Triple API desteği ile maksimum kapsama
|
- **API Kaynağı:** Dual/Triple API desteği ile maksimum kapsama
|
||||||
|
|||||||
+404
-2
@@ -87,10 +87,19 @@ from rekabet_mcp_module.models import (
|
|||||||
RekabetKararTuruGuidEnum
|
RekabetKararTuruGuidEnum
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from sayistay_mcp_module.client import SayistayApiClient
|
||||||
|
from sayistay_mcp_module.models import (
|
||||||
|
GenelKurulSearchRequest, GenelKurulSearchResponse,
|
||||||
|
TemyizKuruluSearchRequest, TemyizKuruluSearchResponse,
|
||||||
|
DaireSearchRequest, DaireSearchResponse,
|
||||||
|
SayistayDocumentMarkdown
|
||||||
|
)
|
||||||
|
from sayistay_mcp_module.enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
|
||||||
|
|
||||||
|
|
||||||
app = FastMCP(
|
app = FastMCP(
|
||||||
name="YargiMCP",
|
name="YargiMCP",
|
||||||
instructions="MCP server for TR legal databases (Yargitay, Danistay, Emsal, Uyusmazlik, Anayasa-Norm, Anayasa-Bireysel, KIK).",
|
instructions="MCP server for TR legal databases (Yargitay, Danistay, Emsal, Uyusmazlik, Anayasa-Norm, Anayasa-Bireysel, KIK, Sayistay).",
|
||||||
dependencies=["httpx", "beautifulsoup4", "markitdown", "pydantic", "aiohttp", "playwright"]
|
dependencies=["httpx", "beautifulsoup4", "markitdown", "pydantic", "aiohttp", "playwright"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,6 +113,7 @@ anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient()
|
|||||||
kik_client_instance = KikApiClient()
|
kik_client_instance = KikApiClient()
|
||||||
rekabet_client_instance = RekabetKurumuApiClient()
|
rekabet_client_instance = RekabetKurumuApiClient()
|
||||||
bedesten_client_instance = BedestenApiClient()
|
bedesten_client_instance = BedestenApiClient()
|
||||||
|
sayistay_client_instance = SayistayApiClient()
|
||||||
|
|
||||||
|
|
||||||
KARAR_TURU_ADI_TO_GUID_ENUM_MAP = {
|
KARAR_TURU_ADI_TO_GUID_ENUM_MAP = {
|
||||||
@@ -1995,6 +2005,397 @@ async def get_kyb_bedesten_document_markdown(
|
|||||||
logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'")
|
logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# --- MCP Tools for Sayıştay (Turkish Court of Accounts) ---
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Search Sayıştay Genel Kurul (General Assembly) decisions - precedent-setting interpretive rulings by the Turkish Court of Accounts on audit and accountability regulations",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": True,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def search_sayistay_genel_kurul(
|
||||||
|
karar_no: Optional[str] = Field(None, description="Decision number to search for (e.g., '5415')"),
|
||||||
|
karar_ek: Optional[str] = Field(None, description="Decision appendix number (max 99, e.g., '1')"),
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2006-2024. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
"""),
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2006-2024. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
"""),
|
||||||
|
karar_tamami: Optional[str] = Field(None, description="""
|
||||||
|
Content/text search within decision summaries (max 400 characters).
|
||||||
|
Searches in decision abstracts and main content.
|
||||||
|
Example: 'belediye taşınmaz tahsis'
|
||||||
|
"""),
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)"),
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
) -> GenelKurulSearchResponse:
|
||||||
|
"""
|
||||||
|
Searches Sayıştay Genel Kurul (General Assembly) decisions.
|
||||||
|
|
||||||
|
Genel Kurul decisions are the highest-level interpretive rulings from Turkey's
|
||||||
|
Court of Accounts, providing authoritative guidance on public audit standards,
|
||||||
|
accountability principles, and financial management regulations.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
• Decision number and appendix filtering
|
||||||
|
• Year-based date range filtering (2006-2024)
|
||||||
|
• Full-text content search in decision summaries
|
||||||
|
• Pagination support for large result sets
|
||||||
|
|
||||||
|
Use Cases:
|
||||||
|
• Research audit precedents and interpretive guidance
|
||||||
|
• Find decisions on specific financial regulations
|
||||||
|
• Study evolution of public accountability standards
|
||||||
|
• Analyze Court of Accounts' institutional positions
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'search_sayistay_genel_kurul' called with params: karar_no={karar_no}, karar_ek={karar_ek}, date_range={karar_tarih_baslangic}-{karar_tarih_bitis}, content={karar_tamami}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
search_request = GenelKurulSearchRequest(
|
||||||
|
karar_no=karar_no,
|
||||||
|
karar_ek=karar_ek,
|
||||||
|
karar_tarih_baslangic=karar_tarih_baslangic,
|
||||||
|
karar_tarih_bitis=karar_tarih_bitis,
|
||||||
|
karar_tamami=karar_tamami,
|
||||||
|
start=start,
|
||||||
|
length=length
|
||||||
|
)
|
||||||
|
return await sayistay_client_instance.search_genel_kurul_decisions(search_request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'search_sayistay_genel_kurul'")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Search Sayıştay Temyiz Kurulu (Appeals Board) decisions - higher-level review of audit chamber findings and sanctions with chamber filtering (1-8), date filtering, and comprehensive search criteria",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": True,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def search_sayistay_temyiz_kurulu(
|
||||||
|
ilam_dairesi: DaireEnum = Field("ALL", description="""
|
||||||
|
Chamber/Department filter for appeals board decisions.
|
||||||
|
• ALL: All chambers (default)
|
||||||
|
• 1-8: Specific chamber number (1. Daire through 8. Daire)
|
||||||
|
Each chamber specializes in different types of public institutions.
|
||||||
|
"""),
|
||||||
|
yili: Optional[str] = Field(None, description="""
|
||||||
|
Account year filter (Hesap Yılı).
|
||||||
|
Available years: 1993-2022. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Refers to the fiscal year being audited, not decision date.
|
||||||
|
"""),
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2000, 2006-2024. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
"""),
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2000, 2006-2024. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
"""),
|
||||||
|
kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="""
|
||||||
|
Public administration type filter:
|
||||||
|
• ALL: All institutions (default)
|
||||||
|
• Genel Bütçe Kapsamındaki İdareler: General budget administrations
|
||||||
|
• Yüksek Öğretim Kurumları: Higher education institutions
|
||||||
|
• Belediyeler ve Bağlı İdareler: Municipalities and affiliates
|
||||||
|
• Other specific institution types
|
||||||
|
"""),
|
||||||
|
ilam_no: Optional[str] = Field(None, description="Audit report number (İlam No, max 50 chars)"),
|
||||||
|
dosya_no: Optional[str] = Field(None, description="File number for the case"),
|
||||||
|
temyiz_tutanak_no: Optional[str] = Field(None, description="Appeals board meeting minutes number"),
|
||||||
|
temyiz_karar: Optional[str] = Field(None, description="""
|
||||||
|
Content search within appeals decisions.
|
||||||
|
Searches decision text and reasoning.
|
||||||
|
Example: 'araç kiralama kasko'
|
||||||
|
"""),
|
||||||
|
web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="""
|
||||||
|
Decision subject category filter:
|
||||||
|
• ALL: All subjects (default)
|
||||||
|
• İhale Mevzuatı ile İlgili Kararlar: Procurement legislation
|
||||||
|
• Personel Mevzuatı ile İlgili Kararlar: Personnel legislation
|
||||||
|
• Harcırah Mevzuatı ile İlgili Kararlar: Travel allowance legislation
|
||||||
|
• Other specialized legal areas
|
||||||
|
"""),
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)"),
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
) -> TemyizKuruluSearchResponse:
|
||||||
|
"""
|
||||||
|
Searches Sayıştay Temyiz Kurulu (Appeals Board) decisions.
|
||||||
|
|
||||||
|
Temyiz Kurulu provides second-level review of audit chamber decisions,
|
||||||
|
examining appeals against sanctions and audit findings. These decisions
|
||||||
|
clarify audit standards and provide guidance on liability determinations.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
• Chamber-specific filtering (8 specialized audit chambers)
|
||||||
|
• Account year and decision date filtering (1993-2024)
|
||||||
|
• Public administration type categorization
|
||||||
|
• Subject matter classification and content search
|
||||||
|
• Case documentation tracking (ilam, dosya, tutanak numbers)
|
||||||
|
|
||||||
|
Use Cases:
|
||||||
|
• Research appeals against specific audit findings
|
||||||
|
• Study chamber specialization patterns
|
||||||
|
• Analyze evolution of audit liability standards
|
||||||
|
• Find precedents for specific types of public institutions
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'search_sayistay_temyiz_kurulu' called with params: chamber={ilam_dairesi}, year={yili}, admin_type={kamu_idaresi_turu}, subject={web_karar_konusu}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
search_request = TemyizKuruluSearchRequest(
|
||||||
|
ilam_dairesi=ilam_dairesi,
|
||||||
|
yili=yili,
|
||||||
|
karar_tarih_baslangic=karar_tarih_baslangic,
|
||||||
|
karar_tarih_bitis=karar_tarih_bitis,
|
||||||
|
kamu_idaresi_turu=kamu_idaresi_turu,
|
||||||
|
ilam_no=ilam_no,
|
||||||
|
dosya_no=dosya_no,
|
||||||
|
temyiz_tutanak_no=temyiz_tutanak_no,
|
||||||
|
temyiz_karar=temyiz_karar,
|
||||||
|
web_karar_konusu=web_karar_konusu,
|
||||||
|
start=start,
|
||||||
|
length=length
|
||||||
|
)
|
||||||
|
return await sayistay_client_instance.search_temyiz_kurulu_decisions(search_request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'search_sayistay_temyiz_kurulu'")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Search Sayıştay Daire (Chamber) decisions - first-instance audit findings and sanctions from individual audit chambers with comprehensive filtering and subject categorization",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": True,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def search_sayistay_daire(
|
||||||
|
yargilama_dairesi: DaireEnum = Field("ALL", description="""
|
||||||
|
Audit chamber filter:
|
||||||
|
• ALL: All chambers (default)
|
||||||
|
• 1-8: Specific chamber number (1. Daire through 8. Daire)
|
||||||
|
Each chamber audits different types of public institutions.
|
||||||
|
"""),
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2012-2025. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
"""),
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2012-2025. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
"""),
|
||||||
|
ilam_no: Optional[str] = Field(None, description="Audit report number (İlam No, max 50 chars)"),
|
||||||
|
kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="""
|
||||||
|
Public administration type filter:
|
||||||
|
• ALL: All institutions (default)
|
||||||
|
• Genel Bütçe Kapsamındaki İdareler: General budget administrations
|
||||||
|
• Yüksek Öğretim Kurumları: Higher education institutions
|
||||||
|
• Belediyeler ve Bağlı İdareler: Municipalities and affiliates
|
||||||
|
• Other specific institution types
|
||||||
|
"""),
|
||||||
|
hesap_yili: Optional[str] = Field(None, description="""
|
||||||
|
Account year filter (Hesap Yılı).
|
||||||
|
Available years: 2005, 2008-2023. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Refers to the fiscal year being audited, not decision date.
|
||||||
|
"""),
|
||||||
|
web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="""
|
||||||
|
Decision subject category filter:
|
||||||
|
• ALL: All subjects (default)
|
||||||
|
• İhale Mevzuatı ile İlgili Kararlar: Procurement legislation
|
||||||
|
• Personel Mevzuatı ile İlgili Kararlar: Personnel legislation
|
||||||
|
• Vergi Resmi Harç ve Diğer Gelirlerle İlgili Kararlar: Tax and fee legislation
|
||||||
|
• Other specialized legal areas
|
||||||
|
"""),
|
||||||
|
web_karar_metni: Optional[str] = Field(None, description="""
|
||||||
|
Content search within chamber decisions.
|
||||||
|
Searches decision text and audit findings.
|
||||||
|
Example: 'birim fiyat revize edilmemesi'
|
||||||
|
"""),
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)"),
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
) -> DaireSearchResponse:
|
||||||
|
"""
|
||||||
|
Searches Sayıştay Daire (Chamber) decisions.
|
||||||
|
|
||||||
|
Chamber decisions represent first-instance audit findings, sanctions, and
|
||||||
|
liability determinations issued by specialized audit chambers. These form
|
||||||
|
the foundation of Turkey's public financial accountability system.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
• Chamber-specific filtering (8 specialized audit chambers)
|
||||||
|
• Decision and account year filtering (2012-2025)
|
||||||
|
• Public administration type categorization
|
||||||
|
• Subject matter classification and full-text search
|
||||||
|
• Audit report tracking and institutional analysis
|
||||||
|
|
||||||
|
Use Cases:
|
||||||
|
• Research specific audit findings and sanctions
|
||||||
|
• Study chamber specialization and jurisdiction
|
||||||
|
• Analyze audit patterns by institution type
|
||||||
|
• Find precedents for financial irregularities
|
||||||
|
• Track audit evolution across fiscal years
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'search_sayistay_daire' called with params: chamber={yargilama_dairesi}, admin_type={kamu_idaresi_turu}, subject={web_karar_konusu}, content={web_karar_metni}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
search_request = DaireSearchRequest(
|
||||||
|
yargilama_dairesi=yargilama_dairesi,
|
||||||
|
karar_tarih_baslangic=karar_tarih_baslangic,
|
||||||
|
karar_tarih_bitis=karar_tarih_bitis,
|
||||||
|
ilam_no=ilam_no,
|
||||||
|
kamu_idaresi_turu=kamu_idaresi_turu,
|
||||||
|
hesap_yili=hesap_yili,
|
||||||
|
web_karar_konusu=web_karar_konusu,
|
||||||
|
web_karar_metni=web_karar_metni,
|
||||||
|
start=start,
|
||||||
|
length=length
|
||||||
|
)
|
||||||
|
return await sayistay_client_instance.search_daire_decisions(search_request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'search_sayistay_daire'")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Retrieve the full text of a Sayıştay Genel Kurul decision document in Markdown format for detailed analysis",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": False,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def get_sayistay_genel_kurul_document_markdown(
|
||||||
|
decision_id: str = Field(..., description="Decision ID from search_sayistay_genel_kurul results")
|
||||||
|
) -> SayistayDocumentMarkdown:
|
||||||
|
"""
|
||||||
|
Retrieves the full text of a Sayıştay Genel Kurul decision in Markdown format.
|
||||||
|
|
||||||
|
This tool converts the original General Assembly decision document into clean,
|
||||||
|
readable Markdown format suitable for legal analysis and research.
|
||||||
|
|
||||||
|
Input Requirements:
|
||||||
|
• decision_id: Use the ID from search_sayistay_genel_kurul results
|
||||||
|
• Decision ID must be non-empty string
|
||||||
|
|
||||||
|
Output Format:
|
||||||
|
• Clean Markdown text with legal formatting preserved
|
||||||
|
• Structured content with reasoning and conclusions
|
||||||
|
• Removes technical artifacts from source documents
|
||||||
|
|
||||||
|
Use for:
|
||||||
|
• Detailed analysis of audit precedents
|
||||||
|
• Research on public accountability standards
|
||||||
|
• Citation and reference building
|
||||||
|
• Legal interpretation and case study development
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'get_sayistay_genel_kurul_document_markdown' called for ID: {decision_id}")
|
||||||
|
|
||||||
|
if not decision_id or not decision_id.strip():
|
||||||
|
raise ValueError("Decision ID must be a non-empty string.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await sayistay_client_instance.get_document_as_markdown(decision_id, "genel_kurul")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'get_sayistay_genel_kurul_document_markdown'")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Retrieve the full text of a Sayıştay Temyiz Kurulu decision document in Markdown format for detailed appeals analysis",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": False,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def get_sayistay_temyiz_kurulu_document_markdown(
|
||||||
|
decision_id: str = Field(..., description="Decision ID from search_sayistay_temyiz_kurulu results")
|
||||||
|
) -> SayistayDocumentMarkdown:
|
||||||
|
"""
|
||||||
|
Retrieves the full text of a Sayıştay Temyiz Kurulu decision in Markdown format.
|
||||||
|
|
||||||
|
This tool converts the original Appeals Board decision document into clean,
|
||||||
|
readable Markdown format for analysis of appellate reasoning and standards.
|
||||||
|
|
||||||
|
Input Requirements:
|
||||||
|
• decision_id: Use the ID from search_sayistay_temyiz_kurulu results
|
||||||
|
• Decision ID must be non-empty string
|
||||||
|
|
||||||
|
Output Format:
|
||||||
|
• Clean Markdown text with appellate reasoning preserved
|
||||||
|
• Structured content with original findings and appeals analysis
|
||||||
|
• Removes technical artifacts from source documents
|
||||||
|
|
||||||
|
Use for:
|
||||||
|
• Analysis of appeals board reasoning and standards
|
||||||
|
• Research on audit liability determination evolution
|
||||||
|
• Understanding chamber decision review criteria
|
||||||
|
• Precedent analysis for audit appeal cases
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'get_sayistay_temyiz_kurulu_document_markdown' called for ID: {decision_id}")
|
||||||
|
|
||||||
|
if not decision_id or not decision_id.strip():
|
||||||
|
raise ValueError("Decision ID must be a non-empty string.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await sayistay_client_instance.get_document_as_markdown(decision_id, "temyiz_kurulu")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'get_sayistay_temyiz_kurulu_document_markdown'")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.tool(
|
||||||
|
description="Retrieve the full text of a Sayıştay Daire decision document in Markdown format for detailed audit findings analysis",
|
||||||
|
annotations={
|
||||||
|
"readOnlyHint": True,
|
||||||
|
"openWorldHint": False,
|
||||||
|
"idempotentHint": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def get_sayistay_daire_document_markdown(
|
||||||
|
decision_id: str = Field(..., description="Decision ID from search_sayistay_daire results")
|
||||||
|
) -> SayistayDocumentMarkdown:
|
||||||
|
"""
|
||||||
|
Retrieves the full text of a Sayıştay Daire decision in Markdown format.
|
||||||
|
|
||||||
|
This tool converts the original chamber decision document into clean,
|
||||||
|
readable Markdown format for analysis of first-instance audit findings.
|
||||||
|
|
||||||
|
Input Requirements:
|
||||||
|
• decision_id: Use the ID from search_sayistay_daire results
|
||||||
|
• Decision ID must be non-empty string
|
||||||
|
|
||||||
|
Output Format:
|
||||||
|
• Clean Markdown text with audit findings preserved
|
||||||
|
• Structured content with violations and sanctions
|
||||||
|
• Removes technical artifacts from source documents
|
||||||
|
|
||||||
|
Use for:
|
||||||
|
• Detailed analysis of audit findings and methodology
|
||||||
|
• Research on specific types of financial irregularities
|
||||||
|
• Understanding chamber jurisdiction and specialization
|
||||||
|
• Case study development for audit training and compliance
|
||||||
|
"""
|
||||||
|
logger.info(f"Tool 'get_sayistay_daire_document_markdown' called for ID: {decision_id}")
|
||||||
|
|
||||||
|
if not decision_id or not decision_id.strip():
|
||||||
|
raise ValueError("Decision ID must be a non-empty string.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await sayistay_client_instance.get_document_as_markdown(decision_id, "daire")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Error in tool 'get_sayistay_daire_document_markdown'")
|
||||||
|
raise
|
||||||
|
|
||||||
# --- Application Shutdown Handling ---
|
# --- Application Shutdown Handling ---
|
||||||
def perform_cleanup():
|
def perform_cleanup():
|
||||||
logger.info("MCP Server performing cleanup...")
|
logger.info("MCP Server performing cleanup...")
|
||||||
@@ -2015,7 +2416,8 @@ def perform_cleanup():
|
|||||||
globals().get('anayasa_bireysel_client_instance'),
|
globals().get('anayasa_bireysel_client_instance'),
|
||||||
globals().get('kik_client_instance'),
|
globals().get('kik_client_instance'),
|
||||||
globals().get('rekabet_client_instance'),
|
globals().get('rekabet_client_instance'),
|
||||||
globals().get('bedesten_client_instance')
|
globals().get('bedesten_client_instance'),
|
||||||
|
globals().get('sayistay_client_instance')
|
||||||
]
|
]
|
||||||
async def close_all_clients_async():
|
async def close_all_clients_async():
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# sayistay_mcp_module/__init__.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
Sayıştay (Turkish Court of Accounts) MCP Module
|
||||||
|
|
||||||
|
This module provides access to three types of Sayıştay decisions:
|
||||||
|
- Genel Kurul (General Assembly) decisions
|
||||||
|
- Temyiz Kurulu (Appeals Board) decisions
|
||||||
|
- Daire (Chamber) decisions
|
||||||
|
|
||||||
|
The module handles ASP.NET WebForms authentication with CSRF tokens
|
||||||
|
and DataTables-based pagination for comprehensive decision search.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .client import SayistayApiClient
|
||||||
|
from .models import (
|
||||||
|
# Genel Kurul models
|
||||||
|
GenelKurulSearchRequest,
|
||||||
|
GenelKurulSearchResponse,
|
||||||
|
GenelKurulDecision,
|
||||||
|
|
||||||
|
# Temyiz Kurulu models
|
||||||
|
TemyizKuruluSearchRequest,
|
||||||
|
TemyizKuruluSearchResponse,
|
||||||
|
TemyizKuruluDecision,
|
||||||
|
|
||||||
|
# Daire models
|
||||||
|
DaireSearchRequest,
|
||||||
|
DaireSearchResponse,
|
||||||
|
DaireDecision,
|
||||||
|
|
||||||
|
# Document models
|
||||||
|
SayistayDocumentMarkdown
|
||||||
|
)
|
||||||
|
from .enums import (
|
||||||
|
DaireEnum,
|
||||||
|
KamuIdaresiTuruEnum,
|
||||||
|
WebKararKonusuEnum
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"SayistayApiClient",
|
||||||
|
"GenelKurulSearchRequest",
|
||||||
|
"GenelKurulSearchResponse",
|
||||||
|
"GenelKurulDecision",
|
||||||
|
"TemyizKuruluSearchRequest",
|
||||||
|
"TemyizKuruluSearchResponse",
|
||||||
|
"TemyizKuruluDecision",
|
||||||
|
"DaireSearchRequest",
|
||||||
|
"DaireSearchResponse",
|
||||||
|
"DaireDecision",
|
||||||
|
"SayistayDocumentMarkdown",
|
||||||
|
"DaireEnum",
|
||||||
|
"KamuIdaresiTuruEnum",
|
||||||
|
"WebKararKonusuEnum"
|
||||||
|
]
|
||||||
@@ -0,0 +1,594 @@
|
|||||||
|
# sayistay_mcp_module/client.py
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import re
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
import logging
|
||||||
|
import html
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from urllib.parse import urlencode, urljoin
|
||||||
|
from markitdown import MarkItDown
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
GenelKurulSearchRequest, GenelKurulSearchResponse, GenelKurulDecision,
|
||||||
|
TemyizKuruluSearchRequest, TemyizKuruluSearchResponse, TemyizKuruluDecision,
|
||||||
|
DaireSearchRequest, DaireSearchResponse, DaireDecision,
|
||||||
|
SayistayDocumentMarkdown
|
||||||
|
)
|
||||||
|
from .enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
if not logger.hasHandlers():
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
class SayistayApiClient:
|
||||||
|
"""
|
||||||
|
API Client for Sayıştay (Turkish Court of Accounts) decision search system.
|
||||||
|
|
||||||
|
Handles three types of decisions:
|
||||||
|
- Genel Kurul (General Assembly): Precedent-setting interpretive decisions
|
||||||
|
- Temyiz Kurulu (Appeals Board): Appeals against chamber decisions
|
||||||
|
- Daire (Chamber): First-instance audit findings and sanctions
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- ASP.NET WebForms session management with CSRF tokens
|
||||||
|
- DataTables-based pagination and filtering
|
||||||
|
- Automatic session refresh on expiration
|
||||||
|
- Document retrieval with Markdown conversion
|
||||||
|
"""
|
||||||
|
|
||||||
|
BASE_URL = "https://www.sayistay.gov.tr"
|
||||||
|
|
||||||
|
# Search endpoints for each decision type
|
||||||
|
GENEL_KURUL_ENDPOINT = "/KararlarGenelKurul/DataTablesList"
|
||||||
|
TEMYIZ_KURULU_ENDPOINT = "/KararlarTemyiz/DataTablesList"
|
||||||
|
DAIRE_ENDPOINT = "/KararlarDaire/DataTablesList"
|
||||||
|
|
||||||
|
# Page endpoints for session initialization and document access
|
||||||
|
GENEL_KURUL_PAGE = "/KararlarGenelKurul"
|
||||||
|
TEMYIZ_KURULU_PAGE = "/KararlarTemyiz"
|
||||||
|
DAIRE_PAGE = "/KararlarDaire"
|
||||||
|
|
||||||
|
def __init__(self, request_timeout: float = 60.0):
|
||||||
|
self.request_timeout = request_timeout
|
||||||
|
self.session_cookies: Dict[str, str] = {}
|
||||||
|
self.csrf_tokens: Dict[str, str] = {} # Store tokens for each endpoint
|
||||||
|
|
||||||
|
self.http_client = httpx.AsyncClient(
|
||||||
|
base_url=self.BASE_URL,
|
||||||
|
headers={
|
||||||
|
"Accept": "application/json, text/javascript, */*; q=0.01",
|
||||||
|
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-origin"
|
||||||
|
},
|
||||||
|
timeout=request_timeout,
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _initialize_session_for_endpoint(self, endpoint_type: str) -> bool:
|
||||||
|
"""
|
||||||
|
Initialize session and obtain CSRF token for specific endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint_type: One of 'genel_kurul', 'temyiz_kurulu', 'daire'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if session initialized successfully, False otherwise
|
||||||
|
"""
|
||||||
|
page_mapping = {
|
||||||
|
'genel_kurul': self.GENEL_KURUL_PAGE,
|
||||||
|
'temyiz_kurulu': self.TEMYIZ_KURULU_PAGE,
|
||||||
|
'daire': self.DAIRE_PAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint_type not in page_mapping:
|
||||||
|
logger.error(f"Invalid endpoint type: {endpoint_type}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
page_url = page_mapping[endpoint_type]
|
||||||
|
logger.info(f"Initializing session for {endpoint_type} endpoint: {page_url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.http_client.get(page_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Extract session cookies
|
||||||
|
for cookie_name, cookie_value in response.cookies.items():
|
||||||
|
self.session_cookies[cookie_name] = cookie_value
|
||||||
|
logger.debug(f"Stored session cookie: {cookie_name}")
|
||||||
|
|
||||||
|
# Extract CSRF token from form
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
csrf_input = soup.find('input', {'name': '__RequestVerificationToken'})
|
||||||
|
|
||||||
|
if csrf_input and csrf_input.get('value'):
|
||||||
|
self.csrf_tokens[endpoint_type] = csrf_input['value']
|
||||||
|
logger.info(f"Extracted CSRF token for {endpoint_type}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"CSRF token not found in {endpoint_type} page")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"HTTP error during session initialization for {endpoint_type}: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error initializing session for {endpoint_type}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _enum_to_form_value(self, enum_value: str, enum_type: str) -> str:
|
||||||
|
"""Convert enum values to form values expected by the API."""
|
||||||
|
if enum_value == "ALL":
|
||||||
|
if enum_type == "daire":
|
||||||
|
return "Tüm Daireler"
|
||||||
|
elif enum_type == "kamu_idaresi":
|
||||||
|
return "Tüm Kurumlar"
|
||||||
|
elif enum_type == "web_karar_konusu":
|
||||||
|
return "Tüm Konular"
|
||||||
|
return enum_value
|
||||||
|
|
||||||
|
def _build_datatables_params(self, start: int, length: int, draw: int = 1) -> List[Tuple[str, str]]:
|
||||||
|
"""Build standard DataTables parameters for all endpoints."""
|
||||||
|
params = [
|
||||||
|
("draw", str(draw)),
|
||||||
|
("start", str(start)),
|
||||||
|
("length", str(length)),
|
||||||
|
("search[value]", ""),
|
||||||
|
("search[regex]", "false")
|
||||||
|
]
|
||||||
|
return params
|
||||||
|
|
||||||
|
def _build_genel_kurul_form_data(self, params: GenelKurulSearchRequest, draw: int = 1) -> List[Tuple[str, str]]:
|
||||||
|
"""Build form data for Genel Kurul search request."""
|
||||||
|
form_data = self._build_datatables_params(params.start, params.length, draw)
|
||||||
|
|
||||||
|
# Add DataTables column definitions (from actual request)
|
||||||
|
column_defs = [
|
||||||
|
("columns[0][data]", "KARARNO"),
|
||||||
|
("columns[0][name]", ""),
|
||||||
|
("columns[0][searchable]", "true"),
|
||||||
|
("columns[0][orderable]", "false"),
|
||||||
|
("columns[0][search][value]", ""),
|
||||||
|
("columns[0][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[1][data]", "KARARNO"),
|
||||||
|
("columns[1][name]", ""),
|
||||||
|
("columns[1][searchable]", "true"),
|
||||||
|
("columns[1][orderable]", "true"),
|
||||||
|
("columns[1][search][value]", ""),
|
||||||
|
("columns[1][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[2][data]", "KARARTARIH"),
|
||||||
|
("columns[2][name]", ""),
|
||||||
|
("columns[2][searchable]", "true"),
|
||||||
|
("columns[2][orderable]", "true"),
|
||||||
|
("columns[2][search][value]", ""),
|
||||||
|
("columns[2][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[3][data]", "KARAROZETI"),
|
||||||
|
("columns[3][name]", ""),
|
||||||
|
("columns[3][searchable]", "true"),
|
||||||
|
("columns[3][orderable]", "false"),
|
||||||
|
("columns[3][search][value]", ""),
|
||||||
|
("columns[3][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[4][data]", ""),
|
||||||
|
("columns[4][name]", ""),
|
||||||
|
("columns[4][searchable]", "true"),
|
||||||
|
("columns[4][orderable]", "false"),
|
||||||
|
("columns[4][search][value]", ""),
|
||||||
|
("columns[4][search][regex]", "false"),
|
||||||
|
|
||||||
|
("order[0][column]", "2"),
|
||||||
|
("order[0][dir]", "desc")
|
||||||
|
]
|
||||||
|
form_data.extend(column_defs)
|
||||||
|
|
||||||
|
# Add search parameters
|
||||||
|
form_data.extend([
|
||||||
|
("KararlarGenelKurulAra.KARARNO", params.karar_no or ""),
|
||||||
|
("__Invariant[]", "KararlarGenelKurulAra.KARARNO"),
|
||||||
|
("__Invariant[]", "KararlarGenelKurulAra.KARAREK"),
|
||||||
|
("KararlarGenelKurulAra.KARAREK", params.karar_ek or ""),
|
||||||
|
("KararlarGenelKurulAra.KARARTARIHBaslangic", params.karar_tarih_baslangic or "Başlangıç Tarihi"),
|
||||||
|
("KararlarGenelKurulAra.KARARTARIHBitis", params.karar_tarih_bitis or "Bitiş Tarihi"),
|
||||||
|
("KararlarGenelKurulAra.KARARTAMAMI", params.karar_tamami or ""),
|
||||||
|
("__RequestVerificationToken", self.csrf_tokens.get('genel_kurul', ''))
|
||||||
|
])
|
||||||
|
|
||||||
|
return form_data
|
||||||
|
|
||||||
|
def _build_temyiz_kurulu_form_data(self, params: TemyizKuruluSearchRequest, draw: int = 1) -> List[Tuple[str, str]]:
|
||||||
|
"""Build form data for Temyiz Kurulu search request."""
|
||||||
|
form_data = self._build_datatables_params(params.start, params.length, draw)
|
||||||
|
|
||||||
|
# Add DataTables column definitions (from actual request)
|
||||||
|
column_defs = [
|
||||||
|
("columns[0][data]", "TEMYIZTUTANAKTARIHI"),
|
||||||
|
("columns[0][name]", ""),
|
||||||
|
("columns[0][searchable]", "true"),
|
||||||
|
("columns[0][orderable]", "false"),
|
||||||
|
("columns[0][search][value]", ""),
|
||||||
|
("columns[0][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[1][data]", "TEMYIZTUTANAKTARIHI"),
|
||||||
|
("columns[1][name]", ""),
|
||||||
|
("columns[1][searchable]", "true"),
|
||||||
|
("columns[1][orderable]", "true"),
|
||||||
|
("columns[1][search][value]", ""),
|
||||||
|
("columns[1][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[2][data]", "ILAMDAIRESI"),
|
||||||
|
("columns[2][name]", ""),
|
||||||
|
("columns[2][searchable]", "true"),
|
||||||
|
("columns[2][orderable]", "true"),
|
||||||
|
("columns[2][search][value]", ""),
|
||||||
|
("columns[2][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[3][data]", "TEMYIZKARAR"),
|
||||||
|
("columns[3][name]", ""),
|
||||||
|
("columns[3][searchable]", "true"),
|
||||||
|
("columns[3][orderable]", "false"),
|
||||||
|
("columns[3][search][value]", ""),
|
||||||
|
("columns[3][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[4][data]", ""),
|
||||||
|
("columns[4][name]", ""),
|
||||||
|
("columns[4][searchable]", "true"),
|
||||||
|
("columns[4][orderable]", "false"),
|
||||||
|
("columns[4][search][value]", ""),
|
||||||
|
("columns[4][search][regex]", "false"),
|
||||||
|
|
||||||
|
("order[0][column]", "1"),
|
||||||
|
("order[0][dir]", "desc")
|
||||||
|
]
|
||||||
|
form_data.extend(column_defs)
|
||||||
|
|
||||||
|
# Add search parameters
|
||||||
|
daire_value = self._enum_to_form_value(params.ilam_dairesi, "daire")
|
||||||
|
kamu_idaresi_value = self._enum_to_form_value(params.kamu_idaresi_turu, "kamu_idaresi")
|
||||||
|
web_karar_konusu_value = self._enum_to_form_value(params.web_karar_konusu, "web_karar_konusu")
|
||||||
|
|
||||||
|
form_data.extend([
|
||||||
|
("KararlarTemyizAra.ILAMDAIRESI", daire_value),
|
||||||
|
("KararlarTemyizAra.YILI", params.yili or ""),
|
||||||
|
("KararlarTemyizAra.KARARTRHBaslangic", params.karar_tarih_baslangic or ""),
|
||||||
|
("KararlarTemyizAra.KARARTRHBitis", params.karar_tarih_bitis or ""),
|
||||||
|
("KararlarTemyizAra.KAMUIDARESITURU", kamu_idaresi_value if kamu_idaresi_value != "Tüm Kurumlar" else ""),
|
||||||
|
("KararlarTemyizAra.ILAMNO", params.ilam_no or ""),
|
||||||
|
("KararlarTemyizAra.DOSYANO", params.dosya_no or ""),
|
||||||
|
("KararlarTemyizAra.TEMYIZTUTANAKNO", params.temyiz_tutanak_no or ""),
|
||||||
|
("__Invariant", "KararlarTemyizAra.TEMYIZTUTANAKNO"),
|
||||||
|
("KararlarTemyizAra.TEMYIZKARAR", params.temyiz_karar or ""),
|
||||||
|
("KararlarTemyizAra.WEBKARARKONUSU", web_karar_konusu_value if web_karar_konusu_value != "Tüm Konular" else ""),
|
||||||
|
("__RequestVerificationToken", self.csrf_tokens.get('temyiz_kurulu', ''))
|
||||||
|
])
|
||||||
|
|
||||||
|
return form_data
|
||||||
|
|
||||||
|
def _build_daire_form_data(self, params: DaireSearchRequest, draw: int = 1) -> List[Tuple[str, str]]:
|
||||||
|
"""Build form data for Daire search request."""
|
||||||
|
form_data = self._build_datatables_params(params.start, params.length, draw)
|
||||||
|
|
||||||
|
# Add DataTables column definitions (from actual request)
|
||||||
|
column_defs = [
|
||||||
|
("columns[0][data]", "YARGILAMADAIRESI"),
|
||||||
|
("columns[0][name]", ""),
|
||||||
|
("columns[0][searchable]", "true"),
|
||||||
|
("columns[0][orderable]", "false"),
|
||||||
|
("columns[0][search][value]", ""),
|
||||||
|
("columns[0][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[1][data]", "KARARTRH"),
|
||||||
|
("columns[1][name]", ""),
|
||||||
|
("columns[1][searchable]", "true"),
|
||||||
|
("columns[1][orderable]", "true"),
|
||||||
|
("columns[1][search][value]", ""),
|
||||||
|
("columns[1][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[2][data]", "KARARNO"),
|
||||||
|
("columns[2][name]", ""),
|
||||||
|
("columns[2][searchable]", "true"),
|
||||||
|
("columns[2][orderable]", "true"),
|
||||||
|
("columns[2][search][value]", ""),
|
||||||
|
("columns[2][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[3][data]", "YARGILAMADAIRESI"),
|
||||||
|
("columns[3][name]", ""),
|
||||||
|
("columns[3][searchable]", "true"),
|
||||||
|
("columns[3][orderable]", "true"),
|
||||||
|
("columns[3][search][value]", ""),
|
||||||
|
("columns[3][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[4][data]", "WEBKARARMETNI"),
|
||||||
|
("columns[4][name]", ""),
|
||||||
|
("columns[4][searchable]", "true"),
|
||||||
|
("columns[4][orderable]", "false"),
|
||||||
|
("columns[4][search][value]", ""),
|
||||||
|
("columns[4][search][regex]", "false"),
|
||||||
|
|
||||||
|
("columns[5][data]", ""),
|
||||||
|
("columns[5][name]", ""),
|
||||||
|
("columns[5][searchable]", "true"),
|
||||||
|
("columns[5][orderable]", "false"),
|
||||||
|
("columns[5][search][value]", ""),
|
||||||
|
("columns[5][search][regex]", "false"),
|
||||||
|
|
||||||
|
("order[0][column]", "2"),
|
||||||
|
("order[0][dir]", "desc")
|
||||||
|
]
|
||||||
|
form_data.extend(column_defs)
|
||||||
|
|
||||||
|
# Add search parameters
|
||||||
|
daire_value = self._enum_to_form_value(params.yargilama_dairesi, "daire")
|
||||||
|
kamu_idaresi_value = self._enum_to_form_value(params.kamu_idaresi_turu, "kamu_idaresi")
|
||||||
|
web_karar_konusu_value = self._enum_to_form_value(params.web_karar_konusu, "web_karar_konusu")
|
||||||
|
|
||||||
|
form_data.extend([
|
||||||
|
("KararlarDaireAra.YARGILAMADAIRESI", daire_value),
|
||||||
|
("KararlarDaireAra.KARARTRHBaslangic", params.karar_tarih_baslangic or ""),
|
||||||
|
("KararlarDaireAra.KARARTRHBitis", params.karar_tarih_bitis or ""),
|
||||||
|
("KararlarDaireAra.ILAMNO", params.ilam_no or ""),
|
||||||
|
("KararlarDaireAra.KAMUIDARESITURU", kamu_idaresi_value if kamu_idaresi_value != "Tüm Kurumlar" else ""),
|
||||||
|
("KararlarDaireAra.HESAPYILI", params.hesap_yili or ""),
|
||||||
|
("KararlarDaireAra.WEBKARARKONUSU", web_karar_konusu_value if web_karar_konusu_value != "Tüm Konular" else ""),
|
||||||
|
("KararlarDaireAra.WEBKARARMETNI", params.web_karar_metni or ""),
|
||||||
|
("__RequestVerificationToken", self.csrf_tokens.get('daire', ''))
|
||||||
|
])
|
||||||
|
|
||||||
|
return form_data
|
||||||
|
|
||||||
|
async def search_genel_kurul_decisions(self, params: GenelKurulSearchRequest) -> GenelKurulSearchResponse:
|
||||||
|
"""
|
||||||
|
Search Sayıştay Genel Kurul (General Assembly) decisions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params: Search parameters for Genel Kurul decisions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GenelKurulSearchResponse with matching decisions
|
||||||
|
"""
|
||||||
|
# Initialize session if needed
|
||||||
|
if 'genel_kurul' not in self.csrf_tokens:
|
||||||
|
if not await self._initialize_session_for_endpoint('genel_kurul'):
|
||||||
|
raise Exception("Failed to initialize session for Genel Kurul endpoint")
|
||||||
|
|
||||||
|
form_data = self._build_genel_kurul_form_data(params)
|
||||||
|
encoded_data = urlencode(form_data, encoding='utf-8')
|
||||||
|
|
||||||
|
logger.info(f"Searching Genel Kurul decisions with parameters: {params.model_dump(exclude_none=True)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update headers with cookies
|
||||||
|
headers = self.http_client.headers.copy()
|
||||||
|
if self.session_cookies:
|
||||||
|
cookie_header = "; ".join([f"{k}={v}" for k, v in self.session_cookies.items()])
|
||||||
|
headers["Cookie"] = cookie_header
|
||||||
|
|
||||||
|
response = await self.http_client.post(
|
||||||
|
self.GENEL_KURUL_ENDPOINT,
|
||||||
|
data=encoded_data,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
response_json = response.json()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
decisions = []
|
||||||
|
for item in response_json.get('data', []):
|
||||||
|
decisions.append(GenelKurulDecision(
|
||||||
|
id=item['Id'],
|
||||||
|
karar_no=item['KARARNO'],
|
||||||
|
karar_tarih=item['KARARTARIH'],
|
||||||
|
karar_ozeti=item['KARAROZETI']
|
||||||
|
))
|
||||||
|
|
||||||
|
return GenelKurulSearchResponse(
|
||||||
|
decisions=decisions,
|
||||||
|
total_records=response_json.get('recordsTotal', 0),
|
||||||
|
total_filtered=response_json.get('recordsFiltered', 0),
|
||||||
|
draw=response_json.get('draw', 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"HTTP error during Genel Kurul search: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing Genel Kurul search: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def search_temyiz_kurulu_decisions(self, params: TemyizKuruluSearchRequest) -> TemyizKuruluSearchResponse:
|
||||||
|
"""
|
||||||
|
Search Sayıştay Temyiz Kurulu (Appeals Board) decisions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params: Search parameters for Temyiz Kurulu decisions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TemyizKuruluSearchResponse with matching decisions
|
||||||
|
"""
|
||||||
|
# Initialize session if needed
|
||||||
|
if 'temyiz_kurulu' not in self.csrf_tokens:
|
||||||
|
if not await self._initialize_session_for_endpoint('temyiz_kurulu'):
|
||||||
|
raise Exception("Failed to initialize session for Temyiz Kurulu endpoint")
|
||||||
|
|
||||||
|
form_data = self._build_temyiz_kurulu_form_data(params)
|
||||||
|
encoded_data = urlencode(form_data, encoding='utf-8')
|
||||||
|
|
||||||
|
logger.info(f"Searching Temyiz Kurulu decisions with parameters: {params.model_dump(exclude_none=True)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update headers with cookies
|
||||||
|
headers = self.http_client.headers.copy()
|
||||||
|
if self.session_cookies:
|
||||||
|
cookie_header = "; ".join([f"{k}={v}" for k, v in self.session_cookies.items()])
|
||||||
|
headers["Cookie"] = cookie_header
|
||||||
|
|
||||||
|
response = await self.http_client.post(
|
||||||
|
self.TEMYIZ_KURULU_ENDPOINT,
|
||||||
|
data=encoded_data,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
response_json = response.json()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
decisions = []
|
||||||
|
for item in response_json.get('data', []):
|
||||||
|
decisions.append(TemyizKuruluDecision(
|
||||||
|
id=item['Id'],
|
||||||
|
temyiz_tutanak_tarihi=item['TEMYIZTUTANAKTARIHI'],
|
||||||
|
ilam_dairesi=item['ILAMDAIRESI'],
|
||||||
|
temyiz_karar=item['TEMYIZKARAR']
|
||||||
|
))
|
||||||
|
|
||||||
|
return TemyizKuruluSearchResponse(
|
||||||
|
decisions=decisions,
|
||||||
|
total_records=response_json.get('recordsTotal', 0),
|
||||||
|
total_filtered=response_json.get('recordsFiltered', 0),
|
||||||
|
draw=response_json.get('draw', 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"HTTP error during Temyiz Kurulu search: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing Temyiz Kurulu search: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def search_daire_decisions(self, params: DaireSearchRequest) -> DaireSearchResponse:
|
||||||
|
"""
|
||||||
|
Search Sayıştay Daire (Chamber) decisions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params: Search parameters for Daire decisions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DaireSearchResponse with matching decisions
|
||||||
|
"""
|
||||||
|
# Initialize session if needed
|
||||||
|
if 'daire' not in self.csrf_tokens:
|
||||||
|
if not await self._initialize_session_for_endpoint('daire'):
|
||||||
|
raise Exception("Failed to initialize session for Daire endpoint")
|
||||||
|
|
||||||
|
form_data = self._build_daire_form_data(params)
|
||||||
|
encoded_data = urlencode(form_data, encoding='utf-8')
|
||||||
|
|
||||||
|
logger.info(f"Searching Daire decisions with parameters: {params.model_dump(exclude_none=True)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update headers with cookies
|
||||||
|
headers = self.http_client.headers.copy()
|
||||||
|
if self.session_cookies:
|
||||||
|
cookie_header = "; ".join([f"{k}={v}" for k, v in self.session_cookies.items()])
|
||||||
|
headers["Cookie"] = cookie_header
|
||||||
|
|
||||||
|
response = await self.http_client.post(
|
||||||
|
self.DAIRE_ENDPOINT,
|
||||||
|
data=encoded_data,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
response_json = response.json()
|
||||||
|
|
||||||
|
# Parse response
|
||||||
|
decisions = []
|
||||||
|
for item in response_json.get('data', []):
|
||||||
|
decisions.append(DaireDecision(
|
||||||
|
id=item['Id'],
|
||||||
|
yargilama_dairesi=item['YARGILAMADAIRESI'],
|
||||||
|
karar_tarih=item['KARARTRH'],
|
||||||
|
karar_no=item['KARARNO'],
|
||||||
|
ilam_no=item.get('ILAMNO'), # Use get() to handle None values
|
||||||
|
madde_no=item['MADDENO'],
|
||||||
|
kamu_idaresi_turu=item['KAMUIDARESITURU'],
|
||||||
|
hesap_yili=item['HESAPYILI'],
|
||||||
|
web_karar_konusu=item['WEBKARARKONUSU'],
|
||||||
|
web_karar_metni=item['WEBKARARMETNI']
|
||||||
|
))
|
||||||
|
|
||||||
|
return DaireSearchResponse(
|
||||||
|
decisions=decisions,
|
||||||
|
total_records=response_json.get('recordsTotal', 0),
|
||||||
|
total_filtered=response_json.get('recordsFiltered', 0),
|
||||||
|
draw=response_json.get('draw', 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"HTTP error during Daire search: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing Daire search: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _convert_html_to_markdown(self, html_content: str) -> Optional[str]:
|
||||||
|
"""Convert HTML content to Markdown using MarkItDown."""
|
||||||
|
if not html_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp_file_path = None
|
||||||
|
try:
|
||||||
|
md_converter = MarkItDown()
|
||||||
|
|
||||||
|
# Write HTML to temp file
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".html", encoding="utf-8") as tmp:
|
||||||
|
tmp.write(html_content)
|
||||||
|
temp_file_path = tmp.name
|
||||||
|
|
||||||
|
# Convert
|
||||||
|
result = md_converter.convert(temp_file_path)
|
||||||
|
markdown_content = result.text_content
|
||||||
|
|
||||||
|
logger.info("Successfully converted HTML to Markdown")
|
||||||
|
return markdown_content
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error converting HTML to Markdown: {e}")
|
||||||
|
return f"Error converting HTML content: {str(e)}"
|
||||||
|
finally:
|
||||||
|
if temp_file_path and os.path.exists(temp_file_path):
|
||||||
|
os.remove(temp_file_path)
|
||||||
|
|
||||||
|
async def get_document_as_markdown(self, decision_id: str, decision_type: str) -> SayistayDocumentMarkdown:
|
||||||
|
"""
|
||||||
|
Retrieve full text of a Sayıştay decision and convert to Markdown.
|
||||||
|
|
||||||
|
Note: This is a placeholder implementation. The actual document endpoints
|
||||||
|
would need to be discovered through further analysis of the website.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
decision_id: Unique decision identifier
|
||||||
|
decision_type: Type of decision ('genel_kurul', 'temyiz_kurulu', 'daire')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SayistayDocumentMarkdown with converted content
|
||||||
|
"""
|
||||||
|
logger.info(f"Retrieving document for {decision_type} decision ID: {decision_id}")
|
||||||
|
|
||||||
|
# Document retrieval endpoints would need to be implemented based on
|
||||||
|
# actual website behavior - this would require analyzing the JavaScript
|
||||||
|
# or finding direct document links
|
||||||
|
|
||||||
|
return SayistayDocumentMarkdown(
|
||||||
|
decision_id=decision_id,
|
||||||
|
decision_type=decision_type,
|
||||||
|
source_url=f"{self.BASE_URL}/document/{decision_id}",
|
||||||
|
markdown_content=None,
|
||||||
|
error_message="Document retrieval endpoint not yet implemented. Further analysis of the website's document access mechanism is required."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def close_client_session(self):
|
||||||
|
"""Close HTTP client session."""
|
||||||
|
if hasattr(self, 'http_client') and self.http_client and not self.http_client.is_closed:
|
||||||
|
await self.http_client.aclose()
|
||||||
|
logger.info("SayistayApiClient: HTTP client session closed.")
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# sayistay_mcp_module/enums.py
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
# Chamber/Daire options for Temyiz Kurulu and Daire endpoints (1-8 + All)
|
||||||
|
DaireEnum = Literal[
|
||||||
|
"ALL", # All chambers/departments
|
||||||
|
"1", # 1. Daire
|
||||||
|
"2", # 2. Daire
|
||||||
|
"3", # 3. Daire
|
||||||
|
"4", # 4. Daire
|
||||||
|
"5", # 5. Daire
|
||||||
|
"6", # 6. Daire
|
||||||
|
"7", # 7. Daire
|
||||||
|
"8" # 8. Daire
|
||||||
|
]
|
||||||
|
|
||||||
|
# Public Administration Types (Kamu İdaresi Türü)
|
||||||
|
KamuIdaresiTuruEnum = Literal[
|
||||||
|
"ALL", # All institutions
|
||||||
|
"Genel Bütçe Kapsamındaki İdareler", # General Budget Administrations
|
||||||
|
"Yüksek Öğretim Kurumları", # Higher Education Institutions
|
||||||
|
"Diğer Özel Bütçeli İdareler", # Other Special Budget Administrations
|
||||||
|
"Düzenleyici ve Denetleyici Kurumlar", # Regulatory and Supervisory Institutions
|
||||||
|
"Sosyal Güvenlik Kurumları", # Social Security Institutions
|
||||||
|
"Özel İdareler", # Special Administrations
|
||||||
|
"Belediyeler ve Bağlı İdareler", # Municipalities and Affiliated Administrations
|
||||||
|
"Diğer" # Other
|
||||||
|
]
|
||||||
|
|
||||||
|
# Decision Subject Categories (Web Karar Konusu)
|
||||||
|
WebKararKonusuEnum = Literal[
|
||||||
|
"ALL", # All subjects
|
||||||
|
"Harcırah Mevzuatı ile İlgili Kararlar", # Travel Allowance Legislation Related Decisions
|
||||||
|
"İhale Mevzuatı ile İlgili Kararlar", # Procurement Legislation Related Decisions
|
||||||
|
"İş Mevzuatı ile İlgili Kararlar", # Labor Legislation Related Decisions
|
||||||
|
"Personel Mevzuatı ile İlgili Kararlar", # Personnel Legislation Related Decisions
|
||||||
|
"Sorumluluk ve Yargılama Usulleri ile İlgili Kararlar", # Liability and Trial Procedures Related Decisions
|
||||||
|
"Vergi Resmi Harç ve Diğer Gelirlerle İlgili Kararlar", # Tax, Official Fee and Other Revenue Related Decisions
|
||||||
|
"Çeşitli Konuları İlgilendiren Kararlar" # Decisions Concerning Various Topics
|
||||||
|
]
|
||||||
|
|
||||||
|
# Year ranges for different endpoints
|
||||||
|
GENEL_KURUL_YEARS = [str(year) for year in range(2006, 2025)] # 2006-2024
|
||||||
|
TEMYIZ_KURULU_YEARS = [str(year) for year in range(1993, 2023)] # 1993-2022
|
||||||
|
DAIRE_YEARS = [str(year) for year in range(2012, 2026)] # 2012-2025
|
||||||
|
|
||||||
|
# Account years for Temyiz Kurulu and Daire endpoints
|
||||||
|
HESAP_YILLARI = [str(year) for year in range(1993, 2024)] # 1993-2023
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
# sayistay_mcp_module/models.py
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List, Union
|
||||||
|
from .enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Genel Kurul (General Assembly) Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class GenelKurulSearchRequest(BaseModel):
|
||||||
|
"""
|
||||||
|
Search request for Sayıştay Genel Kurul (General Assembly) decisions.
|
||||||
|
|
||||||
|
Genel Kurul decisions are precedent-setting rulings made by the full assembly
|
||||||
|
of the Turkish Court of Accounts, typically addressing interpretation of
|
||||||
|
audit and accountability regulations.
|
||||||
|
"""
|
||||||
|
karar_no: Optional[str] = Field(None, description="Decision number (e.g., '5415')")
|
||||||
|
karar_ek: Optional[str] = Field(None, description="Decision appendix number (max 99)")
|
||||||
|
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2006-2024. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2006-2024. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tamami: Optional[str] = Field(None, description="""
|
||||||
|
Content/text search within decision summaries (max 400 characters).
|
||||||
|
Searches in decision abstracts and main content.
|
||||||
|
Example: 'belediye taşınmaz tahsis'
|
||||||
|
""")
|
||||||
|
|
||||||
|
# DataTables pagination
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)")
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
|
||||||
|
class GenelKurulDecision(BaseModel):
|
||||||
|
"""Single Genel Kurul decision entry from search results."""
|
||||||
|
id: int = Field(..., description="Unique decision ID")
|
||||||
|
karar_no: str = Field(..., description="Decision number (e.g., '5415/1')")
|
||||||
|
karar_tarih: str = Field(..., description="Decision date in DD.MM.YYYY format")
|
||||||
|
karar_ozeti: str = Field(..., description="Decision summary/abstract")
|
||||||
|
|
||||||
|
class GenelKurulSearchResponse(BaseModel):
|
||||||
|
"""Response from Genel Kurul search endpoint."""
|
||||||
|
decisions: List[GenelKurulDecision] = Field(default_factory=list, description="List of matching decisions")
|
||||||
|
total_records: int = Field(0, description="Total number of matching records")
|
||||||
|
total_filtered: int = Field(0, description="Number of records after filtering")
|
||||||
|
draw: int = Field(1, description="DataTables draw counter")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Temyiz Kurulu (Appeals Board) Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class TemyizKuruluSearchRequest(BaseModel):
|
||||||
|
"""
|
||||||
|
Search request for Sayıştay Temyiz Kurulu (Appeals Board) decisions.
|
||||||
|
|
||||||
|
Temyiz Kurulu reviews appeals against audit chamber decisions,
|
||||||
|
providing higher-level review of audit findings and sanctions.
|
||||||
|
"""
|
||||||
|
ilam_dairesi: DaireEnum = Field("ALL", description="""
|
||||||
|
Chamber/Department filter for appeals board decisions.
|
||||||
|
• ALL: All chambers (default)
|
||||||
|
• 1-8: Specific chamber number (1. Daire through 8. Daire)
|
||||||
|
Each chamber specializes in different types of public institutions.
|
||||||
|
""")
|
||||||
|
|
||||||
|
yili: Optional[str] = Field(None, description="""
|
||||||
|
Account year filter (Hesap Yılı).
|
||||||
|
Available years: 1993-2022. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Refers to the fiscal year being audited, not decision date.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2000, 2006-2024. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2000, 2006-2024. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="""
|
||||||
|
Public administration type filter:
|
||||||
|
• ALL: All institutions (default)
|
||||||
|
• Genel Bütçe Kapsamındaki İdareler: General budget administrations
|
||||||
|
• Yüksek Öğretim Kurumları: Higher education institutions
|
||||||
|
• Belediyeler ve Bağlı İdareler: Municipalities and affiliates
|
||||||
|
• Other specific institution types
|
||||||
|
""")
|
||||||
|
|
||||||
|
ilam_no: Optional[str] = Field(None, description="Audit report number (İlam No, max 50 chars)")
|
||||||
|
dosya_no: Optional[str] = Field(None, description="File number for the case")
|
||||||
|
temyiz_tutanak_no: Optional[str] = Field(None, description="Appeals board meeting minutes number")
|
||||||
|
|
||||||
|
temyiz_karar: Optional[str] = Field(None, description="""
|
||||||
|
Content search within appeals decisions.
|
||||||
|
Searches decision text and reasoning.
|
||||||
|
Example: 'araç kiralama kasko'
|
||||||
|
""")
|
||||||
|
|
||||||
|
web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="""
|
||||||
|
Decision subject category filter:
|
||||||
|
• ALL: All subjects (default)
|
||||||
|
• İhale Mevzuatı ile İlgili Kararlar: Procurement legislation
|
||||||
|
• Personel Mevzuatı ile İlgili Kararlar: Personnel legislation
|
||||||
|
• Harcırah Mevzuatı ile İlgili Kararlar: Travel allowance legislation
|
||||||
|
• Other specialized legal areas
|
||||||
|
""")
|
||||||
|
|
||||||
|
# DataTables pagination
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)")
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
|
||||||
|
class TemyizKuruluDecision(BaseModel):
|
||||||
|
"""Single Temyiz Kurulu decision entry from search results."""
|
||||||
|
id: int = Field(..., description="Unique decision ID")
|
||||||
|
temyiz_tutanak_tarihi: str = Field(..., description="Appeals board meeting date in DD.MM.YYYY format")
|
||||||
|
ilam_dairesi: int = Field(..., description="Chamber number (1-8)")
|
||||||
|
temyiz_karar: str = Field(..., description="Appeals decision summary and reasoning")
|
||||||
|
|
||||||
|
class TemyizKuruluSearchResponse(BaseModel):
|
||||||
|
"""Response from Temyiz Kurulu search endpoint."""
|
||||||
|
decisions: List[TemyizKuruluDecision] = Field(default_factory=list, description="List of matching appeals decisions")
|
||||||
|
total_records: int = Field(0, description="Total number of matching records")
|
||||||
|
total_filtered: int = Field(0, description="Number of records after filtering")
|
||||||
|
draw: int = Field(1, description="DataTables draw counter")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Daire (Chamber) Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class DaireSearchRequest(BaseModel):
|
||||||
|
"""
|
||||||
|
Search request for Sayıştay Daire (Chamber) decisions.
|
||||||
|
|
||||||
|
Daire decisions are first-instance audit findings and sanctions
|
||||||
|
issued by individual audit chambers before potential appeals.
|
||||||
|
"""
|
||||||
|
yargilama_dairesi: DaireEnum = Field("ALL", description="""
|
||||||
|
Audit chamber filter:
|
||||||
|
• ALL: All chambers (default)
|
||||||
|
• 1-8: Specific chamber number (1. Daire through 8. Daire)
|
||||||
|
Each chamber audits different types of public institutions.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tarih_baslangic: Optional[str] = Field(None, description="""
|
||||||
|
Decision start year for date range filtering.
|
||||||
|
Available years: 2012-2025. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Use with karar_tarih_bitis for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
karar_tarih_bitis: Optional[str] = Field(None, description="""
|
||||||
|
Decision end year for date range filtering.
|
||||||
|
Available years: 2012-2025. Format: 'YYYY' (e.g., '2024')
|
||||||
|
Use with karar_tarih_baslangic for date range filtering.
|
||||||
|
""")
|
||||||
|
|
||||||
|
ilam_no: Optional[str] = Field(None, description="Audit report number (İlam No, max 50 chars)")
|
||||||
|
|
||||||
|
kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="""
|
||||||
|
Public administration type filter:
|
||||||
|
• ALL: All institutions (default)
|
||||||
|
• Genel Bütçe Kapsamındaki İdareler: General budget administrations
|
||||||
|
• Yüksek Öğretim Kurumları: Higher education institutions
|
||||||
|
• Belediyeler ve Bağlı İdareler: Municipalities and affiliates
|
||||||
|
• Other specific institution types
|
||||||
|
""")
|
||||||
|
|
||||||
|
hesap_yili: Optional[str] = Field(None, description="""
|
||||||
|
Account year filter (Hesap Yılı).
|
||||||
|
Available years: 2005, 2008-2023. Format: 'YYYY' (e.g., '2020')
|
||||||
|
Refers to the fiscal year being audited, not decision date.
|
||||||
|
""")
|
||||||
|
|
||||||
|
web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="""
|
||||||
|
Decision subject category filter:
|
||||||
|
• ALL: All subjects (default)
|
||||||
|
• İhale Mevzuatı ile İlgili Kararlar: Procurement legislation
|
||||||
|
• Personel Mevzuatı ile İlgili Kararlar: Personnel legislation
|
||||||
|
• Vergi Resmi Harç ve Diğer Gelirlerle İlgili Kararlar: Tax and fee legislation
|
||||||
|
• Other specialized legal areas
|
||||||
|
""")
|
||||||
|
|
||||||
|
web_karar_metni: Optional[str] = Field(None, description="""
|
||||||
|
Content search within chamber decisions.
|
||||||
|
Searches decision text and audit findings.
|
||||||
|
Example: 'birim fiyat revize edilmemesi'
|
||||||
|
""")
|
||||||
|
|
||||||
|
# DataTables pagination
|
||||||
|
start: int = Field(0, description="Starting record for pagination (0-based)")
|
||||||
|
length: int = Field(10, description="Number of records per page (1-100)")
|
||||||
|
|
||||||
|
class DaireDecision(BaseModel):
|
||||||
|
"""Single Daire decision entry from search results."""
|
||||||
|
id: int = Field(..., description="Unique decision ID")
|
||||||
|
yargilama_dairesi: int = Field(..., description="Chamber number (1-8)")
|
||||||
|
karar_tarih: str = Field(..., description="Decision date in DD.MM.YYYY format")
|
||||||
|
karar_no: str = Field(..., description="Decision number")
|
||||||
|
ilam_no: Optional[str] = Field(None, description="Audit report number (may be null)")
|
||||||
|
madde_no: int = Field(..., description="Article/item number within the decision")
|
||||||
|
kamu_idaresi_turu: str = Field(..., description="Public administration type")
|
||||||
|
hesap_yili: int = Field(..., description="Account year being audited")
|
||||||
|
web_karar_konusu: str = Field(..., description="Decision subject category")
|
||||||
|
web_karar_metni: str = Field(..., description="Decision text/summary")
|
||||||
|
|
||||||
|
class DaireSearchResponse(BaseModel):
|
||||||
|
"""Response from Daire search endpoint."""
|
||||||
|
decisions: List[DaireDecision] = Field(default_factory=list, description="List of matching chamber decisions")
|
||||||
|
total_records: int = Field(0, description="Total number of matching records")
|
||||||
|
total_filtered: int = Field(0, description="Number of records after filtering")
|
||||||
|
draw: int = Field(1, description="DataTables draw counter")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Document Models
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class SayistayDocumentMarkdown(BaseModel):
|
||||||
|
"""
|
||||||
|
Sayıştay decision document converted to Markdown format.
|
||||||
|
|
||||||
|
Used for retrieving full text of decisions from any of the three
|
||||||
|
decision types (Genel Kurul, Temyiz Kurulu, Daire).
|
||||||
|
"""
|
||||||
|
decision_id: str = Field(..., description="Unique decision identifier")
|
||||||
|
decision_type: str = Field(..., description="Type of decision: 'genel_kurul', 'temyiz_kurulu', or 'daire'")
|
||||||
|
source_url: str = Field(..., description="Original URL where the document was retrieved")
|
||||||
|
markdown_content: Optional[str] = Field(None, description="Full decision text converted to Markdown format")
|
||||||
|
retrieval_date: Optional[str] = Field(None, description="Date when document was retrieved (ISO format)")
|
||||||
|
error_message: Optional[str] = Field(None, description="Error message if document retrieval failed")
|
||||||
Reference in New Issue
Block a user