# AP-A Token-Tracking Fundament – Zusammenfassung Dieses Dokument fasst alle Klassen, Methoden und Dateien zusammen, die im Zuge von AP-A (Token- und Kosten-Tracking-Fundament der V3.3-Spezifikation, #74) neu erstellt oder substanziell erweitert wurden. ## Schema-Migration - `pdf-umbenenner-adapter-out/src/main/resources/db/migration/V2__token_tracking.sql` - Sechs neue Spalten in `processing_attempt`: `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `price_input_per_token_nano_usd`, `price_output_per_token_nano_usd`. - Neue Tabelle `model_price` mit Composite Primary Key `(provider, model_name)`, NOT-NULL-Preisen, Currency-CHECK auf `'USD'`, `updated_at`-Spalte. - Zwei zusätzliche Indizes auf `processing_attempt`: `idx_processing_attempt_started_at_provider_fp_model`, `idx_processing_attempt_run_id_provider_model`. - Default-Preise für gpt-4o-mini, gpt-4o, gpt-4.1*, gpt-5*, claude-haiku-4-5, claude-sonnet-4-6 und claude-opus-4-7 (`ON CONFLICT DO NOTHING`). ## Application-Modul ### DTOs (`application/dto`) - `AiUsageMetadata` – Token-Verbrauchsmetadaten mit `empty()`, `hasAnyTokenData()`, `hasCacheTokens()`. - `ModelPriceEntry` – Schreib-/Validierungs-DTO mit Wertgrenzen-Validierung im Konstruktor. - `ModelPriceView` – Lese-/Anzeige-DTO mit nullable `updatedAt` und `invalidUpdatedAt`-Flag. - `ModelPriceKey` – Composite-Key für Löschungen. - `ModelPriceChangeSet` – atomarer Block aus Upserts und Deletions, defensive Listen-Kopie. ### Cost-Komponenten (`application/cost`) - `CostResult` – interpretierte Kosten-Anzeige mit Status-Flags. - `CostCalculator` – `formatRow(...)` und `calculateAttempt(...)` (echt implementiert), `formatTotal(...)` als Stub für AP-B. ### Ports (`application/port/out`) - `ModelPriceRepository` – `findAll`, `findByProviderAndModelName`, `upsert`, `delete`, `saveAllChanges`. - `AiInvocationSuccess` (erweitert) – neues Feld `usageMetadata`. ### Use Cases (`application/usecase`) - `DefaultManageModelPricesUseCase` – CRUD-Fassade mit ChangeSet-Konflikt- validierung (vier Regeln) und Provider-Whitelist beim Upsert. - `ModelPriceValidationException` – deutsche Validierungsfehler-Exception. ### Application-Service-Anpassungen - `AiNamingService` (erweitert) – reicht `AiUsageMetadata` aus dem `AiInvocationSuccess` als Token-Felder in den `AiAttemptContext` weiter. - `DocumentProcessingCoordinator` (erweitert) – - neuer optionaler Konstruktor mit `ModelPriceRepository` und `headlessMode`-Flag. - `loadPriceSnapshot(modelName)` lädt Snapshot-Preis pro Versuch; Lookup- Fehler liefern leeren Snapshot ohne Attempt-Verlust. - `buildAttempt(...)` befüllt jetzt Token- und Preis-Snapshot-Felder im `ProcessingAttempt`. ### Domain-Anpassungen - `AiAttemptContext` (erweitert) – vier nullable Token-Felder (`inputTokens`, `outputTokens`, `cacheCreationInputTokens`, `cacheReadInputTokens`); Backward-compatible Convenience-Konstruktor. - `ProcessingAttempt` (erweitert) – sechs nullable Token-/Preis-Snapshot- Felder; Convenience-Konstruktor und `withoutAiFields(...)` ohne Verhaltens- änderung. ## Adapter-Out-Modul - `SqliteConnectionFactory` (neu) – zentrale Connection-Factory; setzt `PRAGMA journal_mode=WAL` und `PRAGMA busy_timeout=5000`. Foreign-Key-Pragma wird bewusst nicht implizit gesetzt (Verhalten der bisherigen `DriverManager.getConnection`-Stellen erhalten). - `SqliteUnitOfWorkAdapter`, `SqliteProcessingAttemptRepositoryAdapter`, `SqliteHistoryQueryAdapter`, `SqliteDocumentRecordRepositoryAdapter` (jeweils geändert) – nutzen die neue Factory. - `SqliteProcessingAttemptRepositoryAdapter.save()` (erweitert) – INSERT um sechs neue Spalten erweitert, neue Hilfsmethode `setNullableLong(...)`. - `SqliteHistoryQueryAdapter.mapToProcessingAttempt(...)` (erweitert) – liest die sechs neuen Spalten via `readNullableLong(...)`. - `SqliteSchemaInitializationAdapter` (geändert) – erwartete Spalten/Indizes bleiben am V1-Zielschema; Doc-Klarstellung, dass V2 additiv auf der Baseline arbeitet. - `SqliteModelPriceRepositoryAdapter` (neu) – `findAll`, `findByProviderAndModelName`, `upsert`, `delete`, `saveAllChanges` (UPSERT via `ON CONFLICT(provider, model_name) DO UPDATE`, transaktionaler Batch). Lese-Mapping behandelt `DateTimeParseException` als `invalidUpdatedAt`. - `ModelPriceRepositoryException` (neu) – technischer JDBC-Fehler. ### KI-Adapter - `AnthropicClaudeHttpAdapter` (geändert) – neue Methode `extractTokenUsageFromResponse(JSONObject)` für `usage.input_tokens`, `usage.output_tokens`, `usage.cache_creation_input_tokens`, `usage.cache_read_input_tokens` mit Validierung (negativ, > 10 Mio., nicht-numerisch → NULL + WARN). - `OpenAiHttpAdapter` (geändert) – analoge Methode mit Mapping `prompt_tokens → input_tokens`, `completion_tokens → output_tokens`; Cache-Felder bleiben null. ## GUI-Modul ### Neuer Tab "Modell-Preise" - `adapter-in-gui/modelprices/GuiModelPriceManagementPort` (neu) – Bridge-Port für GUI-Zugriff auf Modell-Preise. - `adapter-in-gui/modelprices/GuiModelPricesTab` (neu) – TableView mit editierbaren Preisspalten (`In/1M USD`, `Out/1M USD`), Lösch-Button mit Bestätigungsdialog, Add-Dialog mit Provider-Auswahl, Speichern-Aktion über `ModelPriceChangeSet`. Konvertierung Nano-USD ↔ `$/1M Tokens` mit HALF-UP-Rundung; unbekannte Provider werden read-only mit Tooltip angezeigt; `updatedAt = null` als "ungueltig". ### Anbindung im Workspace - `GuiConfigurationEditorWorkspace` (geändert) – sechster Tab "Modell- Preise" wird angelegt; neue Methode `warnIfActiveModelHasNoPriceEntry()` zeigt vor dem Speichern eine deutsche Warnung an, wenn das aktuell ausgewählte Modell keinen Preis-Eintrag besitzt. - `GuiStartupContext` (geändert) – neues optionales Feld `modelPriceManagementPort` mit Backward-Kompatibilität. - `BootstrapRunner` (geändert) – neue Methode `buildGuiModelPriceManagementPort()` und Helfer für die Verdrahtung; Coordinator wird mit `ModelPriceRepository` und `headlessMode`-Flag versorgt. ### History-Tab - `GuiHistoryTab` (geändert) – drei zusätzliche Spalten in der Versuchstabelle: Input-Tokens, Output-Tokens, Kosten. Cache-only-Versuche zeigen "nur Cache-Tokens, keine Standardkosten"; fehlender Preis-Snapshot führt zu "Preis fehlt"; Mikrobeträge als "< $0.0001"; Cache-Beteiligung ergänzt Suffix "(ohne Cache-Anteil)". ### Summary-Banner - `BatchRunSummaryBanner` (geändert) – aus einzeiliger HBox wurde eine vierzeilige VBox: Status-Zeile, Token-Zeile, Kosten-Zeile, optionale Cache-only-Zeile. Neue Record-Klasse `BatchRunTokenSummary` mit `empty()`-Default; bestehende `update(Map)`-Aufrufer bleiben funktionsfähig. ## Testanpassungen - `pdf-umbenenner-application/.../service/AiNamingServiceTest` und `pdf-umbenenner-bootstrap/.../e2e/StubAiInvocationPort` – alte `AiInvocationSuccess`-Konstruktoraufrufe um `AiUsageMetadata.empty()` ergänzt. - `SqliteSchemaInitializationAdapterTest.fall1_leereDb_processingAttemptHatAlleErwartetenSpalten` prüft jetzt zusätzlich die sechs Token-/Preis-Spalten. - `GuiAdapterSmokeTest.editorWorkspace_startStateShowsEmptyHeaderDefaultsAndOneTab` erwartet jetzt sechs Tabs inkl. "Modell-Preise". ## Build und Verifizierung - `mvn clean verify` läuft auf dem Reactor `pdf-umbenenner-parent` durch (Tests grün auf allen Modulen). - Commit `08ec021` auf `main` gepusht. ## Bewusst ausgesparte Bereiche (für AP-B / AP-C) - `CostCalculator.formatTotal(...)` ist ein Stub und wirft `UnsupportedOperationException`. - `TokenStatisticsReadModelPort`, `QueryCostAnalysisFullUseCase`, `QueryCostAnalysisHeaderOnlyUseCase`, `QueryRunSummaryUseCase`, `SqliteTokenStatisticsReadModelAdapter` sind nicht enthalten. - Summary-Banner zeigt aktuell `0/0` Tokens und `$0.0000` Kosten, da das Read-Model erst in AP-B verdrahtet wird. - CLI-Befehle für Modell-Preise (#99) und Modell-Combobox-Filter (#98) sind AP-C.