162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
package validators
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
terms := glossaryTerms(req)
|
|
decisions := make([]Decision, 0, len(req.CandidateProposal))
|
|
for _, c := range req.CandidateProposal {
|
|
if altersProtectedTerm(c.CorrectionProposal, terms) {
|
|
decisions = append(decisions, rejection(c.ProposalIndex, ReasonProtectedGlossaryTerm, "proposal may alter protected glossary terminology"))
|
|
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
|
|
}
|
|
|
|
func glossaryTerms(req Request) []string {
|
|
if req.Glossary == nil {
|
|
return nil
|
|
}
|
|
out := make([]string, 0)
|
|
for _, e := range req.Glossary.Entries {
|
|
if t := strings.TrimSpace(strings.ToLower(e.Name)); t != "" {
|
|
out = append(out, t)
|
|
}
|
|
for _, a := range e.Aliases {
|
|
if t := strings.TrimSpace(strings.ToLower(a)); t != "" {
|
|
out = append(out, t)
|
|
}
|
|
}
|
|
if t := strings.TrimSpace(strings.ToLower(e.Plural)); t != "" {
|
|
out = append(out, t)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func altersProtectedTerm(p proposals.CorrectionProposal, terms []string) bool {
|
|
if len(terms) == 0 {
|
|
return false
|
|
}
|
|
orig := strings.ToLower(p.OriginalText)
|
|
corr := strings.ToLower(p.CorrectedText)
|
|
for _, t := range terms {
|
|
if strings.Contains(orig, t) && !strings.Contains(corr, t) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|