# WP3-003a – Implementation Plan
## Lobid/GND SubjectHeading Candidate Discovery Adapter

- **Status:** Proposed for PL approval
- **Version:** 0.1
- **Scope:** WP3 – Candidate Discovery
- **Basis:** `reconcile_ADR002.WP2_20260801_9.zip`
- **References:**
  - PRE_WP3-001 – Architekturübersicht Discovery Framework
  - WP3-001 – Discovery Framework Foundation
  - WP3-002 – Local Reconciliation Store Adapter
  - WP3-003 – xTree Candidate Discovery Adapter
  - ADR002 – Source Delivery Model
  - DM001 – Domain Model
  - Reconciliation Service API v0.2

---

# 1. Ziel

WP3-003a bindet GND-Sachbegriffe (`SubjectHeading`) über die öffentliche lobid-GND-Reconciliation-API als dritten produktiven `CandidateDiscoveryAdapter` an Reconcilix an.

Nach Abschluss dieses Work Packages können dieselben Source Values unabhängig gegeneinander abgeglichen werden:

```text
MCT
WNK
GND SubjectHeading
```

Der neue produktive Pfad lautet:

```text
CandidateDiscoveryRouter
        │
        ▼
LobidGndSubjectHeadingAdapter
        │
        ▼
LobidGndReconcileClient
        │
        ▼
https://lobid.org/gnd/reconcile
        │
        ▼
DiscoveredCandidate[]
```

WP3-003a ist bewusst ein kleines Spiel- und Validierungspaket vor WP3-004.

---

# 2. Fachlicher Scope

WP3-003a unterstützt ausschließlich:

```text
GND Entity Type: SubjectHeading
```

Nicht unterstützt werden in diesem Paket:

- Personen,
- Körperschaften,
- Geografika,
- Konferenzen,
- Werke,
- Familien,
- gemischte GND-Typen.

Damit bleibt die erste GND-Integration unmittelbar auf den fachlichen Testfall des Dehio-Projekts begrenzt.

---

# 3. Befund im aktuellen Projektstand

Reconcilix besitzt bereits alle generischen Framework-Komponenten:

```text
DiscoveryRoute
DiscoveryConfigurationRegistry
CandidateDiscoveryAdapter
CandidateDiscoveryAdapterRegistry
CandidateDiscoveryRouter
RuntimeFactory
```

Produktiv angeschlossen sind bereits:

```text
LocalReconciliationStoreAdapter
XtreeJsonApiAdapter
LegacyDiscoveryCompatibilityAdapter
```

Die Provider-Konfiguration enthält bereits ein vorbereitetes GND-Source-System:

```php
'gnd' => [
    'label' => 'GND',
    'default_client' => 'lobid',
    'clients' => [
        'lobid' => [
            'class' => 'gnd-lobid',
            'base_url' => 'https://lobid.org/gnd',
        ],
    ],
],
```

`SourceSystemFactory` unterstützt `gnd-lobid` bisher noch nicht.

In `vocabularies.php` existiert noch kein auswählbares GND-Vocabulary.

---

# 4. Warum die Reconciliation API verwendet wird

Die lobid-GND-Reconciliation-API implementiert denselben grundlegenden Reconciliation-Vertrag, den OpenRefine und Reconcilix verwenden:

```text
query
type
limit
properties
```

Damit muss Reconcilix keine eigene Solr-Such- und Rankinglogik für GND-Sachbegriffe entwickeln.

Die allgemeine lobid Search API

```text
/gnd/search?filter=type:SubjectHeading&q=...
```

bleibt ein möglicher späterer Zugang für fachlich präzisere Suchmethoden.

WP3-003a verwendet ausschließlich:

```text
https://lobid.org/gnd/reconcile
```

---

# 5. Konfigurationsmodell

## 5.1 Neues Vocabulary

In `config/vocabularies.php` wird ein weiteres `Vocabulary` ergänzt:

