Prd/components/backend/test/test_dual_transcript.rb
Lucy Doupalů be9f14ce34 Helpdesk - operator console + patched GrapheneOS Dialer for call handling
A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer
answers technician calls, records both call legs as separate channels, and a
Ruby backend transcribes them through Whisper and files an AI summary against
the caller.

Squashed to a single commit for sharing. No credentials are included; secrets
live outside the repo in /etc/helpdesk/env on the server or a gitignored
.claude/env.local locally. See .claude/env.local.example for the shape.

Start at README.md, then docs/architecture.md.
2026-07-27 18:50:32 +02:00

70 lines
3.6 KiB
Ruby

# frozen_string_literal: true
# Offline tests for merging two per-speaker transcripts (no gems, no network).
# Run: ruby components/backend/test/test_dual_transcript.rb
require_relative "../lib/helpdesk/dual_transcript"
$pass = 0; $fail = 0
def ok(d); $pass += 1; puts " ok #{d}"; end
def bad(d, got = nil); $fail += 1; puts " FAIL #{d}#{got.nil? ? '' : " (got: #{got.inspect})"}"; end
def eq(a, b, d); a == b ? ok(d) : bad(d, a); end
def truthy(x, d); x ? ok(d) : bad(d, x); end
def falsy(x, d); x ? bad(d, x) : ok(d); end
D = Helpdesk::DualTranscript
puts "parsing the service's line format"
p1 = D.parse("[00:00:03] [Speaker 1]: Dobrý den.\n\n[00:01:05] [Speaker 1]: Děkuji.")
eq(p1.length, 2, "two turns parsed")
eq(p1[0][:at], 3, "seconds computed from the timestamp")
eq(p1[1][:at], 65, "minutes carried into seconds")
eq(p1[0][:text], "Dobrý den.", "text without the markup")
eq(D.parse("[01:02:03] [Speaker 1]: x")[0][:at], 3723, "hours counted too")
puts "a wrapped line belongs to the turn above it, not to nobody"
w = D.parse("[00:00:03] [Speaker 1]: první část\ndruhá část\n")
eq(w.length, 1, "the continuation does not become its own turn")
eq(w[0][:text], "první část druhá část", "and its words are kept")
puts "merging two single-speaker channels into a conversation"
op = "[00:00:01] [Speaker 1]: Dobrý den, helpdesk.\n[00:00:09] [Speaker 1]: Rozumím."
cal = "[00:00:05] [Speaker 1]: Dobrý den, mám problém."
m = D.merge(left: op, right: cal)
eq(m.lines.reject { |l| l.strip.empty? }.length, 3, "all three turns present")
truthy(m.include?("[00:00:01] [Operator]: Dobrý den, helpdesk."), "left channel labelled Operator")
truthy(m.include?("[00:00:05] [Caller]: Dobrý den, mám problém."), "right channel labelled Caller")
truthy(m.index("[00:00:01]") < m.index("[00:00:05]"), "ordered by time across channels")
truthy(m.index("[00:00:05]") < m.index("[00:00:09]"), "and interleaved, not concatenated")
falsy(m.include?("Speaker 1"), "the meaningless per-channel speaker numbers are gone")
puts "ties resolve to the operator, who normally speaks first"
t = D.merge(left: "[00:00:04] [Speaker 1]: otázka", right: "[00:00:04] [Speaker 1]: odpověď")
truthy(t.index("Operator") < t.index("Caller"), "same-second turns read question then answer")
puts "one silent channel still produces a usable transcript"
only_op = D.merge(left: "[00:00:02] [Speaker 1]: haló?", right: "")
truthy(only_op.include?("[Operator]: haló?"), "the speaking side survives")
falsy(only_op.include?("Caller"), "and no empty turn is invented for the silent side")
eq(D.merge(left: "", right: ""), "", "two empty channels -> empty transcript")
puts "output keeps the format everything downstream parses"
round = D.parse(D.merge(left: "[00:00:07] [Speaker 1]: a", right: "[00:00:08] [Speaker 1]: b"))
eq(round.length, 2, "the merged text re-parses as turns")
eq(round.map { |e| e[:at] }, [7, 8], "with the timestamps intact")
puts "plausible? guards against a split that gained nothing"
truthy(D.plausible?("[00:00:01] [Speaker 1]: ahoj", "[00:00:02] [Speaker 1]: nazdar"),
"two different speakers is the good case")
falsy(D.plausible?("[00:00:01] [Speaker 1]: ahoj", "[00:00:01] [Speaker 1]: ahoj"),
"identical channels mean the legs were not really separated")
falsy(D.plausible?("[00:00:01] [Speaker 1]: ahoj", ""), "a silent channel is not a plausible split")
falsy(D.plausible?("", ""), "nothing at all is not plausible")
puts "hms formatting"
eq(D.hms(0), "00:00:00", "zero")
eq(D.hms(61), "00:01:01", "a minute and a second")
eq(D.hms(3661), "01:01:01", "an hour in")
puts
puts "#{$pass} passed, #{$fail} failed"
exit($fail.zero? ? 0 : 1)