{
  "openapi": "3.1.0",
  "info": {
    "title": "LoreKeeper Service API",
    "version": "1.0.0",
    "description": "Produktionsreifer Agentic RAG Backend-Dienst für Buch- und Universums-Konsistenz.\nOrchestriert Fastify, Directus Headless CMS und LiteLLM (Google Gemini).\n"
  },
  "servers": [
    {
      "url": "http://localhost:3005",
      "description": "Lokaler Fastify AI Server (Development)"
    },
    {
      "url": "http://lorekeeper-api:3005",
      "description": "Docker Compose internes Netzwerk"
    },
    {
      "url": "https://laefnabeiafnaleiafafe.2.28.78.110.sslip.io",
      "description": "Produktions Directus Instanz"
    }
  ],
  "paths": {
    "/api/books/ingest": {
      "post": {
        "tags": [
          "Books"
        ],
        "summary": "Buch einlesen & Ingestion starten",
        "description": "Nimmt ein E-Book (EPUB 2 oder EPUB 3) als Base64-Buffer oder Multipart-Upload entgegen und startet die vollautomatisierte 2-Phasen Ingestion:\n1. **Phase 1 Text-Analyse:** Kapitel-Parsing, linguistische Bereinigung, semantische Figuren- und Welten-Extraktion.\n2. **Phase 2 Bild-Verarbeitung:** Unüberwachte Bildsegmentierung mit Gemini 3 Flash und Bounding-Box Refinement.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/books/ingest \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bufferBase64\": \"UEsDBBQAAAAIA...\",\n    \"filename\": \"mein-buch.epub\",\n    \"options\": {\n      \"persistToDirectus\": true,\n      \"language\": \"de\",\n      \"targetWordsPerScene\": 350,\n      \"filterPromos\": true,\n      \"includeImages\": true\n    }\n  }'\n```\n\n#### Python Beispiel:\n```python\nimport requests, base64\n\nwith open(\"buch.epub\", \"rb\") as f:\n    b64 = base64.b64encode(f.read()).decode(\"utf-8\")\n\nres = requests.post(\"http://localhost:3005/api/books/ingest\", json={\n    \"bufferBase64\": b64,\n    \"filename\": \"buch.epub\",\n    \"options\": {\"persistToDirectus\": True, \"language\": \"de\"}\n})\nprint(res.json())\n```",
        "operationId": "ingestBook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bufferBase64"
                ],
                "properties": {
                  "bufferBase64": {
                    "type": "string",
                    "description": "Base64-kodierter Inhalt der EPUB-Datei"
                  },
                  "filename": {
                    "type": "string",
                    "example": "abenteuer-im-zauberwald.epub"
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "persistToDirectus": {
                        "type": "boolean",
                        "default": true,
                        "description": "Datensätze direkt in Directus anlegen"
                      },
                      "authorId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "UUID des Autors in Directus"
                      },
                      "seriesId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "UUID der Buchserie"
                      },
                      "language": {
                        "type": "string",
                        "example": "de",
                        "description": "ISO 639-1 Sprachcode"
                      },
                      "targetWordsPerScene": {
                        "type": "integer",
                        "default": 350,
                        "description": "Zielgröße einer Szene in Wörtern"
                      },
                      "filterPromos": {
                        "type": "boolean",
                        "default": true,
                        "description": "Verlags-Werbung & Impressum ausfiltern"
                      },
                      "includeImages": {
                        "type": "boolean",
                        "default": true,
                        "description": "Illustrationen extrahieren und verarbeiten"
                      }
                    }
                  }
                }
              },
              "example": {
                "bufferBase64": "UEsDBBQAAAAIAKBmV1YAAAAAA...",
                "filename": "die-chroniken-von-eldoria.epub",
                "options": {
                  "persistToDirectus": true,
                  "language": "de",
                  "targetWordsPerScene": 350,
                  "filterPromos": true,
                  "includeImages": true
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "EPUB-Datei (bis 100 MB)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ingestion erfolgreich abgeschlossen",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "bookId": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                  "title": "Die Chroniken von Eldoria",
                  "language": "de",
                  "chaptersCount": 12,
                  "scenesCount": 38,
                  "entitiesCount": 14,
                  "imagesCount": 26,
                  "durationMs": 4210
                }
              }
            }
          },
          "400": {
            "description": "Ungültige Parameter oder fehlerhaftes EPUB-Archiv",
            "content": {
              "application/json": {
                "example": {
                  "error": "Validation failed",
                  "details": {
                    "bufferBase64": {
                      "_errors": [
                        "bufferBase64 is required"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/books/ingest/upload": {
      "post": {
        "tags": [
          "Books"
        ],
        "summary": "Direkter Multipart-Upload für große E-Books",
        "description": "Nimmt eine EPUB-Datei als binären `multipart/form-data` Stream bis zu **100 MB** entgegen.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/books/ingest/upload \\\n  -F \"file=@/pfad/zu/buch.epub\" \\\n  -F \"persistToDirectus=true\" \\\n  -F \"language=de\"\n```",
        "operationId": "uploadBookFile",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "EPUB-Datei"
                  },
                  "persistToDirectus": {
                    "type": "boolean",
                    "default": true
                  },
                  "language": {
                    "type": "string",
                    "default": "de"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload und Verarbeitung erfolgreich",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "bookId": "c2b3a4d5-e6f7-8a9b-0c1d-2e3f4a5b6c7d",
                  "title": "Das Geheimnis der alten Burg",
                  "scenesCount": 24,
                  "imagesCount": 18
                }
              }
            }
          }
        }
      }
    },
    "/api/books/{id}/status": {
      "get": {
        "tags": [
          "Books"
        ],
        "summary": "Ingestion-Status eines Buches abfragen",
        "description": "Gibt den aktuellen Fortschritt, den Status der Text- und Bildphasen sowie Fehlerprotokolle zurück.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET http://localhost:3005/api/books/b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d/status\n```",
        "operationId": "getBookStatus",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
            "description": "UUID des Buchs in Directus"
          }
        ],
        "responses": {
          "200": {
            "description": "Statusdetails des Ingestion-Vorgangs",
            "content": {
              "application/json": {
                "example": {
                  "bookId": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                  "status": "completed",
                  "progress": 100,
                  "phases": {
                    "textExtraction": "done",
                    "visualClustering": "done",
                    "bipartiteMatching": "done"
                  },
                  "stats": {
                    "totalChapters": 12,
                    "totalScenes": 38,
                    "totalEntities": 14,
                    "totalCrops": 45
                  }
                }
              }
            }
          },
          "404": {
            "description": "Buch mit dieser ID existiert nicht",
            "content": {
              "application/json": {
                "example": {
                  "error": "Book 'b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d' not found"
                }
              }
            }
          }
        }
      }
    },
    "/api/books/{id}/export": {
      "get": {
        "tags": [
          "Books"
        ],
        "summary": "Buch als EPUB 3 Archiv exportieren",
        "description": "Generiert ein standardkonformes EPUB-Archiv inklusive aller generierten Szenen, Metadaten, CSS-Formatierungen und Illustrationen.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET http://localhost:3005/api/books/b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d/export \\\n  --output exportiertes-buch.epub\n```",
        "operationId": "exportBookEpub",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "200": {
            "description": "Binärer EPUB 3 Dateidownload",
            "content": {
              "application/epub+zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/entities/extract-visual": {
      "post": {
        "tags": [
          "Entities"
        ],
        "summary": "Unüberwachte Bildsegmentierung durchführen",
        "description": "Scannt eine Buch-Illustration mit **Gemini 3 Flash**, erkennt Figuren morphologisch korrekt, erzeugt präzise Bounding-Box-Koordinaten ($0 \\dots 1000$ Promille) und schneidet hochauflösende WebP-Avatare zu.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/entities/extract-visual \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"imageFileId\": \"6761713f-29da-4962-8369-bea14af16d7c\",\n    \"candidates\": [\n      {\"name\": \"Matilda\", \"type\": \"character\"},\n      {\"name\": \"Kokosnuss\", \"type\": \"character\"}\n    ],\n    \"options\": {\n      \"persistToDirectus\": true\n    }\n  }'\n```",
        "operationId": "extractVisualEntities",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "imageFileId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Directus File UUID des Bildes"
                  },
                  "image": {
                    "type": "string",
                    "description": "Alternativ: Base64-kodiertes Bild"
                  },
                  "candidates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "character",
                            "object",
                            "location"
                          ]
                        }
                      }
                    }
                  },
                  "bookId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "sceneId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "persistToDirectus": {
                        "type": "boolean",
                        "default": true
                      }
                    }
                  }
                }
              },
              "example": {
                "imageFileId": "6761713f-29da-4962-8369-bea14af16d7c",
                "candidates": [
                  {
                    "name": "Matilda",
                    "type": "character"
                  },
                  {
                    "name": "Kokosnuss",
                    "type": "character"
                  }
                ],
                "options": {
                  "persistToDirectus": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Erfolgreich extrahierte visuelle Entitäten mit Koordinaten und Crop-File-IDs",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "detectionsCount": 2,
                  "detections": [
                    {
                      "name": "Kokosnuss",
                      "type": "character",
                      "box": [
                        280,
                        120,
                        750,
                        480
                      ],
                      "confidence": 0.96,
                      "cropFileId": "d3e4f5a6-b7c8-9d0e-1f2a-3b4c5d6e7f8a",
                      "visualTraits": {
                        "colors": [
                          "#10b981",
                          "#ef4444"
                        ],
                        "posture": "standing",
                        "expression": "cheerful"
                      }
                    },
                    {
                      "name": "Matilda",
                      "type": "character",
                      "box": [
                        410,
                        490,
                        890,
                        780
                      ],
                      "confidence": 0.94,
                      "cropFileId": "e4f5a6b7-c8d9-0e1f-2a3b-4c5d6e7f8a9b",
                      "visualTraits": {
                        "colors": [
                          "#78350f"
                        ],
                        "posture": "reading",
                        "expression": "attentive"
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/entities": {
      "get": {
        "tags": [
          "Entities"
        ],
        "summary": "Alle Lore-Entitäten abfragen",
        "description": "Liefert alle Figuren, Gegenstände und Schauplätze aus Directus mit flexiblen Filtern nach Typ, Rolle/Wichtigkeit und Buch.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET \"http://localhost:3005/api/entities?type=character&tier=main\"\n```",
        "operationId": "getLoreEntities",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "character",
                "object",
                "location"
              ]
            },
            "example": "character"
          },
          {
            "name": "tier",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "main",
                "supporting",
                "extra"
              ]
            },
            "example": "main"
          },
          {
            "name": "bookId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array aller passenden Lore-Entitäten",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": "33333333-4444-5555-6666-777777777777",
                    "name": "Kokosnuss",
                    "type": "character",
                    "avatar_file_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                    "static_traits": {
                      "importance_tier": "main",
                      "frequency_tier": "frequent",
                      "species": "Kleiner Drache",
                      "exact_colors": [
                        "#10b981",
                        "#ef4444"
                      ],
                      "negative_rules": [
                        "Kein Erwachsener",
                        "Keine Zähne im Mund"
                      ],
                      "relative_scale": {
                        "anchor_entity": "Matilda",
                        "height_ratio": 1.25
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/entities/{id}": {
      "get": {
        "tags": [
          "Entities"
        ],
        "summary": "Vollständigen Steckbrief einer Entität abrufen",
        "description": "Ruft alle statischen Kanon-Eigenschaften, Farbpaletten, Regeln, Beziehungsanker und Vorkommnisse einer Figur ab.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777\n```",
        "operationId": "getEntityById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "responses": {
          "200": {
            "description": "Vollständiges Entitäts-Profil",
            "content": {
              "application/json": {
                "example": {
                  "id": "33333333-4444-5555-6666-777777777777",
                  "name": "Kokosnuss",
                  "type": "character",
                  "description": "Der kleine feuerrote und grüne Drache von der Dracheninsel.",
                  "avatar_file_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                  "static_traits": {
                    "importance_tier": "main",
                    "species": "Drache",
                    "exact_colors": [
                      "#10b981",
                      "#ef4444"
                    ],
                    "physical_features": "Grüner Schuppenkörper, rote Kappe, kleine Flügel",
                    "negative_rules": [
                      "Kein Raubdrache",
                      "Kann anfangs nur schwer fliegen"
                    ],
                    "scene_occurrences": [
                      1,
                      2,
                      4,
                      5,
                      8,
                      9,
                      12
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "Entität nicht gefunden"
          }
        }
      },
      "patch": {
        "tags": [
          "Entities"
        ],
        "summary": "Entität / Profil aktualisieren",
        "description": "Aktualisiert Eigenschaften, Rollen und Merkmale (`static_traits`) eines Entitätsprofils.\n    \n#### cURL Beispiel:\n```bash\ncurl -X PATCH http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777 \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Hauptfigur\",\n    \"role_tier\": \"main\",\n    \"species\": \"Mensch\",\n    \"age_category\": \"child\",\n    \"gender\": \"female\",\n    \"description\": \"Mutige junge Abenteurerin mit gelbem Rucksack.\"\n  }'\n```",
        "operationId": "updateEntity",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Hauptfigur"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "character",
                      "location",
                      "item",
                      "group",
                      "concept"
                    ],
                    "example": "character"
                  },
                  "role_tier": {
                    "type": "string",
                    "enum": [
                      "main",
                      "supporting",
                      "extra"
                    ],
                    "example": "main"
                  },
                  "importance_tier": {
                    "type": "string",
                    "example": "frequent"
                  },
                  "species": {
                    "type": "string",
                    "example": "Mensch"
                  },
                  "age_category": {
                    "type": "string",
                    "example": "child"
                  },
                  "gender": {
                    "type": "string",
                    "example": "female"
                  },
                  "special_features": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "Flügel",
                      "Hörner",
                      "Brille"
                    ]
                  },
                  "has_wings": {
                    "type": "boolean",
                    "nullable": true,
                    "example": false
                  },
                  "description": {
                    "type": "string",
                    "example": "Mutige junge Abenteurerin mit gelbem Rucksack."
                  },
                  "visualTraits": {
                    "type": "string",
                    "example": "Braune Zöpfe, gelbe Mütze, rote Jacke"
                  },
                  "exact_colors": {
                    "type": "string",
                    "example": "gelb, rot, braun"
                  },
                  "status": {
                    "type": "string",
                    "example": "published"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profil erfolgreich aktualisiert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "message": "Entität \"Hauptfigur\" erfolgreich aktualisiert",
                  "entity": {
                    "id": "33333333-4444-5555-6666-777777777777",
                    "name": "Hauptfigur",
                    "type": "character",
                    "role_tier": "main"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Entität nicht gefunden"
          }
        }
      },
      "delete": {
        "tags": [
          "Entities"
        ],
        "summary": "Entität / Profil löschen",
        "description": "Löscht ein Profil unwiderruflich aus Directus und bereinigt alle Referenzen in Szenen-Illustrationen.\n\n#### cURL Beispiel:\n```bash\ncurl -X DELETE http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777\n```",
        "operationId": "deleteEntity",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "responses": {
          "200": {
            "description": "Profil erfolgreich gelöscht",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "message": "Entität \"33333333-4444-5555-6666-777777777777\" erfolgreich gelöscht",
                  "deletedId": "33333333-4444-5555-6666-777777777777"
                }
              }
            }
          },
          "404": {
            "description": "Entität nicht gefunden"
          }
        }
      }
    },
    "/api/entities/{id}/crops": {
      "get": {
        "tags": [
          "Entities"
        ],
        "summary": "Alle visuellen Bildausschnitte einer Figur abrufen",
        "description": "Gibt alle in Buch-Illustrationen erkannten Segmente und Bounding-Boxes für diese Entität zurück.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777/crops\n```",
        "operationId": "getEntityCrops",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "responses": {
          "200": {
            "description": "Array aller Segmente",
            "content": {
              "application/json": {
                "example": {
                  "entityId": "33333333-4444-5555-6666-777777777777",
                  "name": "Kokosnuss",
                  "cropsCount": 5,
                  "crops": [
                    {
                      "cropFileId": "d3e4f5a6-b7c8-9d0e-1f2a-3b4c5d6e7f8a",
                      "sourceImageId": "b1d489f6-3d37-4048-82cc-907abd82bbe1",
                      "box": [
                        280,
                        120,
                        750,
                        480
                      ],
                      "sceneNumber": 2,
                      "chapterNumber": 1
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/entities/{id}/tier": {
      "post": {
        "tags": [
          "Entities"
        ],
        "summary": "Rolle/Bedeutung einer Figur dynamisch ändern",
        "description": "Befördert oder stuft eine Entität um (`main` ➔ Hauptfigur, `supporting` ➔ Nebenrolle, `extra` ➔ Sonstiges). Aktualisiert die Lore-Profile und Filter.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777/tier \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"tier\": \"main\"}'\n```",
        "operationId": "updateEntityTier",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier"
                ],
                "properties": {
                  "tier": {
                    "type": "string",
                    "enum": [
                      "main",
                      "supporting",
                      "extra",
                      "frequent",
                      "moderate",
                      "rare"
                    ],
                    "example": "main"
                  }
                }
              },
              "example": {
                "tier": "main"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rolle erfolgreich aktualisiert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "id": "33333333-4444-5555-6666-777777777777",
                  "newTier": "main"
                }
              }
            }
          }
        }
      }
    },
    "/api/entities/{id}/refine-bbox": {
      "post": {
        "tags": [
          "Entities"
        ],
        "summary": "Bounding-Box via Computer Vision verfeinern",
        "description": "Führt eine Konturanalyse und Hintergrund-Farbprofil-Abtastung durch, um abgeschnittene Gliedmaßen einzuschließen und Boxen an Objektgrenzen einzurasten.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/entities/33333333-4444-5555-6666-777777777777/refine-bbox \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"paddingPercent\": 0.05, \"snapToEdges\": true}'\n```",
        "operationId": "refineEntityBoundingBox",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "33333333-4444-5555-6666-777777777777"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "paddingPercent": {
                    "type": "number",
                    "default": 0.05,
                    "example": 0.05
                  },
                  "snapToEdges": {
                    "type": "boolean",
                    "default": true,
                    "example": true
                  }
                }
              },
              "example": {
                "paddingPercent": 0.05,
                "snapToEdges": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bounding-Box erfolgreich verfeinert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "originalBox": [
                    310,
                    150,
                    710,
                    460
                  ],
                  "refinedBox": [
                    280,
                    120,
                    750,
                    480
                  ],
                  "deltaPromille": 40
                }
              }
            }
          }
        }
      }
    },
    "/api/entities/merge": {
      "post": {
        "tags": [
          "Entities"
        ],
        "summary": "Entitäten zusammenführen (Multi-Profile Merge)",
        "description": "Führt ein oder mehrere Quell-Profile in ein Basisprofil zusammen (z. B. wenn mehrere Entitäten durch unterschiedliche Schreibweisen oder Synonyme denselben Charakter, Gegenstand oder Ort beschreiben).\nKombiniert Traits, Erkennungsregeln, Farbpaletten und visuelle Bildausschnitte (Crops), aktualisiert Vorkommen in Buch-Szenen und löscht die Quell-Entitäten.\nUnterstützt optionale Regie-Anweisungen (`userPrompt`), die bei der KI-Harmonisierung die höchste Priorität genießen und widersprüchliche Angaben aus den Profilen gezielt überschreiben.\n\n#### cURL Beispiel (Multi-Merge mit Regie-Prompt):\n```bash\ncurl -X POST http://localhost:3005/api/entities/merge \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"targetEntityId\": \"11111111-2222-3333-4444-555555555555\",\n    \"sourceEntityIds\": [\"22222222-3333-4444-5555-666666666666\", \"33333333-4444-5555-6666-777777777777\"],\n    \"canonicalName\": \"Kanonischer Name\",\n    \"synthesizeWithAi\": true,\n    \"userPrompt\": \"Der Charakter trägt eine rote Zipfelmütze und hat grüne Augen.\"\n  }'\n```",
        "operationId": "mergeEntities",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "targetEntityId"
                ],
                "properties": {
                  "targetEntityId": {
                    "type": "string",
                    "description": "ID des Basis-Profils, das erhalten bleibt und aktualisiert wird"
                  },
                  "sourceEntityIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Array von IDs der Quell-Profile, die gemergt und danach gelöscht werden"
                  },
                  "sourceEntityId": {
                    "type": "string",
                    "description": "Einzelne Quell-ID (abwärtskompatibel)"
                  },
                  "canonicalName": {
                    "type": "string",
                    "description": "Optionaler kanonischer Name für das Zielprofil"
                  },
                  "synthesizeWithAi": {
                    "type": "boolean",
                    "default": true,
                    "description": "Aktiviert semantische LLM-Synthese (Gemeinsamkeiten destillieren & Widersprüche klären)"
                  },
                  "userPrompt": {
                    "type": "string",
                    "description": "Optionale Benutzer-Regieanweisung mit höchster Priorität für die KI-Harmonisierung"
                  }
                }
              },
              "example": {
                "targetEntityId": "11111111-2222-3333-4444-555555555555",
                "sourceEntityIds": [
                  "22222222-3333-4444-5555-666666666666",
                  "33333333-4444-5555-6666-777777777777"
                ],
                "canonicalName": "Kanonischer Name",
                "synthesizeWithAi": true,
                "userPrompt": "Der Charakter trägt eine rote Zipfelmütze und hat grüne Augen."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entitäten erfolgreich zusammengeführt",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "message": "2 Profile erfolgreich in 'Kanonischer Name' zusammengeführt",
                  "mergedEntity": {
                    "id": "11111111-2222-3333-4444-555555555555",
                    "name": "Kanonischer Name",
                    "static_traits": {
                      "synonyms": [
                        "Kanonischer Name",
                        "Duplikat 1",
                        "Duplikat 2"
                      ],
                      "visual_segments": []
                    }
                  },
                  "deletedEntityIds": [
                    "22222222-3333-4444-5555-666666666666",
                    "33333333-4444-5555-6666-777777777777"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validierungsfehler (z. B. fehlende IDs oder identische IDs)"
          },
          "404": {
            "description": "Ziel- oder Quell-Entität in Directus nicht gefunden"
          }
        }
      }
    },
    "/api/scenes/generate": {
      "post": {
        "tags": [
          "Scenes"
        ],
        "summary": "Narrative Szene generieren (Agentic RAG)",
        "description": "Generiert eine kanontreue Szene im Schreibstil des Autors unter Berücksichtigung aller Figuren-Steckbriefe und bitemporaler State-Patches.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/scenes/generate \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bookId\": \"b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\",\n    \"chapterNumber\": 2,\n    \"sceneNumber\": 1,\n    \"title\": \"Das Floß am Klippenstrand\",\n    \"prompt\": \"Kokosnuss und Matilda bauen gemeinsam ein Floß aus Treibholz.\",\n    \"saveToDirectus\": true\n  }'\n```",
        "operationId": "generateScene",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bookId",
                  "prompt"
                ],
                "properties": {
                  "bookId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "chapterId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "chapterNumber": {
                    "type": "integer",
                    "default": 1
                  },
                  "sceneNumber": {
                    "type": "integer",
                    "default": 1
                  },
                  "title": {
                    "type": "string",
                    "example": "Das Floß am Klippenstrand"
                  },
                  "prompt": {
                    "type": "string",
                    "example": "Kokosnuss und Matilda bauen gemeinsam ein Floß aus Treibholz."
                  },
                  "entityIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "saveToDirectus": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "bookId": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                "chapterNumber": 2,
                "sceneNumber": 1,
                "title": "Das Floß am Klippenstrand",
                "prompt": "Kokosnuss und Matilda bauen gemeinsam ein Floß aus Treibholz.",
                "saveToDirectus": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Szene erfolgreich generiert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "persistedSceneId": "f5a6b7c8-d9e0-1f2a-3b4c-5d6e7f8a9b0c",
                  "sceneText": "Die Wellen schlugen sanft gegen das Ufer der Dracheninsel. Kokosnuss zog einen dicken Ast aus dem Sand...",
                  "tokensUsed": 642,
                  "continuityAudit": {
                    "verdict": "compliant",
                    "score": 100,
                    "issuesCount": 0
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/scenes/audit": {
      "post": {
        "tags": [
          "Scenes"
        ],
        "summary": "Kontinuitäts-Audit für Szenen durchführen",
        "description": "Prüft einen Textabsatz zeilenweise gegen alle Kanon-Regeln, Magiegesetze und Figuren-Traits der Welt.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/scenes/audit \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bookId\": \"b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\",\n    \"sceneText\": \"Kokosnuss schwamm wie ein Fisch durch die tiefe Strömung und rettete Matilda.\"\n  }'\n```",
        "operationId": "auditScene",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bookId",
                  "sceneText"
                ],
                "properties": {
                  "bookId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "sceneText": {
                    "type": "string",
                    "minLength": 10
                  }
                }
              },
              "example": {
                "bookId": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
                "sceneText": "Kokosnuss schwamm wie ein Fisch durch die tiefe Strömung und rettete Matilda."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Detaillierter Audit-Report",
            "content": {
              "application/json": {
                "example": {
                  "verdict": "canon_violation",
                  "score": 45,
                  "summary": "Kanon-Widerspruch entdeckt: Drachen dieser Spezies können laut Regelwerk nicht tauchen.",
                  "issues": [
                    {
                      "severity": "critical",
                      "category": "lore_rule",
                      "description": "Kokosnuss schwimmt unter Wasser, obwohl seine negative Regel 'Kann nicht tauchen' lautet.",
                      "suggestedCorrection": "Lass Kokosnuss stattdessen ein Paddel benutzen oder auf einem Baumstamm balancieren."
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/scenes/{id}": {
      "get": {
        "tags": [
          "Scenes"
        ],
        "summary": "Szene per ID abrufen",
        "description": "Gibt den Fließtext, die Kapitelzuordnung und Metadaten einer Szene aus Directus zurück.",
        "operationId": "getSceneById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "f5a6b7c8-d9e0-1f2a-3b4c-5d6e7f8a9b0c"
          }
        ],
        "responses": {
          "200": {
            "description": "Szenenobjekt",
            "content": {
              "application/json": {
                "example": {
                  "id": "f5a6b7c8-d9e0-1f2a-3b4c-5d6e7f8a9b0c",
                  "title": "Das Floß am Klippenstrand",
                  "chapter_number": 2,
                  "scene_number": 1,
                  "raw_text": "Die Wellen schlugen sanft gegen das Ufer..."
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/chat": {
      "post": {
        "tags": [
          "Agent"
        ],
        "summary": "Interaktiver Dialog mit dem LoreKeeper ReAct-Agenten",
        "description": "Führt eine mehrstufige ReAct-Konversation mit dem autonomen Agenten. Der Agent kann eigenständig Directus-Datenbankabfragen durchführen und kanontreue Antworten formulieren.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/agent/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"message\": \"Welche Farbe hat die Kappe von Kokosnuss und wer sind seine besten Freunde?\",\n    \"bookId\": \"b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\"\n  }'\n```",
        "operationId": "chatWithAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string"
                  },
                  "bookId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "conversationId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "message": "Welche Farbe hat die Kappe von Kokosnuss und wer sind seine besten Freunde?",
                "bookId": "b1a2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Antwort des Agenten inklusive genutzter Tools",
            "content": {
              "application/json": {
                "example": {
                  "response": "Kokosnuss trägt eine markante feuerrote Mütze (#ef4444). Seine engsten Freunde sind das Stachelschwein Matilda und der Fressdrache Oskar.",
                  "toolsExecuted": [
                    "get_entity_lore",
                    "get_character_relationships"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/tools": {
      "get": {
        "tags": [
          "Agent"
        ],
        "summary": "Verfügbare Agenten-Tools abfragen",
        "description": "Listet alle registrierten Function-Calling Tools auf, die der Agent autonom nutzen kann.",
        "operationId": "getAgentTools",
        "responses": {
          "200": {
            "description": "Array aller registrierten Tools mit Parametern",
            "content": {
              "application/json": {
                "example": [
                  {
                    "name": "get_entity_lore",
                    "description": "Liest Steckbrief und Traits einer Figur"
                  },
                  {
                    "name": "find_scene_mentions",
                    "description": "Sucht Szenen, in denen eine Figur auftaucht"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/agent/tools/execute": {
      "post": {
        "tags": [
          "Agent"
        ],
        "summary": "Agenten-Tool programmatisch direkt ausführen",
        "description": "Führt ein registriertes Tool direkt aus (z. B. für automatisierte Skripte oder Frontend-Komponenten).",
        "operationId": "executeAgentTool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "toolName",
                  "arguments"
                ],
                "properties": {
                  "toolName": {
                    "type": "string",
                    "example": "get_entity_lore"
                  },
                  "arguments": {
                    "type": "object",
                    "example": {
                      "name": "Kokosnuss"
                    }
                  }
                }
              },
              "example": {
                "toolName": "get_entity_lore",
                "arguments": {
                  "name": "Kokosnuss"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ergebnis der Tool-Ausführung",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "data": {
                    "name": "Kokosnuss",
                    "species": "Drache"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/images/crop": {
      "post": {
        "tags": [
          "Images"
        ],
        "summary": "Verlustfreies Master-Cropping durchführen",
        "description": "Schneidet anhand relativer Promille-Koordinaten (0..1000) einen verlustfreien WebP-Ausschnitt aus dem Original-Masterbild zu.\n\n#### cURL Beispiel:\n```bash\ncurl -X POST http://localhost:3005/api/images/crop \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"imageFileId\": \"b1d489f6-3d37-4048-82cc-907abd82bbe1\",\n    \"box\": [280, 120, 750, 480],\n    \"format\": \"webp\",\n    \"quality\": 90\n  }'\n```",
        "operationId": "cropImage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "imageFileId",
                  "box"
                ],
                "properties": {
                  "imageFileId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "box": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "minItems": 4,
                    "maxItems": 4,
                    "example": [
                      280,
                      120,
                      750,
                      480
                    ]
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "webp",
                      "png",
                      "jpeg"
                    ],
                    "default": "webp"
                  },
                  "quality": {
                    "type": "integer",
                    "default": 90
                  }
                }
              },
              "example": {
                "imageFileId": "b1d489f6-3d37-4048-82cc-907abd82bbe1",
                "box": [
                  280,
                  120,
                  750,
                  480
                ],
                "format": "webp",
                "quality": 90
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Crop erfolgreich generiert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "cropFileId": "d3e4f5a6-b7c8-9d0e-1f2a-3b4c5d6e7f8a",
                  "dimensions": {
                    "width": 360,
                    "height": 470
                  },
                  "format": "webp"
                }
              }
            }
          }
        }
      }
    },
    "/api/images/generate": {
      "post": {
        "tags": [
          "Images"
        ],
        "summary": "Buch-Illustration generieren",
        "description": "Erzeugt eine neue Illustration im kanonischen Zeichenstil des Buches via Imagen 4 oder Gemini Image.",
        "operationId": "generateBookImage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "example": "Kokosnuss und Matilda am Sandstrand, Aquarell-Stil, warme Töne"
                  },
                  "aspectRatio": {
                    "type": "string",
                    "enum": [
                      "1:1",
                      "4:3",
                      "3:4",
                      "16:9"
                    ],
                    "default": "4:3"
                  }
                }
              },
              "example": {
                "prompt": "Kokosnuss und Matilda am Sandstrand, Aquarell-Stil, warme Töne",
                "aspectRatio": "4:3"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bild generiert",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "fileId": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
                }
              }
            }
          }
        }
      }
    },
    "/api/images/re-prompt": {
      "post": {
        "tags": [
          "Images"
        ],
        "summary": "Konsistenten Bild-Prompt synthetisieren",
        "description": "Kombiniert Szenen-Handlung, Figuren-Farben und Buch-Zeichenstil zu einem optimalen Diffusion-Prompt.",
        "operationId": "synthesizeImagePrompt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sceneDescription"
                ],
                "properties": {
                  "sceneDescription": {
                    "type": "string",
                    "example": "Die Freunde betreten die düstere Höhle"
                  }
                }
              },
              "example": {
                "sceneDescription": "Die Freunde betreten die düstere Höhle"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Synthetisierter Prompt",
            "content": {
              "application/json": {
                "example": {
                  "prompt": "watercolor illustration of a small green dragon holding a torch..."
                }
              }
            }
          }
        }
      }
    },
    "/api/styles/generate-prompt": {
      "post": {
        "tags": [
          "Styles"
        ],
        "summary": "Stil-Prompt generieren",
        "description": "Wandelt eine Stil-Beschreibung in kanonische Prompt-Snippets für Text- und Bildgeneratoren um.",
        "operationId": "generateStylePrompt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "styleName"
                ],
                "properties": {
                  "styleName": {
                    "type": "string",
                    "example": "Klassisches Aquarell-Kinderbuch"
                  }
                }
              },
              "example": {
                "styleName": "Klassisches Aquarell-Kinderbuch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generierter Stil-Snippet",
            "content": {
              "application/json": {
                "example": {
                  "styleSnippet": "delicate pencil linework with soft watercolor washes, gentle paper grain"
                }
              }
            }
          }
        }
      }
    },
    "/api/styles/apply": {
      "post": {
        "tags": [
          "Styles"
        ],
        "summary": "Stilvorlage auf Text anwenden",
        "description": "Überarbeitet einen bestehenden Textabsatz im Tonfall der gewählten Stilvorlage.",
        "operationId": "applyStyleToText",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text",
                  "styleId"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "example": "Er ging schnell durch den Wald."
                  },
                  "styleId": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "text": "Er ging schnell durch den Wald.",
                "styleId": "e1f2a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Umgewandelter Text",
            "content": {
              "application/json": {
                "example": {
                  "transformedText": "Huschenden Schrittes durchquerte er das schattige Dickicht..."
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "System-Healthcheck abfragen",
        "description": "Prüft die Erreichbarkeit und Latenz von Fastify, Directus CMS und dem LiteLLM Proxy.\n\n#### cURL Beispiel:\n```bash\ncurl -X GET http://localhost:3005/health\n```",
        "operationId": "getHealthStatus",
        "responses": {
          "200": {
            "description": "Alle Dienste gesund",
            "content": {
              "application/json": {
                "example": {
                  "status": "ok",
                  "timestamp": "2026-09-21T22:35:00.000Z",
                  "services": {
                    "directus": "healthy",
                    "litellm": "healthy",
                    "keydb": "healthy"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "GenerateSceneInput": {
        "type": "object",
        "required": [
          "bookId",
          "prompt"
        ],
        "properties": {
          "bookId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID des Zielbuchs in Directus"
          },
          "chapterId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional UUID des Kapitels, dem die Szene zugeordnet wird"
          },
          "chapterNumber": {
            "type": "integer",
            "minimum": 1,
            "description": "Kapitelnummer (wird genutzt, falls chapterId nicht angegeben ist)"
          },
          "sceneNumber": {
            "type": "integer",
            "minimum": 1,
            "description": "Szenennummer innerhalb des Kapitels"
          },
          "title": {
            "type": "string",
            "description": "Vorgeschlagener Arbeitstitel der Szene"
          },
          "prompt": {
            "type": "string",
            "minLength": 3,
            "description": "Narrative Zielsetzung, Handlung und Ereignisse der Szene"
          },
          "entityIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optionale UUIDs explizit zu ladender Lore-Entitäten"
          },
          "saveToDirectus": {
            "type": "boolean",
            "default": true,
            "description": "Ob die generierte Szene direkt in der `scenes`-Tabelle persistiert werden soll"
          }
        }
      },
      "SceneGenerationResult": {
        "type": "object",
        "required": [
          "sceneTitle",
          "summary",
          "content",
          "consistencyNotes",
          "retrievedContext"
        ],
        "properties": {
          "sceneTitle": {
            "type": "string",
            "example": "Der Schwur im Schattenwald"
          },
          "summary": {
            "type": "string",
            "example": "Vaelin empfängt brisante Informationen über den feindlichen Truppenaufmarsch."
          },
          "content": {
            "type": "string",
            "description": "Vollständiger generierter Prosatext der Szene"
          },
          "consistencyNotes": {
            "type": "string",
            "description": "Erklärung des Writer-Agenten, wie Weltregeln und Charaktertraits gewahrt wurden"
          },
          "retrievedContext": {
            "type": "object",
            "properties": {
              "charactersCount": {
                "type": "integer"
              },
              "locationsCount": {
                "type": "integer"
              },
              "factionsCount": {
                "type": "integer"
              },
              "rulesAndObjectsCount": {
                "type": "integer"
              },
              "loreStatesCount": {
                "type": "integer"
              }
            }
          },
          "persistedSceneId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID des erstellten Datensatzes in Directus"
          }
        }
      },
      "AuditSceneInput": {
        "type": "object",
        "required": [
          "bookId",
          "sceneText"
        ],
        "properties": {
          "bookId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID des Buchs, dessen Kanon als Prüfgrundlage dient"
          },
          "chapterId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional UUID des Kapitels für kontextspezifische Validierung"
          },
          "sceneTitle": {
            "type": "string",
            "description": "Titel der zu prüfenden Szene"
          },
          "sceneText": {
            "type": "string",
            "minLength": 10,
            "description": "Der zu auditierende Fließtext"
          }
        }
      },
      "AuditIssue": {
        "type": "object",
        "required": [
          "severity",
          "category",
          "description",
          "suggestedCorrection"
        ],
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "critical"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "character_voice",
              "lore_rule",
              "timeline",
              "geography",
              "magic_or_tech"
            ]
          },
          "description": {
            "type": "string",
            "description": "Genaue Beschreibung des Kontinuitätsbruchs"
          },
          "conflictingLoreReference": {
            "type": "string",
            "description": "Zitat oder Verweis auf die widersprüchliche Kanon-Regel"
          },
          "suggestedCorrection": {
            "type": "string",
            "description": "Konkrete Handlungsanweisung zur Behebung"
          }
        }
      },
      "AuditReport": {
        "type": "object",
        "required": [
          "verdict",
          "score",
          "summary",
          "issues"
        ],
        "properties": {
          "verdict": {
            "type": "string",
            "enum": [
              "compliant",
              "minor_discrepancies",
              "canon_violation"
            ]
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Kanon-Konformitäts-Score (100 = absolut fehlerfrei)"
          },
          "summary": {
            "type": "string",
            "description": "Gesamteinschätzung des Auditors"
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditIssue"
            }
          }
        }
      },
      "Scene": {
        "type": "object",
        "required": [
          "id",
          "chapter_id",
          "scene_number"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "chapter_id": {
            "type": "string",
            "format": "uuid"
          },
          "scene_number": {
            "type": "integer"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "raw_text": {
            "type": "string",
            "nullable": true
          },
          "generated_image_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "required": [
          "status",
          "service",
          "environment",
          "timestamp",
          "models",
          "directus"
        ],
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "service": {
            "type": "string",
            "example": "lorekeeper-service"
          },
          "environment": {
            "type": "string",
            "example": "development"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "models": {
            "type": "object",
            "properties": {
              "router": {
                "type": "string",
                "example": "router-model"
              },
              "writer": {
                "type": "string",
                "example": "writer-model"
              }
            }
          },
          "directus": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "example": "https://laefnabeiafnaleiafafe.2.28.78.110.sslip.io"
              },
              "status": {
                "type": "string",
                "example": "connected"
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": "object"
          }
        }
      },
      "AgentChatInput": {
        "type": "object",
        "required": [
          "bookId",
          "messages"
        ],
        "properties": {
          "bookId": {
            "type": "string",
            "format": "uuid",
            "example": "11111111-2222-3333-4444-555555555555"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "contextChapterId": {
            "type": "string",
            "format": "uuid"
          },
          "maxIterations": {
            "type": "integer",
            "minimum": 1,
            "maximum": 15,
            "default": 6
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant"
            ]
          },
          "content": {
            "type": "string"
          }
        }
      },
      "AgentChatResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "$ref": "#/components/schemas/AgentChatResult"
          }
        }
      },
      "AgentChatResult": {
        "type": "object",
        "required": [
          "reply",
          "toolCallsExecuted",
          "thoughtProcess"
        ],
        "properties": {
          "reply": {
            "type": "string"
          },
          "toolCallsExecuted": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolExecutionRecord"
            }
          },
          "thoughtProcess": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ToolExecutionRecord": {
        "type": "object",
        "required": [
          "name",
          "arguments",
          "result"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "search_lore"
          },
          "arguments": {
            "type": "object"
          },
          "result": {
            "description": "Das von Directus oder der internen Logik zurückgegebene Ergebnis"
          }
        }
      },
      "AgentToolsResponse": {
        "type": "object",
        "required": [
          "success",
          "count",
          "tools"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "count": {
            "type": "integer",
            "example": 6
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Books",
      "description": "Buch-Ingestion, Parsing von EPUB/PDF, Status, Kapitelzerlegung und Export"
    },
    {
      "name": "Entities",
      "description": "Visuelle Extraktion, Bounding-Box Refinement, Lore-Steckbriefe und Rollen-Promotion"
    },
    {
      "name": "Scenes",
      "description": "Agentic RAG Szenen-Generierung und Kontinuitäts-Audit gegen Weltenregeln"
    },
    {
      "name": "Agent",
      "description": "Autonomer ReAct-Agent, Konversation und dynamische Directus Tool-Ausführung"
    },
    {
      "name": "Images",
      "description": "Verlustfreies Master-Cropping, Bild-Generierung und Stil-Synthese"
    },
    {
      "name": "Styles",
      "description": "Modulare Stilvorlagen für Text, Bild und Audio"
    },
    {
      "name": "System",
      "description": "Healthchecks, Statusüberwachung und Konfigurationsprüfungen"
    }
  ]
}