```php
new Vocabulary(
    id: 'https://d-nb.info/gnd',
    label: 'GND: Sachbegriffe',
    conceptSchemeId: 'https://d-nb.info/gnd',
    sourceSystem: 'gnd',
    metadata: [
        'lobid_reconciliation_type' => 'SubjectHeading',
        'entity_type_uri' => 'https://d-nb.info/standards/elementset/gnd#SubjectHeading',
    ],
),
```

## 5.2 Bedeutung der Werte

```text
Vocabulary.id
→ fachliches Target Vocabulary in Reconcilix

lobid_reconciliation_type
→ technischer Typfilter der externen Reconciliation API

entity_type_uri
→ semantischer Typ der zurückgegebenen DiscoveredCandidate
```

Der lobid-Typfilter wird nicht mit `targetVocabularyUri` vermischt.

---

# 6. Tenant-Konfiguration

Der gewünschte Tenant erhält in `config/tenants.php`:

```php
'allowed_vocabularies' => [
    // ...
    'https://d-nb.info/gnd',
],
```

und besitzt bereits:

```php
'source_systems' => [
    // ...
    'gnd',
],
```

Der Adapter prüft:

```text
tenant allows vocabulary
AND
tenant allows provider/source system gnd
```

Ist eine der beiden Berechtigungen nicht vorhanden, liefert der Adapter eine leere Kandidatenliste.

---

# 7. Discovery Route

Für das GND-Vocabulary wird genau eine Route erzeugt:

```text
targetVocabularyUri = https://d-nb.info/gnd
scopeVocabularyUri  = https://d-nb.info/gnd
candidateSource     = lobid-gnd
discoveryMethod     = reconciliation-api
adapterKey          = lobid-gnd:subject-heading
enabled             = true
```

WP3-003a führt keine GND-SubVocabularies und keine zweite Route für denselben Scope ein.

---

# 8. Neue Produktionsklasse: LobidGndReconcileClient

**Pfad**

```text
src/Provider/Lobid/LobidGndReconcileClient.php
```

**Namespace**

```php
App\Provider\Lobid
```

**Typ**

```php
class LobidGndReconcileClient
```

Die Klasse bleibt absichtlich nicht `final`, damit ein kontrollierter Fake-Client in den Adaptertests ohne externe HTTP-Anfrage eingesetzt werden kann.

## Konstruktor

```php
public function __construct(
    private readonly string $baseUrl,
)
```

Die konfigurierte Basis-URL ist:

```text
https://lobid.org/gnd
```

Der konkrete Endpoint wird daraus gebildet:

```text
<baseUrl>/reconcile
```

## Öffentliche API

```php
public function reconcile(
    string $query,
    string $type,
    int $limit,
): array
```

## Zusätzlicher Getter

```php
public function baseUrl(): string
```

---

# 9. HTTP-Vertrag des Lobid-Clients

Der Client sendet einen Reconciliation Query Batch mit genau einem Query-Objekt.

Beispiel:

```json
{
  "q0": {
    "query": "Hallenkirche",
    "type": "SubjectHeading",
    "type_strict": "all",
    "limit": 3
  }
}
```

Transport:

```text
POST /gnd/reconcile
Content-Type: application/x-www-form-urlencoded
queries=<URL-encoded JSON>
```

Der Client gibt die dekodierte Antwort unverändert als Array zurück.

Er ist verantwortlich für:

- Request-Aufbau,
- URL-Encoding,
- HTTP-Aufruf,
- Prüfung des HTTP-Status,
- JSON-Decodierung,
- Prüfung der minimalen Response-Struktur.

Er ist nicht verantwortlich für:

- Route-Auswahl,
- Tenant-Prüfung,
- fachliches Candidate-Mapping,
- Match Decisions,
- Persistenz.

---

# 10. Fehlerverhalten des Clients

Explizite Fehler werden ausgelöst bei:

- leerer Basis-URL,
- leerem Suchbegriff,
- leerem Reconciliation Type,
- Limit kleiner 1,
- cURL-Fehler,
- HTTP-Status außerhalb 2xx,
- ungültigem JSON,
- fehlendem Ergebnisobjekt für `q0`,
- fehlender oder ungültiger `result`-Liste.

Technische Fehler des externen Dienstes werden nicht stillschweigend als leere Trefferliste behandelt.

