# QDR-001A – Reconciliation Run Foundation

**Status:** IMPLEMENTED – SERVER TEST PENDING  
**Datum:** 2026-08-29  
**Basis:** LA Architecture Review – Freigabe mit Anpassungen  
**Scope:** Persistente Run-Grundlage für vergleichbare Reconciliation-Verarbeitungen

## 1. Ziel

QDR-001A führt zwischen `SourceDelivery` und der tatsächlich entstandenen Interpretation eine explizite Run-Ebene ein.

```text
SourceDelivery
  └── ReconciliationRun
        ├── ReconciliationRunItem ── SourceValue
        └── InterpretationGraph ───── SourceValue
              └── InterpretationNode
                    └── ReconciliationResult
                          └── CandidateItem
```

Die drei Ebenen bleiben fachlich getrennt:

```text
ReconciliationRun
→ Bedingungen und Konfigurations-Snapshot einer konkreten Verarbeitung

ReconciliationRunItem
→ persistierte Soll-Population des Runs

InterpretationGraph + Resultate
→ tatsächlich entstandene Verarbeitung
```

Damit kann ein Run über 100 SourceValues auch dann korrekt als 100er-Population ausgewertet werden, wenn einzelne Items fehlschlagen und für diese kein `InterpretationGraph` entsteht.

## 2. Domain Objects

Neu:

```text
ReconciliationRun
ReconciliationRunId
ReconciliationRunStatus
ReconciliationRunItem
ReconciliationRunItemStatus
```

Run-Status:

```text
CREATED
RUNNING
COMPLETED
FAILED
```

RunItem-Status:

```text
PENDING
COMPLETED
FAILED
```

Einzelne `FAILED` RunItems verhindern einen `COMPLETED` Run nicht. `COMPLETED` bedeutet, dass alle RunItems abgearbeitet wurden; jedes Item muss dann terminal (`COMPLETED` oder `FAILED`) sein.

## 3. Persistenz

Migration:

```text
database/migrations/0006_reconciliation_run_foundation.sql
```

Neu:

```text
reconciliation_run
reconciliation_run_item
```

Erweiterung:

```text
interpretation_graph.reconciliation_run_id NULL
```

Die Nullable-Eigenschaft ist ausschließlich migrationsbedingt für Altbestände. Neue Run-basierte Verarbeitung persistiert Graphen mit `reconciliation_run_id`.

### 3.1 reconciliation_run

Stabile Vergleichsdimensionen als eigene Felder:

```text
source_delivery_id
target_vocabulary_uri
scope_vocabulary_uri
```

Variable Parameter liegen als unveränderlicher Snapshot in:

```text
configuration_json
```

Beispiel:

```json
{
  "discovery_method": "VECTOR_SIMILARITY",
  "adapter_id": "vector-qdrant",
  "client_id": "qdrant-marburg",
  "context_type_uris": [
    "https://reconcilix.vocnet.org/context/0001"
  ],
  "context_roles": [
    "item_discovery",
    "structural_context_discovery"
  ],
  "language": "*",
  "offset": 0,
  "limit": 100,
  "rerank": false,
  "output_language": "de"
}
```

`ReconciliationRunMapper::update()` verändert bewusst nur `status`, `started_at` und `finished_at`. SourceDelivery, Target/Scope und `configuration_json` werden nach dem Insert nicht überschrieben.

### 3.2 reconciliation_run_item

Ein RunItem enthält:

```text
id
reconciliation_run_id
source_value_id
status
```

DB-Invariante:

```text
UNIQUE(reconciliation_run_id, source_value_id)
```

Damit ist die Run-Population dauerhaft eindeutig.

## 4. InterpretationGraph und Mehrfach-Runs

Vor QDR-001A bestand in der Anwendung noch die WP1-Einschränkung "exactly one InterpretationGraph per SourceValue". Diese Einschränkung musste für Run-Vergleiche aufgehoben werden.

Ein SourceValue darf nun Graphen aus unterschiedlichen Runs besitzen:

```text
SourceValue X
  ├── Graph / Run A
  ├── Graph / Run B
  └── Graph / Run C
```

### 4.1 Entscheidung zur von LA offenen Graph-Eindeutigkeit

Es wird **nicht**

```text
UNIQUE(reconciliation_run_id, source_value_id)
```

auf `interpretation_graph` eingeführt.

Grund: Das bestehende Domain Model beschreibt einen `InterpretationGraph` als interpretierbaren Span und erlaubt grundsätzlich mehrere Graphen/Spans pro SourceValue. Eine harte 1:1-Regel pro Run würde diese bestehende fachliche Möglichkeit unnötig abschneiden.

Stattdessen ersetzt QDR-001A den bisherigen Schlüssel

```text
(source_value_id, span_start, span_end, version)
```

durch den run-sensitiven Schlüssel:

```text
(reconciliation_run_id, source_value_id, span_start, span_end, version)
```

