Minor updates to overlap detection and segment coalescing logic
This commit is contained in:
@@ -3,6 +3,7 @@ package backchannel
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"gitea.maximumdirect.net/eric/seriatim/internal/model"
|
||||
)
|
||||
@@ -10,16 +11,16 @@ import (
|
||||
const Category = "backchannel"
|
||||
|
||||
var patterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)^(yeah|yep|yes|right|okay|ok|sure|mm+h?m+|uh[- ]huh|mhm|mm-hmm)\.?$`),
|
||||
regexp.MustCompile(`(?i)^(yeah|yep|yes|right|okay|ok|sure|mm+h?m+|mm+\s+hmm|uh[- ]huh|mhm|mm-hmm)\.?$`),
|
||||
regexp.MustCompile(`(?i)^(yeah|yep|right|okay|ok)([,.\s]+(yeah|yep|right|okay|ok))*\.?$`),
|
||||
regexp.MustCompile(`(?i)^(i see|got it|makes sense|that makes sense|fair enough|sounds good)\.?$`),
|
||||
regexp.MustCompile(`(?i)^(i see|got it|makes sense|that makes sense|fair enough|sounds good|there you go)\.?$`),
|
||||
}
|
||||
|
||||
// Apply tags matching short acknowledgement segments.
|
||||
func Apply(in model.MergedTranscript) (model.MergedTranscript, int) {
|
||||
func Apply(in model.MergedTranscript, maxDuration float64) (model.MergedTranscript, int) {
|
||||
tagged := 0
|
||||
for index := range in.Segments {
|
||||
if !matches(in.Segments[index]) {
|
||||
if !matches(in.Segments[index], maxDuration) {
|
||||
continue
|
||||
}
|
||||
if hasCategory(in.Segments[index], Category) {
|
||||
@@ -31,15 +32,15 @@ func Apply(in model.MergedTranscript) (model.MergedTranscript, int) {
|
||||
return in, tagged
|
||||
}
|
||||
|
||||
func matches(segment model.Segment) bool {
|
||||
text := strings.TrimSpace(segment.Text)
|
||||
func matches(segment model.Segment, maxDuration float64) bool {
|
||||
text := normalizeForMatching(segment.Text)
|
||||
if text == "" {
|
||||
return false
|
||||
}
|
||||
if len(strings.Fields(text)) > 3 {
|
||||
return false
|
||||
}
|
||||
if segment.End-segment.Start > 1.0 {
|
||||
if segment.End-segment.Start > maxDuration {
|
||||
return false
|
||||
}
|
||||
for _, pattern := range patterns {
|
||||
@@ -50,6 +51,16 @@ func matches(segment model.Segment) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeForMatching(text string) string {
|
||||
text = strings.Map(func(r rune) rune {
|
||||
if unicode.IsPunct(r) {
|
||||
return ' '
|
||||
}
|
||||
return r
|
||||
}, text)
|
||||
return strings.Join(strings.Fields(text), " ")
|
||||
}
|
||||
|
||||
func hasCategory(segment model.Segment, category string) bool {
|
||||
for _, existing := range segment.Categories {
|
||||
if existing == category {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func TestApplyTagsVerySafeBackchannels(t *testing.T) {
|
||||
for _, text := range []string{"yeah", "Yep.", "mmhm", "uh-huh", "mm-hmm"} {
|
||||
t.Run(text, func(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment(text, 1, 1.5)))
|
||||
got, tagged := Apply(transcript(segment(text, 1, 1.5)), 1.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func TestApplyTagsVerySafeBackchannels(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyTagsRepeatedBackchannels(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("Yeah, okay yep.", 1, 1.8)))
|
||||
got, tagged := Apply(transcript(segment("Yeah, okay yep.", 1, 1.8)), 1.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestApplyTagsRepeatedBackchannels(t *testing.T) {
|
||||
func TestApplyTagsShortAcknowledgements(t *testing.T) {
|
||||
for _, text := range []string{"i see", "Got it.", "sounds good"} {
|
||||
t.Run(text, func(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment(text, 1, 1.8)))
|
||||
got, tagged := Apply(transcript(segment(text, 1, 1.8)), 1.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
@@ -40,15 +40,27 @@ func TestApplyTagsShortAcknowledgements(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyMatchesTrimAwareCaseInsensitive(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment(" YES. ", 1, 1.2)))
|
||||
got, tagged := Apply(transcript(segment(" YES. ", 1, 1.2)), 1.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
assertCategories(t, got.Segments[0], []string{Category})
|
||||
}
|
||||
|
||||
func TestApplyIgnoresPunctuationWhenMatching(t *testing.T) {
|
||||
for _, text := range []string{"Okay?!", "Yeah... okay?!", "that makes sense!", "mm-hmm.", "uh... huh"} {
|
||||
t.Run(text, func(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment(text, 1, 1.8)), 1.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
assertCategories(t, got.Segments[0], []string{Category})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDoesNotTagNonMatches(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("yeah I think so", 1, 1.5)))
|
||||
got, tagged := Apply(transcript(segment("yeah I think so", 1, 1.5)), 1.0)
|
||||
if tagged != 0 {
|
||||
t.Fatalf("tagged = %d, want 0", tagged)
|
||||
}
|
||||
@@ -56,15 +68,29 @@ func TestApplyDoesNotTagNonMatches(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyRejectsWordCountOverThree(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("that makes sense okay", 1, 1.5)))
|
||||
got, tagged := Apply(transcript(segment("that makes sense okay", 1, 1.5)), 1.0)
|
||||
if tagged != 0 {
|
||||
t.Fatalf("tagged = %d, want 0", tagged)
|
||||
}
|
||||
assertCategories(t, got.Segments[0], nil)
|
||||
}
|
||||
|
||||
func TestApplyRejectsDurationOverOneSecond(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("yeah", 1, 2.1)))
|
||||
func TestApplyUsesConfiguredMaxDuration(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("yeah", 1, 2.1)), 2.0)
|
||||
if tagged != 1 {
|
||||
t.Fatalf("tagged = %d, want 1", tagged)
|
||||
}
|
||||
assertCategories(t, got.Segments[0], []string{Category})
|
||||
|
||||
got, tagged = Apply(transcript(segment("yeah", 1, 3.1)), 2.0)
|
||||
if tagged != 0 {
|
||||
t.Fatalf("tagged = %d, want 0", tagged)
|
||||
}
|
||||
assertCategories(t, got.Segments[0], nil)
|
||||
}
|
||||
|
||||
func TestApplyRejectsDurationOverConfiguredMax(t *testing.T) {
|
||||
got, tagged := Apply(transcript(segment("yeah", 1, 2.1)), 1.0)
|
||||
if tagged != 0 {
|
||||
t.Fatalf("tagged = %d, want 0", tagged)
|
||||
}
|
||||
@@ -75,7 +101,7 @@ func TestApplyPreservesExistingCategoriesAndAvoidsDuplicate(t *testing.T) {
|
||||
existing := segment("yeah", 1, 1.2)
|
||||
existing.Categories = []string{"manual", Category}
|
||||
|
||||
got, tagged := Apply(transcript(existing))
|
||||
got, tagged := Apply(transcript(existing), 1.0)
|
||||
if tagged != 0 {
|
||||
t.Fatalf("tagged = %d, want 0", tagged)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user