Eine fachlich leere lobid-Antwort

```json
{"q0":{"result":[]}}
```

liefert dagegen regulär:

```text
DiscoveredCandidate[]
→ []
```

---

# 11. Neue Produktionsklasse: LobidGndSubjectHeadingAdapter

**Pfad**

```text
src/Infrastructure/Discovery/Adapter/LobidGndSubjectHeadingAdapter.php
```

**Namespace**

```php
App\Infrastructure\Discovery\Adapter
```

**Typ**

```php
final readonly class LobidGndSubjectHeadingAdapter
    implements CandidateDiscoveryAdapter
```

## Konstruktor

```php
public function __construct(
    private string $targetVocabularyUri,
    private LobidGndReconcileClient $client,
    private TenantContext $tenant,
    private string $reconciliationType = 'SubjectHeading',
    private string $entityTypeUri =
        'https://d-nb.info/standards/elementset/gnd#SubjectHeading',
)
```

## Contract

```php
/** @return list<DiscoveredCandidate> */
public function discover(ReconciliationCommand $command): array
```

---

# 12. Adapter-Ablauf

Der Adapter führt folgende Schritte aus:

1. Prüfen, dass der Command kein SubVocabulary enthält.
2. Prüfen, dass `command.targetVocabularyUri` dem konfigurierten GND-Target entspricht.
3. Tenant-Berechtigung für das GND-Vocabulary prüfen.
4. Tenant-Berechtigung für das Source System `gnd` prüfen.
5. Suchliteral trimmen.
6. Lobid-Client mit `SubjectHeading` und dem Command-Limit aufrufen.
7. `q0.result[]` in `DiscoveredCandidate[]` transformieren.
8. Reihenfolge der lobid-Ergebnisse beibehalten.

---

# 13. Mapping lobid Result → DiscoveredCandidate

Erwartete Reconciliation-Felder:

```text
id
name
type[]
score
match
```

Mapping:

```text
result.id
→ DiscoveredCandidate.uri

result.name
→ DiscoveredCandidate.label

result.score
→ DiscoveredCandidate.score

result.match
→ DiscoveredCandidate.match

GND SubjectHeading URI
→ DiscoveredCandidate.entityTypeUri

lobid base URL
→ DiscoveredCandidate.candidateProviderUri

result.type + Rohdaten
→ DiscoveredCandidate.metadata
```

---

# 14. URI-Normalisierung

Der Adapter behandelt unterschiedliche mögliche `id`-Formen defensiv.

## Absolute URI

```text
https://d-nb.info/gnd/...
http://d-nb.info/gnd/...
https://lobid.org/gnd/...
```

→ unverändert übernehmen.

## Nur GND-Identifier

Beispiel:

```text
4074335-4
```

→ kanonische GND-URI bilden:

```text
https://d-nb.info/gnd/4074335-4
```

Eine leere oder nicht interpretierbare ID erzeugt einen Mapping-Fehler.

---

# 15. Score-Normalisierung

Reconcilix erwartet:

```text
0.0 bis 1.0
```

Lobid-Reconciliation-Ergebnisse verwenden typischerweise einen Score auf einer 0-bis-100-Skala.

KISS-Regel:

```php
$normalizedScore = max(
    0.0,
    min(1.0, ((float) $result['score']) / 100),
);
```

Fehlt der Score, wird:

```text
null
```

verwendet.

Es wird in WP3-003a keine eigene Score-Kalibrierung eingeführt.

---

# 16. Match-Status

Der von lobid gelieferte boolesche Wert:

```text
match
```

wird unverändert übernommen.

Reconcilix führt in WP3-003a keine zusätzliche Auto-Match-Regel ein.

Die fachliche Interpretation unterschiedlicher Scores zwischen

```text
MCT
WNK
GND
```

gehört in den späteren Evaluations- und Semantic-Engineering-Track.

---

# 17. SourceSystemFactory

**Datei**

```text
src/SourceSystem/SourceSystemFactory.php
```

Der bestehende Match-Ausdruck wird ergänzt:

```php
'gnd-lobid'
=> $this->createLobidGndReconcileClient($clientCfg),
```

