Move NPC occurrences to their canonical namespace
This commit is contained in:
@@ -4,7 +4,7 @@ Notarius is a Go CLI for turning source material into structured artifacts with
|
|||||||
configured extraction pipelines. The implemented D&D workflow reads Seriatim
|
configured extraction pipelines. The implemented D&D workflow reads Seriatim
|
||||||
transcript JSON and can produce location registries and occurrences, scene
|
transcript JSON and can produce location registries and occurrences, scene
|
||||||
descriptions, item and currency events, NPC identities, combat turns, NPC
|
descriptions, item and currency events, NPC identities, combat turns, NPC
|
||||||
interactions, enemy events, and spell casts.
|
occurrences, enemy events, and spell casts.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ Combat-turn grounding:
|
|||||||
|
|
||||||
Named combat-opponent grounding:
|
Named combat-opponent grounding:
|
||||||
|
|
||||||
{{ input "npc_interactions" }}
|
{{ input "npc_occurrences" }}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ inputs:
|
|||||||
- name: combat_turns
|
- name: combat_turns
|
||||||
required: true
|
required: true
|
||||||
content_type: application/json
|
content_type: application/json
|
||||||
- name: npc_interactions
|
- name: npc_occurrences
|
||||||
required: true
|
required: true
|
||||||
content_type: application/json
|
content_type: application/json
|
||||||
messages:
|
messages:
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
Extract Dungeons & Dragons NPC interaction occurrences from the supplied
|
Extract Dungeons & Dragons NPC occurrence occurrences from the supplied
|
||||||
transcript. Include an occurrence only when the transcript establishes one
|
transcript. Include an occurrence only when the transcript establishes one
|
||||||
supplied NPC, one interaction kind, and a coherent passage supporting both.
|
supplied NPC, one occurrence kind, and a coherent passage supporting both.
|
||||||
Use the exact `npc_id` and matching `name` pair from the supplied NPC registry;
|
Use the exact `npc_id` and matching `name` pair from the supplied NPC registry;
|
||||||
never invent an ID or substitute a similar name.
|
never invent an ID or substitute a similar name.
|
||||||
|
|
||||||
Do not summarize, infer relationships, sentiment, factions, motives, aliases,
|
Do not summarize, infer relationships, sentiment, factions, motives, aliases,
|
||||||
or persistent state. Do not identify player characters, anonymous groups, or
|
or persistent state. Do not identify player characters, anonymous groups, or
|
||||||
invented NPCs. Split records when an NPC's interaction kind changes, when
|
invented NPCs. Split records when an NPC's occurrence kind changes, when
|
||||||
combat alignment changes, or when an NPC is first mentioned and later becomes
|
combat alignment changes, or when an NPC is first mentioned and later becomes
|
||||||
present.
|
present.
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
id: dnd.npc_interactions
|
id: dnd.npc_occurrences
|
||||||
version: "v1"
|
version: "v1"
|
||||||
default_profile: dnd-extraction
|
default_profile: dnd-extraction
|
||||||
inputs:
|
inputs:
|
||||||
@@ -371,10 +371,10 @@ selected target declares them:
|
|||||||
| **glossary** | Optional text campaign glossary. |
|
| **glossary** | Optional text campaign glossary. |
|
||||||
| **spell_catalog** | Optional JSON spell-catalog overlay for spell extraction and normalization. See [spell-catalog overlays](integrations/dnd-spell-catalog-overlays.md). |
|
| **spell_catalog** | Optional JSON spell-catalog overlay for spell extraction and normalization. See [spell-catalog overlays](integrations/dnd-spell-catalog-overlays.md). |
|
||||||
| **locations** | Required normalized location registry for location-occurrence extraction and normalization. |
|
| **locations** | Required normalized location registry for location-occurrence extraction and normalization. |
|
||||||
| **npc_registry** | Normalized NPC registry. Optional for spells and combat turns; required for NPC interactions and enemy-event extraction and normalization. |
|
| **npc_registry** | Normalized NPC registry. Optional for spells and combat turns; required for NPC occurrences and enemy-event extraction and normalization. |
|
||||||
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn and enemy-event extraction. |
|
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn and enemy-event extraction. |
|
||||||
| **combat_turns** | Required normalized combat-turn artifact for enemy-event extraction. |
|
| **combat_turns** | Required normalized combat-turn artifact for enemy-event extraction. |
|
||||||
| **npc_interactions** | Required normalized NPC-interaction artifact for enemy-event extraction. |
|
| **npc_occurrences** | Required normalized NPC-occurrence artifact for enemy-event extraction. |
|
||||||
|
|
||||||
Location-occurrence and enemy-event artifact slots have the following exact
|
Location-occurrence and enemy-event artifact slots have the following exact
|
||||||
binding contracts. Durable semantics and wire shapes remain in their
|
binding contracts. Durable semantics and wire shapes remain in their
|
||||||
@@ -386,11 +386,11 @@ binding contracts. Durable semantics and wire shapes remain in their
|
|||||||
| `npc_registry` | `dnd/npc-registry` | `application/json` | 1,048,576 bytes | extract and normalize |
|
| `npc_registry` | `dnd/npc-registry` | `application/json` | 1,048,576 bytes | extract and normalize |
|
||||||
| `scene_descriptions` | `dnd/scene-description-list` | `application/json` | 1,048,576 bytes | extract only |
|
| `scene_descriptions` | `dnd/scene-description-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
| `combat_turns` | `dnd/combat-turn-list` | `application/json` | 1,048,576 bytes | extract only |
|
| `combat_turns` | `dnd/combat-turn-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
| `npc_interactions` | `dnd/npc-interaction-list` | `application/json` | 1,048,576 bytes | extract only |
|
| `npc_occurrences` | `dnd/npc-occurrence-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
| `locations` | `dnd/location-list` | `application/json` | 1,048,576 bytes | location-occurrence extract and normalize |
|
| `locations` | `dnd/location-list` | `application/json` | 1,048,576 bytes | location-occurrence extract and normalize |
|
||||||
|
|
||||||
Scene descriptions accept **party**, **players**, and **glossary**, but not
|
Scene descriptions accept **party**, **players**, and **glossary**, but not
|
||||||
**roster**. NPC interactions require **npc_registry** for both extraction and
|
**roster**. NPC occurrences require **npc_registry** for both extraction and
|
||||||
normalization. Combat turns require **scene_descriptions** for extraction; the
|
normalization. Combat turns require **scene_descriptions** for extraction; the
|
||||||
normalized combat-turn module may use optional **npc_registry**. Location occurrences
|
normalized combat-turn module may use optional **npc_registry**. Location occurrences
|
||||||
require **locations** for extraction and normalization. Enemy-event extraction
|
require **locations** for extraction and normalization. Enemy-event extraction
|
||||||
@@ -404,9 +404,9 @@ generated bindings.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Input | **seriatim** |
|
| Input | **seriatim** |
|
||||||
| Chunk | **generic**, **dnd/scenes** |
|
| Chunk | **generic**, **dnd/scenes** |
|
||||||
| Extract | **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
| Extract | **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||||
| Merge | **appendorder** |
|
| Merge | **appendorder** |
|
||||||
| Normalize | **noop**, **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
| Normalize | **noop**, **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/locations**, **dnd/location-occurrences** |
|
||||||
| Output | **json** |
|
| Output | **json** |
|
||||||
|
|
||||||
`dnd/locations` extraction and normalization are `llm_backed`; location
|
`dnd/locations` extraction and normalization are `llm_backed`; location
|
||||||
@@ -418,7 +418,7 @@ step and the occurrence lane in the next.
|
|||||||
The D&D artifact contracts define each emitted schema:
|
The D&D artifact contracts define each emitted schema:
|
||||||
[spells](integrations/dnd-spell-artifacts.md),
|
[spells](integrations/dnd-spell-artifacts.md),
|
||||||
[NPC registry](integrations/dnd-npc-registry-artifacts.md),
|
[NPC registry](integrations/dnd-npc-registry-artifacts.md),
|
||||||
[NPC interactions](integrations/dnd-npc-interaction-artifacts.md),
|
[NPC occurrences](integrations/dnd-npc-occurrence-artifacts.md),
|
||||||
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
||||||
[item events](integrations/dnd-item-event-artifacts.md),
|
[item events](integrations/dnd-item-event-artifacts.md),
|
||||||
[scene descriptions](integrations/dnd-scene-description-artifacts.md), and
|
[scene descriptions](integrations/dnd-scene-description-artifacts.md), and
|
||||||
@@ -437,7 +437,7 @@ Available validator keys are:
|
|||||||
| NPC registry | **extract/dnd/npc-registry/shape**, **extract/dnd/npc-registry/source_refs**, **extract/dnd/npc-registry/source_relatedness**, **normalize/dnd/npc-registry/identity** |
|
| NPC registry | **extract/dnd/npc-registry/shape**, **extract/dnd/npc-registry/source_refs**, **extract/dnd/npc-registry/source_relatedness**, **normalize/dnd/npc-registry/identity** |
|
||||||
| Combat turns | **extract/dnd/combat-turns/shape**, **extract/dnd/combat-turns/source_refs**, **extract/dnd/combat-turns/source_relatedness**, **normalize/dnd/combat-turns/invariants** |
|
| Combat turns | **extract/dnd/combat-turns/shape**, **extract/dnd/combat-turns/source_refs**, **extract/dnd/combat-turns/source_relatedness**, **normalize/dnd/combat-turns/invariants** |
|
||||||
| Item events | **extract/dnd/item-events/shape**, **extract/dnd/item-events/source_refs**, **extract/dnd/item-events/source_relatedness**, **normalize/dnd/item-events/invariants** |
|
| Item events | **extract/dnd/item-events/shape**, **extract/dnd/item-events/source_refs**, **extract/dnd/item-events/source_relatedness**, **normalize/dnd/item-events/invariants** |
|
||||||
| NPC interactions | **extract/dnd/npc-interactions/shape**, **extract/dnd/npc-interactions/registry**, **extract/dnd/npc-interactions/source_refs**, **extract/dnd/npc-interactions/source_relatedness**, **normalize/dnd/npc-interactions/invariants** |
|
| NPC occurrences | **extract/dnd/npc-occurrences/shape**, **extract/dnd/npc-occurrences/registry**, **extract/dnd/npc-occurrences/source_refs**, **extract/dnd/npc-occurrences/source_relatedness**, **normalize/dnd/npc-occurrences/invariants** |
|
||||||
| Scene descriptions | **extract/dnd/scene-descriptions/shape**, **extract/dnd/scene-descriptions/source_refs**, **extract/dnd/scene-descriptions/source_relatedness**, **normalize/dnd/scene-descriptions/invariants** |
|
| Scene descriptions | **extract/dnd/scene-descriptions/shape**, **extract/dnd/scene-descriptions/source_refs**, **extract/dnd/scene-descriptions/source_relatedness**, **normalize/dnd/scene-descriptions/invariants** |
|
||||||
| Enemy events | **extract/dnd/enemy-events/shape**, **extract/dnd/enemy-events/engagements**, **extract/dnd/enemy-events/source_refs**, **extract/dnd/enemy-events/source_relatedness**, **normalize/dnd/enemy-events/invariants** |
|
| Enemy events | **extract/dnd/enemy-events/shape**, **extract/dnd/enemy-events/engagements**, **extract/dnd/enemy-events/source_refs**, **extract/dnd/enemy-events/source_relatedness**, **normalize/dnd/enemy-events/invariants** |
|
||||||
| Locations | **extract/dnd/locations/shape**, **extract/dnd/locations/source_refs**, **extract/dnd/locations/source_relatedness**, **normalize/dnd/locations/identity** |
|
| Locations | **extract/dnd/locations/shape**, **extract/dnd/locations/source_refs**, **extract/dnd/locations/source_relatedness**, **normalize/dnd/locations/identity** |
|
||||||
@@ -453,7 +453,7 @@ same at both stages.
|
|||||||
| NPC registry | generic/valid_json, extract/dnd/npc-registry/shape, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness | generic/valid_json, extract/dnd/npc-registry/shape, normalize/dnd/npc-registry/identity, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness |
|
| NPC registry | generic/valid_json, extract/dnd/npc-registry/shape, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness | generic/valid_json, extract/dnd/npc-registry/shape, normalize/dnd/npc-registry/identity, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness |
|
||||||
| Combat turns | generic/valid_json, extract/dnd/combat-turns/shape, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness | generic/valid_json, extract/dnd/combat-turns/shape, normalize/dnd/combat-turns/invariants, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness |
|
| Combat turns | generic/valid_json, extract/dnd/combat-turns/shape, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness | generic/valid_json, extract/dnd/combat-turns/shape, normalize/dnd/combat-turns/invariants, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness |
|
||||||
| Item events | generic/valid_json, extract/dnd/item-events/shape, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness | generic/valid_json, extract/dnd/item-events/shape, normalize/dnd/item-events/invariants, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness |
|
| Item events | generic/valid_json, extract/dnd/item-events/shape, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness | generic/valid_json, extract/dnd/item-events/shape, normalize/dnd/item-events/invariants, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness |
|
||||||
| NPC interactions | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, normalize/dnd/npc-interactions/invariants, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness |
|
| NPC occurrences | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, normalize/dnd/npc-occurrences/invariants, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness |
|
||||||
| Scene descriptions | generic/valid_json, extract/dnd/scene-descriptions/shape, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness | generic/valid_json, extract/dnd/scene-descriptions/shape, normalize/dnd/scene-descriptions/invariants, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness |
|
| Scene descriptions | generic/valid_json, extract/dnd/scene-descriptions/shape, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness | generic/valid_json, extract/dnd/scene-descriptions/shape, normalize/dnd/scene-descriptions/invariants, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness |
|
||||||
| Enemy events | generic/valid_json, extract/dnd/enemy-events/shape, extract/dnd/enemy-events/engagements, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness | generic/valid_json, extract/dnd/enemy-events/shape, normalize/dnd/enemy-events/invariants, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness |
|
| Enemy events | generic/valid_json, extract/dnd/enemy-events/shape, extract/dnd/enemy-events/engagements, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness | generic/valid_json, extract/dnd/enemy-events/shape, normalize/dnd/enemy-events/invariants, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness |
|
||||||
| Locations | generic/valid_json, extract/dnd/locations/shape, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness | generic/valid_json, extract/dnd/locations/shape, normalize/dnd/locations/identity, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness |
|
| Locations | generic/valid_json, extract/dnd/locations/shape, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness | generic/valid_json, extract/dnd/locations/shape, normalize/dnd/locations/identity, extract/dnd/locations/source_refs, generic/valid_json_schema, extract/dnd/locations/source_relatedness |
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ chronology; and collapses only duplicates with the same actor identity, turn
|
|||||||
kind, and complete valid evidence. It does not infer turns, initiative, or
|
kind, and complete valid evidence. It does not infer turns, initiative, or
|
||||||
actions from registry or scene data.
|
actions from registry or scene data.
|
||||||
|
|
||||||
The [NPC-interaction artifact](dnd-npc-interaction-artifacts.md) records
|
The [NPC-occurrence artifact](dnd-npc-occurrence-artifacts.md) records
|
||||||
broader NPC occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
broader NPC occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
||||||
uses combat turns as grounding only; turns do not establish an enemy event or
|
uses combat turns as grounding only; turns do not establish an enemy event or
|
||||||
its outcome. The [JSON output contract](json-output.md) defines publication,
|
its outcome. The [JSON output contract](json-output.md) defines publication,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ are positive and the start may not follow the end.
|
|||||||
| `incapacitated` | The subject is explicitly rendered unable to continue acting without being established as killed or captured. A missed turn is insufficient. |
|
| `incapacitated` | The subject is explicitly rendered unable to continue acting without being established as killed or captured. A missed turn is insufficient. |
|
||||||
|
|
||||||
The current transcript is the only event evidence. Campaign context and
|
The current transcript is the only event evidence. Campaign context and
|
||||||
normalized NPC, scene-description, combat-turn, and NPC-interaction artifacts
|
normalized NPC, scene-description, combat-turn, and NPC-occurrence artifacts
|
||||||
can ground names or control combat eligibility, but none may supply event
|
can ground names or control combat eligibility, but none may supply event
|
||||||
evidence. An outcome may share evidence with an engagement, in which case both
|
evidence. An outcome may share evidence with an engagement, in which case both
|
||||||
events are retained.
|
events are retained.
|
||||||
|
|||||||
@@ -74,5 +74,5 @@ valid evidence.
|
|||||||
|
|
||||||
The [JSON output contract](json-output.md) defines publication. See
|
The [JSON output contract](json-output.md) defines publication. See
|
||||||
[D&D module internals](../internal/dnd.md) for implementation details and the
|
[D&D module internals](../internal/dnd.md) for implementation details and the
|
||||||
[NPC-interaction artifact](dnd-npc-interaction-artifacts.md) for a distinct
|
[NPC-occurrence artifact](dnd-npc-occurrence-artifacts.md) for a distinct
|
||||||
kind of occurrence.
|
kind of occurrence.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# D&D NPC Interaction Artifact
|
# D&D NPC Occurrence Artifact
|
||||||
|
|
||||||
This contract defines the durable occurrence list produced by
|
This contract defines the durable occurrence list produced by
|
||||||
`dnd/npc-interactions`. It records discrete, source-grounded interactions with
|
`dnd/npc-occurrences`. It records discrete, source-grounded occurrences with
|
||||||
NPCs already present in a normalized registry; it does not extend that registry
|
NPCs already present in a normalized registry; it does not extend that registry
|
||||||
or summarize the session.
|
or summarize the session.
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ Each occurrence has these required fields:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `npc_id` | Exact durable ID from the required NPC registry. |
|
| `npc_id` | Exact durable ID from the required NPC registry. |
|
||||||
| `name` | Non-empty canonical display name from the required NPC registry. |
|
| `name` | Non-empty canonical display name from the required NPC registry. |
|
||||||
| `kind` | One of the interaction categories below. |
|
| `kind` | One of the occurrence categories below. |
|
||||||
| `source_refs` | One or more transcript evidence ranges. |
|
| `source_refs` | One or more transcript evidence ranges. |
|
||||||
|
|
||||||
Each source reference has exactly `source_id`, `start_unit_id`, and
|
Each source reference has exactly `source_id`, `start_unit_id`, and
|
||||||
`end_unit_id`. It identifies an inclusive range in the current transcript;
|
`end_unit_id`. It identifies an inclusive range in the current transcript;
|
||||||
unit IDs are positive and the start may not follow the end. Extraction evidence
|
unit IDs are positive and the start may not follow the end. Extraction evidence
|
||||||
for an interaction is confined to its accepted chunk.
|
for an occurrence is confined to its accepted chunk.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -50,7 +50,7 @@ for an interaction is confined to its accepted chunk.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Interaction categories
|
## Occurrence categories
|
||||||
|
|
||||||
| Kind | Meaning |
|
| Kind | Meaning |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -70,16 +70,16 @@ for uncertain classification.
|
|||||||
The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) resolves
|
The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) resolves
|
||||||
the exact `{npc_id, name}` pair. Unknown IDs and names that do not match their
|
the exact `{npc_id, name}` pair. Unknown IDs and names that do not match their
|
||||||
ID are rejected; normalization does not repair names by similarity. Registry
|
ID are rejected; normalization does not repair names by similarity. Registry
|
||||||
references are provenance only and never replace an interaction's own evidence.
|
references are provenance only and never replace an occurrence's own evidence.
|
||||||
Normalization validates the exact pair, orders and
|
Normalization validates the exact pair, orders and
|
||||||
deduplicates exact source references, then orders interactions by valid source
|
deduplicates exact source references, then orders occurrences by valid source
|
||||||
chronology, NPC comparison identity, display name, kind, and reference sequence.
|
chronology, NPC comparison identity, display name, kind, and reference sequence.
|
||||||
Only entries with the same NPC ID, canonical name, kind, and complete valid evidence
|
Only entries with the same NPC ID, canonical name, kind, and complete valid evidence
|
||||||
sequence are collapsed; distinct categories or evidence remain separate.
|
sequence are collapsed; distinct categories or evidence remain separate.
|
||||||
|
|
||||||
See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action
|
See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action
|
||||||
occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md) consumes
|
occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md) consumes
|
||||||
only `combat_opponent` interactions as grounding; they never establish an enemy
|
only `combat_opponent` occurrences as grounding; they never establish an enemy
|
||||||
event or outcome. The [JSON output contract](json-output.md) defines
|
event or outcome. The [JSON output contract](json-output.md) defines
|
||||||
publication. Pipeline mechanics are described in
|
publication. Pipeline mechanics are described in
|
||||||
[D&D module internals](../internal/dnd.md).
|
[D&D module internals](../internal/dnd.md).
|
||||||
@@ -70,12 +70,12 @@ descriptor.
|
|||||||
Only individually identifiable NPC names with transcript evidence belong in
|
Only individually identifiable NPC names with transcript evidence belong in
|
||||||
this artifact. Groups, generic roles, invented labels, and descriptive
|
this artifact. Groups, generic roles, invented labels, and descriptive
|
||||||
enrichment are excluded. Its source references prove registry provenance; they
|
enrichment are excluded. Its source references prove registry provenance; they
|
||||||
do not become evidence for a spell, interaction, combat, or enemy-event
|
do not become evidence for a spell, occurrence, combat, or enemy-event
|
||||||
occurrence.
|
occurrence.
|
||||||
|
|
||||||
This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md)
|
This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md)
|
||||||
and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to
|
and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to
|
||||||
resolve the canonical `name` in an [NPC interaction](dnd-npc-interaction-artifacts.md).
|
resolve the canonical `name` in an [NPC occurrence](dnd-npc-occurrence-artifacts.md).
|
||||||
The [enemy-event artifact](dnd-enemy-event-artifacts.md) also uses it only for
|
The [enemy-event artifact](dnd-enemy-event-artifacts.md) also uses it only for
|
||||||
subject grounding and canonical display names.
|
subject grounding and canonical display names.
|
||||||
The [JSON output contract](json-output.md) defines publication, and
|
The [JSON output contract](json-output.md) defines publication, and
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Each `lanes/<safe-lane-id>.json` file is the codec-owned normalized JSON for
|
|||||||
that lane. Consumers should use the index descriptor’s schema identity rather
|
that lane. Consumers should use the index descriptor’s schema identity rather
|
||||||
than infer a lane schema from its name. The current D&D payload contracts are
|
than infer a lane schema from its name. The current D&D payload contracts are
|
||||||
[spells](dnd-spell-artifacts.md), [NPC registry](dnd-npc-registry-artifacts.md),
|
[spells](dnd-spell-artifacts.md), [NPC registry](dnd-npc-registry-artifacts.md),
|
||||||
[NPC interactions](dnd-npc-interaction-artifacts.md),
|
[NPC occurrences](dnd-npc-occurrence-artifacts.md),
|
||||||
[combat turns](dnd-combat-turn-artifacts.md),
|
[combat turns](dnd-combat-turn-artifacts.md),
|
||||||
[item events](dnd-item-event-artifacts.md),
|
[item events](dnd-item-event-artifacts.md),
|
||||||
[scene descriptions](dnd-scene-description-artifacts.md), and
|
[scene descriptions](dnd-scene-description-artifacts.md), and
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ does not repeat their JSON shapes or schemas.
|
|||||||
| NPC registry | [NPC registry artifacts](../integrations/dnd-npc-registry-artifacts.md) |
|
| NPC registry | [NPC registry artifacts](../integrations/dnd-npc-registry-artifacts.md) |
|
||||||
| Combat turns | [combat-turn artifacts](../integrations/dnd-combat-turn-artifacts.md) |
|
| Combat turns | [combat-turn artifacts](../integrations/dnd-combat-turn-artifacts.md) |
|
||||||
| Item events | [item-event artifacts](../integrations/dnd-item-event-artifacts.md) |
|
| Item events | [item-event artifacts](../integrations/dnd-item-event-artifacts.md) |
|
||||||
| NPC interactions | [NPC-interaction artifacts](../integrations/dnd-npc-interaction-artifacts.md) |
|
| NPC occurrences | [NPC-occurrence artifacts](../integrations/dnd-npc-occurrence-artifacts.md) |
|
||||||
| Scene descriptions | [scene-description artifacts](../integrations/dnd-scene-description-artifacts.md) |
|
| Scene descriptions | [scene-description artifacts](../integrations/dnd-scene-description-artifacts.md) |
|
||||||
| Enemy events | [enemy-event artifacts](../integrations/dnd-enemy-event-artifacts.md) |
|
| Enemy events | [enemy-event artifacts](../integrations/dnd-enemy-event-artifacts.md) |
|
||||||
| Locations | [location artifacts](../integrations/dnd-location-artifacts.md) |
|
| Locations | [location artifacts](../integrations/dnd-location-artifacts.md) |
|
||||||
@@ -123,7 +123,7 @@ combine results from distinct scenes, so it intentionally does not apply that
|
|||||||
rule. Configuration owns the exact validator key and chain position.
|
rule. Configuration owns the exact validator key and chain position.
|
||||||
|
|
||||||
Normalizers are deterministic for spells, combat turns, item events, NPC
|
Normalizers are deterministic for spells, combat turns, item events, NPC
|
||||||
interactions, scene descriptions, enemy events, and location occurrences. They canonicalize display
|
occurrences, scene descriptions, enemy events, and location occurrences. They canonicalize display
|
||||||
values and evidence, use source-document order for stable output, and issue
|
values and evidence, use source-document order for stable output, and issue
|
||||||
bounded warnings for changes or collapsed duplicates. The NPC and location
|
bounded warnings for changes or collapsed duplicates. The NPC and location
|
||||||
normalizers are intentional exceptions: each first produces a deterministic
|
normalizers are intentional exceptions: each first produces a deterministic
|
||||||
@@ -141,12 +141,12 @@ immutable, validated projection for each operation. External files are checked
|
|||||||
during preparation, while generated artifacts are resolved at the handoff.
|
during preparation, while generated artifacts are resolved at the handoff.
|
||||||
|
|
||||||
NPC registries are names-only grounding projections: they may canonicalize
|
NPC registries are names-only grounding projections: they may canonicalize
|
||||||
actors for spells and combat turns and are required for NPC interactions, but
|
actors for spells and combat turns and are required for NPC occurrences, but
|
||||||
they do not supply evidence. Scene-description registries are eligibility-only
|
they do not supply evidence. Scene-description registries are eligibility-only
|
||||||
projections: they retain the current chunk’s classification data, not scene
|
projections: they retain the current chunk’s classification data, not scene
|
||||||
prose or evidence, and exist to route combat extraction. Enemy-event extraction
|
prose or evidence, and exist to route combat extraction. Enemy-event extraction
|
||||||
also projects combat turns to `actor` and `turn_kind` and filters NPC
|
also projects combat turns to `actor` and `turn_kind` and filters NPC
|
||||||
interactions to `combat_opponent` names and kinds. Location registries project
|
occurrences to `combat_opponent` names and kinds. Location registries project
|
||||||
ordered `{id, name}` pairs to location-occurrence extraction and normalization;
|
ordered `{id, name}` pairs to location-occurrence extraction and normalization;
|
||||||
exact ID/name matching keeps same-name locations distinguishable. These compact
|
exact ID/name matching keeps same-name locations distinguishable. These compact
|
||||||
projections, like NPC grounding, are source-free guidance and never event
|
projections, like NPC grounding, are source-free guidance and never event
|
||||||
@@ -163,9 +163,9 @@ shared helper changes.
|
|||||||
| NPCs | Does not consume an NPC registry. Its normalizer is the LLM-assisted reconciliation exception described above. |
|
| NPCs | Does not consume an NPC registry. Its normalizer is the LLM-assisted reconciliation exception described above. |
|
||||||
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
|
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
|
||||||
| Item events | Uses campaign context for disambiguation but has no NPC-registry or scene-description dependency. |
|
| Item events | Uses campaign context for disambiguation but has no NPC-registry or scene-description dependency. |
|
||||||
| NPC interactions | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
| NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
||||||
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
|
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
|
||||||
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-interaction artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
|
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-occurrence artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
|
||||||
| Locations | Produces a source-anchored, session-scoped registry. Its LLM-assisted reconciliation is proposal-only and never collapses same-name places without validated identity and evidence rules. |
|
| Locations | Produces a source-anchored, session-scoped registry. Its LLM-assisted reconciliation is proposal-only and never collapses same-name places without validated identity and evidence rules. |
|
||||||
| Location occurrences | Requires the normalized location registry for both extraction and normalization. Its [durable occurrence categories](../integrations/dnd-location-occurrence-artifacts.md#occurrence-categories) distinguish explicit speculation from unsupported inference; the deterministic normalizer enforces exact registry grounding and never turns registry provenance into occurrence evidence. |
|
| Location occurrences | Requires the normalized location registry for both extraction and normalization. Its [durable occurrence categories](../integrations/dnd-location-occurrence-artifacts.md#occurrence-categories) distinguish explicit speculation from unsupported inference; the deterministic normalizer enforces exact registry grounding and never turns registry provenance into occurrence evidence. |
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ pipelines:
|
|||||||
- npc_registry
|
- npc_registry
|
||||||
- spells
|
- spells
|
||||||
- combat-turns
|
- combat-turns
|
||||||
- npc-interactions
|
- npc-occurrences
|
||||||
- enemy-events
|
- enemy-events
|
||||||
steps:
|
steps:
|
||||||
# Establish session-wide reference artifacts alongside independent item events.
|
# Establish session-wide reference artifacts alongside independent item events.
|
||||||
@@ -110,12 +110,12 @@ pipelines:
|
|||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/combat-turns
|
normalize: dnd/combat-turns
|
||||||
npc-interactions:
|
npc-occurrences:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/npc-interactions
|
module: dnd/npc-occurrences
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/npc-interactions
|
normalize: dnd/npc-occurrences
|
||||||
location-occurrences:
|
location-occurrences:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/location-occurrences
|
module: dnd/location-occurrences
|
||||||
@@ -136,10 +136,10 @@ pipelines:
|
|||||||
artifact:
|
artifact:
|
||||||
step: extract-events
|
step: extract-events
|
||||||
lane: combat-turns
|
lane: combat-turns
|
||||||
npc_interactions:
|
npc_occurrences:
|
||||||
artifact:
|
artifact:
|
||||||
step: extract-events
|
step: extract-events
|
||||||
lane: npc-interactions
|
lane: npc-occurrences
|
||||||
artifacts:
|
artifacts:
|
||||||
enemy-events:
|
enemy-events:
|
||||||
extract:
|
extract:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
itemevents "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
itemevents "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||||
locationoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
locationoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
locations "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
locations "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||||
npcinteractions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
npcoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
scenedescriptions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
@@ -58,7 +58,7 @@ func TestProductionEnemyEventConfigurationResolvesGeneratedHandoffs(t *testing.T
|
|||||||
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
||||||
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
||||||
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
||||||
"npc_interactions": {step: "extract-events", lane: "npc-interactions"},
|
"npc_occurrences": {step: "extract-events", lane: "npc-occurrences"},
|
||||||
} {
|
} {
|
||||||
binding, found := generatedReferenceBinding(lane.ExtractReferences.Bindings, slot)
|
binding, found := generatedReferenceBinding(lane.ExtractReferences.Bindings, slot)
|
||||||
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
||||||
@@ -78,7 +78,7 @@ func TestProductionEnemyEventConfigurationResolvesGeneratedHandoffs(t *testing.T
|
|||||||
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
||||||
t.Fatalf("enemy event normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
t.Fatalf("enemy event normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
||||||
}
|
}
|
||||||
for _, slot := range []string{"npc_registry", "scene_descriptions", "combat_turns", "npc_interactions"} {
|
for _, slot := range []string{"npc_registry", "scene_descriptions", "combat_turns", "npc_occurrences"} {
|
||||||
if !hasReferenceSlot(extractSpec.ReferenceSlots, slot) {
|
if !hasReferenceSlot(extractSpec.ReferenceSlots, slot) {
|
||||||
t.Fatalf("enemy event extractor slots = %#v, want %q", extractSpec.ReferenceSlots, slot)
|
t.Fatalf("enemy event extractor slots = %#v, want %q", extractSpec.ReferenceSlots, slot)
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,7 @@ func TestMaintainedCompleteExampleProducesEnemyEventsThroughGeneratedHandoffs(t
|
|||||||
for slot, required := range map[string]string{
|
for slot, required := range map[string]string{
|
||||||
"npc_registry": "Kesh",
|
"npc_registry": "Kesh",
|
||||||
"combat_turns": "Kesh",
|
"combat_turns": "Kesh",
|
||||||
"npc_interactions": "Kesh",
|
"npc_occurrences": "Kesh",
|
||||||
} {
|
} {
|
||||||
input, ok := request.Inputs[slot]
|
input, ok := request.Inputs[slot]
|
||||||
if !ok || !strings.Contains(string(input.Content), required) || strings.Contains(string(input.Content), "source_refs") || strings.Contains(string(input.Content), "start_unit_id") {
|
if !ok || !strings.Contains(string(input.Content), required) || strings.Contains(string(input.Content), "source_refs") || strings.Contains(string(input.Content), "start_unit_id") {
|
||||||
@@ -257,7 +257,7 @@ func (client *enemyEventLLMClient) CompleteStructured(ctx context.Context, reque
|
|||||||
content = []byte(`{"events":[]}`)
|
content = []byte(`{"events":[]}`)
|
||||||
case combat.PromptID:
|
case combat.PromptID:
|
||||||
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
||||||
case npcinteractions.PromptID:
|
case npcoccurrences.PromptID:
|
||||||
if combatScene {
|
if combatScene {
|
||||||
var registry struct {
|
var registry struct {
|
||||||
NPCs []struct {
|
NPCs []struct {
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
func TestProductionNPCOccurrencePipelineResolvesAndPrepares(t *testing.T) {
|
||||||
components := productionTestComponents(t)
|
components := productionTestComponents(t)
|
||||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
|||||||
t.Fatalf("resolved pipeline = %#v", resolved)
|
t.Fatalf("resolved pipeline = %#v", resolved)
|
||||||
}
|
}
|
||||||
lane := resolved.Steps[1].ArtifactLanes[0]
|
lane := resolved.Steps[1].ArtifactLanes[0]
|
||||||
if lane.ArtifactKind != dnd.NPCOccurrenceListKind || lane.Extract.Module != interactionextract.Key || lane.Normalize.Module != interactionnormalize.Key {
|
if lane.ArtifactKind != dnd.NPCOccurrenceListKind || lane.Extract.Module != occurrenceextract.Key || lane.Normalize.Module != occurrencenormalize.Key {
|
||||||
t.Fatalf("interaction lane = %#v", lane)
|
t.Fatalf("occurrence lane = %#v", lane)
|
||||||
}
|
}
|
||||||
for _, bindings := range [][]pipeline.ReferenceBinding{lane.ExtractReferences.Bindings, lane.NormalizeReferences.Bindings} {
|
for _, bindings := range [][]pipeline.ReferenceBinding{lane.ExtractReferences.Bindings, lane.NormalizeReferences.Bindings} {
|
||||||
if len(bindings) != 1 || bindings[0].SlotName != "npc_registry" || bindings[0].Artifact == nil || bindings[0].Artifact.Step != "npc-registry" || bindings[0].Artifact.Lane != "npc_registry" {
|
if len(bindings) != 1 || bindings[0].SlotName != "npc_registry" || bindings[0].Artifact == nil || bindings[0].Artifact.Step != "npc-registry" || bindings[0].Artifact.Lane != "npc_registry" {
|
||||||
@@ -39,15 +39,15 @@ func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
|||||||
|
|
||||||
catalog := catalogFromRegistries(components.registries)
|
catalog := catalogFromRegistries(components.registries)
|
||||||
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCOccurrenceListKind)
|
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCOccurrenceListKind)
|
||||||
if !ok || codecSpec.Schema.ID != interactioncodec.SchemaID || codecSpec.Schema.Version != interactioncodec.SchemaVersion {
|
if !ok || codecSpec.Schema.ID != occurrencecodec.SchemaID || codecSpec.Schema.Version != occurrencecodec.SchemaVersion {
|
||||||
t.Fatalf("NPC interaction codec spec = %#v", codecSpec)
|
t.Fatalf("NPC occurrence codec spec = %#v", codecSpec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
func TestProductionNPCOccurrenceReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
||||||
components := productionTestComponents(t)
|
components := productionTestComponents(t)
|
||||||
catalog := catalogFromRegistries(components.registries)
|
catalog := catalogFromRegistries(components.registries)
|
||||||
laterProfile := npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry"))
|
laterProfile := npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry"))
|
||||||
laterProfile.Steps[0].ID = "seed"
|
laterProfile.Steps[0].ID = "seed"
|
||||||
laterProfile.Steps[0].Artifacts["seed"] = laterProfile.Steps[0].Artifacts["npc_registry"]
|
laterProfile.Steps[0].Artifacts["seed"] = laterProfile.Steps[0].Artifacts["npc_registry"]
|
||||||
delete(laterProfile.Steps[0].Artifacts, "npc_registry")
|
delete(laterProfile.Steps[0].Artifacts, "npc_registry")
|
||||||
@@ -60,10 +60,10 @@ func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *t
|
|||||||
profile pipeline.PipelineProfile
|
profile pipeline.PipelineProfile
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{name: "missing", profile: npcInteractionProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
{name: "missing", profile: npcOccurrenceProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
||||||
{name: "same step", profile: npcInteractionProfile(pipeline.GeneratedReference("interactions", "interactions")), want: "earlier step"},
|
{name: "same step", profile: npcOccurrenceProfile(pipeline.GeneratedReference("occurrences", "occurrences")), want: "earlier step"},
|
||||||
{name: "later step", profile: laterProfile, want: "earlier step"},
|
{name: "later step", profile: laterProfile, want: "earlier step"},
|
||||||
{name: "wrong artifact kind", profile: npcInteractionProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), want: "does not accept artifact kind"},
|
{name: "wrong artifact kind", profile: npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), want: "does not accept artifact kind"},
|
||||||
}
|
}
|
||||||
tests[3].profile.Steps[0].Artifacts["npc_registry"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("dnd/spells")}
|
tests[3].profile.Steps[0].Artifacts["npc_registry"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("dnd/spells")}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@@ -76,7 +76,7 @@ func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
func TestProductionNPCOccurrenceReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
||||||
components := productionTestComponents(t)
|
components := productionTestComponents(t)
|
||||||
catalog := catalogFromRegistries(components.registries)
|
catalog := catalogFromRegistries(components.registries)
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
@@ -95,7 +95,7 @@ func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(
|
|||||||
if err := os.WriteFile(path, []byte(test.content), 0o600); err != nil {
|
if err := os.WriteFile(path, []byte(test.content), 0o600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -116,9 +116,9 @@ func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func npcInteractionProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
func npcOccurrenceProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
||||||
profile := pipeline.PipelineProfile{
|
profile := pipeline.PipelineProfile{
|
||||||
ID: "dnd-npc-interactions",
|
ID: "dnd-npc-occurrences",
|
||||||
Input: pipeline.Binding("seriatim"),
|
Input: pipeline.Binding("seriatim"),
|
||||||
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
||||||
Output: pipeline.Binding("json"),
|
Output: pipeline.Binding("json"),
|
||||||
@@ -126,8 +126,8 @@ func npcInteractionProfile(reference pipeline.ReferenceSource) pipeline.Pipeline
|
|||||||
{ID: "npc-registry", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
{ID: "npc-registry", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
"npc_registry": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
"npc_registry": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
||||||
}},
|
}},
|
||||||
{ID: "interactions", References: map[string]pipeline.ReferenceSource{"npc_registry": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
{ID: "occurrences", References: map[string]pipeline.ReferenceSource{"npc_registry": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
"interactions": {Extract: pipeline.Binding(interactionextract.Key), Normalize: pipeline.Binding(interactionnormalize.Key)},
|
"occurrences": {Extract: pipeline.Binding(occurrenceextract.Key), Normalize: pipeline.Binding(occurrencenormalize.Key)},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
|||||||
t.Fatalf("materialize maintained example references for %q: %v", pipelineID, err)
|
t.Fatalf("materialize maintained example references for %q: %v", pipelineID, err)
|
||||||
}
|
}
|
||||||
if example.name == "complete" {
|
if example.name == "complete" {
|
||||||
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-events,locations,npc_registry,scene-descriptions|extract-events:combat-turns,location-occurrences,npc-interactions,spells|track-enemies:enemy-events" {
|
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-events,locations,npc_registry,scene-descriptions|extract-events:combat-turns,location-occurrences,npc-occurrences,spells|track-enemies:enemy-events" {
|
||||||
t.Fatalf("complete example steps and lanes = %v, want the documented D&D extractor composition", got)
|
t.Fatalf("complete example steps and lanes = %v, want the documented D&D extractor composition", got)
|
||||||
}
|
}
|
||||||
locationLane := referenceContractLane(t, materialized, "locations")
|
locationLane := referenceContractLane(t, materialized, "locations")
|
||||||
@@ -102,7 +102,7 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
|||||||
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
"npc_registry": {step: "describe-session", lane: "npc_registry"},
|
||||||
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
||||||
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
||||||
"npc_interactions": {step: "extract-events", lane: "npc-interactions"},
|
"npc_occurrences": {step: "extract-events", lane: "npc-occurrences"},
|
||||||
} {
|
} {
|
||||||
binding, found := generatedReferenceBinding(enemyEventLane.ExtractReferences.Bindings, slot)
|
binding, found := generatedReferenceBinding(enemyEventLane.ExtractReferences.Bindings, slot)
|
||||||
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func TestProductionCatalogCoversMaintainedConfigurations(t *testing.T) {
|
|||||||
{stage: pipeline.StageExtract, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageExtract, key: "dnd/combat-turns", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: "dnd/combat-turns", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageExtract, key: "dnd/item-events", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: "dnd/item-events", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageExtract, key: "dnd/npc-interactions", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: "dnd/npc-occurrences", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageExtract, key: "dnd/scene-descriptions", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: "dnd/scene-descriptions", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageExtract, key: enemyeventextract.Key, want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageExtract, key: enemyeventextract.Key, want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageMerge, key: "appendorder", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageMerge, key: "appendorder", want: contracts.ExecutionClassDeterministic},
|
||||||
@@ -174,7 +174,7 @@ func TestProductionCatalogCoversMaintainedConfigurations(t *testing.T) {
|
|||||||
{stage: pipeline.StageNormalize, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
{stage: pipeline.StageNormalize, key: "dnd/npc-registry", want: contracts.ExecutionClassLLMBacked},
|
||||||
{stage: pipeline.StageNormalize, key: "dnd/combat-turns", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageNormalize, key: "dnd/combat-turns", want: contracts.ExecutionClassDeterministic},
|
||||||
{stage: pipeline.StageNormalize, key: "dnd/item-events", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageNormalize, key: "dnd/item-events", want: contracts.ExecutionClassDeterministic},
|
||||||
{stage: pipeline.StageNormalize, key: "dnd/npc-interactions", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageNormalize, key: "dnd/npc-occurrences", want: contracts.ExecutionClassDeterministic},
|
||||||
{stage: pipeline.StageNormalize, key: "dnd/scene-descriptions", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageNormalize, key: "dnd/scene-descriptions", want: contracts.ExecutionClassDeterministic},
|
||||||
{stage: pipeline.StageNormalize, key: enemyeventnormalize.Key, want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageNormalize, key: enemyeventnormalize.Key, want: contracts.ExecutionClassDeterministic},
|
||||||
{stage: pipeline.StageOutput, key: "json", want: contracts.ExecutionClassDeterministic},
|
{stage: pipeline.StageOutput, key: "json", want: contracts.ExecutionClassDeterministic},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package npcinteractions encodes durable D&D NPC occurrence artifacts.
|
// Package npcoccurrences encodes durable D&D NPC occurrence artifacts.
|
||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -111,7 +111,7 @@ func TestCodecStrictlyRejectsMalformedUnknownAndTrailingJSON(t *testing.T) {
|
|||||||
for _, test := range []struct{ name, raw, want string }{
|
for _, test := range []struct{ name, raw, want string }{
|
||||||
{"malformed", `{`, "decode dnd npc occurrence list"},
|
{"malformed", `{`, "decode dnd npc occurrence list"},
|
||||||
{"unknown top-level", `{"occurrences":[],"unexpected":true}`, "unknown field"},
|
{"unknown top-level", `{"occurrences":[],"unexpected":true}`, "unknown field"},
|
||||||
{"unknown interaction field", strings.Replace(validJSON, `"kind":"dialogue"`, `"kind":"dialogue","unexpected":true`, 1), "unknown field"},
|
{"unknown occurrence field", strings.Replace(validJSON, `"kind":"dialogue"`, `"kind":"dialogue","unexpected":true`, 1), "unknown field"},
|
||||||
{"unknown source reference field", strings.Replace(validJSON, `"end_unit_id":1`, `"end_unit_id":1,"unexpected":true`, 1), "unknown field"},
|
{"unknown source reference field", strings.Replace(validJSON, `"end_unit_id":1`, `"end_unit_id":1,"unexpected":true`, 1), "unknown field"},
|
||||||
{"trailing", `{"occurrences":[]} {}`, "multiple JSON values"},
|
{"trailing", `{"occurrences":[]} {}`, "multiple JSON values"},
|
||||||
} {
|
} {
|
||||||
@@ -147,7 +147,7 @@ func TestCodecRejectsRequiredShapeEnumAndReferenceBoundaries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCodecAcceptsEveryInteractionKind(t *testing.T) {
|
func TestCodecAcceptsEveryOccurrenceKind(t *testing.T) {
|
||||||
for _, kind := range []dnd.NPCOccurrenceKind{
|
for _, kind := range []dnd.NPCOccurrenceKind{
|
||||||
dnd.NPCOccurrenceKindMentioned,
|
dnd.NPCOccurrenceKindMentioned,
|
||||||
dnd.NPCOccurrenceKindNoncombatPresence,
|
dnd.NPCOccurrenceKindNoncombatPresence,
|
||||||
@@ -49,8 +49,8 @@ func TestExtractMapsEnemyEventsInSourceOrder(t *testing.T) {
|
|||||||
if got := string(request.Inputs[CombatTurnReferenceSlot].Content); !strings.Contains(got, `"actor":"Ashfang"`) || strings.Contains(got, "source_ref") {
|
if got := string(request.Inputs[CombatTurnReferenceSlot].Content); !strings.Contains(got, `"actor":"Ashfang"`) || strings.Contains(got, "source_ref") {
|
||||||
t.Fatalf("combat grounding = %s", got)
|
t.Fatalf("combat grounding = %s", got)
|
||||||
}
|
}
|
||||||
if got := string(request.Inputs[NPCInteractionReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
if got := string(request.Inputs[NPCOccurrenceReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||||
t.Fatalf("interaction grounding = %s", got)
|
t.Fatalf("occurrence grounding = %s", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||||
@@ -27,7 +27,7 @@ const (
|
|||||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||||
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
||||||
CombatTurnReferenceSlot = "combat_turns"
|
CombatTurnReferenceSlot = "combat_turns"
|
||||||
NPCInteractionReferenceSlot = "npc_interactions"
|
NPCOccurrenceReferenceSlot = "npc_occurrences"
|
||||||
ReferenceMaxBytes = 1048576
|
ReferenceMaxBytes = 1048576
|
||||||
promptProjectionMediaType = "application/json"
|
promptProjectionMediaType = "application/json"
|
||||||
)
|
)
|
||||||
@@ -67,10 +67,10 @@ func referenceSlots() []contracts.ReferenceSlot {
|
|||||||
MaxBytes: ReferenceMaxBytes,
|
MaxBytes: ReferenceMaxBytes,
|
||||||
},
|
},
|
||||||
contracts.ReferenceSlot{
|
contracts.ReferenceSlot{
|
||||||
Name: NPCInteractionReferenceSlot,
|
Name: NPCOccurrenceReferenceSlot,
|
||||||
Description: "Required NPC-interaction artifact used only as source-free enemy-event grounding.",
|
Description: "Required NPC-occurrence artifact used only as source-free enemy-event grounding.",
|
||||||
Required: true,
|
Required: true,
|
||||||
AcceptedMediaTypes: []string{interactioncodec.MediaType},
|
AcceptedMediaTypes: []string{occurrencecodec.MediaType},
|
||||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCOccurrenceListKind},
|
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCOccurrenceListKind},
|
||||||
MaxBytes: ReferenceMaxBytes,
|
MaxBytes: ReferenceMaxBytes,
|
||||||
},
|
},
|
||||||
@@ -87,13 +87,13 @@ type groundingResolver struct {
|
|||||||
scenes *sceneregistry.Resolver
|
scenes *sceneregistry.Resolver
|
||||||
|
|
||||||
combatTurns *contracts.LLMInputMaterial
|
combatTurns *contracts.LLMInputMaterial
|
||||||
npcInteractions *contracts.LLMInputMaterial
|
npcOccurrences *contracts.LLMInputMaterial
|
||||||
}
|
}
|
||||||
|
|
||||||
type grounding struct {
|
type grounding struct {
|
||||||
npcInput contracts.LLMInputMaterial
|
npcInput contracts.LLMInputMaterial
|
||||||
combatTurnInput contracts.LLMInputMaterial
|
combatTurnInput contracts.LLMInputMaterial
|
||||||
npcInteractionInput contracts.LLMInputMaterial
|
npcOccurrenceInput contracts.LLMInputMaterial
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver, error) {
|
func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver, error) {
|
||||||
@@ -109,7 +109,7 @@ func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
npcInteractions, err := prepareNPCInteractionInput(references)
|
npcOccurrences, err := prepareNPCOccurrenceInput(references)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver
|
|||||||
npcs: npcs,
|
npcs: npcs,
|
||||||
scenes: scenes,
|
scenes: scenes,
|
||||||
combatTurns: combatTurns,
|
combatTurns: combatTurns,
|
||||||
npcInteractions: npcInteractions,
|
npcOccurrences: npcOccurrences,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +136,14 @@ func (r *groundingResolver) Resolve(references contracts.ReferenceSet) (groundin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return grounding{}, err
|
return grounding{}, err
|
||||||
}
|
}
|
||||||
npcInteractions, err := resolveInput(references, NPCInteractionReferenceSlot, r.npcInteractions, prepareNPCInteractionInput)
|
npcOccurrences, err := resolveInput(references, NPCOccurrenceReferenceSlot, r.npcOccurrences, prepareNPCOccurrenceInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return grounding{}, err
|
return grounding{}, err
|
||||||
}
|
}
|
||||||
return grounding{
|
return grounding{
|
||||||
npcInput: npcs.PromptInput(),
|
npcInput: npcs.PromptInput(),
|
||||||
combatTurnInput: combatTurns,
|
combatTurnInput: combatTurns,
|
||||||
npcInteractionInput: npcInteractions,
|
npcOccurrenceInput: npcOccurrences,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func (g grounding) PromptInputs() contracts.LLMInputSet {
|
|||||||
return contracts.LLMInputSet{
|
return contracts.LLMInputSet{
|
||||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||||
NPCInteractionReferenceSlot: g.npcInteractionInput.Clone(),
|
NPCOccurrenceReferenceSlot: g.npcOccurrenceInput.Clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,22 +212,22 @@ func prepareCombatTurnInput(references contracts.ReferenceSet) (*contracts.LLMIn
|
|||||||
return newPromptInput(CombatTurnReferenceSlot, content), nil
|
return newPromptInput(CombatTurnReferenceSlot, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareNPCInteractionInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
func prepareNPCOccurrenceInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||||
item, ok, err := referenceItem(references, NPCInteractionReferenceSlot, interactioncodec.MediaType)
|
item, ok, err := referenceItem(references, NPCOccurrenceReferenceSlot, occurrencecodec.MediaType)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
value, err := interactioncodec.New().Decode(item.Content)
|
value, err := occurrencecodec.New().Decode(item.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decode NPC-interaction grounding: invalid approved NPC-interaction JSON")
|
return nil, fmt.Errorf("decode NPC-occurrence grounding: invalid approved NPC-occurrence JSON")
|
||||||
}
|
}
|
||||||
content, err := json.Marshal(struct {
|
content, err := json.Marshal(struct {
|
||||||
Interactions []npcInteractionProjection `json:"npc_interactions"`
|
Occurrences []npcOccurrenceProjection `json:"npc_occurrences"`
|
||||||
}{Interactions: projectNPCInteractions(value.Occurrences)})
|
}{Occurrences: projectNPCOccurrences(value.Occurrences)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("encode NPC-interaction grounding: %w", err)
|
return nil, fmt.Errorf("encode NPC-occurrence grounding: %w", err)
|
||||||
}
|
}
|
||||||
return newPromptInput(NPCInteractionReferenceSlot, content), nil
|
return newPromptInput(NPCOccurrenceReferenceSlot, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func referenceItem(references contracts.ReferenceSet, slotName, expectedMediaType string) (contracts.ReferenceItem, bool, error) {
|
func referenceItem(references contracts.ReferenceSet, slotName, expectedMediaType string) (contracts.ReferenceItem, bool, error) {
|
||||||
@@ -271,16 +271,16 @@ func projectCombatTurns(turns []dnd.CombatTurn) []combatTurnProjection {
|
|||||||
return projection
|
return projection
|
||||||
}
|
}
|
||||||
|
|
||||||
type npcInteractionProjection struct {
|
type npcOccurrenceProjection struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Kind dnd.NPCOccurrenceKind `json:"kind"`
|
Kind dnd.NPCOccurrenceKind `json:"kind"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func projectNPCInteractions(occurrences []dnd.NPCOccurrence) []npcInteractionProjection {
|
func projectNPCOccurrences(occurrences []dnd.NPCOccurrence) []npcOccurrenceProjection {
|
||||||
projection := make([]npcInteractionProjection, 0, len(occurrences))
|
projection := make([]npcOccurrenceProjection, 0, len(occurrences))
|
||||||
for _, occurrence := range occurrences {
|
for _, occurrence := range occurrences {
|
||||||
if occurrence.Kind == dnd.NPCOccurrenceKindCombatOpponent {
|
if occurrence.Kind == dnd.NPCOccurrenceKindCombatOpponent {
|
||||||
projection = append(projection, npcInteractionProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
projection = append(projection, npcOccurrenceProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return projection
|
return projection
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||||
@@ -36,7 +36,7 @@ func TestReferenceSlotsDescribeRequiredTypedArtifacts(t *testing.T) {
|
|||||||
{NPCRegistryReferenceSlot, dnd.NPCRegistryKind},
|
{NPCRegistryReferenceSlot, dnd.NPCRegistryKind},
|
||||||
{SceneDescriptionReferenceSlot, dnd.SceneDescriptionListKind},
|
{SceneDescriptionReferenceSlot, dnd.SceneDescriptionListKind},
|
||||||
{CombatTurnReferenceSlot, dnd.CombatTurnListKind},
|
{CombatTurnReferenceSlot, dnd.CombatTurnListKind},
|
||||||
{NPCInteractionReferenceSlot, dnd.NPCOccurrenceListKind},
|
{NPCOccurrenceReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||||
} {
|
} {
|
||||||
slot, ok := byName[want.name]
|
slot, ok := byName[want.name]
|
||||||
if !ok || !slot.Required || slot.MaxBytes != ReferenceMaxBytes || len(slot.AcceptedMediaTypes) != 1 || slot.AcceptedMediaTypes[0] != "application/json" || len(slot.AcceptedArtifactKinds) != 1 || slot.AcceptedArtifactKinds[0] != want.kind {
|
if !ok || !slot.Required || slot.MaxBytes != ReferenceMaxBytes || len(slot.AcceptedMediaTypes) != 1 || slot.AcceptedMediaTypes[0] != "application/json" || len(slot.AcceptedArtifactKinds) != 1 || slot.AcceptedArtifactKinds[0] != want.kind {
|
||||||
@@ -63,7 +63,7 @@ func TestGroundingProducesExactSourceFreePromptInputs(t *testing.T) {
|
|||||||
want := map[string]string{
|
want := map[string]string{
|
||||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||||
NPCInteractionReferenceSlot: `{"npc_interactions":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
NPCOccurrenceReferenceSlot: `{"npc_occurrences":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||||
}
|
}
|
||||||
if len(inputs) != len(want) {
|
if len(inputs) != len(want) {
|
||||||
t.Fatalf("PromptInputs() = %#v", inputs)
|
t.Fatalf("PromptInputs() = %#v", inputs)
|
||||||
@@ -153,7 +153,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
|||||||
{NPCRegistryReferenceSlot, "NPC registry"},
|
{NPCRegistryReferenceSlot, "NPC registry"},
|
||||||
{SceneDescriptionReferenceSlot, "scene descriptions"},
|
{SceneDescriptionReferenceSlot, "scene descriptions"},
|
||||||
{CombatTurnReferenceSlot, CombatTurnReferenceSlot},
|
{CombatTurnReferenceSlot, CombatTurnReferenceSlot},
|
||||||
{NPCInteractionReferenceSlot, NPCInteractionReferenceSlot},
|
{NPCOccurrenceReferenceSlot, NPCOccurrenceReferenceSlot},
|
||||||
} {
|
} {
|
||||||
t.Run("missing "+test.slot, func(t *testing.T) {
|
t.Run("missing "+test.slot, func(t *testing.T) {
|
||||||
resolver, err := newGroundingResolver(withoutSlot(valid, test.slot))
|
resolver, err := newGroundingResolver(withoutSlot(valid, test.slot))
|
||||||
@@ -176,7 +176,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
|||||||
set contracts.ReferenceSet
|
set contracts.ReferenceSet
|
||||||
}{
|
}{
|
||||||
{"multiple items", replaceSlot(valid, CombatTurnReferenceSlot, contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{{MediaType: "application/json"}, {MediaType: "application/json"}}})},
|
{"multiple items", replaceSlot(valid, CombatTurnReferenceSlot, contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{{MediaType: "application/json"}, {MediaType: "application/json"}}})},
|
||||||
{"malformed durable content", replaceItem(valid, NPCInteractionReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
{"malformed durable content", replaceItem(valid, NPCOccurrenceReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
||||||
{"wrong media type", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "text/plain", Content: valid.Slots[CombatTurnReferenceSlot].Items[0].Content})},
|
{"wrong media type", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "text/plain", Content: valid.Slots[CombatTurnReferenceSlot].Items[0].Content})},
|
||||||
{"oversize", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: make([]byte, ReferenceMaxBytes+1)})},
|
{"oversize", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: make([]byte, ReferenceMaxBytes+1)})},
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
interactionContent, err := interactioncodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
interactionContent, err := occurrencecodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||||
{NPCID: identity.DeriveID(enemy), Name: enemy, Kind: dnd.NPCOccurrenceKindCombatOpponent, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 1, EndUnitID: 1}}},
|
{NPCID: identity.DeriveID(enemy), Name: enemy, Kind: dnd.NPCOccurrenceKindCombatOpponent, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 1, EndUnitID: 1}}},
|
||||||
{NPCID: identity.DeriveID("Aria"), Name: "Aria", Kind: dnd.NPCOccurrenceKindCombatAlly, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 2, EndUnitID: 2}}},
|
{NPCID: identity.DeriveID("Aria"), Name: "Aria", Kind: dnd.NPCOccurrenceKindCombatAlly, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 2, EndUnitID: 2}}},
|
||||||
}})
|
}})
|
||||||
@@ -231,7 +231,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
|||||||
NPCRegistryReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCRegistryReferenceSlot, npcContent)}},
|
NPCRegistryReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCRegistryReferenceSlot, npcContent)}},
|
||||||
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
|
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
|
||||||
CombatTurnReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(CombatTurnReferenceSlot, turnContent)}},
|
CombatTurnReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(CombatTurnReferenceSlot, turnContent)}},
|
||||||
NPCInteractionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCInteractionReferenceSlot, interactionContent)}},
|
NPCOccurrenceReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCOccurrenceReferenceSlot, interactionContent)}},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func TestRegisterPromptAssetsAndPrepareEnemyEventPrompt(t *testing.T) {
|
|||||||
|
|
||||||
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
||||||
engine := newEnemyEventPromptEngine(t)
|
engine := newEnemyEventPromptEngine(t)
|
||||||
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_interactions"} {
|
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_occurrences"} {
|
||||||
t.Run(inputName, func(t *testing.T) {
|
t.Run(inputName, func(t *testing.T) {
|
||||||
inputs := enemyEventPromptInputs()
|
inputs := enemyEventPromptInputs()
|
||||||
delete(inputs, inputName)
|
delete(inputs, inputName)
|
||||||
@@ -85,6 +85,6 @@ func enemyEventPromptInputs() map[string]promptkit.ArtifactRef {
|
|||||||
"glossary": promptkit.Inline("enemy-glossary"),
|
"glossary": promptkit.Inline("enemy-glossary"),
|
||||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
"npc_occurrences": promptkit.Inline(`{"npc_occurrences":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package npcinteractions
|
|
||||||
|
|
||||||
type extractionResponse struct {
|
|
||||||
Occurrences []occurrenceResponse `json:"occurrences"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type occurrenceResponse struct {
|
|
||||||
NPCID string `json:"npc_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
SourceRefs []interactionSourceRefResponse `json:"source_refs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type interactionSourceRefResponse struct {
|
|
||||||
StartUnitID int `json:"start_unit_id"`
|
|
||||||
EndUnitID int `json:"end_unit_id"`
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
@@ -66,7 +66,7 @@ func canonicalOccurrenceList(response extractionResponse, sourceID string) dnd.N
|
|||||||
return dnd.NPCOccurrenceList{Occurrences: occurrences}
|
return dnd.NPCOccurrenceList{Occurrences: occurrences}
|
||||||
}
|
}
|
||||||
|
|
||||||
func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) []source.SourceRef {
|
func canonicalSourceRefs(refs []occurrenceSourceRefResponse, sourceID string) []source.SourceRef {
|
||||||
if refs == nil {
|
if refs == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -77,13 +77,13 @@ func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) [
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func occurrenceResponseRefs(refs []source.SourceRef) []interactionSourceRefResponse {
|
func occurrenceResponseRefs(refs []source.SourceRef) []occurrenceSourceRefResponse {
|
||||||
if refs == nil {
|
if refs == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
values := make([]interactionSourceRefResponse, len(refs))
|
values := make([]occurrenceSourceRefResponse, len(refs))
|
||||||
for index, ref := range refs {
|
for index, ref := range refs {
|
||||||
values[index] = interactionSourceRefResponse{StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
values[index] = occurrenceSourceRefResponse{StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "dnd/npc-interactions"
|
Key = "dnd/npc-occurrences"
|
||||||
mappingPolicy = "dnd.npc_interactions.extract_mapping.v2"
|
mappingPolicy = "dnd.npc_occurrences.extract_mapping.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -28,21 +28,21 @@ var requiredCapabilities = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var providedCapabilities = []string{
|
var providedCapabilities = []string{
|
||||||
"dnd.npc_interactions",
|
"dnd.npc_occurrences",
|
||||||
}
|
}
|
||||||
|
|
||||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
Glossary: "Optional campaign glossary reference material used only for occurrence disambiguation.",
|
||||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
Party: "Optional party roster reference material used only for occurrence disambiguation.",
|
||||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||||
Roster: "Deprecated alias for party roster reference material used only for interaction disambiguation.",
|
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||||
}
|
}
|
||||||
|
|
||||||
func referenceSlots() []contracts.ReferenceSlot {
|
func referenceSlots() []contracts.ReferenceSlot {
|
||||||
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
||||||
slots = append(slots, contracts.ReferenceSlot{
|
slots = append(slots, contracts.ReferenceSlot{
|
||||||
Name: NPCRegistryReferenceSlot,
|
Name: NPCRegistryReferenceSlot,
|
||||||
Description: "Required normalized NPC registry used only for interaction identity grounding, never as interaction evidence.",
|
Description: "Required normalized NPC registry used only for occurrence identity grounding, never as occurrence evidence.",
|
||||||
Required: true,
|
Required: true,
|
||||||
AcceptedMediaTypes: []string{"application/json"},
|
AcceptedMediaTypes: []string{"application/json"},
|
||||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
||||||
@@ -225,5 +225,5 @@ func DecodeOptions(options map[string]any) (Options, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractorErrorf(format string, args ...any) error {
|
func extractorErrorf(format string, args ...any) error {
|
||||||
return fmt.Errorf("dnd NPC interactions extractor: "+format, args...)
|
return fmt.Errorf("dnd NPC occurrences extractor: "+format, args...)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -17,14 +17,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||||
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: interactionRefs(30, 30)},
|
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: occurrenceRefs(30, 30)},
|
||||||
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: interactionRefs(20, 20)},
|
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: occurrenceRefs(20, 20)},
|
||||||
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: interactionRefs(5, 5)},
|
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: occurrenceRefs(5, 5)},
|
||||||
{NPCID: identity.DeriveID("Speaker"), Name: "Speaker", Kind: "dialogue", SourceRefs: append(interactionRefs(2, 2), interactionRefs(2, 2)...)},
|
{NPCID: identity.DeriveID("Speaker"), Name: "Speaker", Kind: "dialogue", SourceRefs: append(occurrenceRefs(2, 2), occurrenceRefs(2, 2)...)},
|
||||||
{NPCID: identity.DeriveID("Present"), Name: "Present", Kind: "noncombat_presence", SourceRefs: interactionRefs(7, 7)},
|
{NPCID: identity.DeriveID("Present"), Name: "Present", Kind: "noncombat_presence", SourceRefs: occurrenceRefs(7, 7)},
|
||||||
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: interactionRefs(10, 10)},
|
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||||
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: interactionRefs(0, 0)},
|
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: occurrenceRefs(0, 0)},
|
||||||
}}}
|
}}}
|
||||||
references := requiredRegistryReferences(t, "Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid")
|
references := requiredRegistryReferences(t, "Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid")
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
@@ -34,10 +34,10 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Extract() error = %v", err)
|
t.Fatalf("Extract() error = %v", err)
|
||||||
}
|
}
|
||||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
||||||
t.Fatalf("interaction order = %#v", got)
|
t.Fatalf("occurrence order = %#v", got)
|
||||||
}
|
}
|
||||||
if got := interactionKinds(result.Value); !reflect.DeepEqual(got, []dnd.NPCOccurrenceKind{
|
if got := occurrenceKinds(result.Value); !reflect.DeepEqual(got, []dnd.NPCOccurrenceKind{
|
||||||
dnd.NPCOccurrenceKindMentioned,
|
dnd.NPCOccurrenceKindMentioned,
|
||||||
dnd.NPCOccurrenceKindDialogue,
|
dnd.NPCOccurrenceKindDialogue,
|
||||||
dnd.NPCOccurrenceKindNoncombatPresence,
|
dnd.NPCOccurrenceKindNoncombatPresence,
|
||||||
@@ -46,7 +46,7 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
|||||||
dnd.NPCOccurrenceKindOther,
|
dnd.NPCOccurrenceKindOther,
|
||||||
"unsupported",
|
"unsupported",
|
||||||
}) {
|
}) {
|
||||||
t.Fatalf("interaction kinds = %#v", got)
|
t.Fatalf("occurrence kinds = %#v", got)
|
||||||
}
|
}
|
||||||
if refs := result.Value.Occurrences[1].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "session-alpha", StartUnitID: 2, EndUnitID: 2}}) {
|
if refs := result.Value.Occurrences[1].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "session-alpha", StartUnitID: 2, EndUnitID: 2}}) {
|
||||||
t.Fatalf("canonical source refs = %#v", refs)
|
t.Fatalf("canonical source refs = %#v", refs)
|
||||||
@@ -59,16 +59,16 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
func TestExtractUsesDocumentOrderForReferencesAndOccurrences(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||||
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)},
|
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)},
|
||||||
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []interactionSourceRefResponse{
|
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []occurrenceSourceRefResponse{
|
||||||
{StartUnitID: 10, EndUnitID: 10},
|
{StartUnitID: 10, EndUnitID: 10},
|
||||||
{StartUnitID: 30, EndUnitID: 30},
|
{StartUnitID: 30, EndUnitID: 30},
|
||||||
{StartUnitID: 30, EndUnitID: 30},
|
{StartUnitID: 30, EndUnitID: 30},
|
||||||
{StartUnitID: 999, EndUnitID: 0},
|
{StartUnitID: 999, EndUnitID: 0},
|
||||||
}},
|
}},
|
||||||
{NPCID: identity.DeriveID("Second"), Name: "Second", Kind: "other", SourceRefs: interactionRefs(30, 30)},
|
{NPCID: identity.DeriveID("Second"), Name: "Second", Kind: "other", SourceRefs: occurrenceRefs(30, 30)},
|
||||||
}}}
|
}}}
|
||||||
references := requiredRegistryReferences(t, "Later", "First", "Second")
|
references := requiredRegistryReferences(t, "Later", "First", "Second")
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
@@ -81,16 +81,16 @@ func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Extract() error = %v", err)
|
t.Fatalf("Extract() error = %v", err)
|
||||||
}
|
}
|
||||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
||||||
t.Fatalf("interaction order = %#v, want document chronology with stable equal-evidence ties", got)
|
t.Fatalf("occurrence order = %#v, want document chronology with stable equal-evidence ties", got)
|
||||||
}
|
}
|
||||||
refs := result.Value.Occurrences[0].SourceRefs
|
refs := result.Value.Occurrences[0].SourceRefs
|
||||||
if got := []int{refs[0].StartUnitID, refs[1].StartUnitID, refs[2].StartUnitID}; !reflect.DeepEqual(got, []int{30, 10, 999}) {
|
if got := []int{refs[0].StartUnitID, refs[1].StartUnitID, refs[2].StartUnitID}; !reflect.DeepEqual(got, []int{30, 10, 999}) {
|
||||||
t.Fatalf("source refs = %#v, want document order with exact duplicate removed", refs)
|
t.Fatalf("source refs = %#v, want document order with exact duplicate removed", refs)
|
||||||
}
|
}
|
||||||
refs[0].StartUnitID = 777
|
refs[0].StartUnitID = 777
|
||||||
for _, interaction := range client.response.Occurrences {
|
for _, occurrence := range client.response.Occurrences {
|
||||||
for _, ref := range interaction.SourceRefs {
|
for _, ref := range occurrence.SourceRefs {
|
||||||
if ref.StartUnitID == 777 {
|
if ref.StartUnitID == 777 {
|
||||||
t.Fatal("result source references alias the model response")
|
t.Fatal("result source references alias the model response")
|
||||||
}
|
}
|
||||||
@@ -102,14 +102,14 @@ func TestNewRequiresLLMAndRejectsAmbiguousReferenceSets(t *testing.T) {
|
|||||||
if _, err := New(nil, Options{}); err == nil || !strings.Contains(err.Error(), "LLM client") {
|
if _, err := New(nil, Options{}); err == nil || !strings.Contains(err.Error(), "LLM client") {
|
||||||
t.Fatalf("New(nil) error = %v", err)
|
t.Fatalf("New(nil) error = %v", err)
|
||||||
}
|
}
|
||||||
if _, err := New(&fakeInteractionsLLMClient{}, Options{}, contracts.ReferenceSet{}, contracts.ReferenceSet{}); err == nil || !strings.Contains(err.Error(), "at most one reference set") {
|
if _, err := New(&fakeOccurrencesLLMClient{}, Options{}, contracts.ReferenceSet{}, contracts.ReferenceSet{}); err == nil || !strings.Contains(err.Error(), "at most one reference set") {
|
||||||
t.Fatalf("New() error = %v", err)
|
t.Fatalf("New() error = %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||||
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: interactionRefs(10, 10),
|
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10),
|
||||||
}}}}
|
}}}}
|
||||||
references := requiredRegistryReferences(t, "Mira Thorn", "Hooded Guard")
|
references := requiredRegistryReferences(t, "Mira Thorn", "Hooded Guard")
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
@@ -130,7 +130,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
|||||||
if strings.Contains(string(request.Inputs["transcript"].Content), "other-session") {
|
if strings.Contains(string(request.Inputs["transcript"].Content), "other-session") {
|
||||||
t.Fatal("transcript input contains registry evidence")
|
t.Fatal("transcript input contains registry evidence")
|
||||||
}
|
}
|
||||||
metadata, err := json.Marshal(newExtractor(t, &fakeInteractionsLLMClient{}, references).ManifestMetadata())
|
metadata, err := json.Marshal(newExtractor(t, &fakeOccurrencesLLMClient{}, references).ManifestMetadata())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractRequiresBoundRegistryBeforeLLMCall(t *testing.T) {
|
func TestExtractRequiresBoundRegistryBeforeLLMCall(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{}
|
client := &fakeOccurrencesLLMClient{}
|
||||||
if _, err := newExtractor(t, client).Extract(context.Background(), extractionRequest()); err == nil || !strings.Contains(err.Error(), "NPC registry reference is required") {
|
if _, err := newExtractor(t, client).Extract(context.Background(), extractionRequest()); err == nil || !strings.Contains(err.Error(), "NPC registry reference is required") {
|
||||||
t.Fatalf("Extract() error = %v, want required registry failure", err)
|
t.Fatalf("Extract() error = %v, want required registry failure", err)
|
||||||
}
|
}
|
||||||
@@ -169,11 +169,11 @@ func TestExtractRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
|||||||
occurrence occurrenceResponse
|
occurrence occurrenceResponse
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"unknown ID", occurrenceResponse{NPCID: "npc:unknown", Name: "Mira Thorn", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)}, "npc_id is not in the NPC registry"},
|
{"unknown ID", occurrenceResponse{NPCID: "npc:unknown", Name: "Mira Thorn", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)}, "npc_id is not in the NPC registry"},
|
||||||
{"mismatched name", occurrenceResponse{NPCID: identity.DeriveID("Mira Thorn"), Name: "Hooded Guard", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)}, "name does not match npc_id"},
|
{"mismatched name", occurrenceResponse{NPCID: identity.DeriveID("Mira Thorn"), Name: "Hooded Guard", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)}, "name does not match npc_id"},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{test.occurrence}}}
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{test.occurrence}}}
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
req.References = references
|
req.References = references
|
||||||
if _, err := newExtractor(t, client, references).Extract(context.Background(), req); err == nil || !strings.Contains(err.Error(), test.want) {
|
if _, err := newExtractor(t, client, references).Extract(context.Background(), req); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||||
@@ -184,7 +184,7 @@ func TestExtractRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractResolvesGeneratedRegistryAtOperationTime(t *testing.T) {
|
func TestExtractResolvesGeneratedRegistryAtOperationTime(t *testing.T) {
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
req.References = references
|
req.References = references
|
||||||
@@ -212,12 +212,12 @@ func TestExtractAcceptsEmptyBoundRegistryAndEmptyResponse(t *testing.T) {
|
|||||||
Items: []contracts.ReferenceItem{{SlotName: NPCRegistryReferenceSlot, MediaType: npccodec.MediaType, Content: content}},
|
Items: []contracts.ReferenceItem{{SlotName: NPCRegistryReferenceSlot, MediaType: npccodec.MediaType, Content: content}},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||||
req := extractionRequest()
|
req := extractionRequest()
|
||||||
req.References = references
|
req.References = references
|
||||||
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
||||||
if err != nil || result.Value.Occurrences == nil || len(result.Value.Occurrences) != 0 {
|
if err != nil || result.Value.Occurrences == nil || len(result.Value.Occurrences) != 0 {
|
||||||
t.Fatalf("Extract() = %#v, %v; want present empty interactions", result, err)
|
t.Fatalf("Extract() = %#v, %v; want present empty occurrences", result, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ func TestExtractRejectsInvalidRequestsAndProviderFailures(t *testing.T) {
|
|||||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||||
valid := extractionRequest()
|
valid := extractionRequest()
|
||||||
valid.References = references
|
valid.References = references
|
||||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||||
var nilExtractor *Extractor
|
var nilExtractor *Extractor
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -244,14 +244,14 @@ func TestExtractRejectsInvalidRequestsAndProviderFailures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if _, err := newExtractor(t, &fakeInteractionsLLMClient{err: errors.New("provider unavailable")}, references).Extract(context.Background(), valid); err == nil || !strings.Contains(err.Error(), "provider unavailable") {
|
if _, err := newExtractor(t, &fakeOccurrencesLLMClient{err: errors.New("provider unavailable")}, references).Extract(context.Background(), valid); err == nil || !strings.Contains(err.Error(), "provider unavailable") {
|
||||||
t.Fatalf("provider error = %v", err)
|
t.Fatalf("provider error = %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||||
got := ModuleSpec()
|
got := ModuleSpec()
|
||||||
if got.Key != Key || got.Stage != pipeline.StageExtract || got.ArtifactKind != dnd.NPCOccurrenceListKind || !reflect.DeepEqual(got.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(got.Provides, []string{"dnd.npc_interactions"}) {
|
if got.Key != Key || got.Stage != pipeline.StageExtract || got.ArtifactKind != dnd.NPCOccurrenceListKind || !reflect.DeepEqual(got.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(got.Provides, []string{"dnd.npc_occurrences"}) {
|
||||||
t.Fatalf("ModuleSpec() = %#v", got)
|
t.Fatalf("ModuleSpec() = %#v", got)
|
||||||
}
|
}
|
||||||
var registrySlot contracts.ReferenceSlot
|
var registrySlot contracts.ReferenceSlot
|
||||||
@@ -267,9 +267,9 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
|||||||
if ModuleSpec().ReferenceSlots[0].AcceptedMediaTypes[0] == "changed" {
|
if ModuleSpec().ReferenceSlots[0].AcceptedMediaTypes[0] == "changed" {
|
||||||
t.Fatal("ModuleSpec() returned mutable reference slots")
|
t.Fatal("ModuleSpec() returned mutable reference slots")
|
||||||
}
|
}
|
||||||
extractorSlots := newExtractor(t, &fakeInteractionsLLMClient{}).ReferenceSlots()
|
extractorSlots := newExtractor(t, &fakeOccurrencesLLMClient{}).ReferenceSlots()
|
||||||
extractorSlots[0].AcceptedMediaTypes[0] = "changed"
|
extractorSlots[0].AcceptedMediaTypes[0] = "changed"
|
||||||
if newExtractor(t, &fakeInteractionsLLMClient{}).ReferenceSlots()[0].AcceptedMediaTypes[0] == "changed" {
|
if newExtractor(t, &fakeOccurrencesLLMClient{}).ReferenceSlots()[0].AcceptedMediaTypes[0] == "changed" {
|
||||||
t.Fatal("ReferenceSlots() returned mutable reference slots")
|
t.Fatal("ReferenceSlots() returned mutable reference slots")
|
||||||
}
|
}
|
||||||
registry := pipeline.NewExtractorRegistry()
|
registry := pipeline.NewExtractorRegistry()
|
||||||
@@ -284,7 +284,7 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||||
metadata := extractor.ManifestMetadata()
|
metadata := extractor.ManifestMetadata()
|
||||||
for key, want := range map[string]string{
|
for key, want := range map[string]string{
|
||||||
"prompt_id": PromptID, "prompt_version": SchemaVersion, "mapping_policy": mappingPolicy,
|
"prompt_id": PromptID, "prompt_version": SchemaVersion, "mapping_policy": mappingPolicy,
|
||||||
@@ -320,7 +320,7 @@ func extractionRequest() contracts.TypedExtractionRequest {
|
|||||||
return contracts.TypedExtractionRequest{
|
return contracts.TypedExtractionRequest{
|
||||||
Source: doc, Chunk: chunk,
|
Source: doc, Chunk: chunk,
|
||||||
SourceInput: contracts.NewLLMInputMaterial("source", chunk.MediaType, chunk.Content, "sha256:chunk", "file:///session-alpha.json"),
|
SourceInput: contracts.NewLLMInputMaterial("source", chunk.MediaType, chunk.Content, "sha256:chunk", "file:///session-alpha.json"),
|
||||||
SessionID: "session-123", LLMProfile: "profile-npc-interactions",
|
SessionID: "session-123", LLMProfile: "profile-npc-occurrences",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,22 +359,22 @@ func requiredRegistryReferences(t *testing.T, names ...string) contracts.Referen
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interactionRefs(start, end int) []interactionSourceRefResponse {
|
func occurrenceRefs(start, end int) []occurrenceSourceRefResponse {
|
||||||
return []interactionSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
return []occurrenceSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interactionNames(value dnd.NPCOccurrenceList) []string {
|
func occurrenceNames(value dnd.NPCOccurrenceList) []string {
|
||||||
names := make([]string, len(value.Occurrences))
|
names := make([]string, len(value.Occurrences))
|
||||||
for index, interaction := range value.Occurrences {
|
for index, occurrence := range value.Occurrences {
|
||||||
names[index] = interaction.Name
|
names[index] = occurrence.Name
|
||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func interactionKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
func occurrenceKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
||||||
kinds := make([]dnd.NPCOccurrenceKind, len(value.Occurrences))
|
kinds := make([]dnd.NPCOccurrenceKind, len(value.Occurrences))
|
||||||
for index, interaction := range value.Occurrences {
|
for index, occurrence := range value.Occurrences {
|
||||||
kinds[index] = interaction.Kind
|
kinds[index] = occurrence.Kind
|
||||||
}
|
}
|
||||||
return kinds
|
return kinds
|
||||||
}
|
}
|
||||||
@@ -393,13 +393,13 @@ func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references
|
|||||||
return extractor
|
return extractor
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeInteractionsLLMClient struct {
|
type fakeOccurrencesLLMClient struct {
|
||||||
response extractionResponse
|
response extractionResponse
|
||||||
err error
|
err error
|
||||||
requests []contracts.StructuredCompletionRequest
|
requests []contracts.StructuredCompletionRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *fakeInteractionsLLMClient) CompleteStructured(_ context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
func (client *fakeOccurrencesLLMClient) CompleteStructured(_ context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||||
client.requests = append(client.requests, cloneStructuredCompletionRequest(req))
|
client.requests = append(client.requests, cloneStructuredCompletionRequest(req))
|
||||||
if client.err != nil {
|
if client.err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, client.err
|
return contracts.StructuredCompletionResponse{}, client.err
|
||||||
17
internal/modules/dnd/extract/npcoccurrences/model.go
Normal file
17
internal/modules/dnd/extract/npcoccurrences/model.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package npcoccurrences
|
||||||
|
|
||||||
|
type extractionResponse struct {
|
||||||
|
Occurrences []occurrenceResponse `json:"occurrences"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type occurrenceResponse struct {
|
||||||
|
NPCID string `json:"npc_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
SourceRefs []occurrenceSourceRefResponse `json:"source_refs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type occurrenceSourceRefResponse struct {
|
||||||
|
StartUnitID int `json:"start_unit_id"`
|
||||||
|
EndUnitID int `json:"end_unit_id"`
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,7 +12,7 @@ func TestExtractionResponsePreservesValidatorOwnedSemantics(t *testing.T) {
|
|||||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||||
}
|
}
|
||||||
occurrence := response.Occurrences[0]
|
occurrence := response.Occurrences[0]
|
||||||
if occurrence.NPCID != "" || occurrence.Name != "" || occurrence.Kind != "unsupported" || occurrence.SourceRefs[0] != (interactionSourceRefResponse{StartUnitID: 0, EndUnitID: -1}) {
|
if occurrence.NPCID != "" || occurrence.Name != "" || occurrence.Kind != "unsupported" || occurrence.SourceRefs[0] != (occurrenceSourceRefResponse{StartUnitID: 0, EndUnitID: -1}) {
|
||||||
t.Fatalf("decoded response = %#v", occurrence)
|
t.Fatalf("decoded response = %#v", occurrence)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
const promptAssetRoot = "assets/prompts"
|
const promptAssetRoot = "assets/prompts"
|
||||||
|
|
||||||
var promptAssetManifest = shared.PromptAssetManifest{
|
var promptAssetManifest = shared.PromptAssetManifest{
|
||||||
ModuleDir: "dnd.npc_interactions",
|
ModuleDir: "dnd.npc_occurrences",
|
||||||
ModuleFiles: []promptfs.ModulePromptFile{
|
ModuleFiles: []promptfs.ModulePromptFile{
|
||||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||||
{Name: "instructions.md", Path: "prompts/instructions.md"},
|
{Name: "instructions.md", Path: "prompts/instructions.md"},
|
||||||
@@ -30,9 +30,9 @@ var promptAssetManifest = shared.PromptAssetManifest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func moduleAssetFS() (fs.FS, error) {
|
func moduleAssetFS() (fs.FS, error) {
|
||||||
assets, err := fs.Sub(rootassets.FS(), "dnd/npc-interactions")
|
assets, err := fs.Sub(rootassets.FS(), "dnd/npc-occurrences")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("scope NPC-interaction assets: %w", err)
|
return nil, fmt.Errorf("scope NPC-occurrence assets: %w", err)
|
||||||
}
|
}
|
||||||
return assets, nil
|
return assets, nil
|
||||||
}
|
}
|
||||||
@@ -44,14 +44,14 @@ func RegisterPromptAssets(registry *llm.AssetRegistry) error {
|
|||||||
}
|
}
|
||||||
promptFS, err := promptAssetManifest.PromptFS(assets)
|
promptFS, err := promptAssetManifest.PromptFS(assets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare NPC-interaction prompt assets: %w", err)
|
return fmt.Errorf("prepare NPC-occurrence prompt assets: %w", err)
|
||||||
}
|
}
|
||||||
if err := registry.RegisterPromptFS(promptFS, promptAssetRoot); err != nil {
|
if err := registry.RegisterPromptFS(promptFS, promptAssetRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
schemas, err := fs.Sub(assets, "schemas")
|
schemas, err := fs.Sub(assets, "schemas")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("scope NPC-interaction schemas: %w", err)
|
return fmt.Errorf("scope NPC-occurrence schemas: %w", err)
|
||||||
}
|
}
|
||||||
return registry.RegisterSchemaFS(schemas, ".")
|
return registry.RegisterSchemaFS(schemas, ".")
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/promptkit"
|
"gitea.maximumdirect.net/eric/promptkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
func TestRegisterPromptAssetsAndPrepareOccurrencePrompt(t *testing.T) {
|
||||||
registry := llm.NewAssetRegistry()
|
registry := llm.NewAssetRegistry()
|
||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -28,21 +28,21 @@ func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
||||||
ID: "npc-interactions-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-interactions-test-model",
|
ID: "npc-occurrences-test-profile", Endpoint: "http://127.0.0.1:1/v1", Model: "npc-occurrences-test-model",
|
||||||
})))
|
})))
|
||||||
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
transcript := `{"units":[{"sentinel":"interaction-transcript"}]}`
|
transcript := `{"units":[{"sentinel":"occurrence-transcript"}]}`
|
||||||
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-interactions-test-profile",
|
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "npc-occurrences-test-profile",
|
||||||
Inputs: map[string]promptkit.ArtifactRef{
|
Inputs: map[string]promptkit.ArtifactRef{
|
||||||
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
||||||
"players": promptkit.Inline("interaction-player"),
|
"players": promptkit.Inline("occurrence-player"),
|
||||||
"party": promptkit.Inline("Mira: ranger"),
|
"party": promptkit.Inline("Mira: ranger"),
|
||||||
"glossary": promptkit.Inline("Greencloak: title"),
|
"glossary": promptkit.Inline("Greencloak: title"),
|
||||||
"npc_registry": promptkit.Inline(`{"npcs":[{"id":"npc:sha256:test","name":"interaction-npc"}]}`),
|
"npc_registry": promptkit.Inline(`{"npcs":[{"id":"npc:sha256:test","name":"occurrence-npc"}]}`),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,7 +58,7 @@ func TestPromptMetadataDoesNotExposeAssetContent(t *testing.T) {
|
|||||||
if err != nil || !strings.HasPrefix(hash, "sha256:") {
|
if err != nil || !strings.HasPrefix(hash, "sha256:") {
|
||||||
t.Fatalf("promptAssetMetadata() = %q, %v", hash, err)
|
t.Fatalf("promptAssetMetadata() = %q, %v", hash, err)
|
||||||
}
|
}
|
||||||
metadata := newExtractor(t, &fakeInteractionsLLMClient{}).ManifestMetadata()
|
metadata := newExtractor(t, &fakeOccurrencesLLMClient{}).ManifestMetadata()
|
||||||
for _, forbidden := range []string{"common-dnd-system", "dnd_npc_occurrences_llm.v1.json"} {
|
for _, forbidden := range []string{"common-dnd-system", "dnd_npc_occurrences_llm.v1.json"} {
|
||||||
if strings.Contains(strings.Join(mapValues(metadata), " "), forbidden) {
|
if strings.Contains(strings.Join(mapValues(metadata), " "), forbidden) {
|
||||||
t.Fatalf("metadata leaked prompt or schema content %q: %#v", forbidden, metadata)
|
t.Fatalf("metadata leaked prompt or schema content %q: %#v", forbidden, metadata)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import "gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
import "gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PromptID = "dnd.npc_interactions"
|
PromptID = "dnd.npc_occurrences"
|
||||||
ResponseSchemaKey = llm.ResponseSchemaKey("dnd_npc_occurrences_llm")
|
ResponseSchemaKey = llm.ResponseSchemaKey("dnd_npc_occurrences_llm")
|
||||||
ResponseSchemaID = "notarius.dnd.npc_occurrences.llm"
|
ResponseSchemaID = "notarius.dnd.npc_occurrences.llm"
|
||||||
ResponseSchemaName = "notarius_dnd_npc_occurrences_llm_v1"
|
ResponseSchemaName = "notarius_dnd_npc_occurrences_llm_v1"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -17,7 +17,7 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
|||||||
if schema.Key != ResponseSchemaKey || schema.ID != ResponseSchemaID || schema.Name != ResponseSchemaName || schema.Version != SchemaVersion || !strings.HasPrefix(schema.SHA256, "sha256:") || !json.Valid(schema.JSONSchema) {
|
if schema.Key != ResponseSchemaKey || schema.ID != ResponseSchemaID || schema.Name != ResponseSchemaName || schema.Version != SchemaVersion || !strings.HasPrefix(schema.SHA256, "sha256:") || !json.Valid(schema.JSONSchema) {
|
||||||
t.Fatalf("schema = %#v", schema)
|
t.Fatalf("schema = %#v", schema)
|
||||||
}
|
}
|
||||||
valid := validInteractionResponse()
|
valid := validOccurrenceResponse()
|
||||||
content, err := json.Marshal(valid)
|
content, err := json.Marshal(valid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -26,11 +26,11 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
|||||||
t.Fatalf("valid response rejected: %v", err)
|
t.Fatalf("valid response rejected: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
semanticCandidate := validInteractionResponse()
|
semanticCandidate := validOccurrenceResponse()
|
||||||
interaction := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
occurrence := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
||||||
interaction["name"] = ""
|
occurrence["name"] = ""
|
||||||
interaction["kind"] = "unsupported"
|
occurrence["kind"] = "unsupported"
|
||||||
ref := interaction["source_refs"].([]any)[0].(map[string]any)
|
ref := occurrence["source_refs"].([]any)[0].(map[string]any)
|
||||||
ref["start_unit_id"] = 0
|
ref["start_unit_id"] = 0
|
||||||
ref["end_unit_id"] = -1
|
ref["end_unit_id"] = -1
|
||||||
content, err = json.Marshal(semanticCandidate)
|
content, err = json.Marshal(semanticCandidate)
|
||||||
@@ -50,7 +50,7 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
|||||||
record["source_refs"].([]any)[0].(map[string]any)["source_id"] = "assigned later"
|
record["source_refs"].([]any)[0].(map[string]any)["source_id"] = "assigned later"
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
candidate := validInteractionResponse()
|
candidate := validOccurrenceResponse()
|
||||||
mutate(candidate["occurrences"].([]any)[0].(map[string]any))
|
mutate(candidate["occurrences"].([]any)[0].(map[string]any))
|
||||||
content, err := json.Marshal(candidate)
|
content, err := json.Marshal(candidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -79,7 +79,7 @@ func TestResponseSchemaIsDefensiveAndContentSafe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validInteractionResponse() map[string]any {
|
func validOccurrenceResponse() map[string]any {
|
||||||
return map[string]any{"occurrences": []any{map[string]any{
|
return map[string]any{"occurrences": []any{map[string]any{
|
||||||
"npc_id": "npc:sha256:test", "name": "Mira Thorn", "kind": "dialogue",
|
"npc_id": "npc:sha256:test", "name": "Mira Thorn", "kind": "dialogue",
|
||||||
"source_refs": []any{map[string]any{"start_unit_id": 1, "end_unit_id": 2}},
|
"source_refs": []any{map[string]any{"start_unit_id": 1, "end_unit_id": 2}},
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package npcinteractions normalizes merged D&D NPC interaction candidates.
|
// Package npcoccurrences normalizes merged D&D NPC occurrence candidates.
|
||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -10,21 +10,21 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcinteractions"
|
occurrencemodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
|
||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "dnd/npc-interactions"
|
Key = "dnd/npc-occurrences"
|
||||||
normalizationPolicy = "dnd.npc_interactions.normalize.v2"
|
normalizationPolicy = "dnd.npc_occurrences.normalize.v2"
|
||||||
NormalizationPolicy = normalizationPolicy
|
NormalizationPolicy = normalizationPolicy
|
||||||
|
|
||||||
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
||||||
ReasonCodeInteractionsReordered = "npc_interactions_reordered"
|
ReasonCodeOccurrencesReordered = "npc_occurrences_reordered"
|
||||||
ReasonCodeDuplicateCollapsed = "duplicate_npc_interaction_collapsed"
|
ReasonCodeDuplicateCollapsed = "duplicate_npc_occurrence_collapsed"
|
||||||
ReasonCodeWarningsOmitted = "npc_interaction_normalization_warnings_omitted"
|
ReasonCodeWarningsOmitted = "npc_occurrence_normalization_warnings_omitted"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -36,10 +36,10 @@ var requiredCapabilities = []string{"merged"}
|
|||||||
var providedCapabilities = []string{"normalized"}
|
var providedCapabilities = []string{"normalized"}
|
||||||
|
|
||||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
Glossary: "Optional campaign glossary reference material used only for occurrence disambiguation.",
|
||||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
Party: "Optional party roster reference material used only for occurrence disambiguation.",
|
||||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||||
Roster: "Deprecated alias for party roster reference material used only for interaction disambiguation.",
|
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.Normalizer[dnd.NPCOccurrenceList] = (*Normalizer)(nil)
|
var _ contracts.Normalizer[dnd.NPCOccurrenceList] = (*Normalizer)(nil)
|
||||||
@@ -152,7 +152,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
|||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(records, func(left, right int) bool {
|
sort.SliceStable(records, func(left, right int) bool {
|
||||||
return interactionmodel.Less(order, records[left].occurrence, records[right].occurrence)
|
return occurrencemodel.Less(order, records[left].occurrence, records[right].occurrence)
|
||||||
})
|
})
|
||||||
for position, record := range records {
|
for position, record := range records {
|
||||||
if position == record.inputIndex {
|
if position == record.inputIndex {
|
||||||
@@ -160,7 +160,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
|||||||
}
|
}
|
||||||
warnings = append(warnings, contracts.Warning{
|
warnings = append(warnings, contracts.Warning{
|
||||||
Scope: occurrenceScope(record.inputIndex),
|
Scope: occurrenceScope(record.inputIndex),
|
||||||
ReasonCode: ReasonCodeInteractionsReordered,
|
ReasonCode: ReasonCodeOccurrencesReordered,
|
||||||
Message: fmt.Sprintf("input index %d moved to normalized position %d by source chronology", record.inputIndex, position),
|
Message: fmt.Sprintf("input index %d moved to normalized position %d by source chronology", record.inputIndex, position),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
|||||||
output, duplicateWarnings := collapseDuplicates(records, documentIndex)
|
output, duplicateWarnings := collapseDuplicates(records, documentIndex)
|
||||||
warnings = append(warnings, duplicateWarnings...)
|
warnings = append(warnings, duplicateWarnings...)
|
||||||
return dnd.NPCOccurrenceList{Occurrences: output},
|
return dnd.NPCOccurrenceList{Occurrences: output},
|
||||||
diagnostics.LimitWarnings(warnings, "npc_interactions", ReasonCodeWarningsOmitted), nil
|
diagnostics.LimitWarnings(warnings, "npc_occurrences", ReasonCodeWarningsOmitted), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, registry *npcregistry.Registry) (dnd.NPCOccurrence, bool, error) {
|
func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, registry *npcregistry.Registry) (dnd.NPCOccurrence, bool, error) {
|
||||||
@@ -181,7 +181,7 @@ func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, r
|
|||||||
}
|
}
|
||||||
output := cloneOccurrence(input)
|
output := cloneOccurrence(input)
|
||||||
output.SourceRefs = order.Canonicalize(input.SourceRefs)
|
output.SourceRefs = order.Canonicalize(input.SourceRefs)
|
||||||
return output, !interactionmodel.SourceRefsEqual(input.SourceRefs, output.SourceRefs), nil
|
return output, !occurrencemodel.SourceRefsEqual(input.SourceRefs, output.SourceRefs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneOccurrence(input dnd.NPCOccurrence) dnd.NPCOccurrence {
|
func cloneOccurrence(input dnd.NPCOccurrence) dnd.NPCOccurrence {
|
||||||
@@ -205,11 +205,11 @@ func collapseDuplicates(records []normalizedRecord, documentIndex source.Documen
|
|||||||
groups := make([]duplicateGroup, 0)
|
groups := make([]duplicateGroup, 0)
|
||||||
groupByKey := make(map[string]int)
|
groupByKey := make(map[string]int)
|
||||||
for index, record := range records {
|
for index, record := range records {
|
||||||
if !interactionmodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
if !occurrencemodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
||||||
keep[index] = true
|
keep[index] = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := interactionmodel.ExactIdentity(record.occurrence)
|
key := occurrencemodel.ExactIdentity(record.occurrence)
|
||||||
groupIndex, exists := groupByKey[key]
|
groupIndex, exists := groupByKey[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
groupByKey[key] = len(groups)
|
groupByKey[key] = len(groups)
|
||||||
@@ -243,7 +243,7 @@ func duplicateWarning(retainedIndex int, removed []int) contracts.Warning {
|
|||||||
Scope: occurrenceScope(retainedIndex),
|
Scope: occurrenceScope(retainedIndex),
|
||||||
ReasonCode: ReasonCodeDuplicateCollapsed,
|
ReasonCode: ReasonCodeDuplicateCollapsed,
|
||||||
Message: diagnostics.Aggregate(
|
Message: diagnostics.Aggregate(
|
||||||
fmt.Sprintf("duplicate NPC interaction collapsed; retained input index %d", retainedIndex), issues),
|
fmt.Sprintf("duplicate NPC occurrence collapsed; retained input index %d", retainedIndex), issues),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ func referenceSlots() []contracts.ReferenceSlot {
|
|||||||
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
||||||
slots = append(slots, contracts.ReferenceSlot{
|
slots = append(slots, contracts.ReferenceSlot{
|
||||||
Name: NPCRegistryReferenceSlot,
|
Name: NPCRegistryReferenceSlot,
|
||||||
Description: "Required normalized NPC registry used only for interaction identity grounding, never as interaction evidence.",
|
Description: "Required normalized NPC registry used only for occurrence identity grounding, never as occurrence evidence.",
|
||||||
Required: true,
|
Required: true,
|
||||||
AcceptedMediaTypes: []string{"application/json"},
|
AcceptedMediaTypes: []string{"application/json"},
|
||||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
||||||
@@ -295,5 +295,5 @@ func DecodeOptions(options map[string]any) (Options, error) {
|
|||||||
func validateOptions(options map[string]any) error { _, err := DecodeOptions(options); return err }
|
func validateOptions(options map[string]any) error { _, err := DecodeOptions(options); return err }
|
||||||
|
|
||||||
func normalizerErrorf(format string, args ...any) error {
|
func normalizerErrorf(format string, args ...any) error {
|
||||||
return fmt.Errorf("dnd NPC interactions normalizer: "+format, args...)
|
return fmt.Errorf("dnd NPC occurrences normalizer: "+format, args...)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -31,7 +31,7 @@ func TestNormalizeValidatesPairsAndClones(t *testing.T) {
|
|||||||
}
|
}
|
||||||
got := result.Value.Occurrences[0]
|
got := result.Value.Occurrences[0]
|
||||||
if got.NPCID != identity.DeriveID("Ária") || got.Name != "Ária" || !reflect.DeepEqual(got.SourceRefs, []source.SourceRef{{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 30}, {SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}) {
|
if got.NPCID != identity.DeriveID("Ária") || got.Name != "Ária" || !reflect.DeepEqual(got.SourceRefs, []source.SourceRef{{SourceID: doc.ID, StartUnitID: 30, EndUnitID: 30}, {SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}) {
|
||||||
t.Fatalf("normalized interaction = %#v", got)
|
t.Fatalf("normalized occurrence = %#v", got)
|
||||||
}
|
}
|
||||||
if !hasWarning(result.Warnings, ReasonCodeSourceRefsNormalized) {
|
if !hasWarning(result.Warnings, ReasonCodeSourceRefsNormalized) {
|
||||||
t.Fatalf("warnings = %#v", result.Warnings)
|
t.Fatalf("warnings = %#v", result.Warnings)
|
||||||
@@ -77,7 +77,7 @@ func TestNormalizeRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, occurrence := range []dnd.NPCOccurrence{
|
for _, occurrence := range []dnd.NPCOccurrence{
|
||||||
interaction("Unknown NPC", dnd.NPCOccurrenceKindOther, source.SourceRef{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}),
|
occurrence("Unknown NPC", dnd.NPCOccurrenceKindOther, source.SourceRef{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}),
|
||||||
{NPCID: identity.DeriveID("Ária"), Name: "Borin", Kind: dnd.NPCOccurrenceKindOther, SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}},
|
{NPCID: identity.DeriveID("Ária"), Name: "Borin", Kind: dnd.NPCOccurrenceKindOther, SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: 10, EndUnitID: 10}}},
|
||||||
} {
|
} {
|
||||||
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{occurrence}}
|
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{occurrence}}
|
||||||
@@ -92,14 +92,14 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
|||||||
ref := func(unit int) source.SourceRef {
|
ref := func(unit int) source.SourceRef {
|
||||||
return source.SourceRef{SourceID: doc.ID, StartUnitID: unit, EndUnitID: unit}
|
return source.SourceRef{SourceID: doc.ID, StartUnitID: unit, EndUnitID: unit}
|
||||||
}
|
}
|
||||||
first := interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(50))
|
first := occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(50))
|
||||||
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||||
interaction("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
occurrence("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
||||||
first,
|
first,
|
||||||
first,
|
first,
|
||||||
interaction("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
occurrence("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
||||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
||||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
||||||
}}
|
}}
|
||||||
normalizer, err := New(Options{}, npcReferences(t))
|
normalizer, err := New(Options{}, npcReferences(t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,12 +111,12 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
got := result.Value.Occurrences
|
got := result.Value.Occurrences
|
||||||
if len(got) != 5 {
|
if len(got) != 5 {
|
||||||
t.Fatalf("interaction count = %d, want 5: %#v", len(got), got)
|
t.Fatalf("occurrence count = %d, want 5: %#v", len(got), got)
|
||||||
}
|
}
|
||||||
if got[0].Kind != dnd.NPCOccurrenceKindCombatAlly || got[0].SourceRefs[0].StartUnitID != 50 || got[1].SourceRefs[0].StartUnitID != 50 || got[2].SourceRefs[0].StartUnitID != 10 || got[3].SourceRefs[0].StartUnitID != 90 || got[4].SourceRefs[0].StartUnitID != 999 {
|
if got[0].Kind != dnd.NPCOccurrenceKindCombatAlly || got[0].SourceRefs[0].StartUnitID != 50 || got[1].SourceRefs[0].StartUnitID != 50 || got[2].SourceRefs[0].StartUnitID != 10 || got[3].SourceRefs[0].StartUnitID != 90 || got[4].SourceRefs[0].StartUnitID != 999 {
|
||||||
t.Fatalf("canonical order = %#v", got)
|
t.Fatalf("canonical order = %#v", got)
|
||||||
}
|
}
|
||||||
if !hasWarning(result.Warnings, ReasonCodeInteractionsReordered) || !hasWarning(result.Warnings, ReasonCodeDuplicateCollapsed) {
|
if !hasWarning(result.Warnings, ReasonCodeOccurrencesReordered) || !hasWarning(result.Warnings, ReasonCodeDuplicateCollapsed) {
|
||||||
t.Fatalf("warnings = %#v", result.Warnings)
|
t.Fatalf("warnings = %#v", result.Warnings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ func TestNormalizeBoundsWarnings(t *testing.T) {
|
|||||||
for index := range doc.Units {
|
for index := range doc.Units {
|
||||||
doc.Units[index].ID = index + 1
|
doc.Units[index].ID = index + 1
|
||||||
unitID := count - index
|
unitID := count - index
|
||||||
input.Occurrences[index] = interaction(
|
input.Occurrences[index] = occurrence(
|
||||||
"Ária",
|
"Ária",
|
||||||
dnd.NPCOccurrenceKindDialogue,
|
dnd.NPCOccurrenceKindDialogue,
|
||||||
source.SourceRef{SourceID: doc.ID, StartUnitID: unitID, EndUnitID: unitID},
|
source.SourceRef{SourceID: doc.ID, StartUnitID: unitID, EndUnitID: unitID},
|
||||||
@@ -169,7 +169,7 @@ func TestNormalizeBoundsWarnings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interaction(name string, kind dnd.NPCOccurrenceKind, ref source.SourceRef) dnd.NPCOccurrence {
|
func occurrence(name string, kind dnd.NPCOccurrenceKind, ref source.SourceRef) dnd.NPCOccurrence {
|
||||||
return dnd.NPCOccurrence{NPCID: identity.DeriveID(name), Name: name, Kind: kind, SourceRefs: []source.SourceRef{ref}}
|
return dnd.NPCOccurrence{NPCID: identity.DeriveID(name), Name: name, Kind: kind, SourceRefs: []source.SourceRef{ref}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package npcinteractions owns canonical ordering and exact-identity rules for
|
// Package npcoccurrences owns canonical ordering and exact-identity rules for
|
||||||
// D&D NPC interaction artifacts.
|
// D&D NPC occurrence artifacts.
|
||||||
package npcinteractions
|
package npcoccurrences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||||
@@ -33,20 +33,20 @@ import (
|
|||||||
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
||||||
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
||||||
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
||||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||||
occurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||||
occurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
locationoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||||
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
||||||
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
||||||
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
||||||
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
||||||
interactioninvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/invariants"
|
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
npcoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_relatedness"
|
||||||
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
||||||
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
||||||
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
||||||
@@ -202,32 +202,32 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}},
|
}},
|
||||||
{name: "npc interactions validator chain", register: func() error {
|
{name: "npc occurrences validator chain", register: func() error {
|
||||||
return registry.Register(pipeline.ValidatorChainMapping{
|
return registry.Register(pipeline.ValidatorChainMapping{
|
||||||
Stage: pipeline.StageExtract,
|
Stage: pipeline.StageExtract,
|
||||||
Module: interactionextract.Key,
|
Module: occurrenceextract.Key,
|
||||||
Validators: []pipeline.ModuleBinding{
|
Validators: []pipeline.ModuleBinding{
|
||||||
pipeline.Binding(validjson.Key),
|
pipeline.Binding(validjson.Key),
|
||||||
pipeline.Binding(interactionshape.Key),
|
pipeline.Binding(npcoccurrenceshape.Key),
|
||||||
pipeline.Binding(interactionregistry.Key),
|
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||||
pipeline.Binding(interactionrefs.Key),
|
pipeline.Binding(npcoccurrencerefs.Key),
|
||||||
pipeline.Binding(validjsonschema.Key),
|
pipeline.Binding(validjsonschema.Key),
|
||||||
pipeline.Binding(interactionrelatedness.Key),
|
pipeline.Binding(npcoccurrencerelatedness.Key),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}},
|
}},
|
||||||
{name: "npc interactions normalize validator chain", register: func() error {
|
{name: "npc occurrences normalize validator chain", register: func() error {
|
||||||
return registry.Register(pipeline.ValidatorChainMapping{
|
return registry.Register(pipeline.ValidatorChainMapping{
|
||||||
Stage: pipeline.StageNormalize,
|
Stage: pipeline.StageNormalize,
|
||||||
Module: interactionnormalize.Key,
|
Module: occurrencenormalize.Key,
|
||||||
Validators: []pipeline.ModuleBinding{
|
Validators: []pipeline.ModuleBinding{
|
||||||
pipeline.Binding(validjson.Key),
|
pipeline.Binding(validjson.Key),
|
||||||
pipeline.Binding(interactionshape.Key),
|
pipeline.Binding(npcoccurrenceshape.Key),
|
||||||
pipeline.Binding(interactionregistry.Key),
|
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||||
pipeline.Binding(interactioninvariants.Key),
|
pipeline.Binding(npcoccurrenceinvariants.Key),
|
||||||
pipeline.Binding(interactionrefs.Key),
|
pipeline.Binding(npcoccurrencerefs.Key),
|
||||||
pipeline.Binding(validjsonschema.Key),
|
pipeline.Binding(validjsonschema.Key),
|
||||||
pipeline.Binding(interactionrelatedness.Key),
|
pipeline.Binding(npcoccurrencerelatedness.Key),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}},
|
}},
|
||||||
@@ -280,8 +280,8 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
|||||||
return registry.Register(pipeline.ValidatorChainMapping{
|
return registry.Register(pipeline.ValidatorChainMapping{
|
||||||
Stage: pipeline.StageExtract, Module: locationoccurrenceextract.Key,
|
Stage: pipeline.StageExtract, Module: locationoccurrenceextract.Key,
|
||||||
Validators: []pipeline.ModuleBinding{
|
Validators: []pipeline.ModuleBinding{
|
||||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.Key),
|
||||||
pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(occurrencerelatedness.Key),
|
pipeline.Binding(locationoccurrencerefs.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(locationoccurrencerelatedness.Key),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}},
|
}},
|
||||||
@@ -289,9 +289,9 @@ func registerDefaultChains(registry *pipeline.ValidatorChainRegistry) error {
|
|||||||
return registry.Register(pipeline.ValidatorChainMapping{
|
return registry.Register(pipeline.ValidatorChainMapping{
|
||||||
Stage: pipeline.StageNormalize, Module: locationoccurrencenormalize.Key,
|
Stage: pipeline.StageNormalize, Module: locationoccurrencenormalize.Key,
|
||||||
Validators: []pipeline.ModuleBinding{
|
Validators: []pipeline.ModuleBinding{
|
||||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.Key),
|
||||||
pipeline.Binding(occurrenceinvariants.Key), pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
pipeline.Binding(locationoccurrenceinvariants.Key), pipeline.Binding(locationoccurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
||||||
pipeline.Binding(occurrencerelatedness.Key),
|
pipeline.Binding(locationoccurrencerelatedness.Key),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}},
|
}},
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ func registerEvidence(registry *pipeline.ArtifactEvidenceRegistry) error {
|
|||||||
{name: "item events evidence", register: func() error {
|
{name: "item events evidence", register: func() error {
|
||||||
return pipeline.RegisterArtifactEvidence(registry, dnd.ItemEventListKind, itemEventEvidence)
|
return pipeline.RegisterArtifactEvidence(registry, dnd.ItemEventListKind, itemEventEvidence)
|
||||||
}},
|
}},
|
||||||
{name: "npc interactions evidence", register: func() error {
|
{name: "npc occurrences evidence", register: func() error {
|
||||||
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcInteractionEvidence)
|
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcOccurrenceEvidence)
|
||||||
}},
|
}},
|
||||||
{name: "scene descriptions evidence", register: func() error {
|
{name: "scene descriptions evidence", register: func() error {
|
||||||
return pipeline.RegisterArtifactEvidence(registry, dnd.SceneDescriptionListKind, sceneDescriptionEvidence)
|
return pipeline.RegisterArtifactEvidence(registry, dnd.SceneDescriptionListKind, sceneDescriptionEvidence)
|
||||||
@@ -74,7 +74,7 @@ func itemEventEvidence(value dnd.ItemEventList) []source.SourceRef {
|
|||||||
return append([]source.SourceRef(nil), refs...)
|
return append([]source.SourceRef(nil), refs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func npcInteractionEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
func npcOccurrenceEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
||||||
var refs []source.SourceRef
|
var refs []source.SourceRef
|
||||||
for _, record := range value.Occurrences {
|
for _, record := range value.Occurrences {
|
||||||
refs = append(refs, record.SourceRefs...)
|
refs = append(refs, record.SourceRefs...)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func appendItemEventLists(values []dnd.ItemEventList) (dnd.ItemEventList, error)
|
|||||||
return combined, nil
|
return combined, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendNPCInteractionLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
func appendNPCOccurrenceLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
||||||
count := 0
|
count := 0
|
||||||
present := false
|
present := false
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
itemeventcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemevents"
|
itemeventcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemevents"
|
||||||
locationoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationoccurrences"
|
locationoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationoccurrences"
|
||||||
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locations"
|
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locations"
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
scenedescriptioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
scenedescriptioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||||
spellcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/spells"
|
spellcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/spells"
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||||
@@ -45,7 +45,7 @@ func registerModules(registries pipeline.Registries) error {
|
|||||||
{name: "combat turns codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, combatcodec.New()) }},
|
{name: "combat turns codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, combatcodec.New()) }},
|
||||||
{name: "enemy events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, enemyeventcodec.New()) }},
|
{name: "enemy events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, enemyeventcodec.New()) }},
|
||||||
{name: "item events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, itemeventcodec.New()) }},
|
{name: "item events codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, itemeventcodec.New()) }},
|
||||||
{name: "npc interactions codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, interactioncodec.New()) }},
|
{name: "npc occurrences codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, occurrencecodec.New()) }},
|
||||||
{name: "scene descriptions codec", register: func() error {
|
{name: "scene descriptions codec", register: func() error {
|
||||||
return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, scenedescriptioncodec.New())
|
return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, scenedescriptioncodec.New())
|
||||||
}},
|
}},
|
||||||
@@ -59,7 +59,7 @@ func registerModules(registries pipeline.Registries) error {
|
|||||||
{name: "combat turns extractor", register: func() error { return combatextract.Register(registries.Extractors) }},
|
{name: "combat turns extractor", register: func() error { return combatextract.Register(registries.Extractors) }},
|
||||||
{name: "enemy events extractor", register: func() error { return enemyeventextract.Register(registries.Extractors) }},
|
{name: "enemy events extractor", register: func() error { return enemyeventextract.Register(registries.Extractors) }},
|
||||||
{name: "item events extractor", register: func() error { return itemeventextract.Register(registries.Extractors) }},
|
{name: "item events extractor", register: func() error { return itemeventextract.Register(registries.Extractors) }},
|
||||||
{name: "npc interactions extractor", register: func() error { return interactionextract.Register(registries.Extractors) }},
|
{name: "npc occurrences extractor", register: func() error { return occurrenceextract.Register(registries.Extractors) }},
|
||||||
{name: "scene descriptions extractor", register: func() error { return scenedescriptionextract.Register(registries.Extractors) }},
|
{name: "scene descriptions extractor", register: func() error { return scenedescriptionextract.Register(registries.Extractors) }},
|
||||||
{name: "locations extractor", register: func() error { return locationextract.Register(registries.Extractors) }},
|
{name: "locations extractor", register: func() error { return locationextract.Register(registries.Extractors) }},
|
||||||
{name: "location occurrences extractor", register: func() error { return locationoccurrenceextract.Register(registries.Extractors) }},
|
{name: "location occurrences extractor", register: func() error { return locationoccurrenceextract.Register(registries.Extractors) }},
|
||||||
@@ -78,8 +78,8 @@ func registerModules(registries pipeline.Registries) error {
|
|||||||
{name: "item-event-list appendorder merger", register: func() error {
|
{name: "item-event-list appendorder merger", register: func() error {
|
||||||
return appendorder.RegisterTyped(registries.Mergers, dnd.ItemEventListKind, appendItemEventLists)
|
return appendorder.RegisterTyped(registries.Mergers, dnd.ItemEventListKind, appendItemEventLists)
|
||||||
}},
|
}},
|
||||||
{name: "npc-interaction-list appendorder merger", register: func() error {
|
{name: "npc-occurrence-list appendorder merger", register: func() error {
|
||||||
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCInteractionLists)
|
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCOccurrenceLists)
|
||||||
}},
|
}},
|
||||||
{name: "scene-description-list appendorder merger", register: func() error {
|
{name: "scene-description-list appendorder merger", register: func() error {
|
||||||
return appendorder.RegisterTyped(registries.Mergers, dnd.SceneDescriptionListKind, appendSceneDescriptionLists)
|
return appendorder.RegisterTyped(registries.Mergers, dnd.SceneDescriptionListKind, appendSceneDescriptionLists)
|
||||||
@@ -95,7 +95,7 @@ func registerModules(registries pipeline.Registries) error {
|
|||||||
{name: "combat turns normalizer", register: func() error { return combatnormalize.Register(registries.Normalizers) }},
|
{name: "combat turns normalizer", register: func() error { return combatnormalize.Register(registries.Normalizers) }},
|
||||||
{name: "enemy events normalizer", register: func() error { return enemyeventnormalize.Register(registries.Normalizers) }},
|
{name: "enemy events normalizer", register: func() error { return enemyeventnormalize.Register(registries.Normalizers) }},
|
||||||
{name: "item events normalizer", register: func() error { return itemeventnormalize.Register(registries.Normalizers) }},
|
{name: "item events normalizer", register: func() error { return itemeventnormalize.Register(registries.Normalizers) }},
|
||||||
{name: "npc interactions normalizer", register: func() error { return interactionnormalize.Register(registries.Normalizers) }},
|
{name: "npc occurrences normalizer", register: func() error { return occurrencenormalize.Register(registries.Normalizers) }},
|
||||||
{name: "scene descriptions normalizer", register: func() error { return scenedescriptionnormalize.Register(registries.Normalizers) }},
|
{name: "scene descriptions normalizer", register: func() error { return scenedescriptionnormalize.Register(registries.Normalizers) }},
|
||||||
{name: "locations normalizer", register: func() error { return locationnormalize.Register(registries.Normalizers) }},
|
{name: "locations normalizer", register: func() error { return locationnormalize.Register(registries.Normalizers) }},
|
||||||
{name: "location occurrences normalizer", register: func() error { return locationoccurrencenormalize.Register(registries.Normalizers) }},
|
{name: "location occurrences normalizer", register: func() error { return locationoccurrencenormalize.Register(registries.Normalizers) }},
|
||||||
@@ -114,7 +114,7 @@ func registerModules(registries pipeline.Registries) error {
|
|||||||
{name: "item-event-list noop normalizer", register: func() error {
|
{name: "item-event-list noop normalizer", register: func() error {
|
||||||
return noop.RegisterTyped[dnd.ItemEventList](registries.Normalizers, dnd.ItemEventListKind)
|
return noop.RegisterTyped[dnd.ItemEventList](registries.Normalizers, dnd.ItemEventListKind)
|
||||||
}},
|
}},
|
||||||
{name: "npc-interaction-list noop normalizer", register: func() error {
|
{name: "npc-occurrence-list noop normalizer", register: func() error {
|
||||||
return noop.RegisterTyped[dnd.NPCOccurrenceList](registries.Normalizers, dnd.NPCOccurrenceListKind)
|
return noop.RegisterTyped[dnd.NPCOccurrenceList](registries.Normalizers, dnd.NPCOccurrenceListKind)
|
||||||
}},
|
}},
|
||||||
{name: "scene-description-list noop normalizer", register: func() error {
|
{name: "scene-description-list noop normalizer", register: func() error {
|
||||||
@@ -139,7 +139,7 @@ func registerPromptAssets(assets *llm.AssetRegistry) error {
|
|||||||
{name: "combat turns prompt assets", register: func() error { return combatextract.RegisterPromptAssets(assets) }},
|
{name: "combat turns prompt assets", register: func() error { return combatextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "enemy events prompt assets", register: func() error { return enemyeventextract.RegisterPromptAssets(assets) }},
|
{name: "enemy events prompt assets", register: func() error { return enemyeventextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "item events prompt assets", register: func() error { return itemeventextract.RegisterPromptAssets(assets) }},
|
{name: "item events prompt assets", register: func() error { return itemeventextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "npc interactions prompt assets", register: func() error { return interactionextract.RegisterPromptAssets(assets) }},
|
{name: "npc occurrences prompt assets", register: func() error { return occurrenceextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "scene descriptions prompt assets", register: func() error { return scenedescriptionextract.RegisterPromptAssets(assets) }},
|
{name: "scene descriptions prompt assets", register: func() error { return scenedescriptionextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "locations prompt assets", register: func() error { return locationextract.RegisterPromptAssets(assets) }},
|
{name: "locations prompt assets", register: func() error { return locationextract.RegisterPromptAssets(assets) }},
|
||||||
{name: "location normalization prompt assets", register: func() error { return locationnormalize.RegisterPromptAssets(assets) }},
|
{name: "location normalization prompt assets", register: func() error { return locationnormalize.RegisterPromptAssets(assets) }},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
@@ -68,18 +68,18 @@ func TestExtractionPromptComposition(t *testing.T) {
|
|||||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||||
"npc_interactions": promptkit.Inline(`{"sentinel":"npc-interactions-sentinel"}`),
|
"npc_occurrences": promptkit.Inline(`{"sentinel":"npc-occurrences-sentinel"}`),
|
||||||
}),
|
}),
|
||||||
suffixGroups: [][]string{
|
suffixGroups: [][]string{
|
||||||
{evidenceSentinel},
|
{evidenceSentinel},
|
||||||
{npcSentinel},
|
{npcSentinel},
|
||||||
{"combat-turns-sentinel", "npc-interactions-sentinel"},
|
{"combat-turns-sentinel", "npc-occurrences-sentinel"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "npc interactions",
|
name: "npc occurrences",
|
||||||
promptID: interactionextract.PromptID,
|
promptID: occurrenceextract.PromptID,
|
||||||
promptVersion: interactionextract.SchemaVersion,
|
promptVersion: occurrenceextract.SchemaVersion,
|
||||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locations"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
itemeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemevents"
|
||||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locations"
|
||||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||||
@@ -52,7 +52,7 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
"dnd.combat_turns/prompt.yaml",
|
"dnd.combat_turns/prompt.yaml",
|
||||||
"dnd.enemy_events/prompt.yaml",
|
"dnd.enemy_events/prompt.yaml",
|
||||||
"dnd.item_events/prompt.yaml",
|
"dnd.item_events/prompt.yaml",
|
||||||
"dnd.npc_interactions/prompt.yaml",
|
"dnd.npc_occurrences/prompt.yaml",
|
||||||
"dnd.scene_descriptions/prompt.yaml",
|
"dnd.scene_descriptions/prompt.yaml",
|
||||||
"dnd.npc_registry.normalize/prompt.yaml",
|
"dnd.npc_registry.normalize/prompt.yaml",
|
||||||
"dnd.locations/prompt.yaml",
|
"dnd.locations/prompt.yaml",
|
||||||
@@ -82,8 +82,8 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
t.Fatalf("entity reconciliation schema asset = %v, want registered shared schema", err)
|
t.Fatalf("entity reconciliation schema asset = %v, want registered shared schema", err)
|
||||||
}
|
}
|
||||||
assertContainsKeys(t, "chunkers", registries.Chunkers.RegisteredKeys(), []string{"dnd/scenes"})
|
assertContainsKeys(t, "chunkers", registries.Chunkers.RegisteredKeys(), []string{"dnd/scenes"})
|
||||||
assertContainsKeys(t, "extractors", registries.Extractors.RegisteredKeys(), []string{"dnd/spells", npcextract.Key, combatextract.Key, enemyeventextract.Key, itemeventextract.Key, interactionextract.Key, scenedescriptionextract.Key, locationextract.Key, locationoccurrenceextract.Key})
|
assertContainsKeys(t, "extractors", registries.Extractors.RegisteredKeys(), []string{"dnd/spells", npcextract.Key, combatextract.Key, enemyeventextract.Key, itemeventextract.Key, occurrenceextract.Key, scenedescriptionextract.Key, locationextract.Key, locationoccurrenceextract.Key})
|
||||||
assertContainsKeys(t, "normalizers", registries.Normalizers.RegisteredKeys(), []string{spellnormalize.Key, npcnormalize.Key, combatnormalize.Key, enemyeventnormalize.Key, itemeventnormalize.Key, interactionnormalize.Key, scenedescriptionnormalize.Key, locationnormalize.Key, locationoccurrencenormalize.Key, pipeline.DefaultNormalizeModule})
|
assertContainsKeys(t, "normalizers", registries.Normalizers.RegisteredKeys(), []string{spellnormalize.Key, npcnormalize.Key, combatnormalize.Key, enemyeventnormalize.Key, itemeventnormalize.Key, occurrencenormalize.Key, scenedescriptionnormalize.Key, locationnormalize.Key, locationoccurrencenormalize.Key, pipeline.DefaultNormalizeModule})
|
||||||
assertContainsArtifactKinds(t, registries.ArtifactCodecs.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
assertContainsArtifactKinds(t, registries.ArtifactCodecs.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||||
assertContainsArtifactKinds(t, registries.ArtifactEvidence.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
assertContainsArtifactKinds(t, registries.ArtifactEvidence.RegisteredKinds(), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||||
assertContainsArtifactKinds(t, registries.Mergers.RegisteredArtifactKinds(pipeline.DefaultMergeModule), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
assertContainsArtifactKinds(t, registries.Mergers.RegisteredArtifactKinds(pipeline.DefaultMergeModule), []contracts.ArtifactKind{dnd.SpellListKind, dnd.NPCRegistryKind, dnd.CombatTurnListKind, dnd.EnemyEventListKind, dnd.ItemEventListKind, dnd.NPCOccurrenceListKind, dnd.SceneDescriptionListKind, dnd.LocationListKind, dnd.LocationOccurrenceListKind})
|
||||||
@@ -92,7 +92,7 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(combatnormalize.Key), []contracts.ArtifactKind{dnd.CombatTurnListKind})
|
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(combatnormalize.Key), []contracts.ArtifactKind{dnd.CombatTurnListKind})
|
||||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(enemyeventnormalize.Key), []contracts.ArtifactKind{dnd.EnemyEventListKind})
|
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(enemyeventnormalize.Key), []contracts.ArtifactKind{dnd.EnemyEventListKind})
|
||||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(itemeventnormalize.Key), []contracts.ArtifactKind{dnd.ItemEventListKind})
|
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(itemeventnormalize.Key), []contracts.ArtifactKind{dnd.ItemEventListKind})
|
||||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(interactionnormalize.Key), []contracts.ArtifactKind{dnd.NPCOccurrenceListKind})
|
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(occurrencenormalize.Key), []contracts.ArtifactKind{dnd.NPCOccurrenceListKind})
|
||||||
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(scenedescriptionnormalize.Key), []contracts.ArtifactKind{dnd.SceneDescriptionListKind})
|
assertContainsArtifactKinds(t, registries.Normalizers.RegisteredArtifactKinds(scenedescriptionnormalize.Key), []contracts.ArtifactKind{dnd.SceneDescriptionListKind})
|
||||||
assertContainsKeys(t, "validators", registries.Validators.RegisteredKeys(), []string{
|
assertContainsKeys(t, "validators", registries.Validators.RegisteredKeys(), []string{
|
||||||
"extract/dnd/locations/shape", "normalize/dnd/locations/identity", "extract/dnd/locations/source_refs", "extract/dnd/locations/source_relatedness",
|
"extract/dnd/locations/shape", "normalize/dnd/locations/identity", "extract/dnd/locations/source_refs", "extract/dnd/locations/source_relatedness",
|
||||||
@@ -118,11 +118,11 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
"extract/dnd/item-events/source_refs",
|
"extract/dnd/item-events/source_refs",
|
||||||
"extract/dnd/item-events/source_relatedness",
|
"extract/dnd/item-events/source_relatedness",
|
||||||
"normalize/dnd/item-events/invariants",
|
"normalize/dnd/item-events/invariants",
|
||||||
"extract/dnd/npc-interactions/shape",
|
"extract/dnd/npc-occurrences/shape",
|
||||||
"extract/dnd/npc-interactions/registry",
|
"extract/dnd/npc-occurrences/registry",
|
||||||
"extract/dnd/npc-interactions/source_refs",
|
"extract/dnd/npc-occurrences/source_refs",
|
||||||
"extract/dnd/npc-interactions/source_relatedness",
|
"extract/dnd/npc-occurrences/source_relatedness",
|
||||||
"normalize/dnd/npc-interactions/invariants",
|
"normalize/dnd/npc-occurrences/invariants",
|
||||||
"extract/dnd/scene-descriptions/shape",
|
"extract/dnd/scene-descriptions/shape",
|
||||||
"extract/dnd/scene-descriptions/source_refs",
|
"extract/dnd/scene-descriptions/source_refs",
|
||||||
"extract/dnd/scene-descriptions/source_relatedness",
|
"extract/dnd/scene-descriptions/source_relatedness",
|
||||||
@@ -245,28 +245,28 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, itemeventnormalize.Key); !reflect.DeepEqual(got, itemEventNormalizeChain) {
|
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, itemeventnormalize.Key); !reflect.DeepEqual(got, itemEventNormalizeChain) {
|
||||||
t.Fatalf("item event normalize validator chain = %#v, want %#v", got, itemEventNormalizeChain)
|
t.Fatalf("item event normalize validator chain = %#v, want %#v", got, itemEventNormalizeChain)
|
||||||
}
|
}
|
||||||
interactionExtractChain := []pipeline.ModuleBinding{
|
npcOccurrenceExtractChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/shape"),
|
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/source_relatedness"),
|
pipeline.Binding("extract/dnd/npc-occurrences/source_relatedness"),
|
||||||
}
|
}
|
||||||
interactionNormalizeChain := []pipeline.ModuleBinding{
|
npcOccurrenceNormalizeChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/shape"),
|
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||||
pipeline.Binding("normalize/dnd/npc-interactions/invariants"),
|
pipeline.Binding("normalize/dnd/npc-occurrences/invariants"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npc-interactions/source_relatedness"),
|
pipeline.Binding("extract/dnd/npc-occurrences/source_relatedness"),
|
||||||
}
|
}
|
||||||
if got := registries.ValidatorChains.Validators(pipeline.StageExtract, interactionextract.Key); !reflect.DeepEqual(got, interactionExtractChain) {
|
if got := registries.ValidatorChains.Validators(pipeline.StageExtract, occurrenceextract.Key); !reflect.DeepEqual(got, npcOccurrenceExtractChain) {
|
||||||
t.Fatalf("NPC interaction extract validator chain = %#v, want %#v", got, interactionExtractChain)
|
t.Fatalf("NPC occurrence extract validator chain = %#v, want %#v", got, npcOccurrenceExtractChain)
|
||||||
}
|
}
|
||||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, interactionnormalize.Key); !reflect.DeepEqual(got, interactionNormalizeChain) {
|
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, occurrencenormalize.Key); !reflect.DeepEqual(got, npcOccurrenceNormalizeChain) {
|
||||||
t.Fatalf("NPC interaction normalize validator chain = %#v, want %#v", got, interactionNormalizeChain)
|
t.Fatalf("NPC occurrence normalize validator chain = %#v, want %#v", got, npcOccurrenceNormalizeChain)
|
||||||
}
|
}
|
||||||
sceneExtractChain := []pipeline.ModuleBinding{
|
sceneExtractChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
@@ -292,8 +292,8 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, npcextract.Key); got != nil {
|
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, npcextract.Key); got != nil {
|
||||||
t.Fatalf("NPC merge validator chain = %#v, want absent", got)
|
t.Fatalf("NPC merge validator chain = %#v, want absent", got)
|
||||||
}
|
}
|
||||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, interactionextract.Key); got != nil {
|
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, occurrenceextract.Key); got != nil {
|
||||||
t.Fatalf("NPC interaction merge validator chain = %#v, want absent", got)
|
t.Fatalf("NPC occurrence merge validator chain = %#v, want absent", got)
|
||||||
}
|
}
|
||||||
assertAssetNamesContain(t, assets.SchemaFS, []string{
|
assertAssetNamesContain(t, assets.SchemaFS, []string{
|
||||||
"dnd_scenes_llm.v1.json",
|
"dnd_scenes_llm.v1.json",
|
||||||
@@ -351,31 +351,31 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
t.Fatalf("item event extractor has a generated-reference dependency: %#v", slot)
|
t.Fatalf("item event extractor has a generated-reference dependency: %#v", slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interactionExtractSpec, extractOK := registries.Extractors.Spec(interactionextract.Key)
|
occurrenceExtractSpec, extractOK := registries.Extractors.Spec(occurrenceextract.Key)
|
||||||
interactionNormalizeSpec, normalizeOK := registries.Normalizers.Spec(interactionnormalize.Key)
|
occurrenceNormalizeSpec, normalizeOK := registries.Normalizers.Spec(occurrencenormalize.Key)
|
||||||
if !extractOK || interactionExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || interactionNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || interactionNormalizeSpec.Stage != pipeline.StageNormalize {
|
if !extractOK || occurrenceExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || occurrenceNormalizeSpec.Stage != pipeline.StageNormalize {
|
||||||
t.Fatalf("NPC interaction specs = %#v / %#v, present = %t / %t", interactionExtractSpec, interactionNormalizeSpec, extractOK, normalizeOK)
|
t.Fatalf("NPC occurrence specs = %#v / %#v, present = %t / %t", occurrenceExtractSpec, occurrenceNormalizeSpec, extractOK, normalizeOK)
|
||||||
}
|
}
|
||||||
locationExtractSpec, locationExtractOK := registries.Extractors.Spec(locationextract.Key)
|
locationExtractSpec, locationExtractOK := registries.Extractors.Spec(locationextract.Key)
|
||||||
locationNormalizeSpec, locationNormalizeOK := registries.Normalizers.Spec(locationnormalize.Key)
|
locationNormalizeSpec, locationNormalizeOK := registries.Normalizers.Spec(locationnormalize.Key)
|
||||||
if !locationExtractOK || locationExtractSpec.ArtifactKind != dnd.LocationListKind || locationExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !locationNormalizeOK || locationNormalizeSpec.ArtifactKind != dnd.LocationListKind || locationNormalizeSpec.ExecutionClass != contracts.ExecutionClassLLMBacked {
|
if !locationExtractOK || locationExtractSpec.ArtifactKind != dnd.LocationListKind || locationExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !locationNormalizeOK || locationNormalizeSpec.ArtifactKind != dnd.LocationListKind || locationNormalizeSpec.ExecutionClass != contracts.ExecutionClassLLMBacked {
|
||||||
t.Fatalf("location specs = %#v / %#v", locationExtractSpec, locationNormalizeSpec)
|
t.Fatalf("location specs = %#v / %#v", locationExtractSpec, locationNormalizeSpec)
|
||||||
}
|
}
|
||||||
occurrenceExtractSpec, occurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
locationOccurrenceExtractSpec, locationOccurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
||||||
occurrenceNormalizeSpec, occurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
locationOccurrenceNormalizeSpec, locationOccurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
||||||
if !occurrenceExtractOK || occurrenceExtractSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !occurrenceNormalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceNormalizeSpec.ExecutionClass != contracts.ExecutionClassDeterministic {
|
if !locationOccurrenceExtractOK || locationOccurrenceExtractSpec.ArtifactKind != dnd.LocationOccurrenceListKind || locationOccurrenceExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !locationOccurrenceNormalizeOK || locationOccurrenceNormalizeSpec.ArtifactKind != dnd.LocationOccurrenceListKind || locationOccurrenceNormalizeSpec.ExecutionClass != contracts.ExecutionClassDeterministic {
|
||||||
t.Fatalf("location occurrence specs = %#v / %#v", occurrenceExtractSpec, occurrenceNormalizeSpec)
|
t.Fatalf("location occurrence specs = %#v / %#v", locationOccurrenceExtractSpec, locationOccurrenceNormalizeSpec)
|
||||||
}
|
}
|
||||||
locationRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "locations")
|
locationRegistrySlot := referenceSlot(locationOccurrenceExtractSpec.ReferenceSlots, "locations")
|
||||||
occurrenceNormalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "locations")
|
occurrenceNormalizeRegistrySlot := referenceSlot(locationOccurrenceNormalizeSpec.ReferenceSlots, "locations")
|
||||||
if len(occurrenceExtractSpec.ReferenceSlots) != 5 || len(occurrenceNormalizeSpec.ReferenceSlots) != 1 {
|
if len(locationOccurrenceExtractSpec.ReferenceSlots) != 5 || len(locationOccurrenceNormalizeSpec.ReferenceSlots) != 1 {
|
||||||
t.Fatalf("location occurrence reference slots = %#v / %#v, want extractor campaign context and normalizer registry only", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
t.Fatalf("location occurrence reference slots = %#v / %#v, want extractor campaign context and normalizer registry only", locationOccurrenceExtractSpec.ReferenceSlots, locationOccurrenceNormalizeSpec.ReferenceSlots)
|
||||||
}
|
}
|
||||||
if !locationRegistrySlot.Required || !reflect.DeepEqual(locationRegistrySlot.AcceptedMediaTypes, []string{"application/json"}) || !reflect.DeepEqual(locationRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.LocationListKind}) || locationRegistrySlot.MaxBytes != 1048576 || !sameReferenceSlotContract(locationRegistrySlot, occurrenceNormalizeRegistrySlot) {
|
if !locationRegistrySlot.Required || !reflect.DeepEqual(locationRegistrySlot.AcceptedMediaTypes, []string{"application/json"}) || !reflect.DeepEqual(locationRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.LocationListKind}) || locationRegistrySlot.MaxBytes != 1048576 || !sameReferenceSlotContract(locationRegistrySlot, occurrenceNormalizeRegistrySlot) {
|
||||||
t.Fatalf("location registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
t.Fatalf("location registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||||
}
|
}
|
||||||
for _, name := range []string{"party", "roster", "players", "glossary"} {
|
for _, name := range []string{"party", "roster", "players", "glossary"} {
|
||||||
slot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, name)
|
slot := referenceSlot(locationOccurrenceExtractSpec.ReferenceSlots, name)
|
||||||
if slot.Name != name || slot.Required || len(slot.AcceptedArtifactKinds) != 0 {
|
if slot.Name != name || slot.Required || len(slot.AcceptedArtifactKinds) != 0 {
|
||||||
t.Fatalf("location occurrence extractor campaign slot %q = %#v, want optional text context", name, slot)
|
t.Fatalf("location occurrence extractor campaign slot %q = %#v, want optional text context", name, slot)
|
||||||
}
|
}
|
||||||
@@ -388,10 +388,10 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
|||||||
if len(sceneExtractSpec.ReferenceSlots) != 3 || len(sceneNormalizeSpec.ReferenceSlots) != 0 {
|
if len(sceneExtractSpec.ReferenceSlots) != 3 || len(sceneNormalizeSpec.ReferenceSlots) != 0 {
|
||||||
t.Fatalf("scene description reference slots = %#v / %#v, want extractor campaign slots only", sceneExtractSpec.ReferenceSlots, sceneNormalizeSpec.ReferenceSlots)
|
t.Fatalf("scene description reference slots = %#v / %#v, want extractor campaign slots only", sceneExtractSpec.ReferenceSlots, sceneNormalizeSpec.ReferenceSlots)
|
||||||
}
|
}
|
||||||
extractRegistrySlot := referenceSlot(interactionExtractSpec.ReferenceSlots, "npc_registry")
|
extractRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "npc_registry")
|
||||||
normalizeRegistrySlot := referenceSlot(interactionNormalizeSpec.ReferenceSlots, "npc_registry")
|
normalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "npc_registry")
|
||||||
if !extractRegistrySlot.Required || !reflect.DeepEqual(extractRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.NPCRegistryKind}) || !sameReferenceSlotContract(extractRegistrySlot, normalizeRegistrySlot) {
|
if !extractRegistrySlot.Required || !reflect.DeepEqual(extractRegistrySlot.AcceptedArtifactKinds, []contracts.ArtifactKind{dnd.NPCRegistryKind}) || !sameReferenceSlotContract(extractRegistrySlot, normalizeRegistrySlot) {
|
||||||
t.Fatalf("NPC interaction registry slots disagree: %#v / %#v", interactionExtractSpec.ReferenceSlots, interactionNormalizeSpec.ReferenceSlots)
|
t.Fatalf("NPC occurrence registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,8 +418,8 @@ func TestEvidenceProjectorsPreserveDirectReferencesWithIndependentStorage(t *tes
|
|||||||
{name: "item events", project: func() []source.SourceRef {
|
{name: "item events", project: func() []source.SourceRef {
|
||||||
return itemEventEvidence(dnd.ItemEventList{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{first, second}}}})
|
return itemEventEvidence(dnd.ItemEventList{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||||
}, want: []source.SourceRef{first, second}},
|
}, want: []source.SourceRef{first, second}},
|
||||||
{name: "npc interactions", project: func() []source.SourceRef {
|
{name: "npc occurrences", project: func() []source.SourceRef {
|
||||||
return npcInteractionEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
return npcOccurrenceEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||||
}, want: []source.SourceRef{first, second}},
|
}, want: []source.SourceRef{first, second}},
|
||||||
{name: "scene descriptions", project: func() []source.SourceRef {
|
{name: "scene descriptions", project: func() []source.SourceRef {
|
||||||
return sceneDescriptionEvidence(dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{{SourceRef: first}, {SourceRef: second}}})
|
return sceneDescriptionEvidence(dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{{SourceRef: first}, {SourceRef: second}}})
|
||||||
@@ -559,7 +559,7 @@ func TestAppendSpellListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppendNPCInteractionListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
func TestAppendNPCOccurrenceListsPreservesOrderPresenceAndOwnership(t *testing.T) {
|
||||||
refs := []source.SourceRef{{SourceID: "session", StartUnitID: 10, EndUnitID: 10}}
|
refs := []source.SourceRef{{SourceID: "session", StartUnitID: 10, EndUnitID: 10}}
|
||||||
input := []dnd.NPCOccurrenceList{
|
input := []dnd.NPCOccurrenceList{
|
||||||
{},
|
{},
|
||||||
@@ -567,21 +567,21 @@ func TestAppendNPCInteractionListsPreservesOrderPresenceAndOwnership(t *testing.
|
|||||||
{Occurrences: []dnd.NPCOccurrence{{Name: "Aria", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: refs}}},
|
{Occurrences: []dnd.NPCOccurrence{{Name: "Aria", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: refs}}},
|
||||||
{Occurrences: []dnd.NPCOccurrence{{Name: "Borin", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 20, EndUnitID: 20}}}}},
|
{Occurrences: []dnd.NPCOccurrence{{Name: "Borin", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 20, EndUnitID: 20}}}}},
|
||||||
}
|
}
|
||||||
got, err := appendNPCInteractionLists(input)
|
got, err := appendNPCOccurrenceLists(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("appendNPCInteractionLists() error = %v", err)
|
t.Fatalf("appendNPCOccurrenceLists() error = %v", err)
|
||||||
}
|
}
|
||||||
if got.Occurrences == nil || !reflect.DeepEqual([]string{got.Occurrences[0].Name, got.Occurrences[1].Name}, []string{"Aria", "Borin"}) {
|
if got.Occurrences == nil || !reflect.DeepEqual([]string{got.Occurrences[0].Name, got.Occurrences[1].Name}, []string{"Aria", "Borin"}) {
|
||||||
t.Fatalf("combined interactions = %#v", got)
|
t.Fatalf("combined occurrences = %#v", got)
|
||||||
}
|
}
|
||||||
got.Occurrences[0].SourceRefs[0].StartUnitID = 999
|
got.Occurrences[0].SourceRefs[0].StartUnitID = 999
|
||||||
if input[2].Occurrences[0].SourceRefs[0].StartUnitID == 999 {
|
if input[2].Occurrences[0].SourceRefs[0].StartUnitID == 999 {
|
||||||
t.Fatal("merged interactions share source reference storage")
|
t.Fatal("merged occurrences share source reference storage")
|
||||||
}
|
}
|
||||||
for _, values := range [][]dnd.NPCOccurrenceList{nil, []dnd.NPCOccurrenceList{{}, {}}} {
|
for _, values := range [][]dnd.NPCOccurrenceList{nil, []dnd.NPCOccurrenceList{{}, {}}} {
|
||||||
result, err := appendNPCInteractionLists(values)
|
result, err := appendNPCOccurrenceLists(values)
|
||||||
if err != nil || result.Occurrences != nil {
|
if err != nil || result.Occurrences != nil {
|
||||||
t.Fatalf("nil-only merge = %#v, %v; want nil interactions", result, err)
|
t.Fatalf("nil-only merge = %#v, %v; want nil occurrences", result, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -711,9 +711,9 @@ func TestAppendListsPreserveNestedSourceReferencePresence(t *testing.T) {
|
|||||||
t.Fatalf("appendEnemyEventLists() = %#v, %v; want present-empty source refs", enemyEvents, err)
|
t.Fatalf("appendEnemyEventLists() = %#v, %v; want present-empty source refs", enemyEvents, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
interactions, err := appendNPCInteractionLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
occurrences, err := appendNPCOccurrenceLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
||||||
if err != nil || interactions.Occurrences[0].SourceRefs == nil {
|
if err != nil || occurrences.Occurrences[0].SourceRefs == nil {
|
||||||
t.Fatalf("appendNPCInteractionLists() = %#v, %v; want present-empty source refs", interactions, err)
|
t.Fatalf("appendNPCOccurrenceLists() = %#v, %v; want present-empty source refs", occurrences, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := appendItemEventLists([]dnd.ItemEventList{{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{}}}}})
|
events, err := appendItemEventLists([]dnd.ItemEventList{{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{}}}}})
|
||||||
|
|||||||
@@ -16,20 +16,20 @@ import (
|
|||||||
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
itemeventshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/shape"
|
||||||
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
itemeventrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_refs"
|
||||||
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
itemeventrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemevents/source_relatedness"
|
||||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||||
occurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||||
occurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
locationoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_relatedness"
|
||||||
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/identity"
|
||||||
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
locationshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/shape"
|
||||||
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
locationrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_refs"
|
||||||
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
locationrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locations/source_relatedness"
|
||||||
interactioninvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/invariants"
|
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
npcoccurrencerelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_relatedness"
|
||||||
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/identity"
|
||||||
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
|
||||||
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/source_refs"
|
||||||
@@ -69,11 +69,11 @@ func registerValidators(registries pipeline.Registries) error {
|
|||||||
{name: "item event source references validator", register: func() error { return itemeventrefs.Register(registries.Validators) }},
|
{name: "item event source references validator", register: func() error { return itemeventrefs.Register(registries.Validators) }},
|
||||||
{name: "item event source relatedness validator", register: func() error { return itemeventrelatedness.Register(registries.Validators) }},
|
{name: "item event source relatedness validator", register: func() error { return itemeventrelatedness.Register(registries.Validators) }},
|
||||||
{name: "item event normalized invariants validator", register: func() error { return itemeventinvariants.Register(registries.Validators) }},
|
{name: "item event normalized invariants validator", register: func() error { return itemeventinvariants.Register(registries.Validators) }},
|
||||||
{name: "npc interaction shape validator", register: func() error { return interactionshape.Register(registries.Validators) }},
|
{name: "npc occurrence shape validator", register: func() error { return npcoccurrenceshape.Register(registries.Validators) }},
|
||||||
{name: "npc interaction registry validator", register: func() error { return interactionregistry.Register(registries.Validators) }},
|
{name: "npc occurrence registry validator", register: func() error { return npcoccurrenceregistry.Register(registries.Validators) }},
|
||||||
{name: "npc interaction source references validator", register: func() error { return interactionrefs.Register(registries.Validators) }},
|
{name: "npc occurrence source references validator", register: func() error { return npcoccurrencerefs.Register(registries.Validators) }},
|
||||||
{name: "npc interaction source relatedness validator", register: func() error { return interactionrelatedness.Register(registries.Validators) }},
|
{name: "npc occurrence source relatedness validator", register: func() error { return npcoccurrencerelatedness.Register(registries.Validators) }},
|
||||||
{name: "npc interaction normalized invariants validator", register: func() error { return interactioninvariants.Register(registries.Validators) }},
|
{name: "npc occurrence normalized invariants validator", register: func() error { return npcoccurrenceinvariants.Register(registries.Validators) }},
|
||||||
{name: "scene description shape validator", register: func() error { return sceneshape.Register(registries.Validators) }},
|
{name: "scene description shape validator", register: func() error { return sceneshape.Register(registries.Validators) }},
|
||||||
{name: "scene description source references validator", register: func() error { return scenerefs.Register(registries.Validators) }},
|
{name: "scene description source references validator", register: func() error { return scenerefs.Register(registries.Validators) }},
|
||||||
{name: "scene description source relatedness validator", register: func() error { return scenerelatedness.Register(registries.Validators) }},
|
{name: "scene description source relatedness validator", register: func() error { return scenerelatedness.Register(registries.Validators) }},
|
||||||
@@ -82,11 +82,11 @@ func registerValidators(registries pipeline.Registries) error {
|
|||||||
{name: "location identity validator", register: func() error { return locationidentity.Register(registries.Validators) }},
|
{name: "location identity validator", register: func() error { return locationidentity.Register(registries.Validators) }},
|
||||||
{name: "location source references validator", register: func() error { return locationrefs.Register(registries.Validators) }},
|
{name: "location source references validator", register: func() error { return locationrefs.Register(registries.Validators) }},
|
||||||
{name: "location source relatedness validator", register: func() error { return locationrelatedness.Register(registries.Validators) }},
|
{name: "location source relatedness validator", register: func() error { return locationrelatedness.Register(registries.Validators) }},
|
||||||
{name: "location occurrence shape validator", register: func() error { return occurrenceshape.Register(registries.Validators) }},
|
{name: "location occurrence shape validator", register: func() error { return locationoccurrenceshape.Register(registries.Validators) }},
|
||||||
{name: "location occurrence registry validator", register: func() error { return occurrenceregistry.Register(registries.Validators) }},
|
{name: "location occurrence registry validator", register: func() error { return locationoccurrenceregistry.Register(registries.Validators) }},
|
||||||
{name: "location occurrence normalized invariants validator", register: func() error { return occurrenceinvariants.Register(registries.Validators) }},
|
{name: "location occurrence normalized invariants validator", register: func() error { return locationoccurrenceinvariants.Register(registries.Validators) }},
|
||||||
{name: "location occurrence source references validator", register: func() error { return occurrencerefs.Register(registries.Validators) }},
|
{name: "location occurrence source references validator", register: func() error { return locationoccurrencerefs.Register(registries.Validators) }},
|
||||||
{name: "location occurrence source relatedness validator", register: func() error { return occurrencerelatedness.Register(registries.Validators) }},
|
{name: "location occurrence source relatedness validator", register: func() error { return locationoccurrencerelatedness.Register(registries.Validators) }},
|
||||||
{name: "spell-list always accept validator", register: func() error {
|
{name: "spell-list always accept validator", register: func() error {
|
||||||
return alwaysaccept.RegisterTyped[dnd.SpellList](registries.Validators, dnd.SpellListKind)
|
return alwaysaccept.RegisterTyped[dnd.SpellList](registries.Validators, dnd.SpellListKind)
|
||||||
}},
|
}},
|
||||||
@@ -117,10 +117,10 @@ func registerValidators(registries pipeline.Registries) error {
|
|||||||
{name: "item-event-list always reject validator", register: func() error {
|
{name: "item-event-list always reject validator", register: func() error {
|
||||||
return alwaysreject.RegisterTyped[dnd.ItemEventList](registries.Validators, dnd.ItemEventListKind)
|
return alwaysreject.RegisterTyped[dnd.ItemEventList](registries.Validators, dnd.ItemEventListKind)
|
||||||
}},
|
}},
|
||||||
{name: "npc-interaction-list always accept validator", register: func() error {
|
{name: "npc-occurrence-list always accept validator", register: func() error {
|
||||||
return alwaysaccept.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
return alwaysaccept.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
||||||
}},
|
}},
|
||||||
{name: "npc-interaction-list always reject validator", register: func() error {
|
{name: "npc-occurrence-list always reject validator", register: func() error {
|
||||||
return alwaysreject.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
return alwaysreject.RegisterTyped[dnd.NPCOccurrenceList](registries.Validators, dnd.NPCOccurrenceListKind)
|
||||||
}},
|
}},
|
||||||
{name: "scene-description-list always accept validator", register: func() error {
|
{name: "scene-description-list always accept validator", register: func() error {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package invariants validates normalized D&D NPC interaction artifacts.
|
// Package invariants validates normalized D&D NPC occurrence artifacts.
|
||||||
package invariants
|
package invariants
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,17 +10,17 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcinteractions"
|
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
|
||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "normalize/dnd/npc-interactions/invariants"
|
Key = "normalize/dnd/npc-occurrences/invariants"
|
||||||
ReasonCode = "invalid_npc_interaction_normalization"
|
ReasonCode = "invalid_npc_interaction_normalization"
|
||||||
policy = "dnd.npc_interactions.validator.normalized.v1"
|
policy = "dnd.npc_occurrences.validator.normalized.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
@@ -35,7 +35,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
|||||||
|
|
||||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||||
if len(references) > 1 {
|
if len(references) > 1 {
|
||||||
return nil, fmt.Errorf("NPC interaction invariants validator accepts at most one reference set")
|
return nil, fmt.Errorf("NPC occurrence invariants validator accepts at most one reference set")
|
||||||
}
|
}
|
||||||
var referenceSet contracts.ReferenceSet
|
var referenceSet contracts.ReferenceSet
|
||||||
if len(references) == 1 {
|
if len(references) == 1 {
|
||||||
@@ -77,7 +77,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||||
if interactionshape.Validate(req.Value) != nil {
|
if occurrenceshape.Validate(req.Value) != nil {
|
||||||
return contracts.ValidationResult{Approved: true}, nil
|
return contracts.ValidationResult{Approved: true}, nil
|
||||||
}
|
}
|
||||||
index := source.NewDocumentIndex(req.Source)
|
index := source.NewDocumentIndex(req.Source)
|
||||||
@@ -85,14 +85,14 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
|||||||
return contracts.ValidationResult{Approved: true}, nil
|
return contracts.ValidationResult{Approved: true}, nil
|
||||||
}
|
}
|
||||||
if v == nil || v.npcResolver == nil {
|
if v == nil || v.npcResolver == nil {
|
||||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction invariants validator must not be nil")
|
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence invariants validator must not be nil")
|
||||||
}
|
}
|
||||||
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return contracts.ValidationResult{}, fmt.Errorf("resolve NPC registry: %w", err)
|
return contracts.ValidationResult{}, fmt.Errorf("resolve NPC registry: %w", err)
|
||||||
}
|
}
|
||||||
if !npcRegistry.Bound() {
|
if !npcRegistry.Bound() {
|
||||||
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC interaction normalization: NPC registry reference is required"}, nil
|
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC occurrence normalization: NPC registry reference is required"}, nil
|
||||||
}
|
}
|
||||||
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), req.Value, npcRegistry)
|
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), req.Value, npcRegistry)
|
||||||
if len(issues) == 0 {
|
if len(issues) == 0 {
|
||||||
@@ -101,7 +101,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
|||||||
return contracts.ValidationResult{
|
return contracts.ValidationResult{
|
||||||
Approved: false,
|
Approved: false,
|
||||||
ReasonCode: ReasonCode,
|
ReasonCode: ReasonCode,
|
||||||
Message: diagnostics.Aggregate("invalid NPC interaction normalization", issues),
|
Message: diagnostics.Aggregate("invalid NPC occurrence normalization", issues),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidatorApprovesCanonicalNormalizedInteractions(t *testing.T) {
|
func TestValidatorApprovesCanonicalNormalizedOccurrences(t *testing.T) {
|
||||||
references := registryReferences(t, "Aria", "Borin")
|
references := registryReferences(t, "Aria", "Borin")
|
||||||
result, err := newValidator(t, references).Validate(context.Background(), request(references, normalizedList()))
|
result, err := newValidator(t, references).Validate(context.Background(), request(references, normalizedList()))
|
||||||
if err != nil || !result.Approved {
|
if err != nil || !result.Approved {
|
||||||
@@ -143,9 +143,9 @@ func normalizedList() dnd.NPCOccurrenceList {
|
|||||||
|
|
||||||
func cloneList(value dnd.NPCOccurrenceList) dnd.NPCOccurrenceList {
|
func cloneList(value dnd.NPCOccurrenceList) dnd.NPCOccurrenceList {
|
||||||
copyValue := dnd.NPCOccurrenceList{Occurrences: make([]dnd.NPCOccurrence, len(value.Occurrences))}
|
copyValue := dnd.NPCOccurrenceList{Occurrences: make([]dnd.NPCOccurrence, len(value.Occurrences))}
|
||||||
for index, interaction := range value.Occurrences {
|
for index, occurrence := range value.Occurrences {
|
||||||
copyValue.Occurrences[index] = interaction
|
copyValue.Occurrences[index] = occurrence
|
||||||
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), interaction.SourceRefs...)
|
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), occurrence.SourceRefs...)
|
||||||
}
|
}
|
||||||
return copyValue
|
return copyValue
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package registry validates D&D NPC interaction names against NPC grounding.
|
// Package registry validates D&D NPC occurrence names against NPC grounding.
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "extract/dnd/npc-interactions/registry"
|
Key = "extract/dnd/npc-occurrences/registry"
|
||||||
ReasonCode = "invalid_npc_interaction_registry"
|
ReasonCode = "invalid_npc_interaction_registry"
|
||||||
policy = "dnd.npc_interactions.validator.registry.v1"
|
policy = "dnd.npc_occurrences.validator.registry.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
@@ -31,7 +31,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
|||||||
|
|
||||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||||
if len(references) > 1 {
|
if len(references) > 1 {
|
||||||
return nil, fmt.Errorf("NPC interaction registry validator accepts at most one reference set")
|
return nil, fmt.Errorf("NPC occurrence registry validator accepts at most one reference set")
|
||||||
}
|
}
|
||||||
var referenceSet contracts.ReferenceSet
|
var referenceSet contracts.ReferenceSet
|
||||||
if len(references) == 1 {
|
if len(references) == 1 {
|
||||||
@@ -73,11 +73,11 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||||
if interactionshape.Validate(req.Value) != nil {
|
if occurrenceshape.Validate(req.Value) != nil {
|
||||||
return contracts.ValidationResult{Approved: true}, nil
|
return contracts.ValidationResult{Approved: true}, nil
|
||||||
}
|
}
|
||||||
if v == nil || v.npcResolver == nil {
|
if v == nil || v.npcResolver == nil {
|
||||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction registry validator must not be nil")
|
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence registry validator must not be nil")
|
||||||
}
|
}
|
||||||
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
npcRegistry, err := v.npcResolver.Resolve(req.References)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,7 +107,7 @@ func rejection(issues []string) contracts.ValidationResult {
|
|||||||
return contracts.ValidationResult{
|
return contracts.ValidationResult{
|
||||||
Approved: false,
|
Approved: false,
|
||||||
ReasonCode: ReasonCode,
|
ReasonCode: ReasonCode,
|
||||||
Message: diagnostics.Aggregate("invalid NPC interaction registry", issues),
|
Message: diagnostics.Aggregate("invalid NPC occurrence registry", issues),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package shape validates required D&D NPC interaction candidate fields.
|
// Package shape validates required D&D NPC occurrence candidate fields.
|
||||||
package shape
|
package shape
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "extract/dnd/npc-interactions/shape"
|
Key = "extract/dnd/npc-occurrences/shape"
|
||||||
ReasonCode = "invalid_npc_interaction_shape"
|
ReasonCode = "invalid_npc_interaction_shape"
|
||||||
policy = "dnd.npc_interactions.validator.shape.v1"
|
policy = "dnd.npc_occurrences.validator.shape.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
@@ -45,7 +45,7 @@ func Validate(value dnd.NPCOccurrenceList) error {
|
|||||||
if len(issues) == 0 {
|
if len(issues) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC interaction shape", issues))
|
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC occurrence shape", issues))
|
||||||
}
|
}
|
||||||
|
|
||||||
func issuesFor(value dnd.NPCOccurrenceList) []string {
|
func issuesFor(value dnd.NPCOccurrenceList) []string {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package sourcerefs validates D&D NPC interaction transcript evidence.
|
// Package sourcerefs validates D&D NPC occurrence transcript evidence.
|
||||||
package sourcerefs
|
package sourcerefs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "extract/dnd/npc-interactions/source_refs"
|
Key = "extract/dnd/npc-occurrences/source_refs"
|
||||||
ReasonCode = "invalid_npc_interaction_source_refs"
|
ReasonCode = "invalid_npc_interaction_source_refs"
|
||||||
policy = "dnd.npc_interactions.validator.source_refs.v2"
|
policy = "dnd.npc_occurrences.validator.source_refs.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
@@ -36,9 +36,9 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
|||||||
|
|
||||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||||
if req.Stage == string(pipeline.StageExtract) && req.Chunk == nil {
|
if req.Stage == string(pipeline.StageExtract) && req.Chunk == nil {
|
||||||
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction source-reference validator requires the current extraction chunk")
|
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence source-reference validator requires the current extraction chunk")
|
||||||
}
|
}
|
||||||
if interactionshape.Validate(req.Value) != nil {
|
if occurrenceshape.Validate(req.Value) != nil {
|
||||||
return contracts.ValidationResult{Approved: true}, nil
|
return contracts.ValidationResult{Approved: true}, nil
|
||||||
}
|
}
|
||||||
index := source.NewDocumentIndex(req.Source)
|
index := source.NewDocumentIndex(req.Source)
|
||||||
@@ -63,7 +63,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
|||||||
return contracts.ValidationResult{
|
return contracts.ValidationResult{
|
||||||
Approved: false,
|
Approved: false,
|
||||||
ReasonCode: ReasonCode,
|
ReasonCode: ReasonCode,
|
||||||
Message: diagnostics.Aggregate("invalid NPC interaction source references", issues),
|
Message: diagnostics.Aggregate("invalid NPC occurrence source references", issues),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package sourcerelatedness warns about NPC interaction evidence unrelated to its NPC.
|
// Package sourcerelatedness warns about NPC occurrence evidence unrelated to its NPC.
|
||||||
package sourcerelatedness
|
package sourcerelatedness
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,14 +10,14 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Key = "extract/dnd/npc-interactions/source_relatedness"
|
Key = "extract/dnd/npc-occurrences/source_relatedness"
|
||||||
WarningReasonCode = "npc_interaction_not_near_source"
|
WarningReasonCode = "npc_interaction_not_near_source"
|
||||||
OmittedReasonCode = "npc_interaction_relatedness_warnings_omitted"
|
OmittedReasonCode = "npc_interaction_relatedness_warnings_omitted"
|
||||||
policy = "dnd.npc_interactions.validator.source_relatedness.v2"
|
policy = "dnd.npc_occurrences.validator.source_relatedness.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
@@ -36,7 +36,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||||
if interactionshape.Validate(req.Value) != nil {
|
if occurrenceshape.Validate(req.Value) != nil {
|
||||||
return contracts.ValidationResult{Approved: true}, nil
|
return contracts.ValidationResult{Approved: true}, nil
|
||||||
}
|
}
|
||||||
resolver, err := shared.NewCitationResolver(req.Source)
|
resolver, err := shared.NewCitationResolver(req.Source)
|
||||||
@@ -59,12 +59,12 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
|||||||
warnings = append(warnings, contracts.Warning{
|
warnings = append(warnings, contracts.Warning{
|
||||||
Scope: fmt.Sprintf("occurrences[%d]", index),
|
Scope: fmt.Sprintf("occurrences[%d]", index),
|
||||||
ReasonCode: WarningReasonCode,
|
ReasonCode: WarningReasonCode,
|
||||||
Message: fmt.Sprintf("NPC interaction name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
|
Message: fmt.Sprintf("NPC occurrence name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return contracts.ValidationResult{
|
return contracts.ValidationResult{
|
||||||
Approved: true,
|
Approved: true,
|
||||||
Warnings: diagnostics.LimitWarnings(warnings, "npc_interactions", OmittedReasonCode),
|
Warnings: diagnostics.LimitWarnings(warnings, "npc_occurrences", OmittedReasonCode),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerInteraction(t *testing.T) {
|
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerOccurrence(t *testing.T) {
|
||||||
value := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
value := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||||
{NPCID: "npc:test", Name: "O'Rin Thorn", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}},
|
{NPCID: "npc:test", Name: "O'Rin Thorn", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}},
|
||||||
{NPCID: "npc:test", Name: "Missing\nNPC", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 2, EndUnitID: 2}, {SourceID: "session", StartUnitID: 2, EndUnitID: 2}}},
|
{NPCID: "npc:test", Name: "Missing\nNPC", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 2, EndUnitID: 2}, {SourceID: "session", StartUnitID: 2, EndUnitID: 2}}},
|
||||||
@@ -53,9 +53,9 @@ func TestValidatorDefersMalformedShapeAndInvalidRanges(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidatorBoundsWarnings(t *testing.T) {
|
func TestValidatorBoundsWarnings(t *testing.T) {
|
||||||
count := diagnostics.MaxWarnings + 5
|
count := diagnostics.MaxWarnings + 5
|
||||||
interactions := make([]dnd.NPCOccurrence, count)
|
occurrences := make([]dnd.NPCOccurrence, count)
|
||||||
for index := range interactions {
|
for index := range occurrences {
|
||||||
interactions[index] = dnd.NPCOccurrence{
|
occurrences[index] = dnd.NPCOccurrence{
|
||||||
NPCID: "npc:test",
|
NPCID: "npc:test",
|
||||||
Name: "Missing NPC",
|
Name: "Missing NPC",
|
||||||
Kind: dnd.NPCOccurrenceKindMentioned,
|
Kind: dnd.NPCOccurrenceKindMentioned,
|
||||||
@@ -64,7 +64,7 @@ func TestValidatorBoundsWarnings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
|
result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
|
||||||
Source: &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 1, Text: "The party waits."}}},
|
Source: &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 1, Text: "The party waits."}}},
|
||||||
Value: dnd.NPCOccurrenceList{Occurrences: interactions},
|
Value: dnd.NPCOccurrenceList{Occurrences: occurrences},
|
||||||
})
|
})
|
||||||
if err != nil || !result.Approved {
|
if err != nil || !result.Approved {
|
||||||
t.Fatalf("Validate() = %#v, %v", result, err)
|
t.Fatalf("Validate() = %#v, %v", result, err)
|
||||||
@@ -14,73 +14,73 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNPCInteractionPipelineUsesAcceptedRegistryAndCurrentEvidence(t *testing.T) {
|
func TestNPCOccurrencePipelineUsesAcceptedRegistryAndCurrentEvidence(t *testing.T) {
|
||||||
registries := productionNPCRegistries(t)
|
registries := productionNPCRegistries(t)
|
||||||
resolved := resolveNPCInteractionPipeline(t, registries)
|
resolved := resolveNPCOccurrencePipeline(t, registries)
|
||||||
client := &npcInteractionLLMClient{}
|
client := &npcOccurrenceLLMClient{}
|
||||||
|
|
||||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Run() error = %v", err)
|
t.Fatalf("Run() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 2 {
|
if len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 2 {
|
||||||
t.Fatalf("run outputs = %#v rejected = %#v, want NPC and interaction artifacts", output.NormalizeOutputs, output.Rejected)
|
t.Fatalf("run outputs = %#v rejected = %#v, want NPC and occurrence artifacts", output.NormalizeOutputs, output.Rejected)
|
||||||
}
|
}
|
||||||
|
|
||||||
request := client.requestFor(t, interactionextract.PromptID)
|
request := client.requestFor(t, occurrenceextract.PromptID)
|
||||||
wantRegistry := `{"npcs":[{"id":"` + identity.DeriveID("Mira Thorn") + `","name":"Mira Thorn"},{"id":"` + identity.DeriveID("Hooded Guard") + `","name":"Hooded Guard"}]}`
|
wantRegistry := `{"npcs":[{"id":"` + identity.DeriveID("Mira Thorn") + `","name":"Mira Thorn"},{"id":"` + identity.DeriveID("Hooded Guard") + `","name":"Hooded Guard"}]}`
|
||||||
if got := string(request.Inputs["npc_registry"].Content); got != wantRegistry {
|
if got := string(request.Inputs["npc_registry"].Content); got != wantRegistry {
|
||||||
t.Fatalf("interaction registry input = %s, want names-only projection %s", got, wantRegistry)
|
t.Fatalf("occurrence registry input = %s, want names-only projection %s", got, wantRegistry)
|
||||||
}
|
}
|
||||||
if request.Inputs["npc_registry"].MediaType != npccodec.MediaType {
|
if request.Inputs["npc_registry"].MediaType != npccodec.MediaType {
|
||||||
t.Fatalf("interaction registry media type = %q, want %q", request.Inputs["npc_registry"].MediaType, npccodec.MediaType)
|
t.Fatalf("occurrence registry media type = %q, want %q", request.Inputs["npc_registry"].MediaType, npccodec.MediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
serialized := normalizedLane(t, output, "interactions")
|
serialized := normalizedLane(t, output, "occurrences")
|
||||||
if serialized.Artifact.Schema.ID != interactioncodec.SchemaID || serialized.Artifact.Schema.Version != interactioncodec.SchemaVersion {
|
if serialized.Artifact.Schema.ID != occurrencecodec.SchemaID || serialized.Artifact.Schema.Version != occurrencecodec.SchemaVersion {
|
||||||
t.Fatalf("interaction artifact schema = %#v", serialized.Artifact.Schema)
|
t.Fatalf("occurrence artifact schema = %#v", serialized.Artifact.Schema)
|
||||||
}
|
}
|
||||||
interactions, err := interactioncodec.New().Decode(serialized.Artifact.Content)
|
occurrences, err := occurrencecodec.New().Decode(serialized.Artifact.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Decode(interaction output) error = %v", err)
|
t.Fatalf("Decode(occurrence output) error = %v", err)
|
||||||
}
|
}
|
||||||
if len(interactions.Occurrences) != 2 {
|
if len(occurrences.Occurrences) != 2 {
|
||||||
t.Fatalf("interactions = %#v, want two occurrences", interactions)
|
t.Fatalf("occurrences = %#v, want two occurrences", occurrences)
|
||||||
}
|
}
|
||||||
first, second := interactions.Occurrences[0], interactions.Occurrences[1]
|
first, second := occurrences.Occurrences[0], occurrences.Occurrences[1]
|
||||||
if first.Name != "Mira Thorn" || string(first.Kind) != "dialogue" || second.Name != "Hooded Guard" || string(second.Kind) != "noncombat_presence" {
|
if first.Name != "Mira Thorn" || string(first.Kind) != "dialogue" || second.Name != "Hooded Guard" || string(second.Kind) != "noncombat_presence" {
|
||||||
t.Fatalf("interactions = %#v, want canonical names, kinds, and source chronology", interactions)
|
t.Fatalf("occurrences = %#v, want canonical names, kinds, and source chronology", occurrences)
|
||||||
}
|
}
|
||||||
assertInteractionEvidence(t, first.SourceRefs)
|
assertOccurrenceEvidence(t, first.SourceRefs)
|
||||||
assertInteractionEvidence(t, second.SourceRefs)
|
assertOccurrenceEvidence(t, second.SourceRefs)
|
||||||
if first.SourceRefs[0].StartUnitID >= second.SourceRefs[0].StartUnitID {
|
if first.SourceRefs[0].StartUnitID >= second.SourceRefs[0].StartUnitID {
|
||||||
t.Fatalf("interaction chronology = %#v, want source order", interactions.Occurrences)
|
t.Fatalf("occurrence chronology = %#v, want source order", occurrences.Occurrences)
|
||||||
}
|
}
|
||||||
|
|
||||||
var durable map[string]json.RawMessage
|
var durable map[string]json.RawMessage
|
||||||
if err := json.Unmarshal(serialized.Artifact.Content, &durable); err != nil {
|
if err := json.Unmarshal(serialized.Artifact.Content, &durable); err != nil {
|
||||||
t.Fatalf("unmarshal durable interaction payload: %v", err)
|
t.Fatalf("unmarshal durable occurrence payload: %v", err)
|
||||||
}
|
}
|
||||||
if len(durable) != 1 || durable["occurrences"] == nil {
|
if len(durable) != 1 || durable["occurrences"] == nil {
|
||||||
t.Fatalf("durable interaction payload = %#v, want only occurrences", durable)
|
t.Fatalf("durable occurrence payload = %#v, want only occurrences", durable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
||||||
registries := productionNPCRegistries(t)
|
registries := productionNPCRegistries(t)
|
||||||
cfg := loadNPCInteractionPipelineConfig(t)
|
cfg := loadNPCOccurrencePipelineConfig(t)
|
||||||
profile := cfg.Pipelines["dnd-npc-interactions-fixture"]
|
profile := cfg.Pipelines["dnd-npc-occurrences-fixture"]
|
||||||
profile.Chunk = pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}}
|
profile.Chunk = pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}}
|
||||||
cfg.Pipelines["dnd-npc-interactions-fixture"] = profile
|
cfg.Pipelines["dnd-npc-occurrences-fixture"] = profile
|
||||||
effective, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-npc-interactions-fixture", Catalog: moduleCatalog(registries)})
|
effective, err := cfg.Resolve(config.ResolveInput{PipelineID: "dnd-npc-occurrences-fixture", Catalog: moduleCatalog(registries)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
|||||||
if err != nil || len(warnings) != 0 {
|
if err != nil || len(warnings) != 0 {
|
||||||
t.Fatalf("MaterializeReferences() error = %v warnings = %#v", err, warnings)
|
t.Fatalf("MaterializeReferences() error = %v warnings = %#v", err, warnings)
|
||||||
}
|
}
|
||||||
client := &semanticNPCInteractionClient{}
|
client := &semanticNPCOccurrenceClient{}
|
||||||
raw := []byte(`{"metadata":{"id":"semantic-session","title":"Semantic NPC session"},"segments":[{"id":1,"start":0,"end":1,"speaker":"DM","text":"Mira Thorn enters."},{"id":2,"start":1,"end":2,"speaker":"DM","text":"Mira Thorn, the Greencloak, waves."}]}`)
|
raw := []byte(`{"metadata":{"id":"semantic-session","title":"Semantic NPC session"},"segments":[{"id":1,"start":0,"end":1,"speaker":"DM","text":"Mira Thorn enters."},{"id":2,"start":1,"end":2,"speaker":"DM","text":"Mira Thorn, the Greencloak, waves."}]}`)
|
||||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: raw})
|
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: raw})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,11 +108,11 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
|||||||
if refs := npcsValue.NPCs[0].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "semantic-session", StartUnitID: 1, EndUnitID: 1}, {SourceID: "semantic-session", StartUnitID: 2, EndUnitID: 2}}) {
|
if refs := npcsValue.NPCs[0].SourceRefs; !reflect.DeepEqual(refs, []source.SourceRef{{SourceID: "semantic-session", StartUnitID: 1, EndUnitID: 1}, {SourceID: "semantic-session", StartUnitID: 2, EndUnitID: 2}}) {
|
||||||
t.Fatalf("NPC evidence = %#v, want original extraction evidence union", refs)
|
t.Fatalf("NPC evidence = %#v, want original extraction evidence union", refs)
|
||||||
}
|
}
|
||||||
interactionOutput := normalizedLane(t, output, "interactions")
|
occurrenceOutput := normalizedLane(t, output, "occurrences")
|
||||||
if interactionOutput.StepID != "extract-interactions" {
|
if occurrenceOutput.StepID != "extract-occurrences" {
|
||||||
t.Fatalf("interaction output step = %q, want ordered downstream step", interactionOutput.StepID)
|
t.Fatalf("occurrence output step = %q, want ordered downstream step", occurrenceOutput.StepID)
|
||||||
}
|
}
|
||||||
registryRequest := client.requestFor(t, interactionextract.PromptID)
|
registryRequest := client.requestFor(t, occurrenceextract.PromptID)
|
||||||
if got := string(registryRequest.Inputs["npc_registry"].Content); got != `{"npcs":[{"id":"`+identity.DeriveID("Mira Thorn")+`","name":"Mira Thorn"}]}` {
|
if got := string(registryRequest.Inputs["npc_registry"].Content); got != `{"npcs":[{"id":"`+identity.DeriveID("Mira Thorn")+`","name":"Mira Thorn"}]}` {
|
||||||
t.Fatalf("downstream registry = %s, want one canonical NPC identity", got)
|
t.Fatalf("downstream registry = %s, want one canonical NPC identity", got)
|
||||||
}
|
}
|
||||||
@@ -127,10 +127,10 @@ func TestSemanticNPCNormalizationCrossesOrderedRegistryHandoff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNPCInteractionPipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing.T) {
|
func TestNPCOccurrencePipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing.T) {
|
||||||
registries := productionNPCRegistries(t)
|
registries := productionNPCRegistries(t)
|
||||||
resolved := resolveNPCInteractionPipeline(t, registries)
|
resolved := resolveNPCOccurrencePipeline(t, registries)
|
||||||
client := &npcInteractionLLMClient{rejectNPCs: true}
|
client := &npcOccurrenceLLMClient{rejectNPCs: true}
|
||||||
|
|
||||||
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
output, err := runPreparedPipeline(t, registries, resolved, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -139,15 +139,15 @@ func TestNPCInteractionPipelineSkipsConsumerWhenNPCProducerIsRejected(t *testing
|
|||||||
if client.requestCount(npcregistry.PromptID) != 1 {
|
if client.requestCount(npcregistry.PromptID) != 1 {
|
||||||
t.Fatalf("NPC requests = %d, want rejected producer", client.requestCount(npcregistry.PromptID))
|
t.Fatalf("NPC requests = %d, want rejected producer", client.requestCount(npcregistry.PromptID))
|
||||||
}
|
}
|
||||||
if client.requestCount(interactionextract.PromptID) != 0 {
|
if client.requestCount(occurrenceextract.PromptID) != 0 {
|
||||||
t.Fatalf("interaction requests = %d, want none after rejected producer", client.requestCount(interactionextract.PromptID))
|
t.Fatalf("occurrence requests = %d, want none after rejected producer", client.requestCount(occurrenceextract.PromptID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveNPCInteractionPipeline(t *testing.T, registries pipeline.Registries) pipeline.ResolvedPipeline {
|
func resolveNPCOccurrencePipeline(t *testing.T, registries pipeline.Registries) pipeline.ResolvedPipeline {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
configValue := loadNPCInteractionPipelineConfig(t)
|
configValue := loadNPCOccurrencePipelineConfig(t)
|
||||||
effective, err := configValue.Resolve(config.ResolveInput{PipelineID: "dnd-npc-interactions-fixture", Catalog: moduleCatalog(registries)})
|
effective, err := configValue.Resolve(config.ResolveInput{PipelineID: "dnd-npc-occurrences-fixture", Catalog: moduleCatalog(registries)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Resolve() error = %v", err)
|
t.Fatalf("Resolve() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -158,11 +158,11 @@ func resolveNPCInteractionPipeline(t *testing.T, registries pipeline.Registries)
|
|||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadNPCInteractionPipelineConfig(t *testing.T) config.Config {
|
func loadNPCOccurrencePipelineConfig(t *testing.T) config.Config {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
data, err := os.ReadFile("testdata/dnd_npc_interactions_pipeline.yml")
|
data, err := os.ReadFile("testdata/dnd_npc_occurrences_pipeline.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadFile(dnd_npc_interactions_pipeline.yml) error = %v", err)
|
t.Fatalf("ReadFile(dnd_npc_occurrences_pipeline.yml) error = %v", err)
|
||||||
}
|
}
|
||||||
fileConfig, err := config.ParseFileConfigYAML(data)
|
fileConfig, err := config.ParseFileConfigYAML(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -186,25 +186,25 @@ func normalizedLane(t *testing.T, output pipeline.RunOutput, laneID string) cont
|
|||||||
return contracts.SerializedOutput{}
|
return contracts.SerializedOutput{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertInteractionEvidence(t *testing.T, references []source.SourceRef) {
|
func assertOccurrenceEvidence(t *testing.T, references []source.SourceRef) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if len(references) == 0 {
|
if len(references) == 0 {
|
||||||
t.Fatal("interaction has no current-source evidence")
|
t.Fatal("occurrence has no current-source evidence")
|
||||||
}
|
}
|
||||||
for _, reference := range references {
|
for _, reference := range references {
|
||||||
if reference.SourceID != "npc-session" {
|
if reference.SourceID != "npc-session" {
|
||||||
t.Fatalf("interaction evidence = %#v, want current source only", reference)
|
t.Fatalf("occurrence evidence = %#v, want current source only", reference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type npcInteractionLLMClient struct {
|
type npcOccurrenceLLMClient struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
requests []contracts.StructuredCompletionRequest
|
requests []contracts.StructuredCompletionRequest
|
||||||
rejectNPCs bool
|
rejectNPCs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *npcInteractionLLMClient) CompleteStructured(ctx context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
func (client *npcOccurrenceLLMClient) CompleteStructured(ctx context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, err
|
return contracts.StructuredCompletionResponse{}, err
|
||||||
}
|
}
|
||||||
@@ -227,25 +227,25 @@ func (client *npcInteractionLLMClient) CompleteStructured(ctx context.Context, r
|
|||||||
}
|
}
|
||||||
case npcnormalize.PromptID:
|
case npcnormalize.PromptID:
|
||||||
payload = map[string]any{"duplicate_groups": []any{}}
|
payload = map[string]any{"duplicate_groups": []any{}}
|
||||||
case interactionextract.PromptID:
|
case occurrenceextract.PromptID:
|
||||||
payload = map[string]any{"occurrences": []any{
|
payload = map[string]any{"occurrences": []any{
|
||||||
map[string]any{"npc_id": identity.DeriveID("Hooded Guard"), "name": "Hooded Guard", "kind": "noncombat_presence", "source_refs": []any{map[string]int{"start_unit_id": 3, "end_unit_id": 3}}},
|
map[string]any{"npc_id": identity.DeriveID("Hooded Guard"), "name": "Hooded Guard", "kind": "noncombat_presence", "source_refs": []any{map[string]int{"start_unit_id": 3, "end_unit_id": 3}}},
|
||||||
map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 2, "end_unit_id": 2}}},
|
map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 2, "end_unit_id": 2}}},
|
||||||
}}
|
}}
|
||||||
default:
|
default:
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected interaction prompt %q", request.PromptID)
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected occurrence prompt %q", request.PromptID)
|
||||||
}
|
}
|
||||||
content, err := json.Marshal(payload)
|
content, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, err
|
return contracts.StructuredCompletionResponse{}, err
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(content, out); err != nil {
|
if err := json.Unmarshal(content, out); err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate interaction response: %w", err)
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate occurrence response: %w", err)
|
||||||
}
|
}
|
||||||
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "interaction-fake"}, nil
|
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "occurrence-fake"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *npcInteractionLLMClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
func (client *npcOccurrenceLLMClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
client.mu.Lock()
|
client.mu.Lock()
|
||||||
defer client.mu.Unlock()
|
defer client.mu.Unlock()
|
||||||
@@ -258,7 +258,7 @@ func (client *npcInteractionLLMClient) requestFor(t *testing.T, promptID string)
|
|||||||
return contracts.StructuredCompletionRequest{}
|
return contracts.StructuredCompletionRequest{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *npcInteractionLLMClient) requestCount(promptID string) int {
|
func (client *npcOccurrenceLLMClient) requestCount(promptID string) int {
|
||||||
client.mu.Lock()
|
client.mu.Lock()
|
||||||
defer client.mu.Unlock()
|
defer client.mu.Unlock()
|
||||||
count := 0
|
count := 0
|
||||||
@@ -270,14 +270,14 @@ func (client *npcInteractionLLMClient) requestCount(promptID string) int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.StructuredLLMClient = (*npcInteractionLLMClient)(nil)
|
var _ contracts.StructuredLLMClient = (*npcOccurrenceLLMClient)(nil)
|
||||||
|
|
||||||
type semanticNPCInteractionClient struct {
|
type semanticNPCOccurrenceClient struct {
|
||||||
requests []contracts.StructuredCompletionRequest
|
requests []contracts.StructuredCompletionRequest
|
||||||
npcCalls int
|
npcCalls int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
func (client *semanticNPCOccurrenceClient) CompleteStructured(_ context.Context, request contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||||
client.requests = append(client.requests, cloneStructuredCompletionRequest(request))
|
client.requests = append(client.requests, cloneStructuredCompletionRequest(request))
|
||||||
var payload any
|
var payload any
|
||||||
switch request.PromptID {
|
switch request.PromptID {
|
||||||
@@ -290,7 +290,7 @@ func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context
|
|||||||
payload = map[string]any{"npcs": []any{map[string]any{"name": name, "source_refs": []any{map[string]int{"start_unit_id": client.npcCalls, "end_unit_id": client.npcCalls}}}}}
|
payload = map[string]any{"npcs": []any{map[string]any{"name": name, "source_refs": []any{map[string]int{"start_unit_id": client.npcCalls, "end_unit_id": client.npcCalls}}}}}
|
||||||
case npcnormalize.PromptID:
|
case npcnormalize.PromptID:
|
||||||
payload = map[string]any{"duplicate_groups": []any{map[string]any{"members": []string{"candidate-000001", "candidate-000002"}, "canonical": "candidate-000001"}}}
|
payload = map[string]any{"duplicate_groups": []any{map[string]any{"members": []string{"candidate-000001", "candidate-000002"}, "canonical": "candidate-000001"}}}
|
||||||
case interactionextract.PromptID:
|
case occurrenceextract.PromptID:
|
||||||
payload = map[string]any{"occurrences": []any{map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}}}}}
|
payload = map[string]any{"occurrences": []any{map[string]any{"npc_id": identity.DeriveID("Mira Thorn"), "name": "Mira Thorn", "kind": "dialogue", "source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}}}}}
|
||||||
default:
|
default:
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected semantic pipeline prompt %q", request.PromptID)
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected semantic pipeline prompt %q", request.PromptID)
|
||||||
@@ -305,7 +305,7 @@ func (client *semanticNPCInteractionClient) CompleteStructured(_ context.Context
|
|||||||
return contracts.StructuredCompletionResponse{Content: content}, nil
|
return contracts.StructuredCompletionResponse{Content: content}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *semanticNPCInteractionClient) requestCount(promptID string) int {
|
func (client *semanticNPCOccurrenceClient) requestCount(promptID string) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, request := range client.requests {
|
for _, request := range client.requests {
|
||||||
if request.PromptID == promptID {
|
if request.PromptID == promptID {
|
||||||
@@ -315,7 +315,7 @@ func (client *semanticNPCInteractionClient) requestCount(promptID string) int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *semanticNPCInteractionClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
func (client *semanticNPCOccurrenceClient) requestFor(t *testing.T, promptID string) contracts.StructuredCompletionRequest {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, request := range client.requests {
|
for _, request := range client.requests {
|
||||||
if request.PromptID == promptID {
|
if request.PromptID == promptID {
|
||||||
@@ -326,4 +326,4 @@ func (client *semanticNPCInteractionClient) requestFor(t *testing.T, promptID st
|
|||||||
return contracts.StructuredCompletionRequest{}
|
return contracts.StructuredCompletionRequest{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.StructuredLLMClient = (*semanticNPCInteractionClient)(nil)
|
var _ contracts.StructuredLLMClient = (*semanticNPCOccurrenceClient)(nil)
|
||||||
@@ -8,7 +8,7 @@ cache:
|
|||||||
debug:
|
debug:
|
||||||
directory: ./notarius-debug
|
directory: ./notarius-debug
|
||||||
pipelines:
|
pipelines:
|
||||||
dnd-npc-interactions-fixture:
|
dnd-npc-occurrences-fixture:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
steps:
|
steps:
|
||||||
- id: identify-npcs
|
- id: identify-npcs
|
||||||
@@ -16,13 +16,13 @@ pipelines:
|
|||||||
npc_registry:
|
npc_registry:
|
||||||
extract: dnd/npc-registry
|
extract: dnd/npc-registry
|
||||||
normalize: dnd/npc-registry
|
normalize: dnd/npc-registry
|
||||||
- id: extract-interactions
|
- id: extract-occurrences
|
||||||
references:
|
references:
|
||||||
npc_registry:
|
npc_registry:
|
||||||
artifact:
|
artifact:
|
||||||
step: identify-npcs
|
step: identify-npcs
|
||||||
lane: npc_registry
|
lane: npc_registry
|
||||||
artifacts:
|
artifacts:
|
||||||
interactions:
|
occurrences:
|
||||||
extract: dnd/npc-interactions
|
extract: dnd/npc-occurrences
|
||||||
normalize: dnd/npc-interactions
|
normalize: dnd/npc-occurrences
|
||||||
Reference in New Issue
Block a user