Share D&D cited source traversal

This commit is contained in:
2026-07-21 14:37:12 +00:00
parent 732b13669f
commit 07460341e3
11 changed files with 322 additions and 129 deletions

View File

@@ -3,15 +3,12 @@ package sourcerelatedness
import (
"context"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
combatshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/combatturns/shape"
)
@@ -38,13 +35,21 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
}
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.CombatTurnList]) (contracts.ValidationResult, error) {
if combatshape.Validate(req.Value) != nil || !sourceRefsValid(req.Source, req.Value) {
if combatshape.Validate(req.Value) != nil {
return contracts.ValidationResult{Approved: true}, nil
}
citedTexts := make([]string, len(req.Value.CombatTurns))
for turnIndex, turn := range req.Value.CombatTurns {
citedText, err := shared.CitedText(req.Source, turn.SourceRefs)
if err != nil {
return contracts.ValidationResult{Approved: true}, nil
}
citedTexts[turnIndex] = citedText
}
warnings := make([]contracts.Warning, 0)
for turnIndex, turn := range req.Value.CombatTurns {
citedText := citedTextKey(req.Source, turn.SourceRefs)
citedText := citedTexts[turnIndex]
issues := make([]string, 0)
if !actorAppearsInCitedText(citedText, turn.Actor) {
issues = append(issues, fmt.Sprintf("actor %s was not found in cited source text", diagnostics.Quote(turn.Actor)))
@@ -65,47 +70,13 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
}
return contracts.ValidationResult{Approved: true, Warnings: warnings}, nil
}
func sourceRefsValid(doc *source.SourceDocument, value dnd.CombatTurnList) bool {
for _, turn := range value.CombatTurns {
for _, ref := range turn.SourceRefs {
if source.ValidateRef(doc, ref) != nil {
return false
}
}
}
return true
}
func citedTextKey(doc *source.SourceDocument, refs []source.SourceRef) string {
if doc == nil {
return ""
}
included := make([]bool, len(doc.Units))
for _, ref := range refs {
start, _ := source.UnitIndex(doc, ref.StartUnitID)
end, _ := source.UnitIndex(doc, ref.EndUnitID)
for index := start; index <= end && index < len(included); index++ {
included[index] = true
}
}
parts := make([]string, 0)
for index, unit := range doc.Units {
if included[index] {
parts = append(parts, unit.Text)
}
}
return identity.ComparisonKey(strings.Join(parts, " "))
}
func actorAppearsInCitedText(citedText string, actor string) bool {
key := identity.ComparisonKey(actor)
return key != "" && strings.Contains(citedText, key)
return shared.ContainsTokenSequence(citedText, actor)
}
func declarationAppearsInCitedText(citedText string, declaration string) bool {
citedTokens := tokenSet(citedText)
for _, token := range comparisonTokens(declaration) {
for _, token := range shared.NormalizedTokens(declaration) {
if utf8.RuneCountInString(token) >= 4 {
if _, ok := citedTokens[token]; ok {
return true
@@ -115,16 +86,8 @@ func declarationAppearsInCitedText(citedText string, declaration string) bool {
return false
}
func comparisonTokens(value string) []string {
value = identity.ComparisonKey(value)
if value == "" {
return nil
}
return strings.FieldsFunc(value, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) })
}
func tokenSet(value string) map[string]struct{} {
tokens := comparisonTokens(value)
tokens := shared.NormalizedTokens(value)
set := make(map[string]struct{}, len(tokens))
for _, token := range tokens {
set[token] = struct{}{}