package grammar import ( "context" "fmt" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" "gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation" "gitea.maximumdirect.net/eric/audita/internal/framework/proposals" "gitea.maximumdirect.net/eric/audita/internal/framework/validators" ) type Module struct { validators []contracts.Validator } func New() (*Module, error) { grammarOnlyGuard, err := validators.NewLLMBackedValidator("grammar_only_guard", validators.LLMValidatorTypeGrammarReview, "") if err != nil { return nil, err } meaningReversal, err := validators.NewLLMBackedValidator("meaning_reversal_review", validators.LLMValidatorTypeMeaningReversal, "") if err != nil { return nil, err } return &Module{ validators: []contracts.Validator{ validators.NoEffectValidator{}, validators.OriginalTextPresenceValidator{}, validators.ConfidenceThresholdValidator{}, validators.ProtectedGlossaryTermValidator{}, validators.NonEmptyCorrectionValidator{}, grammarOnlyGuard, meaningReversal, }, }, nil } func (m *Module) Key() string { return "grammar" } func (m *Module) ReplacementPolicy() proposals.ReplacementPolicy { // Conservative single-span replacement. return proposals.ReplacementPolicyRequireUnique } func (m *Module) Validators() []contracts.Validator { return append([]contracts.Validator(nil), m.validators...) } func (m *Module) Propose(ctx context.Context, req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) { sectionTranscript := transcriptForSection(req.WorkingTranscript, req.Section) sectionIndex := 0 if req.Section != nil { sectionIndex = req.Section.Index } transcriptDescription := "" if req.Config != nil { transcriptDescription = req.Config.TranscriptDescription } messages, err := BuildProposalMessages(sectionTranscript, req.Glossary, sectionIndex, transcriptDescription) if err != nil { return nil, err } generated, err := proposal_generation.GenerateCandidates(ctx, proposal_generation.Request{ ModuleKey: req.RunSpec.ModuleKey, ModuleInstance: req.RunSpec.InstanceName, ReplacementPolicy: req.RunSpec.ReplacementPolicy, WorkingTranscript: req.WorkingTranscript, Section: req.Section, Glossary: req.Glossary, Config: req.Config, Messages: messages, StageName: proposalStageName(req), StartIndex: 0, LLMClient: req.LLMClient, Scheduler: req.LLMScheduler, DiagnosticsDir: req.DiagnosticsDir, }) if err != nil { return nil, err } return generated.Corrections, nil } func transcriptForSection(transcript *schema.Transcript, section *contracts.SectionMetadata) *schema.Transcript { if section == nil || transcript == nil { return transcript } segments := make([]schema.Segment, 0, len(transcript.Segments)) for _, seg := range transcript.Segments { if seg.ID >= section.StartSegmentID && seg.ID <= section.EndSegmentID { segments = append(segments, seg) } } return &schema.Transcript{Segments: segments} } func proposalStageName(req contracts.ProposalRequest) string { if req.Section == nil || req.Section.Index == 0 { return fmt.Sprintf("%s:proposal", req.RunSpec.InstanceName) } return fmt.Sprintf("%s:proposal:section-%04d", req.RunSpec.InstanceName, req.Section.Index) }