Implemented a module to detect backchannel segments, and updated the coalesce module to ignore them when coalescing same-speaker turns

This commit is contained in:
2026-04-27 19:49:25 -05:00
parent aab6d12730
commit bbfb8aba44
10 changed files with 360 additions and 6 deletions

View File

@@ -90,6 +90,7 @@ func TestMergeWritesMergedOutputAndReport(t *testing.T) {
"placeholder-merger",
"detect-overlaps",
"resolve-overlaps",
"backchannel",
"coalesce",
"detect-overlaps",
"autocorrect",
@@ -585,6 +586,92 @@ func TestMergeCoalesceGapOverridePreventsMerge(t *testing.T) {
}
}
func TestMergeTagsBackchannelSegments(t *testing.T) {
dir := t.TempDir()
input := writeJSONFile(t, dir, "input.json", `{
"segments": [
{"start": 1, "end": 1.5, "text": " Yeah. "},
{"start": 6, "end": 7, "text": "not a backchannel"}
]
}`)
output := filepath.Join(dir, "merged.json")
reportPath := filepath.Join(dir, "report.json")
err := executeMerge(
"--input-file", input,
"--output-file", output,
"--report-file", reportPath,
)
if err != nil {
t.Fatalf("merge failed: %v", err)
}
var transcript model.FinalTranscript
readJSON(t, output, &transcript)
if len(transcript.Segments) != 2 {
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
}
if !equalStrings(transcript.Segments[0].Categories, []string{"backchannel"}) {
t.Fatalf("segment categories = %v, want [backchannel]", transcript.Segments[0].Categories)
}
if len(transcript.Segments[1].Categories) != 0 {
t.Fatalf("unexpected categories = %v", transcript.Segments[1].Categories)
}
var rpt report.Report
readJSON(t, reportPath, &rpt)
if !hasReportEvent(rpt, "postprocessing", "backchannel", "tagged 1 backchannel segment(s)") {
t.Fatal("expected backchannel report event")
}
}
func TestMergeCoalescesAroundDifferentSpeakerBackchannel(t *testing.T) {
dir := t.TempDir()
inputA := writeJSONFile(t, dir, "a.json", `{
"segments": [
{"start": 1, "end": 2, "text": "first"},
{"start": 3, "end": 4, "text": "second"}
]
}`)
inputB := writeJSONFile(t, dir, "b.json", `{
"segments": [
{"start": 2.2, "end": 2.5, "text": "yeah"}
]
}`)
speakers := writeYAMLFile(t, dir, "speakers.yml", `match:
- speaker: Alice
match: ["a.json"]
- speaker: Bob
match: ["b.json"]
`)
output := filepath.Join(dir, "merged.json")
err := executeMerge(
"--input-file", inputA,
"--input-file", inputB,
"--speakers", speakers,
"--output-file", output,
)
if err != nil {
t.Fatalf("merge failed: %v", err)
}
var transcript model.FinalTranscript
readJSON(t, output, &transcript)
if len(transcript.Segments) != 2 {
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
}
if transcript.Segments[0].Speaker != "Alice" || transcript.Segments[0].Text != "first second" {
t.Fatalf("first segment = %#v, want coalesced Alice", transcript.Segments[0])
}
if len(transcript.Segments[0].Categories) != 0 {
t.Fatalf("coalesced segment categories = %v, want none", transcript.Segments[0].Categories)
}
if transcript.Segments[1].Speaker != "Bob" || !equalStrings(transcript.Segments[1].Categories, []string{"backchannel"}) {
t.Fatalf("second segment = %#v, want Bob backchannel", transcript.Segments[1])
}
}
func TestSpeakerMatchingUsesFirstMatchingRuleCaseInsensitive(t *testing.T) {
dir := t.TempDir()
input := writeJSONFile(t, dir, "2026-04-19-Adam_Rakestraw.json", `{