import unicodedata from typing import Any from data.database import get_connection, initialize_database def normalize_text(value: str) -> str: """ Türkçe karakter ve büyük-küçük harf farklılıklarını azaltarak arama işlemlerini daha dayanıklı hâle getirir. Örnek: "KÖRLÜK" -> "korluk" "Suç ve Ceza" -> "suc ve ceza" """ value = value.strip().casefold() replacements = { "ı": "i", "ğ": "g", "ü": "u", "ş": "s", "ö": "o", "ç": "c", } for source, target in replacements.items(): value = value.replace(source, target) return "".join( character for character in unicodedata.normalize("NFKD", value) if not unicodedata.combining(character) ) def serialize_book(book: Any) -> dict[str, Any]: """SQLite kitap satırını JSON uyumlu sözlüğe dönüştürür.""" return { "book_id": book["id"], "title": book["title"], "author": book["author"], "category": book["category"], "price": round(float(book["price"]), 2), "stock": int(book["stock"]), "available": int(book["stock"]) > 0, } def search_books( query: str | None = None, author: str | None = None, category: str | None = None, in_stock_only: bool = False, limit: int = 10, ) -> dict[str, Any]: """ Kitapları başlık, yazar veya kategoriye göre arar. Model, kitap bilgilerini kendisi üretmek yerine bu fonksiyondan dönen gerçek verileri kullanmalıdır. """ initialize_database() if limit < 1: return { "success": False, "error": "limit değeri en az 1 olmalıdır.", } limit = min(limit, 20) normalized_query = normalize_text(query) if query else None normalized_author = normalize_text(author) if author else None normalized_category = normalize_text(category) if category else None with get_connection() as connection: rows = connection.execute( """ SELECT id, title, author, category, price, stock FROM books ORDER BY title """ ).fetchall() matched_books: list[dict[str, Any]] = [] for row in rows: normalized_title_value = normalize_text(row["title"]) normalized_author_value = normalize_text(row["author"]) normalized_category_value = normalize_text(row["category"]) if normalized_query: query_matches = ( normalized_query in normalized_title_value or normalized_query in normalized_author_value or normalized_query in normalized_category_value ) if not query_matches: continue if ( normalized_author and normalized_author not in normalized_author_value ): continue if ( normalized_category and normalized_category not in normalized_category_value ): continue if in_stock_only and row["stock"] <= 0: continue matched_books.append(serialize_book(row)) if len(matched_books) >= limit: break return { "success": True, "count": len(matched_books), "filters": { "query": query, "author": author, "category": category, "in_stock_only": in_stock_only, "limit": limit, }, "books": matched_books, "message": ( f"{len(matched_books)} kitap bulundu." if matched_books else "Arama kriterlerine uygun kitap bulunamadı." ), } def create_order( book_id: int, quantity: int, customer_name: str, ) -> dict[str, Any]: """ Sipariş oluşturur ve kitap stoğunu düşürür. Sipariş kaydı ile stok güncellemesi aynı transaction içinde yapılır. Böylece işlemlerden biri başarısız olursa veritabanı yarım kalmaz. """ initialize_database() customer_name = customer_name.strip() if not customer_name: return { "success": False, "error": "Müşteri adı boş bırakılamaz.", } if not isinstance(book_id, int) or isinstance(book_id, bool): return { "success": False, "error": "book_id tam sayı olmalıdır.", } if not isinstance(quantity, int) or isinstance(quantity, bool): return { "success": False, "error": "quantity tam sayı olmalıdır.", } if quantity < 1: return { "success": False, "error": "Sipariş miktarı en az 1 olmalıdır.", } connection = get_connection() try: # Aynı anda gelen iki siparişin aynı stoğu kullanmasını önlemek # için yazma kilidiyle transaction başlatılır. connection.execute("BEGIN IMMEDIATE") book = connection.execute( """ SELECT id, title, author, category, price, stock FROM books WHERE id = ? """, (book_id,), ).fetchone() if book is None: connection.rollback() return { "success": False, "error": "Belirtilen kimliğe sahip kitap bulunamadı.", "book_id": book_id, } current_stock = int(book["stock"]) if current_stock < quantity: connection.rollback() return { "success": False, "error": "Yeterli stok bulunmuyor.", "book_id": book_id, "title": book["title"], "requested_quantity": quantity, "available_stock": current_stock, } unit_price = round(float(book["price"]), 2) total_price = round(unit_price * quantity, 2) new_stock = current_stock - quantity cursor = connection.execute( """ INSERT INTO orders ( customer_name, book_id, quantity, unit_price, total_price, status ) VALUES (?, ?, ?, ?, ?, ?) """, ( customer_name, book_id, quantity, unit_price, total_price, "Hazırlanıyor", ), ) order_id = cursor.lastrowid connection.execute( """ UPDATE books SET stock = ? WHERE id = ? """, ( new_stock, book_id, ), ) connection.commit() return { "success": True, "message": "Sipariş başarıyla oluşturuldu.", "order": { "order_id": order_id, "customer_name": customer_name, "book_id": book_id, "title": book["title"], "author": book["author"], "quantity": quantity, "unit_price": unit_price, "total_price": total_price, "status": "Hazırlanıyor", }, "stock_update": { "previous_stock": current_stock, "new_stock": new_stock, }, } except Exception as error: connection.rollback() return { "success": False, "error": "Sipariş oluşturulurken veritabanı hatası oluştu.", "detail": str(error), } finally: connection.close() def get_order_status(order_id: int) -> dict[str, Any]: """Sipariş numarasına göre sipariş bilgilerini getirir.""" initialize_database() if not isinstance(order_id, int) or isinstance(order_id, bool): return { "success": False, "error": "order_id tam sayı olmalıdır.", } with get_connection() as connection: order = connection.execute( """ SELECT orders.id AS order_id, orders.customer_name, orders.quantity, orders.unit_price, orders.total_price, orders.status, orders.created_at, books.id AS book_id, books.title, books.author FROM orders INNER JOIN books ON books.id = orders.book_id WHERE orders.id = ? """, (order_id,), ).fetchone() if order is None: return { "success": False, "error": "Sipariş bulunamadı.", "order_id": order_id, } return { "success": True, "order": { "order_id": order["order_id"], "customer_name": order["customer_name"], "book_id": order["book_id"], "title": order["title"], "author": order["author"], "quantity": order["quantity"], "unit_price": round(float(order["unit_price"]), 2), "total_price": round(float(order["total_price"]), 2), "status": order["status"], "created_at": order["created_at"], }, } TOOL_FUNCTIONS = { "search_books": search_books, "create_order": create_order, "get_order_status": get_order_status, } def execute_tool( tool_name: str, arguments: dict[str, Any], ) -> dict[str, Any]: """ Model tarafından seçilen tool adını ilgili Python fonksiyonuna yönlendirir. """ tool_function = TOOL_FUNCTIONS.get(tool_name) if tool_function is None: return { "success": False, "error": f"Desteklenmeyen tool: {tool_name}", } try: return tool_function(**arguments) except TypeError as error: return { "success": False, "error": "Tool parametreleri geçersiz veya eksik.", "detail": str(error), } except Exception as error: return { "success": False, "error": "Tool çalıştırılırken beklenmeyen hata oluştu.", "detail": str(error), }