From d9b5c25cfe95aef5a35225c0d36770d9677fcbde Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 3 Jul 2026 23:29:08 +0000 Subject: [PATCH] Add D&D spells extractor skeleton --- .../modules/extract/dnd/spells/extractor.go | 65 +++++++++++ internal/modules/extract/dnd/spells/model.go | 22 ++++ .../extract/dnd/spells/registry_test.go | 105 ++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 internal/modules/extract/dnd/spells/extractor.go create mode 100644 internal/modules/extract/dnd/spells/model.go create mode 100644 internal/modules/extract/dnd/spells/registry_test.go diff --git a/internal/modules/extract/dnd/spells/extractor.go b/internal/modules/extract/dnd/spells/extractor.go new file mode 100644 index 0000000..cfc1094 --- /dev/null +++ b/internal/modules/extract/dnd/spells/extractor.go @@ -0,0 +1,65 @@ +package spells + +import ( + "context" + "fmt" + + "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" + "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" +) + +const Key = "dnd/spells" +const ArtifactType = "dnd.spell_cast" +const SchemaVersion = "v1" + +var requiredCapabilities = []string{ + "chunks", + "source.transcript", +} + +var providedCapabilities = []string{ + "dnd.spell_casts", +} + +var _ contracts.Extractor = (*Extractor)(nil) + +type Extractor struct{} + +func New() *Extractor { + return &Extractor{} +} + +func (e *Extractor) Key() string { + return Key +} + +func (e *Extractor) ArtifactType() string { + return ArtifactType +} + +func (e *Extractor) SchemaVersion() string { + return SchemaVersion +} + +func (e *Extractor) Validators() []contracts.Validator { + return nil +} + +func (e *Extractor) Extract(ctx context.Context, req contracts.ExtractionRequest) (contracts.ExtractionResult, error) { + return contracts.ExtractionResult{}, fmt.Errorf("dnd spells extractor: extraction is not implemented") +} + +func ModuleSpec() pipeline.ModuleSpec { + return pipeline.ModuleSpec{ + Key: Key, + Stage: pipeline.StageExtract, + Requires: append([]string(nil), requiredCapabilities...), + Provides: append([]string(nil), providedCapabilities...), + } +} + +func Register(registry *pipeline.ExtractorRegistry) error { + return registry.RegisterWithSpec(ModuleSpec(), func() (contracts.Extractor, error) { + return New(), nil + }) +} diff --git a/internal/modules/extract/dnd/spells/model.go b/internal/modules/extract/dnd/spells/model.go new file mode 100644 index 0000000..9454d07 --- /dev/null +++ b/internal/modules/extract/dnd/spells/model.go @@ -0,0 +1,22 @@ +package spells + +import "gitea.maximumdirect.net/eric/notarius/internal/core/source" + +type SpellCast struct { + Caster string `json:"caster"` + Spell string `json:"spell"` + Effect string `json:"effect"` + NarrativeDescription string `json:"narrative_description"` +} + +type extractionResponse struct { + SpellCasts []spellCastResponse `json:"spell_casts"` +} + +type spellCastResponse struct { + Caster string `json:"caster"` + Spell string `json:"spell"` + Effect string `json:"effect"` + NarrativeDescription string `json:"narrative_description"` + SourceRefs []source.SourceRef `json:"source_refs"` +} diff --git a/internal/modules/extract/dnd/spells/registry_test.go b/internal/modules/extract/dnd/spells/registry_test.go new file mode 100644 index 0000000..a54a66c --- /dev/null +++ b/internal/modules/extract/dnd/spells/registry_test.go @@ -0,0 +1,105 @@ +package spells + +import ( + "context" + "reflect" + "strings" + "testing" + + "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" + "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" +) + +func TestNewReturnsExtractorWithMetadata(t *testing.T) { + extractor := New() + if extractor == nil { + t.Fatal("New() = nil, want extractor") + } + if extractor.Key() != Key { + t.Fatalf("extractor.Key() = %q, want %q", extractor.Key(), Key) + } + if extractor.ArtifactType() != ArtifactType { + t.Fatalf("extractor.ArtifactType() = %q, want %q", extractor.ArtifactType(), ArtifactType) + } + if extractor.SchemaVersion() != SchemaVersion { + t.Fatalf("extractor.SchemaVersion() = %q, want %q", extractor.SchemaVersion(), SchemaVersion) + } +} + +func TestModuleSpec(t *testing.T) { + got := ModuleSpec() + want := pipeline.ModuleSpec{ + Key: Key, + Stage: pipeline.StageExtract, + Requires: []string{ + "chunks", + "source.transcript", + }, + Provides: []string{ + "dnd.spell_casts", + }, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("ModuleSpec() = %#v, want %#v", got, want) + } + + got.Requires[0] = "changed" + got.Provides[0] = "changed" + again := ModuleSpec() + if !reflect.DeepEqual(again, want) { + t.Fatalf("ModuleSpec() after caller mutation = %#v, want %#v", again, want) + } +} + +func TestRegisterMakesExtractorBuildable(t *testing.T) { + registry := pipeline.NewExtractorRegistry() + + if err := Register(registry); err != nil { + t.Fatalf("Register() error = %v, want nil", err) + } + + extractor, err := registry.Build(Key) + if err != nil { + t.Fatalf("Build() error = %v, want nil", err) + } + if extractor.Key() != Key { + t.Fatalf("extractor.Key() = %q, want %q", extractor.Key(), Key) + } +} + +func TestRegisterStoresModuleSpec(t *testing.T) { + registry := pipeline.NewExtractorRegistry() + + if err := Register(registry); err != nil { + t.Fatalf("Register() error = %v, want nil", err) + } + + got, ok := registry.Spec(Key) + if !ok { + t.Fatal("Spec() ok = false, want true") + } + want := ModuleSpec() + if !reflect.DeepEqual(got, want) { + t.Fatalf("Spec() = %#v, want %#v", got, want) + } +} + +func TestRegisterNilRegistryReturnsError(t *testing.T) { + err := Register(nil) + if err == nil { + t.Fatal("Register(nil) error = nil, want error") + } + if !strings.Contains(err.Error(), "extractor registry") { + t.Fatalf("Register(nil) error = %q, want registry context", err.Error()) + } +} + +func TestExtractReportsNotImplemented(t *testing.T) { + _, err := New().Extract(context.Background(), contracts.ExtractionRequest{}) + if err == nil { + t.Fatal("Extract() error = nil, want error") + } + if !strings.Contains(err.Error(), "dnd spells") || !strings.Contains(err.Error(), "not implemented") { + t.Fatalf("Extract() error = %q, want not implemented D&D spells context", err.Error()) + } +}