Neue private Methode:

```php
private function createLobidGndReconcileClient(
    array $clientCfg,
): LobidGndReconcileClient
```

Der lobid-Client benötigt:

- keine Credentials,
- keinen Tenant-spezifischen Login,
- keine Session,
- kein Cookie.

Trotzdem wird er innerhalb der bestehenden Source-System-Grenze erzeugt.

---

# 18. RuntimeFactory

**Datei**

```text
src/Infrastructure/Factory/RuntimeFactory.php
```

Neue Konstante:

```php
private const LOBID_GND_ADAPTER_KEY =
    'lobid-gnd:subject-heading';
```

In `createDiscoveryConfiguration()` wird für

```text
Vocabulary.sourceSystem == gnd
```

eine Lobid-Route und Adapterinstanz erzeugt.

Private Hilfsmethode:

```php
private function appendLobidGndRouteAndAdapter(
    array &$routes,
    array &$adapters,
    Vocabulary $vocabulary,
    RuntimeContext $runtimeContext,
): void
```

Die Methode:

1. liest den Client aus `SourceSystemFactory`,
2. prüft auf `LobidGndReconcileClient`,
3. liest `lobid_reconciliation_type` aus `Vocabulary.metadata`,
4. liest `entity_type_uri` aus `Vocabulary.metadata`,
5. erzeugt die Route,
6. registriert den Adapter.

---

# 19. Manifest und OpenRefine

`ManifestBuilder` benötigt keine Codeänderung.

Sobald

```text
GND: Sachbegriffe
```

in `vocabularies.php` eingetragen und für den Tenant erlaubt ist, erscheint das Vocabulary automatisch in:

```text
defaultTypes
```

OpenRefine sendet anschließend:

```json
{
  "query": "Hallenkirche",
  "type": "https://d-nb.info/gnd",
  "type_strict": "should"
}
```

`OpenRefineRequestMapper` behandelt das GND-Vocabulary als Root Vocabulary:

```text
targetVocabularyUri = https://d-nb.info/gnd
subVocabularyId     = null
```

Es ist keine Mapper-Anpassung vorgesehen.

---

# 20. Preview

WP3-003a verändert die bestehende Reconcilix-Preview nicht.

Der neue Candidate besitzt eine resolvierbare GND-URI.

Eine direkte lobid- oder DNB-Preview kann später als separates Paket ergänzt werden.

Für den ersten Expert:innen-Test reicht:

```text
OpenRefine Candidate
→ GND URI
→ externer Link
```

---

# 21. Tests ohne externen Netzwerkzugriff

Automatisierte Tests dürfen nicht von der Erreichbarkeit von lobid.org abhängen.

## 21.1 LobidGndReconcileClientContractTest

**Pfad**

```text
tests/Provider/Lobid/LobidGndReconcileClientContractTest.php
```

Prüft mit kontrollierter Transport-/Client-Testvariante:

- korrekten Endpoint,
- POST-Methode,
- Content-Type,
- `queries`-Formfeld,
- `SubjectHeading`,
- `type_strict`,
- Limit,
- JSON-Decodierung,
- HTTP-Fehler,
- ungültiges JSON,
- fehlende `q0.result`-Liste.

Erwartete Ausgabe:

```text
WP3-003a lobid GND Reconcile Client Contract: OK
```

## 21.2 LobidGndSubjectHeadingAdapterTest

**Pfad**

```text
tests/Infrastructure/Discovery/Adapter/LobidGndSubjectHeadingAdapterTest.php
```

Testfälle:

1. Adapter implementiert `CandidateDiscoveryAdapter`.
2. `SubjectHeading` wird an den Client übergeben.
3. Command-Limit wird übernommen.
4. Ergebnisreihenfolge bleibt erhalten.
5. GND-Identifier wird zur kanonischen URI.
6. Absolute URI bleibt erhalten.
7. Score wird normalisiert.
8. Match-Status bleibt erhalten.
9. GND Entity Type URI wird gesetzt.
10. Ergebnis-Typen werden in Metadata übernommen.
11. Unberechtigter Tenant erhält leere Liste.
12. Falsches Target Vocabulary erzeugt einen eindeutigen Fehler.
13. SubVocabulary-Command wird abgewiesen.
14. Leere lobid-Trefferliste liefert leeres Ergebnis.