Damit sind identische Whole-Value-Graphen für verschiedene Runs möglich, ohne die Span-Semantik des Interpretation-Modells zu verlieren.

Da `reconciliation_run_id` für Legacy-Daten nullable ist, bleiben bestehende Graphen migrationskompatibel.

## 5. Repositories und Application Service

Neu:

```text
ReconciliationRunRepository
RelationalReconciliationRunRepository
ReconciliationRunMapper
ReconciliationRunItemMapper
```

Zusätzlich wurde ein append-only Pfad für Graphen bereits bestehender SourceValues geschaffen:

```text
InterpretationGraphRepository
RelationalInterpretationGraphRepository
```

Dies wird in QDR-001B benötigt, weil Qdrant auf bereits importierten SourceValues arbeitet und dafür keine neuen SourceValue-Datensätze erzeugt werden dürfen.

`ReconciliationRunService` steuert die minimale v0.1-Lifecycle-Logik:

```text
createRun()
definePopulation()
start()
markItemCompleted()
markItemFailed()
complete()
fail()
```

Bei `definePopulation()` wird geprüft:

- SourceValue existiert,
- SourceValue gehört zur SourceDelivery des Runs,
- SourceValue kommt im Run nur einmal vor.

`start()` verlangt mindestens ein RunItem. `complete()` verlangt, dass kein RunItem mehr `PENDING` ist.

## 6. Composition Root

`ReconciliationCompositionRoot` stellt zusätzlich bereit:

```text
createReconciliationRunRepository()
createInterpretationGraphRepository()
createReconciliationRunService()
```

Damit kann QDR-001B auf der bestehenden Composition-/Persistence-Infrastruktur aufsetzen.

## 7. Nicht Bestandteil von QDR-001A

Weiterhin ausdrücklich nicht umgesetzt:

- Qdrant HTTP Client
- Qdrant Credentials
- Vector Request-/Response-Mapping
- `VECTOR_SIMILARITY` Adapter
- Discovery Testbench GUI
- Candidate Rendering
- LLM Candidate Assessment
- Prompt-/Modellverwaltung
- Evaluation-Metriken

Diese Punkte gehören zu QDR-001B bzw. QDR-001C.

## 8. Tests

Neu:

```text
tests/Domain/Reconciliation/ReconciliationRunTest.php
tests/Domain/Reconciliation/ReconciliationRunItemTest.php
tests/Domain/Reconciliation/InterpretationGraphRunTest.php
tests/Application/Reconciliation/ReconciliationRunServiceTest.php
tests/Migration/ReconciliationRunMigrationTest.php
```

Zusätzlich wurde `tests/Migration/SchemaAndMigrationsTest.php` auf die Migrationen 0005 und 0006 aktualisiert.

Lokaler ED-Teststand:

```text
QDR-001A ReconciliationRun Domain: OK
QDR-001A ReconciliationRunItem Domain: OK
QDR-001A InterpretationGraph Run Association: OK
QDR-001A ReconciliationRun Service: OK
QDR-001A Migration Contract: OK
WP2-004 SchemaAndMigrations: OK
PHP syntax check src/ + tests/: OK
```

Der vollständige Suite-Lauf enthält in der ED-Umgebung weiterhin bekannte umgebungsabhängige Fehler (fehlende lokale `config/database.php`, fehlende `mbstring`-Extension sowie Tests mit erforderlichem externem Input). Diese Fehler sind nicht durch QDR-001A verursacht.

## 9. Server-Test

Nach Einspielen der Änderungen:

```bash
php tools/migrate.php
```

Danach mindestens:

```bash
php tests/Domain/Reconciliation/ReconciliationRunTest.php
php tests/Domain/Reconciliation/ReconciliationRunItemTest.php
php tests/Domain/Reconciliation/InterpretationGraphRunTest.php
php tests/Application/Reconciliation/ReconciliationRunServiceTest.php
php tests/Migration/ReconciliationRunMigrationTest.php
php tests/Migration/SchemaAndMigrationsTest.php
```

Optional DB-Kontrolle:

```sql
SHOW CREATE TABLE reconciliation_run;
SHOW CREATE TABLE reconciliation_run_item;
SHOW CREATE TABLE interpretation_graph;
```

## 10. Exit Criteria

QDR-001A ist abgeschlossen, wenn:

1. Migration 0006 auf der Rx-DB erfolgreich ausgeführt wurde.
2. Domain-/Migrationstests auf dem Zielsystem erfolgreich sind.
3. `reconciliation_run` und `reconciliation_run_item` vorhanden sind.
4. `interpretation_graph.reconciliation_run_id` vorhanden und nullable ist.
5. Existing OpenRefine-/Legacy-Verarbeitung weiterhin Graphen ohne Run-Zuordnung persistieren kann.
6. QDR-001B einen neuen Run samt Population anlegen und anschließend einen run-gebundenen Graph für einen bereits persistierten SourceValue speichern kann.
