package proposals import ( "reflect" "testing" "gitea.maximumdirect.net/eric/audita/internal/core/schema" ) func TestApplyProposalsApplyingOneProposal(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "gestures"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "gestures", CorrectedText: "Jesters", Confidence: 0.95, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "glossary", ModuleInstance: "glossary_1"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if got := result.Transcript.Segments[0].Text; got != "Jesters" { t.Fatalf("expected corrected text Jesters, got %q", got) } if len(result.Applied) != 1 { t.Fatalf("expected 1 applied change, got %d", len(result.Applied)) } if len(result.Skipped) != 0 { t.Fatalf("expected 0 skipped changes, got %d", len(result.Skipped)) } } func TestApplyProposalsApplyingMultipleProposalsInStableOrder(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "rank rank"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "Hrank", CorrectedText: "X", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 2, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "rank", CorrectedText: "Hrank", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 1, ModuleKey: "homophones", ModuleInstance: "homophones"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyReplaceAll) if got := result.Transcript.Segments[0].Text; got != "X X" { t.Fatalf("expected final text X X, got %q", got) } if len(result.Applied) != 2 { t.Fatalf("expected 2 applied changes, got %d", len(result.Applied)) } if result.Applied[0].ProposalIndex != 1 || result.Applied[1].ProposalIndex != 2 { t.Fatalf("expected applied order [1,2], got [%d,%d]", result.Applied[0].ProposalIndex, result.Applied[1].ProposalIndex) } } func TestApplyProposalsStaleProposalSkippedAfterEarlierChange(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "foo bar"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "foo bar", CorrectedText: "foo-bar", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "foo bar", CorrectedText: "foobar", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 1, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if got := result.Transcript.Segments[0].Text; got != "foo-bar" { t.Fatalf("expected final text foo-bar, got %q", got) } if len(result.Applied) != 1 { t.Fatalf("expected 1 applied change, got %d", len(result.Applied)) } if len(result.Skipped) != 1 { t.Fatalf("expected 1 skipped change, got %d", len(result.Skipped)) } if result.Skipped[0].SkipReason != SkipReasonMissingOriginalText { t.Fatalf("expected stale skip reason %q, got %q", SkipReasonMissingOriginalText, result.Skipped[0].SkipReason) } } func TestApplyProposalsMissingSegmentSkip(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "hello"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 999, OriginalText: "hello", CorrectedText: "hi", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if len(result.Skipped) != 1 { t.Fatalf("expected 1 skipped change, got %d", len(result.Skipped)) } if result.Skipped[0].SkipReason != SkipReasonMissingSegment { t.Fatalf("expected skip reason %q, got %q", SkipReasonMissingSegment, result.Skipped[0].SkipReason) } } func TestApplyProposalsRequireUniqueAmbiguousSkip(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "uh uh"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "uh", CorrectedText: "um", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "spoken_word", ModuleInstance: "spoken_word"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if len(result.Skipped) != 1 { t.Fatalf("expected 1 skipped change, got %d", len(result.Skipped)) } if result.Skipped[0].SkipReason != SkipReasonAmbiguousOriginal { t.Fatalf("expected skip reason %q, got %q", SkipReasonAmbiguousOriginal, result.Skipped[0].SkipReason) } } func TestApplyProposalsReplaceAllMultipleReplacements(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "uh uh uh"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "uh", CorrectedText: "um", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "spoken_word", ModuleInstance: "spoken_word"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyReplaceAll) if len(result.Applied) != 1 { t.Fatalf("expected 1 applied change, got %d", len(result.Applied)) } if got := result.Transcript.Segments[0].Text; got != "um um um" { t.Fatalf("expected final text um um um, got %q", got) } if result.Applied[0].ReplacementCount != 3 { t.Fatalf("expected replacement count 3, got %d", result.Applied[0].ReplacementCount) } } func TestApplyProposalsNoMutationOfInputTranscript(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "hello"}}} before := cloneTranscript(transcript) proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "hello", CorrectedText: "hi", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, } _ = ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if !reflect.DeepEqual(transcript, before) { t.Fatalf("input transcript mutated: before=%+v after=%+v", before, transcript) } } func TestApplyProposalsSegmentMetadataPreservation(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ ID: 1, Speaker: "A", Start: 12.5, End: 15.0, Text: "rank", Categories: []string{"combat", "term"}, }}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "rank", CorrectedText: "Hrank", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "glossary", ModuleInstance: "glossary_1"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) seg := result.Transcript.Segments[0] if seg.Speaker != "A" || seg.Start != 12.5 || seg.End != 15.0 { t.Fatalf("segment metadata changed: %+v", seg) } if !reflect.DeepEqual(seg.Categories, []string{"combat", "term"}) { t.Fatalf("segment categories changed: %v", seg.Categories) } } func TestApplyProposalsAppliedAndSkippedRecords(t *testing.T) { transcript := &schema.Transcript{Segments: []schema.Segment{{ID: 1, Text: "hello"}}} proposals := []EnrichedCorrectionProposal{ { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 1, OriginalText: "hello", CorrectedText: "hi", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 0, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, { CorrectionProposal: CorrectionProposal{ TargetSegmentID: 999, OriginalText: "x", CorrectedText: "y", Confidence: 0.9, }, ProposalMetadata: ProposalMetadata{ProposalIndex: 1, ModuleKey: "grammar", ModuleInstance: "grammar_1"}, }, } result := ApplyProposals(transcript, proposals, ReplacementPolicyRequireUnique) if len(result.Applied) != 1 || len(result.Skipped) != 1 { t.Fatalf("expected 1 applied and 1 skipped, got applied=%d skipped=%d", len(result.Applied), len(result.Skipped)) } applied := result.Applied[0] if applied.ProposalIndex != 0 || applied.ModuleKey != "grammar" || applied.ModuleInstance != "grammar_1" { t.Fatalf("unexpected applied record metadata: %+v", applied) } if applied.TargetSegmentID != 1 || applied.OriginalText != "hello" || applied.CorrectedText != "hi" || applied.ReplacementCount != 1 { t.Fatalf("unexpected applied record contents: %+v", applied) } skipped := result.Skipped[0] if skipped.ProposalIndex != 1 || skipped.ModuleKey != "grammar" || skipped.ModuleInstance != "grammar_1" { t.Fatalf("unexpected skipped record metadata: %+v", skipped) } if skipped.TargetSegmentID != 999 || skipped.OriginalText != "x" || skipped.CorrectedText != "y" { t.Fatalf("unexpected skipped record contents: %+v", skipped) } if skipped.SkipReason != SkipReasonMissingSegment { t.Fatalf("unexpected skipped reason: %q", skipped.SkipReason) } if skipped.Message == "" { t.Fatal("expected non-empty skipped message") } }