Move NPC occurrences to their canonical namespace
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Package npcinteractions encodes durable D&D NPC occurrence artifacts.
|
||||
package npcinteractions
|
||||
// Package npcoccurrences encodes durable D&D NPC occurrence artifacts.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"embed"
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -111,7 +111,7 @@ func TestCodecStrictlyRejectsMalformedUnknownAndTrailingJSON(t *testing.T) {
|
||||
for _, test := range []struct{ name, raw, want string }{
|
||||
{"malformed", `{`, "decode dnd npc occurrence list"},
|
||||
{"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"},
|
||||
{"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{
|
||||
dnd.NPCOccurrenceKindMentioned,
|
||||
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") {
|
||||
t.Fatalf("combat grounding = %s", got)
|
||||
}
|
||||
if got := string(request.Inputs[NPCInteractionReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
t.Fatalf("interaction grounding = %s", got)
|
||||
if got := string(request.Inputs[NPCOccurrenceReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
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/modules/dnd"
|
||||
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"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
||||
CombatTurnReferenceSlot = "combat_turns"
|
||||
NPCInteractionReferenceSlot = "npc_interactions"
|
||||
NPCOccurrenceReferenceSlot = "npc_occurrences"
|
||||
ReferenceMaxBytes = 1048576
|
||||
promptProjectionMediaType = "application/json"
|
||||
)
|
||||
@@ -67,10 +67,10 @@ func referenceSlots() []contracts.ReferenceSlot {
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
contracts.ReferenceSlot{
|
||||
Name: NPCInteractionReferenceSlot,
|
||||
Description: "Required NPC-interaction artifact used only as source-free enemy-event grounding.",
|
||||
Name: NPCOccurrenceReferenceSlot,
|
||||
Description: "Required NPC-occurrence artifact used only as source-free enemy-event grounding.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{interactioncodec.MediaType},
|
||||
AcceptedMediaTypes: []string{occurrencecodec.MediaType},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCOccurrenceListKind},
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
@@ -86,14 +86,14 @@ type groundingResolver struct {
|
||||
npcs *npcregistry.Resolver
|
||||
scenes *sceneregistry.Resolver
|
||||
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcInteractions *contracts.LLMInputMaterial
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcOccurrences *contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
type grounding struct {
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcInteractionInput contracts.LLMInputMaterial
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcOccurrenceInput contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver, error) {
|
||||
@@ -109,15 +109,15 @@ func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
npcInteractions, err := prepareNPCInteractionInput(references)
|
||||
npcOccurrences, err := prepareNPCOccurrenceInput(references)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &groundingResolver{
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcInteractions: npcInteractions,
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcOccurrences: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -136,14 +136,14 @@ func (r *groundingResolver) Resolve(references contracts.ReferenceSet) (groundin
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
npcInteractions, err := resolveInput(references, NPCInteractionReferenceSlot, r.npcInteractions, prepareNPCInteractionInput)
|
||||
npcOccurrences, err := resolveInput(references, NPCOccurrenceReferenceSlot, r.npcOccurrences, prepareNPCOccurrenceInput)
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
return grounding{
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcInteractionInput: npcInteractions,
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcOccurrenceInput: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ func resolveInput(references contracts.ReferenceSet, slot string, seeded *contra
|
||||
|
||||
func (g grounding) PromptInputs() contracts.LLMInputSet {
|
||||
return contracts.LLMInputSet{
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCInteractionReferenceSlot: g.npcInteractionInput.Clone(),
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCOccurrenceReferenceSlot: g.npcOccurrenceInput.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,22 +212,22 @@ func prepareCombatTurnInput(references contracts.ReferenceSet) (*contracts.LLMIn
|
||||
return newPromptInput(CombatTurnReferenceSlot, content), nil
|
||||
}
|
||||
|
||||
func prepareNPCInteractionInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCInteractionReferenceSlot, interactioncodec.MediaType)
|
||||
func prepareNPCOccurrenceInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCOccurrenceReferenceSlot, occurrencecodec.MediaType)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
value, err := interactioncodec.New().Decode(item.Content)
|
||||
value, err := occurrencecodec.New().Decode(item.Content)
|
||||
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 {
|
||||
Interactions []npcInteractionProjection `json:"npc_interactions"`
|
||||
}{Interactions: projectNPCInteractions(value.Occurrences)})
|
||||
Occurrences []npcOccurrenceProjection `json:"npc_occurrences"`
|
||||
}{Occurrences: projectNPCOccurrences(value.Occurrences)})
|
||||
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) {
|
||||
@@ -271,16 +271,16 @@ func projectCombatTurns(turns []dnd.CombatTurn) []combatTurnProjection {
|
||||
return projection
|
||||
}
|
||||
|
||||
type npcInteractionProjection struct {
|
||||
type npcOccurrenceProjection struct {
|
||||
Name string `json:"name"`
|
||||
Kind dnd.NPCOccurrenceKind `json:"kind"`
|
||||
}
|
||||
|
||||
func projectNPCInteractions(occurrences []dnd.NPCOccurrence) []npcInteractionProjection {
|
||||
projection := make([]npcInteractionProjection, 0, len(occurrences))
|
||||
func projectNPCOccurrences(occurrences []dnd.NPCOccurrence) []npcOccurrenceProjection {
|
||||
projection := make([]npcOccurrenceProjection, 0, len(occurrences))
|
||||
for _, occurrence := range occurrences {
|
||||
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
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
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"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||
@@ -36,7 +36,7 @@ func TestReferenceSlotsDescribeRequiredTypedArtifacts(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, dnd.NPCRegistryKind},
|
||||
{SceneDescriptionReferenceSlot, dnd.SceneDescriptionListKind},
|
||||
{CombatTurnReferenceSlot, dnd.CombatTurnListKind},
|
||||
{NPCInteractionReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
{NPCOccurrenceReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
} {
|
||||
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 {
|
||||
@@ -61,9 +61,9 @@ func TestGroundingProducesExactSourceFreePromptInputs(t *testing.T) {
|
||||
}
|
||||
inputs := resolved.PromptInputs()
|
||||
want := map[string]string{
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCInteractionReferenceSlot: `{"npc_interactions":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCOccurrenceReferenceSlot: `{"npc_occurrences":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
}
|
||||
if len(inputs) != len(want) {
|
||||
t.Fatalf("PromptInputs() = %#v", inputs)
|
||||
@@ -153,7 +153,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, "NPC registry"},
|
||||
{SceneDescriptionReferenceSlot, "scene descriptions"},
|
||||
{CombatTurnReferenceSlot, CombatTurnReferenceSlot},
|
||||
{NPCInteractionReferenceSlot, NPCInteractionReferenceSlot},
|
||||
{NPCOccurrenceReferenceSlot, NPCOccurrenceReferenceSlot},
|
||||
} {
|
||||
t.Run("missing "+test.slot, func(t *testing.T) {
|
||||
resolver, err := newGroundingResolver(withoutSlot(valid, test.slot))
|
||||
@@ -176,7 +176,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
set contracts.ReferenceSet
|
||||
}{
|
||||
{"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})},
|
||||
{"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 {
|
||||
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("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)}},
|
||||
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
|
||||
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) {
|
||||
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) {
|
||||
inputs := enemyEventPromptInputs()
|
||||
delete(inputs, inputName)
|
||||
@@ -79,12 +79,12 @@ func newEnemyEventPromptEngine(t *testing.T) *promptkit.Engine {
|
||||
|
||||
func enemyEventPromptInputs() map[string]promptkit.ArtifactRef {
|
||||
return map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"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 (
|
||||
"sort"
|
||||
@@ -66,7 +66,7 @@ func canonicalOccurrenceList(response extractionResponse, sourceID string) dnd.N
|
||||
return dnd.NPCOccurrenceList{Occurrences: occurrences}
|
||||
}
|
||||
|
||||
func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) []source.SourceRef {
|
||||
func canonicalSourceRefs(refs []occurrenceSourceRefResponse, sourceID string) []source.SourceRef {
|
||||
if refs == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -77,13 +77,13 @@ func canonicalSourceRefs(refs []interactionSourceRefResponse, sourceID string) [
|
||||
return out
|
||||
}
|
||||
|
||||
func occurrenceResponseRefs(refs []source.SourceRef) []interactionSourceRefResponse {
|
||||
func occurrenceResponseRefs(refs []source.SourceRef) []occurrenceSourceRefResponse {
|
||||
if refs == nil {
|
||||
return nil
|
||||
}
|
||||
values := make([]interactionSourceRefResponse, len(refs))
|
||||
values := make([]occurrenceSourceRefResponse, len(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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "dnd/npc-interactions"
|
||||
mappingPolicy = "dnd.npc_interactions.extract_mapping.v2"
|
||||
Key = "dnd/npc-occurrences"
|
||||
mappingPolicy = "dnd.npc_occurrences.extract_mapping.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,21 +28,21 @@ var requiredCapabilities = []string{
|
||||
}
|
||||
|
||||
var providedCapabilities = []string{
|
||||
"dnd.npc_interactions",
|
||||
"dnd.npc_occurrences",
|
||||
}
|
||||
|
||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
||||
Roster: "Deprecated alias for party roster 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 occurrence disambiguation.",
|
||||
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||
}
|
||||
|
||||
func referenceSlots() []contracts.ReferenceSlot {
|
||||
slots := shared.ReferenceSlots(referenceSlotDescriptions)
|
||||
slots = append(slots, contracts.ReferenceSlot{
|
||||
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,
|
||||
AcceptedMediaTypes: []string{"application/json"},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCRegistryKind},
|
||||
@@ -225,5 +225,5 @@ func DecodeOptions(options map[string]any) (Options, 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 (
|
||||
"context"
|
||||
@@ -17,14 +17,14 @@ import (
|
||||
)
|
||||
|
||||
func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: interactionRefs(30, 30)},
|
||||
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: interactionRefs(20, 20)},
|
||||
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: interactionRefs(5, 5)},
|
||||
{NPCID: identity.DeriveID("Speaker"), Name: "Speaker", Kind: "dialogue", SourceRefs: append(interactionRefs(2, 2), interactionRefs(2, 2)...)},
|
||||
{NPCID: identity.DeriveID("Present"), Name: "Present", Kind: "noncombat_presence", SourceRefs: interactionRefs(7, 7)},
|
||||
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: interactionRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: interactionRefs(0, 0)},
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Other"), Name: "Other", Kind: "other", SourceRefs: occurrenceRefs(30, 30)},
|
||||
{NPCID: identity.DeriveID("Opponent"), Name: "Opponent", Kind: "combat_opponent", SourceRefs: occurrenceRefs(20, 20)},
|
||||
{NPCID: identity.DeriveID("Ally"), Name: "Ally", Kind: "combat_ally", SourceRefs: occurrenceRefs(5, 5)},
|
||||
{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: occurrenceRefs(7, 7)},
|
||||
{NPCID: identity.DeriveID("Mentioned"), Name: "Mentioned", Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("Invalid"), Name: "Invalid", Kind: "unsupported", SourceRefs: occurrenceRefs(0, 0)},
|
||||
}}}
|
||||
references := requiredRegistryReferences(t, "Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid")
|
||||
req := extractionRequest()
|
||||
@@ -34,10 +34,10 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v", err)
|
||||
}
|
||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
||||
t.Fatalf("interaction order = %#v", got)
|
||||
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"Mentioned", "Speaker", "Present", "Ally", "Opponent", "Other", "Invalid"}) {
|
||||
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.NPCOccurrenceKindDialogue,
|
||||
dnd.NPCOccurrenceKindNoncombatPresence,
|
||||
@@ -46,7 +46,7 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
dnd.NPCOccurrenceKindOther,
|
||||
"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}}) {
|
||||
t.Fatalf("canonical source refs = %#v", refs)
|
||||
@@ -59,16 +59,16 @@ func TestExtractMapsEveryKindAndOrdersBySourcePosition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []interactionSourceRefResponse{
|
||||
func TestExtractUsesDocumentOrderForReferencesAndOccurrences(t *testing.T) {
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{NPCID: identity.DeriveID("Later"), Name: "Later", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{NPCID: identity.DeriveID("First"), Name: "First", Kind: "mentioned", SourceRefs: []occurrenceSourceRefResponse{
|
||||
{StartUnitID: 10, EndUnitID: 10},
|
||||
{StartUnitID: 30, EndUnitID: 30},
|
||||
{StartUnitID: 30, EndUnitID: 30},
|
||||
{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")
|
||||
req := extractionRequest()
|
||||
@@ -81,16 +81,16 @@ func TestExtractUsesDocumentOrderForReferencesAndInteractions(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v", err)
|
||||
}
|
||||
if got := interactionNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
||||
t.Fatalf("interaction order = %#v, want document chronology with stable equal-evidence ties", got)
|
||||
if got := occurrenceNames(result.Value); !reflect.DeepEqual(got, []string{"First", "Second", "Later"}) {
|
||||
t.Fatalf("occurrence order = %#v, want document chronology with stable equal-evidence ties", got)
|
||||
}
|
||||
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}) {
|
||||
t.Fatalf("source refs = %#v, want document order with exact duplicate removed", refs)
|
||||
}
|
||||
refs[0].StartUnitID = 777
|
||||
for _, interaction := range client.response.Occurrences {
|
||||
for _, ref := range interaction.SourceRefs {
|
||||
for _, occurrence := range client.response.Occurrences {
|
||||
for _, ref := range occurrence.SourceRefs {
|
||||
if ref.StartUnitID == 777 {
|
||||
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") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: interactionRefs(10, 10),
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
NPCID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Kind: "dialogue", SourceRefs: occurrenceRefs(10, 10),
|
||||
}}}}
|
||||
references := requiredRegistryReferences(t, "Mira Thorn", "Hooded Guard")
|
||||
req := extractionRequest()
|
||||
@@ -130,7 +130,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(t *testing.T) {
|
||||
if strings.Contains(string(request.Inputs["transcript"].Content), "other-session") {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func TestExtractUsesRegistryIDsAndCurrentTranscriptEvidence(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") {
|
||||
t.Fatalf("Extract() error = %v, want required registry failure", err)
|
||||
}
|
||||
@@ -169,11 +169,11 @@ func TestExtractRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
||||
occurrence occurrenceResponse
|
||||
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"},
|
||||
{"mismatched name", occurrenceResponse{NPCID: identity.DeriveID("Mira Thorn"), Name: "Hooded Guard", Kind: "dialogue", SourceRefs: interactionRefs(10, 10)}, "name does not match npc_id"},
|
||||
{"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: occurrenceRefs(10, 10)}, "name does not match npc_id"},
|
||||
} {
|
||||
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.References = references
|
||||
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) {
|
||||
client := &fakeInteractionsLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{}}}
|
||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
@@ -212,12 +212,12 @@ func TestExtractAcceptsEmptyBoundRegistryAndEmptyResponse(t *testing.T) {
|
||||
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.References = references
|
||||
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
||||
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")
|
||||
valid := extractionRequest()
|
||||
valid.References = references
|
||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
||||
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||
var nilExtractor *Extractor
|
||||
for _, test := range []struct {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
var registrySlot contracts.ReferenceSlot
|
||||
@@ -267,9 +267,9 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
if ModuleSpec().ReferenceSlots[0].AcceptedMediaTypes[0] == "changed" {
|
||||
t.Fatal("ModuleSpec() returned mutable reference slots")
|
||||
}
|
||||
extractorSlots := newExtractor(t, &fakeInteractionsLLMClient{}).ReferenceSlots()
|
||||
extractorSlots := newExtractor(t, &fakeOccurrencesLLMClient{}).ReferenceSlots()
|
||||
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")
|
||||
}
|
||||
registry := pipeline.NewExtractorRegistry()
|
||||
@@ -284,7 +284,7 @@ func TestModuleSpecRegistrationMetadataAndFingerprints(t *testing.T) {
|
||||
}
|
||||
|
||||
references := requiredRegistryReferences(t, "Mira Thorn")
|
||||
extractor := newExtractor(t, &fakeInteractionsLLMClient{}, references)
|
||||
extractor := newExtractor(t, &fakeOccurrencesLLMClient{}, references)
|
||||
metadata := extractor.ManifestMetadata()
|
||||
for key, want := range map[string]string{
|
||||
"prompt_id": PromptID, "prompt_version": SchemaVersion, "mapping_policy": mappingPolicy,
|
||||
@@ -320,7 +320,7 @@ func extractionRequest() contracts.TypedExtractionRequest {
|
||||
return contracts.TypedExtractionRequest{
|
||||
Source: doc, Chunk: chunk,
|
||||
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 {
|
||||
return []interactionSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||
func occurrenceRefs(start, end int) []occurrenceSourceRefResponse {
|
||||
return []occurrenceSourceRefResponse{{StartUnitID: start, EndUnitID: end}}
|
||||
}
|
||||
|
||||
func interactionNames(value dnd.NPCOccurrenceList) []string {
|
||||
func occurrenceNames(value dnd.NPCOccurrenceList) []string {
|
||||
names := make([]string, len(value.Occurrences))
|
||||
for index, interaction := range value.Occurrences {
|
||||
names[index] = interaction.Name
|
||||
for index, occurrence := range value.Occurrences {
|
||||
names[index] = occurrence.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func interactionKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
||||
func occurrenceKinds(value dnd.NPCOccurrenceList) []dnd.NPCOccurrenceKind {
|
||||
kinds := make([]dnd.NPCOccurrenceKind, len(value.Occurrences))
|
||||
for index, interaction := range value.Occurrences {
|
||||
kinds[index] = interaction.Kind
|
||||
for index, occurrence := range value.Occurrences {
|
||||
kinds[index] = occurrence.Kind
|
||||
}
|
||||
return kinds
|
||||
}
|
||||
@@ -393,13 +393,13 @@ func newExtractor(t *testing.T, client contracts.StructuredLLMClient, references
|
||||
return extractor
|
||||
}
|
||||
|
||||
type fakeInteractionsLLMClient struct {
|
||||
type fakeOccurrencesLLMClient struct {
|
||||
response extractionResponse
|
||||
err error
|
||||
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))
|
||||
if client.err != nil {
|
||||
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 (
|
||||
"encoding/json"
|
||||
@@ -12,7 +12,7 @@ func TestExtractionResponsePreservesValidatorOwnedSemantics(t *testing.T) {
|
||||
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
const promptAssetRoot = "assets/prompts"
|
||||
|
||||
var promptAssetManifest = shared.PromptAssetManifest{
|
||||
ModuleDir: "dnd.npc_interactions",
|
||||
ModuleDir: "dnd.npc_occurrences",
|
||||
ModuleFiles: []promptfs.ModulePromptFile{
|
||||
{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
|
||||
{Name: "instructions.md", Path: "prompts/instructions.md"},
|
||||
@@ -30,9 +30,9 @@ var promptAssetManifest = shared.PromptAssetManifest{
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("scope NPC-interaction assets: %w", err)
|
||||
return nil, fmt.Errorf("scope NPC-occurrence assets: %w", err)
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
@@ -44,14 +44,14 @@ func RegisterPromptAssets(registry *llm.AssetRegistry) error {
|
||||
}
|
||||
promptFS, err := promptAssetManifest.PromptFS(assets)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
schemas, err := fs.Sub(assets, "schemas")
|
||||
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, ".")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/promptkit"
|
||||
)
|
||||
|
||||
func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
||||
func TestRegisterPromptAssetsAndPrepareOccurrencePrompt(t *testing.T) {
|
||||
registry := llm.NewAssetRegistry()
|
||||
if err := RegisterPromptAssets(registry); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -28,21 +28,21 @@ func TestRegisterPromptAssetsAndPrepareInteractionPrompt(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
transcript := `{"units":[{"sentinel":"interaction-transcript"}]}`
|
||||
transcript := `{"units":[{"sentinel":"occurrence-transcript"}]}`
|
||||
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{
|
||||
"transcript": promptkit.InlineWithURI("file:///session.json", transcript),
|
||||
"players": promptkit.Inline("interaction-player"),
|
||||
"players": promptkit.Inline("occurrence-player"),
|
||||
"party": promptkit.Inline("Mira: ranger"),
|
||||
"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 {
|
||||
@@ -58,7 +58,7 @@ func TestPromptMetadataDoesNotExposeAssetContent(t *testing.T) {
|
||||
if err != nil || !strings.HasPrefix(hash, "sha256:") {
|
||||
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"} {
|
||||
if strings.Contains(strings.Join(mapValues(metadata), " "), forbidden) {
|
||||
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"
|
||||
|
||||
const (
|
||||
PromptID = "dnd.npc_interactions"
|
||||
PromptID = "dnd.npc_occurrences"
|
||||
ResponseSchemaKey = llm.ResponseSchemaKey("dnd_npc_occurrences_llm")
|
||||
ResponseSchemaID = "notarius.dnd.npc_occurrences.llm"
|
||||
ResponseSchemaName = "notarius_dnd_npc_occurrences_llm_v1"
|
||||
@@ -1,4 +1,4 @@
|
||||
package npcinteractions
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"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) {
|
||||
t.Fatalf("schema = %#v", schema)
|
||||
}
|
||||
valid := validInteractionResponse()
|
||||
valid := validOccurrenceResponse()
|
||||
content, err := json.Marshal(valid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -26,11 +26,11 @@ func TestResponseSchemaOwnsOnlyPrivateStructuralContract(t *testing.T) {
|
||||
t.Fatalf("valid response rejected: %v", err)
|
||||
}
|
||||
|
||||
semanticCandidate := validInteractionResponse()
|
||||
interaction := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
||||
interaction["name"] = ""
|
||||
interaction["kind"] = "unsupported"
|
||||
ref := interaction["source_refs"].([]any)[0].(map[string]any)
|
||||
semanticCandidate := validOccurrenceResponse()
|
||||
occurrence := semanticCandidate["occurrences"].([]any)[0].(map[string]any)
|
||||
occurrence["name"] = ""
|
||||
occurrence["kind"] = "unsupported"
|
||||
ref := occurrence["source_refs"].([]any)[0].(map[string]any)
|
||||
ref["start_unit_id"] = 0
|
||||
ref["end_unit_id"] = -1
|
||||
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"
|
||||
},
|
||||
} {
|
||||
candidate := validInteractionResponse()
|
||||
candidate := validOccurrenceResponse()
|
||||
mutate(candidate["occurrences"].([]any)[0].(map[string]any))
|
||||
content, err := json.Marshal(candidate)
|
||||
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{
|
||||
"npc_id": "npc:sha256:test", "name": "Mira Thorn", "kind": "dialogue",
|
||||
"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 npcinteractions
|
||||
// Package npcoccurrences normalizes merged D&D NPC occurrence candidates.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -10,21 +10,21 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"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"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "dnd/npc-interactions"
|
||||
normalizationPolicy = "dnd.npc_interactions.normalize.v2"
|
||||
Key = "dnd/npc-occurrences"
|
||||
normalizationPolicy = "dnd.npc_occurrences.normalize.v2"
|
||||
NormalizationPolicy = normalizationPolicy
|
||||
|
||||
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
||||
ReasonCodeInteractionsReordered = "npc_interactions_reordered"
|
||||
ReasonCodeDuplicateCollapsed = "duplicate_npc_interaction_collapsed"
|
||||
ReasonCodeWarningsOmitted = "npc_interaction_normalization_warnings_omitted"
|
||||
ReasonCodeSourceRefsNormalized = "source_references_normalized"
|
||||
ReasonCodeOccurrencesReordered = "npc_occurrences_reordered"
|
||||
ReasonCodeDuplicateCollapsed = "duplicate_npc_occurrence_collapsed"
|
||||
ReasonCodeWarningsOmitted = "npc_occurrence_normalization_warnings_omitted"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,10 +36,10 @@ var requiredCapabilities = []string{"merged"}
|
||||
var providedCapabilities = []string{"normalized"}
|
||||
|
||||
var referenceSlotDescriptions = shared.ReferenceSlotDescriptions{
|
||||
Glossary: "Optional campaign glossary reference material used only for interaction disambiguation.",
|
||||
Party: "Optional party roster reference material used only for interaction disambiguation.",
|
||||
Players: "Optional player list reference material used only for interaction disambiguation.",
|
||||
Roster: "Deprecated alias for party roster 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 occurrence disambiguation.",
|
||||
Players: "Optional player list reference material used only for occurrence disambiguation.",
|
||||
Roster: "Deprecated alias for party roster reference material used only for occurrence disambiguation.",
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
if position == record.inputIndex {
|
||||
@@ -160,7 +160,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
||||
}
|
||||
warnings = append(warnings, contracts.Warning{
|
||||
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),
|
||||
})
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func normalizeList(input dnd.NPCOccurrenceList, documentIndex source.DocumentInd
|
||||
output, duplicateWarnings := collapseDuplicates(records, documentIndex)
|
||||
warnings = append(warnings, duplicateWarnings...)
|
||||
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) {
|
||||
@@ -181,7 +181,7 @@ func normalizeOccurrence(input dnd.NPCOccurrence, order shared.SourceRefOrder, r
|
||||
}
|
||||
output := cloneOccurrence(input)
|
||||
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 {
|
||||
@@ -205,11 +205,11 @@ func collapseDuplicates(records []normalizedRecord, documentIndex source.Documen
|
||||
groups := make([]duplicateGroup, 0)
|
||||
groupByKey := make(map[string]int)
|
||||
for index, record := range records {
|
||||
if !interactionmodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
||||
if !occurrencemodel.ValidSourceRefs(documentIndex, record.occurrence.SourceRefs) {
|
||||
keep[index] = true
|
||||
continue
|
||||
}
|
||||
key := interactionmodel.ExactIdentity(record.occurrence)
|
||||
key := occurrencemodel.ExactIdentity(record.occurrence)
|
||||
groupIndex, exists := groupByKey[key]
|
||||
if !exists {
|
||||
groupByKey[key] = len(groups)
|
||||
@@ -243,7 +243,7 @@ func duplicateWarning(retainedIndex int, removed []int) contracts.Warning {
|
||||
Scope: occurrenceScope(retainedIndex),
|
||||
ReasonCode: ReasonCodeDuplicateCollapsed,
|
||||
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 = append(slots, contracts.ReferenceSlot{
|
||||
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,
|
||||
AcceptedMediaTypes: []string{"application/json"},
|
||||
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 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 (
|
||||
"context"
|
||||
@@ -31,7 +31,7 @@ func TestNormalizeValidatesPairsAndClones(t *testing.T) {
|
||||
}
|
||||
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}}) {
|
||||
t.Fatalf("normalized interaction = %#v", got)
|
||||
t.Fatalf("normalized occurrence = %#v", got)
|
||||
}
|
||||
if !hasWarning(result.Warnings, ReasonCodeSourceRefsNormalized) {
|
||||
t.Fatalf("warnings = %#v", result.Warnings)
|
||||
@@ -77,7 +77,7 @@ func TestNormalizeRejectsUnknownIDsAndMismatchedNames(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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}}},
|
||||
} {
|
||||
input := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{occurrence}}
|
||||
@@ -92,14 +92,14 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
||||
ref := func(unit int) source.SourceRef {
|
||||
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{
|
||||
interaction("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
||||
occurrence("Borin", dnd.NPCOccurrenceKindMentioned, ref(90)),
|
||||
first,
|
||||
first,
|
||||
interaction("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
||||
interaction("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindCombatAlly, ref(50)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(10)),
|
||||
occurrence("Ária", dnd.NPCOccurrenceKindDialogue, ref(999)),
|
||||
}}
|
||||
normalizer, err := New(Options{}, npcReferences(t))
|
||||
if err != nil {
|
||||
@@ -111,12 +111,12 @@ func TestNormalizeOrdersAndCollapsesExactDuplicatesOnly(t *testing.T) {
|
||||
}
|
||||
got := result.Value.Occurrences
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestNormalizeBoundsWarnings(t *testing.T) {
|
||||
for index := range doc.Units {
|
||||
doc.Units[index].ID = index + 1
|
||||
unitID := count - index
|
||||
input.Occurrences[index] = interaction(
|
||||
input.Occurrences[index] = occurrence(
|
||||
"Ária",
|
||||
dnd.NPCOccurrenceKindDialogue,
|
||||
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}}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package npcinteractions owns canonical ordering and exact-identity rules for
|
||||
// D&D NPC interaction artifacts.
|
||||
package npcinteractions
|
||||
// Package npcoccurrences owns canonical ordering and exact-identity rules for
|
||||
// D&D NPC occurrence artifacts.
|
||||
package npcoccurrences
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
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"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
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"
|
||||
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"
|
||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
occurrencerefs "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"
|
||||
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
||||
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||
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"
|
||||
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"
|
||||
@@ -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{
|
||||
Stage: pipeline.StageExtract,
|
||||
Module: interactionextract.Key,
|
||||
Module: occurrenceextract.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key),
|
||||
pipeline.Binding(interactionshape.Key),
|
||||
pipeline.Binding(interactionregistry.Key),
|
||||
pipeline.Binding(interactionrefs.Key),
|
||||
pipeline.Binding(npcoccurrenceshape.Key),
|
||||
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||
pipeline.Binding(npcoccurrencerefs.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{
|
||||
Stage: pipeline.StageNormalize,
|
||||
Module: interactionnormalize.Key,
|
||||
Module: occurrencenormalize.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key),
|
||||
pipeline.Binding(interactionshape.Key),
|
||||
pipeline.Binding(interactionregistry.Key),
|
||||
pipeline.Binding(interactioninvariants.Key),
|
||||
pipeline.Binding(interactionrefs.Key),
|
||||
pipeline.Binding(npcoccurrenceshape.Key),
|
||||
pipeline.Binding(npcoccurrenceregistry.Key),
|
||||
pipeline.Binding(npcoccurrenceinvariants.Key),
|
||||
pipeline.Binding(npcoccurrencerefs.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{
|
||||
Stage: pipeline.StageExtract, Module: locationoccurrenceextract.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
||||
pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(occurrencerelatedness.Key),
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.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{
|
||||
Stage: pipeline.StageNormalize, Module: locationoccurrencenormalize.Key,
|
||||
Validators: []pipeline.ModuleBinding{
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(occurrenceshape.Key), pipeline.Binding(occurrenceregistry.Key),
|
||||
pipeline.Binding(occurrenceinvariants.Key), pipeline.Binding(occurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(occurrencerelatedness.Key),
|
||||
pipeline.Binding(validjson.Key), pipeline.Binding(locationoccurrenceshape.Key), pipeline.Binding(locationoccurrenceregistry.Key),
|
||||
pipeline.Binding(locationoccurrenceinvariants.Key), pipeline.Binding(locationoccurrencerefs.Key), pipeline.Binding(validjsonschema.Key),
|
||||
pipeline.Binding(locationoccurrencerelatedness.Key),
|
||||
},
|
||||
})
|
||||
}},
|
||||
|
||||
@@ -19,8 +19,8 @@ func registerEvidence(registry *pipeline.ArtifactEvidenceRegistry) error {
|
||||
{name: "item events evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.ItemEventListKind, itemEventEvidence)
|
||||
}},
|
||||
{name: "npc interactions evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcInteractionEvidence)
|
||||
{name: "npc occurrences evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.NPCOccurrenceListKind, npcOccurrenceEvidence)
|
||||
}},
|
||||
{name: "scene descriptions evidence", register: func() error {
|
||||
return pipeline.RegisterArtifactEvidence(registry, dnd.SceneDescriptionListKind, sceneDescriptionEvidence)
|
||||
@@ -74,7 +74,7 @@ func itemEventEvidence(value dnd.ItemEventList) []source.SourceRef {
|
||||
return append([]source.SourceRef(nil), refs...)
|
||||
}
|
||||
|
||||
func npcInteractionEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
||||
func npcOccurrenceEvidence(value dnd.NPCOccurrenceList) []source.SourceRef {
|
||||
var refs []source.SourceRef
|
||||
for _, record := range value.Occurrences {
|
||||
refs = append(refs, record.SourceRefs...)
|
||||
|
||||
@@ -112,7 +112,7 @@ func appendItemEventLists(values []dnd.ItemEventList) (dnd.ItemEventList, error)
|
||||
return combined, nil
|
||||
}
|
||||
|
||||
func appendNPCInteractionLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
||||
func appendNPCOccurrenceLists(values []dnd.NPCOccurrenceList) (dnd.NPCOccurrenceList, error) {
|
||||
count := 0
|
||||
present := false
|
||||
for _, value := range values {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
itemeventcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemevents"
|
||||
locationoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationoccurrences"
|
||||
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"
|
||||
scenedescriptioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
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"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
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"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
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: "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: "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 {
|
||||
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: "enemy events extractor", register: func() error { return enemyeventextract.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: "locations extractor", register: func() error { return locationextract.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 {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.ItemEventListKind, appendItemEventLists)
|
||||
}},
|
||||
{name: "npc-interaction-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCInteractionLists)
|
||||
{name: "npc-occurrence-list appendorder merger", register: func() error {
|
||||
return appendorder.RegisterTyped(registries.Mergers, dnd.NPCOccurrenceListKind, appendNPCOccurrenceLists)
|
||||
}},
|
||||
{name: "scene-description-list appendorder merger", register: func() error {
|
||||
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: "enemy events normalizer", register: func() error { return enemyeventnormalize.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: "locations normalizer", register: func() error { return locationnormalize.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 {
|
||||
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)
|
||||
}},
|
||||
{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: "enemy events prompt assets", register: func() error { return enemyeventextract.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: "locations prompt assets", register: func() error { return locationextract.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"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
spellextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||
@@ -66,20 +66,20 @@ func TestExtractionPromptComposition(t *testing.T) {
|
||||
promptID: enemyeventextract.PromptID,
|
||||
promptVersion: enemyeventextract.SchemaVersion,
|
||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||
"npc_interactions": promptkit.Inline(`{"sentinel":"npc-interactions-sentinel"}`),
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||
"npc_occurrences": promptkit.Inline(`{"sentinel":"npc-occurrences-sentinel"}`),
|
||||
}),
|
||||
suffixGroups: [][]string{
|
||||
{evidenceSentinel},
|
||||
{npcSentinel},
|
||||
{"combat-turns-sentinel", "npc-interactions-sentinel"},
|
||||
{"combat-turns-sentinel", "npc-occurrences-sentinel"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "npc interactions",
|
||||
promptID: interactionextract.PromptID,
|
||||
promptVersion: interactionextract.SchemaVersion,
|
||||
name: "npc occurrences",
|
||||
promptID: occurrenceextract.PromptID,
|
||||
promptVersion: occurrenceextract.SchemaVersion,
|
||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||
"npc_registry": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||
}),
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
itemeventextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemevents"
|
||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||
"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"
|
||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||
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"
|
||||
scenedescriptionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/scenedescriptions"
|
||||
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.enemy_events/prompt.yaml",
|
||||
"dnd.item_events/prompt.yaml",
|
||||
"dnd.npc_interactions/prompt.yaml",
|
||||
"dnd.npc_occurrences/prompt.yaml",
|
||||
"dnd.scene_descriptions/prompt.yaml",
|
||||
"dnd.npc_registry.normalize/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)
|
||||
}
|
||||
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, "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, "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, 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.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})
|
||||
@@ -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(enemyeventnormalize.Key), []contracts.ArtifactKind{dnd.EnemyEventListKind})
|
||||
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})
|
||||
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",
|
||||
@@ -118,11 +118,11 @@ func TestRegisterAddsDNDFamily(t *testing.T) {
|
||||
"extract/dnd/item-events/source_refs",
|
||||
"extract/dnd/item-events/source_relatedness",
|
||||
"normalize/dnd/item-events/invariants",
|
||||
"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",
|
||||
"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",
|
||||
"extract/dnd/scene-descriptions/shape",
|
||||
"extract/dnd/scene-descriptions/source_refs",
|
||||
"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) {
|
||||
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("extract/dnd/npc-interactions/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||
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("extract/dnd/npc-interactions/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/registry"),
|
||||
pipeline.Binding("normalize/dnd/npc-interactions/invariants"),
|
||||
pipeline.Binding("extract/dnd/npc-interactions/source_refs"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/shape"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/registry"),
|
||||
pipeline.Binding("normalize/dnd/npc-occurrences/invariants"),
|
||||
pipeline.Binding("extract/dnd/npc-occurrences/source_refs"),
|
||||
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) {
|
||||
t.Fatalf("NPC interaction extract validator chain = %#v, want %#v", got, interactionExtractChain)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageExtract, occurrenceextract.Key); !reflect.DeepEqual(got, npcOccurrenceExtractChain) {
|
||||
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) {
|
||||
t.Fatalf("NPC interaction normalize validator chain = %#v, want %#v", got, interactionNormalizeChain)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageNormalize, occurrencenormalize.Key); !reflect.DeepEqual(got, npcOccurrenceNormalizeChain) {
|
||||
t.Fatalf("NPC occurrence normalize validator chain = %#v, want %#v", got, npcOccurrenceNormalizeChain)
|
||||
}
|
||||
sceneExtractChain := []pipeline.ModuleBinding{
|
||||
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 {
|
||||
t.Fatalf("NPC merge validator chain = %#v, want absent", got)
|
||||
}
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, interactionextract.Key); got != nil {
|
||||
t.Fatalf("NPC interaction merge validator chain = %#v, want absent", got)
|
||||
if got := registries.ValidatorChains.Validators(pipeline.StageMerge, occurrenceextract.Key); got != nil {
|
||||
t.Fatalf("NPC occurrence merge validator chain = %#v, want absent", got)
|
||||
}
|
||||
assertAssetNamesContain(t, assets.SchemaFS, []string{
|
||||
"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)
|
||||
}
|
||||
}
|
||||
interactionExtractSpec, extractOK := registries.Extractors.Spec(interactionextract.Key)
|
||||
interactionNormalizeSpec, normalizeOK := registries.Normalizers.Spec(interactionnormalize.Key)
|
||||
if !extractOK || interactionExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || interactionNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || interactionNormalizeSpec.Stage != pipeline.StageNormalize {
|
||||
t.Fatalf("NPC interaction specs = %#v / %#v, present = %t / %t", interactionExtractSpec, interactionNormalizeSpec, extractOK, normalizeOK)
|
||||
occurrenceExtractSpec, extractOK := registries.Extractors.Spec(occurrenceextract.Key)
|
||||
occurrenceNormalizeSpec, normalizeOK := registries.Normalizers.Spec(occurrencenormalize.Key)
|
||||
if !extractOK || occurrenceExtractSpec.ArtifactKind != dnd.NPCOccurrenceListKind || !normalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.NPCOccurrenceListKind || occurrenceNormalizeSpec.Stage != pipeline.StageNormalize {
|
||||
t.Fatalf("NPC occurrence specs = %#v / %#v, present = %t / %t", occurrenceExtractSpec, occurrenceNormalizeSpec, extractOK, normalizeOK)
|
||||
}
|
||||
locationExtractSpec, locationExtractOK := registries.Extractors.Spec(locationextract.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 {
|
||||
t.Fatalf("location specs = %#v / %#v", locationExtractSpec, locationNormalizeSpec)
|
||||
}
|
||||
occurrenceExtractSpec, occurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
||||
occurrenceNormalizeSpec, occurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
||||
if !occurrenceExtractOK || occurrenceExtractSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceExtractSpec.ExecutionClass != contracts.ExecutionClassLLMBacked || !occurrenceNormalizeOK || occurrenceNormalizeSpec.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceNormalizeSpec.ExecutionClass != contracts.ExecutionClassDeterministic {
|
||||
t.Fatalf("location occurrence specs = %#v / %#v", occurrenceExtractSpec, occurrenceNormalizeSpec)
|
||||
locationOccurrenceExtractSpec, locationOccurrenceExtractOK := registries.Extractors.Spec(locationoccurrenceextract.Key)
|
||||
locationOccurrenceNormalizeSpec, locationOccurrenceNormalizeOK := registries.Normalizers.Spec(locationoccurrencenormalize.Key)
|
||||
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", locationOccurrenceExtractSpec, locationOccurrenceNormalizeSpec)
|
||||
}
|
||||
locationRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "locations")
|
||||
occurrenceNormalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "locations")
|
||||
if len(occurrenceExtractSpec.ReferenceSlots) != 5 || len(occurrenceNormalizeSpec.ReferenceSlots) != 1 {
|
||||
t.Fatalf("location occurrence reference slots = %#v / %#v, want extractor campaign context and normalizer registry only", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||
locationRegistrySlot := referenceSlot(locationOccurrenceExtractSpec.ReferenceSlots, "locations")
|
||||
occurrenceNormalizeRegistrySlot := referenceSlot(locationOccurrenceNormalizeSpec.ReferenceSlots, "locations")
|
||||
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", 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) {
|
||||
t.Fatalf("location registry slots disagree: %#v / %#v", occurrenceExtractSpec.ReferenceSlots, occurrenceNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
t.Fatalf("scene description reference slots = %#v / %#v, want extractor campaign slots only", sceneExtractSpec.ReferenceSlots, sceneNormalizeSpec.ReferenceSlots)
|
||||
}
|
||||
extractRegistrySlot := referenceSlot(interactionExtractSpec.ReferenceSlots, "npc_registry")
|
||||
normalizeRegistrySlot := referenceSlot(interactionNormalizeSpec.ReferenceSlots, "npc_registry")
|
||||
extractRegistrySlot := referenceSlot(occurrenceExtractSpec.ReferenceSlots, "npc_registry")
|
||||
normalizeRegistrySlot := referenceSlot(occurrenceNormalizeSpec.ReferenceSlots, "npc_registry")
|
||||
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 {
|
||||
return itemEventEvidence(dnd.ItemEventList{Events: []dnd.ItemEvent{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
}, want: []source.SourceRef{first, second}},
|
||||
{name: "npc interactions", project: func() []source.SourceRef {
|
||||
return npcInteractionEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
{name: "npc occurrences", project: func() []source.SourceRef {
|
||||
return npcOccurrenceEvidence(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{first, second}}}})
|
||||
}, want: []source.SourceRef{first, second}},
|
||||
{name: "scene descriptions", project: func() []source.SourceRef {
|
||||
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}}
|
||||
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: "Borin", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 20, EndUnitID: 20}}}}},
|
||||
}
|
||||
got, err := appendNPCInteractionLists(input)
|
||||
got, err := appendNPCOccurrenceLists(input)
|
||||
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"}) {
|
||||
t.Fatalf("combined interactions = %#v", got)
|
||||
t.Fatalf("combined occurrences = %#v", got)
|
||||
}
|
||||
got.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{{}, {}}} {
|
||||
result, err := appendNPCInteractionLists(values)
|
||||
result, err := appendNPCOccurrenceLists(values)
|
||||
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)
|
||||
}
|
||||
|
||||
interactions, err := appendNPCInteractionLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || interactions.Occurrences[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCInteractionLists() = %#v, %v; want present-empty source refs", interactions, err)
|
||||
occurrences, err := appendNPCOccurrenceLists([]dnd.NPCOccurrenceList{{Occurrences: []dnd.NPCOccurrence{{SourceRefs: []source.SourceRef{}}}}})
|
||||
if err != nil || occurrences.Occurrences[0].SourceRefs == nil {
|
||||
t.Fatalf("appendNPCOccurrenceLists() = %#v, %v; want present-empty source refs", occurrences, err)
|
||||
}
|
||||
|
||||
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"
|
||||
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"
|
||||
occurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
occurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
occurrencerefs "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"
|
||||
locationoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/invariants"
|
||||
locationoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/registry"
|
||||
locationoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
|
||||
locationoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/source_refs"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
interactionregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/registry"
|
||||
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
|
||||
interactionrefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_refs"
|
||||
interactionrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/source_relatedness"
|
||||
npcoccurrenceinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/invariants"
|
||||
npcoccurrenceregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/registry"
|
||||
npcoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
|
||||
npcoccurrencerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/source_refs"
|
||||
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"
|
||||
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"
|
||||
@@ -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 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: "npc interaction shape validator", register: func() error { return interactionshape.Register(registries.Validators) }},
|
||||
{name: "npc interaction registry validator", register: func() error { return interactionregistry.Register(registries.Validators) }},
|
||||
{name: "npc interaction source references validator", register: func() error { return interactionrefs.Register(registries.Validators) }},
|
||||
{name: "npc interaction source relatedness validator", register: func() error { return interactionrelatedness.Register(registries.Validators) }},
|
||||
{name: "npc interaction normalized invariants validator", register: func() error { return interactioninvariants.Register(registries.Validators) }},
|
||||
{name: "npc occurrence shape validator", register: func() error { return npcoccurrenceshape.Register(registries.Validators) }},
|
||||
{name: "npc occurrence registry validator", register: func() error { return npcoccurrenceregistry.Register(registries.Validators) }},
|
||||
{name: "npc occurrence source references validator", register: func() error { return npcoccurrencerefs.Register(registries.Validators) }},
|
||||
{name: "npc occurrence source relatedness validator", register: func() error { return npcoccurrencerelatedness.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 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) }},
|
||||
@@ -82,11 +82,11 @@ func registerValidators(registries pipeline.Registries) error {
|
||||
{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 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 registry validator", register: func() error { return occurrenceregistry.Register(registries.Validators) }},
|
||||
{name: "location occurrence normalized invariants validator", register: func() error { return occurrenceinvariants.Register(registries.Validators) }},
|
||||
{name: "location occurrence source references validator", register: func() error { return occurrencerefs.Register(registries.Validators) }},
|
||||
{name: "location occurrence source relatedness validator", register: func() error { return occurrencerelatedness.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 locationoccurrenceregistry.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 locationoccurrencerefs.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 {
|
||||
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 {
|
||||
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)
|
||||
}},
|
||||
{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)
|
||||
}},
|
||||
{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
|
||||
|
||||
import (
|
||||
@@ -10,17 +10,17 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"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"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
||||
"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 (
|
||||
Key = "normalize/dnd/npc-interactions/invariants"
|
||||
Key = "normalize/dnd/npc-occurrences/invariants"
|
||||
ReasonCode = "invalid_npc_interaction_normalization"
|
||||
policy = "dnd.npc_interactions.validator.normalized.v1"
|
||||
policy = "dnd.npc_occurrences.validator.normalized.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -35,7 +35,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
||||
|
||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||
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
|
||||
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) {
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, nil
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
@@ -85,14 +85,14 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{Approved: true}, 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)
|
||||
if err != nil {
|
||||
return contracts.ValidationResult{}, fmt.Errorf("resolve NPC registry: %w", err)
|
||||
}
|
||||
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)
|
||||
if len(issues) == 0 {
|
||||
@@ -101,7 +101,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
ReasonCode: ReasonCode,
|
||||
Message: diagnostics.Aggregate("invalid NPC interaction normalization", issues),
|
||||
Message: diagnostics.Aggregate("invalid NPC occurrence normalization", issues),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
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")
|
||||
result, err := newValidator(t, references).Validate(context.Background(), request(references, normalizedList()))
|
||||
if err != nil || !result.Approved {
|
||||
@@ -143,9 +143,9 @@ func normalizedList() dnd.NPCOccurrenceList {
|
||||
|
||||
func cloneList(value dnd.NPCOccurrenceList) dnd.NPCOccurrenceList {
|
||||
copyValue := dnd.NPCOccurrenceList{Occurrences: make([]dnd.NPCOccurrence, len(value.Occurrences))}
|
||||
for index, interaction := range value.Occurrences {
|
||||
copyValue.Occurrences[index] = interaction
|
||||
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), interaction.SourceRefs...)
|
||||
for index, occurrence := range value.Occurrences {
|
||||
copyValue.Occurrences[index] = occurrence
|
||||
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), occurrence.SourceRefs...)
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
"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 (
|
||||
Key = "extract/dnd/npc-interactions/registry"
|
||||
Key = "extract/dnd/npc-occurrences/registry"
|
||||
ReasonCode = "invalid_npc_interaction_registry"
|
||||
policy = "dnd.npc_interactions.validator.registry.v1"
|
||||
policy = "dnd.npc_occurrences.validator.registry.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -31,7 +31,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
|
||||
|
||||
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
|
||||
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
|
||||
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) {
|
||||
if interactionshape.Validate(req.Value) != nil {
|
||||
if occurrenceshape.Validate(req.Value) != nil {
|
||||
return contracts.ValidationResult{Approved: true}, 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)
|
||||
if err != nil {
|
||||
@@ -107,7 +107,7 @@ func rejection(issues []string) contracts.ValidationResult {
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "extract/dnd/npc-interactions/shape"
|
||||
Key = "extract/dnd/npc-occurrences/shape"
|
||||
ReasonCode = "invalid_npc_interaction_shape"
|
||||
policy = "dnd.npc_interactions.validator.shape.v1"
|
||||
policy = "dnd.npc_occurrences.validator.shape.v1"
|
||||
)
|
||||
|
||||
type Options struct{}
|
||||
@@ -45,7 +45,7 @@ func Validate(value dnd.NPCOccurrenceList) error {
|
||||
if len(issues) == 0 {
|
||||
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 {
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package sourcerefs validates D&D NPC interaction transcript evidence.
|
||||
// Package sourcerefs validates D&D NPC occurrence transcript evidence.
|
||||
package sourcerefs
|
||||
|
||||
import (
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
"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 (
|
||||
Key = "extract/dnd/npc-interactions/source_refs"
|
||||
Key = "extract/dnd/npc-occurrences/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{}
|
||||
@@ -36,9 +36,9 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
|
||||
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
|
||||
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
|
||||
}
|
||||
index := source.NewDocumentIndex(req.Source)
|
||||
@@ -63,7 +63,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
return contracts.ValidationResult{
|
||||
Approved: false,
|
||||
ReasonCode: ReasonCode,
|
||||
Message: diagnostics.Aggregate("invalid NPC interaction source references", issues),
|
||||
Message: diagnostics.Aggregate("invalid NPC occurrence source references", issues),
|
||||
}, 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
|
||||
|
||||
import (
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"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/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 (
|
||||
Key = "extract/dnd/npc-interactions/source_relatedness"
|
||||
Key = "extract/dnd/npc-occurrences/source_relatedness"
|
||||
WarningReasonCode = "npc_interaction_not_near_source"
|
||||
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{}
|
||||
@@ -36,7 +36,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
resolver, err := shared.NewCitationResolver(req.Source)
|
||||
@@ -59,12 +59,12 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
|
||||
warnings = append(warnings, contracts.Warning{
|
||||
Scope: fmt.Sprintf("occurrences[%d]", index),
|
||||
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{
|
||||
Approved: true,
|
||||
Warnings: diagnostics.LimitWarnings(warnings, "npc_interactions", OmittedReasonCode),
|
||||
Warnings: diagnostics.LimitWarnings(warnings, "npc_occurrences", OmittedReasonCode),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"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{
|
||||
{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}}},
|
||||
@@ -53,9 +53,9 @@ func TestValidatorDefersMalformedShapeAndInvalidRanges(t *testing.T) {
|
||||
|
||||
func TestValidatorBoundsWarnings(t *testing.T) {
|
||||
count := diagnostics.MaxWarnings + 5
|
||||
interactions := make([]dnd.NPCOccurrence, count)
|
||||
for index := range interactions {
|
||||
interactions[index] = dnd.NPCOccurrence{
|
||||
occurrences := make([]dnd.NPCOccurrence, count)
|
||||
for index := range occurrences {
|
||||
occurrences[index] = dnd.NPCOccurrence{
|
||||
NPCID: "npc:test",
|
||||
Name: "Missing NPC",
|
||||
Kind: dnd.NPCOccurrenceKindMentioned,
|
||||
@@ -64,7 +64,7 @@ func TestValidatorBoundsWarnings(t *testing.T) {
|
||||
}
|
||||
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."}}},
|
||||
Value: dnd.NPCOccurrenceList{Occurrences: interactions},
|
||||
Value: dnd.NPCOccurrenceList{Occurrences: occurrences},
|
||||
})
|
||||
if err != nil || !result.Approved {
|
||||
t.Fatalf("Validate() = %#v, %v", result, err)
|
||||
Reference in New Issue
Block a user