Move NPC occurrences to their canonical namespace
This commit is contained in:
@@ -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}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user