Erwartete Ausgabe:

```text
WP3-003a Lobid GND SubjectHeading Adapter: OK
```

## 21.3 LobidGndRuntimeIntegrationTest

**Pfad**

```text
tests/Integration/LobidGndRuntimeIntegrationTest.php
```

Testfälle:

1. GND SubjectHeading erscheint als Root Vocabulary.
2. Es wird genau eine GND-Route erzeugt.
3. Route verwendet `lobid-gnd:subject-heading`.
4. Adapter ist in der Registry registriert.
5. Router delegiert an den Lobid-Adapter.
6. MCT Local Store bleibt unverändert.
7. MCT und WNK xTree-Routen bleiben unverändert.
8. Legacy Adapter wird für GND nicht aufgerufen.
9. Tenant ohne GND-Berechtigung sieht GND nicht im Manifest.
10. Tenant ohne GND-Berechtigung kann die Route nicht nutzen.

Erwartete Ausgabe:

```text
WP3-003a Lobid GND Runtime Integration: OK
```

---

# 22. Manueller Smoke-Test gegen lobid

Nach erfolgreichem automatisiertem Test erfolgt ein realer Request in der Zielumgebung.

Beispielbegriffe:

```text
Hallenkirche
Altarretabel
Brücke
Maler
```

Geprüft werden:

- HTTPS-Verbindung erfolgreich,
- HTTP 2xx,
- gültige Reconciliation Response,
- ausschließlich plausible GND-Sachbegriffe,
- GND-URI vorhanden,
- Label vorhanden,
- Score im Bereich 0.0 bis 1.0,
- OpenRefine-Response formal gültig.

Anschließend erfolgt der erste Dreifachvergleich:

```text
MCT
WNK
GND: Sachbegriffe
```

Die fachliche Bewertung ist noch nicht Bestandteil des Work Packages.

---

# 23. Geplanter Änderungsumfang

## Neue Produktionsdateien

```text
src/Provider/Lobid/LobidGndReconcileClient.php
src/Infrastructure/Discovery/Adapter/LobidGndSubjectHeadingAdapter.php
```

## Geänderte Produktionsdateien

```text
src/SourceSystem/SourceSystemFactory.php
src/Infrastructure/Factory/RuntimeFactory.php
config/vocabularies.php
config/tenants.php
```

## Voraussichtlich unveränderte Produktionsdateien

```text
src/Infrastructure/Composition/ReconciliationCompositionRoot.php
src/Manifest/ManifestBuilder.php
src/OpenRefine/OpenRefineRequestMapper.php
src/Infrastructure/Discovery/Routing/CandidateDiscoveryRouter.php
src/Infrastructure/Discovery/Configuration/DiscoveryConfigurationRegistry.php
src/Infrastructure/Discovery/Adapter/CandidateDiscoveryAdapterRegistry.php
src/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapter.php
src/Infrastructure/Discovery/Adapter/XtreeJsonApiAdapter.php
public/reconcile.php
```

## Neue Testdateien

```text
tests/Provider/Lobid/LobidGndReconcileClientContractTest.php
tests/Infrastructure/Discovery/Adapter/LobidGndSubjectHeadingAdapterTest.php
tests/Integration/LobidGndRuntimeIntegrationTest.php
```

## Voraussichtlich angepasste Tests

```text
tests/Factory/RuntimeFactoryDiscoveryIntegrationTest.php
tests/Integration/XtreeRuntimeIntegrationTest.php
tests/Integration/LocalReconciliationStoreRuntimeIntegrationTest.php
tests/Manifest/ManifestBuilderTest.php
```

---

# 24. Akzeptanzkriterien

WP3-003a ist abgeschlossen, wenn:

