# 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.
