Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased — neutral test fixtures

* Tests use made-up team slugs (`team-a`, `team-b`), names and synthetic
session ids instead of real ones. No behavior change.

## Unreleased — no deployment- or organization-specific references

* Package author is `twentyone.ist`.
Expand Down
2 changes: 1 addition & 1 deletion docs/tee-summarization-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ did not clear the bar to become the default.
3. **Baselines are historical**, not fresh re-runs: same prompts and
temperature, but generated between 2026-05 and 2026-09 against whatever
`claude-sonnet-4-6` resolved to then.
4. **One baseline predates a prompt change.** `01KQZ561CR` (TR) was
4. **One baseline predates a prompt change.** One Turkish meeting was
summarized 2026-05-06, before the 2026-05-24 prompt edit. The other 9
used prompts identical to the candidate runs; the default summary
prompts have not changed since (v0.17.0 only *added* iteration-plan
Expand Down
2 changes: 1 addition & 1 deletion millet/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def absorb_tiny_speakers(
real conversation, not to name it.

A small cluster holding a real sentence (:func:`is_dense_utterance`) is not
noise and is left for a human. Incident 2026-11-02: in a 58 s phone
noise and is left for a human. In a 58 s phone
interview, the only segment pyannote split off for the second speaker
(4.4 s, 21 words) was folded into the interviewer, so the session came back
with one speaker. Replaying 704 sessions, this keeps 6 tiny clusters raw.
Expand Down
2 changes: 1 addition & 1 deletion millet/speaker_repair.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Per-segment speaker repair for the mono diarization path.

pyannote sometimes puts two people into one cluster. Incident 2026-11-02: in
pyannote sometimes puts two people into one cluster. In
a 58 s phone interview with quick question/answer turns it produced one turn
for the first 43 s, and the interviewee's answers all landed in the
interviewer's cluster. The two voices were easy to tell apart (centroid
Expand Down
4 changes: 2 additions & 2 deletions millet/summarize.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def verify_model(backend: str, model: str, *, timeout: int = 10) -> str | None:

# Hard endpoint limit, not a budget choice: the attested vision endpoints
# reject a request carrying more than 10 images outright — verified live
# 2026-09-17 (session 01M2P6FTRG4WAKKE5T7TV6HNFM, 18 cue frames → venice
# 2026-09-17 (a session with 18 cue frames → venice
# 400 "At most 10 image(s) may be provided in one request"). The job log
# only carries the chain's last error, so tinfoil's own failure text went
# uncaptured — the cap is applied to every vision backend so whichever one
Expand Down Expand Up @@ -1335,7 +1335,7 @@ class _TinfoilAttemptStuck(TimeoutError):
def _run_under_deadline(fn, *, seconds: float):
"""Run ``fn()`` under a hard wall-clock deadline in a daemon thread.

Incident 2026-09-15: the Tinfoil SDK's custom httpx transport can block
The Tinfoil SDK's custom httpx transport can block
in ``ssl.read()`` with no effective timeout — a summary attempt pinned a
vezir job in ``transcribing`` for 30+ minutes while millet's per-request
``timeout=600`` never fired (faulthandler stack: openai -> httpx ->
Expand Down
22 changes: 11 additions & 11 deletions tests/test_clips.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,10 @@ def test_relabel_clips_file(tmp_path):
segs = _segs()
path = write_clips(_summary({"clips": [_clip(segs, 2, 5)]}), _txn(segs), tmp_path, "s")
before = json.loads(path.read_text())["clips"][0]
assert relabel_clips_file(path, {"SPEAKER_00": "Kemal"})
assert relabel_clips_file(path, {"SPEAKER_00": "Nora"})
after = json.loads(path.read_text())["clips"][0]
assert after["speakers"] == ["Kemal"]
assert after["text"].startswith("Kemal: line 2")
assert after["speakers"] == ["Nora"]
assert after["text"].startswith("Nora: line 2")
assert after["start_s"] == before["start_s"]
assert not relabel_clips_file(path, {"NOBODY": "x"})

Expand All @@ -288,11 +288,11 @@ def test_regenerate_writes_clips(self, tmp_path):
with patch("millet.summarize.is_backend_available", return_value=True), \
patch("millet.summarize.summarize", return_value=fake), \
patch("millet.transcribe.ensure_gpu_available", lambda *a, **k: None):
files = apply_labels(sdir, {"SPEAKER_00": "Kemal"}, regenerate_summary=True,
files = apply_labels(sdir, {"SPEAKER_00": "Nora"}, regenerate_summary=True,
summary_template="video-clipping")
assert files["clips"] == sdir / "01ABC.clips.json"
doc = json.loads(files["clips"].read_text())
assert doc["clips"][0]["speakers"] == ["Kemal"]
assert doc["clips"][0]["speakers"] == ["Nora"]
assert (sdir / "01ABC.video-clipping.md").exists()

def test_other_templates_write_no_clips(self, tmp_path):
Expand All @@ -307,9 +307,9 @@ def test_other_templates_write_no_clips(self, tmp_path):
def test_relabel_without_regenerate_renames_inside_clips(self, tmp_path):
sdir, segs = self._session(tmp_path)
write_clips(_summary({"clips": [_clip(segs, 2, 5)]}), _txn(segs), sdir, "01ABC")
apply_labels(sdir, {"SPEAKER_00": "Kemal"}, regenerate_summary=False)
apply_labels(sdir, {"SPEAKER_00": "Nora"}, regenerate_summary=False)
doc = json.loads((sdir / "01ABC.clips.json").read_text())
assert doc["clips"][0]["speakers"] == ["Kemal"]
assert doc["clips"][0]["speakers"] == ["Nora"]

def test_clips_json_never_mistaken_for_transcript(self, tmp_path):
# "<base>.clips.json" sorts before "<base>.json". Use a session dir
Expand Down Expand Up @@ -574,18 +574,18 @@ def test_merges_speakers_and_skips_odd_entries(self, tmp_path):
{"speakers": ["A", "B"], "text": "A: hi\nB: yo\nno label here"},
{"speakers": "A", "text": None},
]}))
assert relabel_clips_file(p, {"A": "Kemal", "B": "Kemal"})
assert relabel_clips_file(p, {"A": "Nora", "B": "Nora"})
clip = json.loads(p.read_text())["clips"][1]
assert clip["speakers"] == ["Kemal"]
assert clip["text"] == "Kemal: hi\nKemal: yo\nno label here"
assert clip["speakers"] == ["Nora"]
assert clip["text"] == "Nora: hi\nNora: yo\nno label here"

def test_relabel_failure_is_logged_not_raised(self, tmp_path):
sdir = tmp_path / "01ABC"
sdir.mkdir()
_txn(_segs()).save(sdir, basename="01ABC")
(sdir / "01ABC.clips.json").write_text("not json")
logs: list[str] = []
apply_labels(sdir, {"SPEAKER_00": "Kemal"}, regenerate_summary=False,
apply_labels(sdir, {"SPEAKER_00": "Nora"}, regenerate_summary=False,
progress_callback=logs.append)
assert any("Clip relabel failed" in m for m in logs)

Expand Down
6 changes: 3 additions & 3 deletions tests/test_frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@ def test_minimal_context(self):

def test_crosstalk_is_never_a_participant(self):
ctx = FrontmatterContext(
transcript_speakers=["Kemal", "CROSSTALK"],
transcript_speakers=["Nora", "CROSSTALK"],
speaker_channels={"CROSSTALK": "system"},
)
fm = build_frontmatter({"participants": ["crosstalk", "Lukas"]}, ctx)
fm = build_frontmatter({"participants": ["crosstalk", "Pia"]}, ctx)
validate_frontmatter(fm)
assert [p["name"] for p in fm["participants"]] == ["Kemal", "Lukas"]
assert [p["name"] for p in fm["participants"]] == ["Nora", "Pia"]

def test_with_extracted_data(self):
ctx = FrontmatterContext(
Expand Down
Loading
Loading