1. `LobidGndSubjectHeadingAdapter` den `CandidateDiscoveryAdapter` implementiert.
2. Die öffentliche lobid-GND-Reconciliation-API verwendet wird.
3. Jede Anfrage technisch auf `SubjectHeading` beschränkt wird.
4. GND als eigenes Root Vocabulary in Reconcilix konfiguriert ist.
5. GND für berechtigte Tenants im Manifest auswählbar ist.
6. Der Router GND-Anfragen an den Lobid-Adapter delegiert.
7. MCT Local Store unverändert funktioniert.
8. MCT und WNK über xTree unverändert funktionieren.
9. lobid-Ergebnisse korrekt in `DiscoveredCandidate[]` überführt werden.
10. Candidate-URIs als resolvierbare GND-URIs ausgegeben werden.
11. Scores auf 0.0 bis 1.0 normalisiert werden.
12. Technische lobid-Fehler sichtbar bleiben.
13. Automatisierte Tests keinen externen Netzwerkzugriff benötigen.
14. Alle neuen Tests erfolgreich sind.
15. Alle Tests aus WP3-001, WP3-002 und WP3-003 weiterhin erfolgreich sind.
16. OpenRefine-Reconciliation gegen GND SubjectHeading im Smoke-Test erfolgreich ist.
17. Ein manueller Vergleich MCT / WNK / GND technisch möglich ist.

---

# 25. Out of Scope

Nicht Bestandteil sind:

- andere GND Entity Types,
- allgemeine lobid Search API,
- DNB SRU,
- DNB QLever,
- GND Data Extension API,
- Preview-Integration für GND,
- fachliche Score-Kalibrierung,
- Ranking-Vergleich zwischen MCT, WNK und GND,
- automatisches Multi-Vocabulary-Matching,
- parallele Discovery Routes,
- Result Fusion,
- Fallback Chains,
- Candidate Interpretation,
- Semantic-Engineering-Auswertung,
- persistente Discovery Configuration,
- Retry, Circuit Breaker oder Cache.

---

# 26. Risiken

## R1 – lobid-Reconciliation-Response weicht im Detail von der angenommenen Fixture ab

**Gegenmaßnahme**

Vor Abschluss des Mappers wird ein realer Smoke-Response gespeichert und als Test-Fixture anonymisiert beziehungsweise bereinigt übernommen.

## R2 – `SubjectHeading` wird vom Dienst nicht strikt interpretiert

**Gegenmaßnahme**

Request verwendet Typ und strikte Typoption. Der Smoke-Test prüft zusätzlich die zurückgegebenen Result Types.

## R3 – GND-Identifier wird nicht als absolute URI geliefert

**Gegenmaßnahme**

Defensive URI-Normalisierung auf `https://d-nb.info/gnd/<id>`.

## R4 – Scores sind zwischen lobid und xTree nicht vergleichbar

**Gegenmaßnahme**

WP3-003a normalisiert nur technisch auf 0.0 bis 1.0. Eine fachliche Vergleichbarkeit wird ausdrücklich nicht behauptet.

## R5 – Externer Dienst ist zeitweise nicht erreichbar

**Gegenmaßnahme**

Automatisierte Tests bleiben netzwerkunabhängig. Der technische Fehler wird im realen Betrieb explizit ausgegeben.

## R6 – RuntimeFactory wächst weiter

**Gegenmaßnahme**

WP3-003a ergänzt nur eine klar abgegrenzte private Helper-Methode. Vor WP3-004 wird die Runtime-Konfiguration insgesamt überprüft und gegebenenfalls in einen eigenen Builder ausgelagert.

---

# 27. Engineering-Votum

WP3-003a ist eine echte Low-Hanging-Fruit-Erweiterung:

```text
kein Login
keine Credentials
keine Session
kein lokaler Dump
keine neue Rankinglogik
```

Die Integration nutzt einen bereits standardisierten Reconciliation-Dienst und den in WP3-001 geschaffenen Adapter-Contract.

Mit Abschluss von WP3-003a unterstützt Reconcilix drei technisch unterschiedliche Discovery-Quellen:

```text
Local Reconciliation Store
xTree JSON API
lobid GND Reconciliation API
```

Damit ist der technische Ausgangspunkt für die späteren Expert:innen- und Semantic-Engineering-Vergleiche zwischen MCT, WNK und GND hergestellt.
