package validators import ( "context" "fmt" "strings" ) type ConfidenceThresholdValidator struct{} func (v ConfidenceThresholdValidator) Name() string { return "confidence_threshold" } func (v ConfidenceThresholdValidator) Validate(_ context.Context, req Request) (Result, error) { threshold := confidenceThresholdForModule(req.ModuleKey, req.Config) decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { if c.Confidence < threshold { decisions = append(decisions, rejection(c.ProposalIndex, ReasonLowConfidence, fmt.Sprintf("confidence %.4f below threshold %.4f", c.Confidence, threshold))) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } type OriginalTextPresenceValidator struct{} func (v OriginalTextPresenceValidator) Name() string { return "original_text_presence" } func (v OriginalTextPresenceValidator) Validate(_ context.Context, req Request) (Result, error) { byID := make(map[int]string) if req.WorkingTranscript != nil { for _, seg := range req.WorkingTranscript.Segments { byID[seg.ID] = seg.Text } } decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { text, ok := byID[c.TargetSegmentID] if !ok { decisions = append(decisions, rejection(c.ProposalIndex, ReasonMissingTargetSegment, "target segment was not found")) continue } if !strings.Contains(text, c.OriginalText) { decisions = append(decisions, rejection(c.ProposalIndex, ReasonMissingOriginalText, "original_text was not found in current segment text")) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } type NonEmptyCorrectionValidator struct{} func (v NonEmptyCorrectionValidator) Name() string { return "non_empty_correction" } func (v NonEmptyCorrectionValidator) Validate(_ context.Context, req Request) (Result, error) { decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { if strings.TrimSpace(c.CorrectedText) == "" { decisions = append(decisions, rejection(c.ProposalIndex, ReasonEmptyCorrectedText, "corrected_text must not be empty")) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } type NoEffectValidator struct{} func (v NoEffectValidator) Name() string { return "no_effect" } func (v NoEffectValidator) Validate(_ context.Context, req Request) (Result, error) { decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { if c.OriginalText == c.CorrectedText { decisions = append(decisions, rejection(c.ProposalIndex, ReasonNoEffect, "original_text and corrected_text are identical")) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } type ProtectedGlossaryTermValidator struct{} func (v ProtectedGlossaryTermValidator) Name() string { return "protected_glossary_terms" } func (v ProtectedGlossaryTermValidator) Validate(_ context.Context, req Request) (Result, error) { if req.ModuleKey == "glossary" { decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { decisions = append(decisions, approval(c.ProposalIndex)) } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } vocab := NewProtectedVocabulary(req.Glossary) decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { reason := vocab.violationReason(c.OriginalText, c.CorrectedText) if reason != "" { decisions = append(decisions, rejection(c.ProposalIndex, ReasonProtectedGlossaryTerm, reason)) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil } type GlossaryStageProtectedGlossaryTermValidator struct{} func (v GlossaryStageProtectedGlossaryTermValidator) Name() string { return "glossary_stage_protected_glossary_terms" } func (v GlossaryStageProtectedGlossaryTermValidator) Validate(_ context.Context, req Request) (Result, error) { vocab := NewProtectedVocabulary(req.Glossary) decisions := make([]Decision, 0, len(req.CandidateProposal)) for _, c := range req.CandidateProposal { reason := vocab.glossaryStageViolationReason(c.OriginalText, c.CorrectedText) if reason != "" { decisions = append(decisions, rejection(c.ProposalIndex, ReasonProtectedGlossaryTerm, reason)) continue } decisions = append(decisions, approval(c.ProposalIndex)) } if err := EnforceDecisionCardinality(req.CandidateProposal, decisions); err != nil { return Result{}, err } return Result{ValidatorName: v.Name(), Decisions: decisions}, nil }