personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'hopper-saxpwyru-show-all-segments'

+45 -19
+8 -10
apps/speakers/routes.py
··· 226 226 227 227 228 228 def _scan_segment_embeddings(day: str) -> list[dict]: 229 - """Scan a day for segments with embeddings and 1+ speakers. 229 + """Scan a day for segments with audio embeddings. 230 230 231 - Only includes segments that have: 232 - 1. Audio embedding NPZ files 233 - 2. A speakers.json file with 1 or more speaker names 231 + Only includes segments that have audio embedding NPZ files. 232 + Segments with a speakers.json file will include speaker names; 233 + segments without speakers.json will have an empty speakers list. 234 234 235 235 Returns list of segment info dicts with keys: 236 236 - key: segment directory name (HHMMSS_LEN) ··· 263 263 if not sources: 264 264 continue 265 265 266 - # Load speakers.json - require at least one speaker 266 + # Load speakers.json (may be empty if not yet processed) 267 267 speakers = _load_segment_speakers(s_path) 268 - if not speakers: 269 - continue 270 268 271 269 # Calculate duration from start and end times 272 270 duration = _time_to_seconds(end_time) - _time_to_seconds(start_time) ··· 469 467 def api_stats(month: str) -> Any: 470 468 """Return segment counts for each day in a month. 471 469 472 - Used by calendar heatmap to show days with speaker segments. 470 + Used by calendar heatmap to show days with embedding segments. 473 471 """ 474 472 if not re.fullmatch(r"\d{6}", month): 475 473 return error_response("Invalid month format, expected YYYYMM", 400) ··· 489 487 490 488 @speakers_bp.route("/api/segments/<day>") 491 489 def api_segments(day: str) -> Any: 492 - """Return segments with embeddings and 1+ speakers for a day.""" 490 + """Return segments with audio embeddings for a day.""" 493 491 if not DATE_RE.fullmatch(day): 494 492 return error_response("Invalid day format", 400) 495 493 ··· 513 511 segment_dir = get_segment_path(day, segment_key, stream) 514 512 speakers = _load_segment_speakers(segment_dir) 515 513 if not speakers: 516 - return error_response("No speakers found for segment", 404) 514 + return jsonify({"matched": [], "unmatched": []}) 517 515 518 516 # Load all journal entities for matching 519 517 journal_entities = load_all_journal_entities()
+31 -5
apps/speakers/tests/test_routes.py
··· 426 426 assert speakers == [] 427 427 428 428 429 - def test_scan_segment_embeddings_requires_speakers(speakers_env): 430 - """Test that segments without speakers.json are filtered out.""" 429 + def test_scan_segment_embeddings_without_speakers(speakers_env): 430 + """Test that segments without speakers.json are included with empty speakers.""" 431 431 from apps.speakers.routes import _scan_segment_embeddings 432 432 433 433 env = speakers_env() ··· 435 435 env.create_segment("20240101", "143022_300", ["mic_audio"]) 436 436 437 437 segments = _scan_segment_embeddings("20240101") 438 - assert segments == [] 438 + assert len(segments) == 1 439 + assert segments[0]["key"] == "143022_300" 440 + assert segments[0]["speakers"] == [] 441 + assert segments[0]["speaker_count"] == 0 439 442 440 443 441 444 def test_scan_segment_embeddings_single_speaker(speakers_env): ··· 453 456 454 457 455 458 def test_scan_segment_embeddings_empty_speakers(speakers_env): 456 - """Test that segments with 0 speakers are filtered out.""" 459 + """Test that segments with empty speakers.json are included.""" 457 460 from apps.speakers.routes import _scan_segment_embeddings 458 461 459 462 env = speakers_env() ··· 461 464 env.create_speakers_json("20240101", "143022_300", []) # No speakers 462 465 463 466 segments = _scan_segment_embeddings("20240101") 464 - assert segments == [] 467 + assert len(segments) == 1 468 + assert segments[0]["speakers"] == [] 469 + assert segments[0]["speaker_count"] == 0 465 470 466 471 467 472 def test_scan_segment_embeddings_includes_speaker_data(speakers_env): ··· 477 482 assert len(segments) == 1 478 483 assert segments[0]["speakers"] == ["Alice", "Bob"] 479 484 assert segments[0]["speaker_count"] == 2 485 + 486 + 487 + def test_api_speakers_empty_when_no_speakers_json(speakers_env): 488 + """Test /api/speakers/ returns empty matched/unmatched when no speakers.json.""" 489 + from flask import Flask 490 + 491 + from apps.speakers.routes import speakers_bp 492 + 493 + env = speakers_env() 494 + env.create_segment("20240101", "143022_300", ["mic_audio"]) 495 + # No speakers.json created 496 + 497 + app = Flask(__name__) 498 + app.register_blueprint(speakers_bp) 499 + 500 + with app.test_client() as client: 501 + response = client.get("/app/speakers/api/speakers/20240101/test/143022_300") 502 + assert response.status_code == 200 503 + data = response.get_json() 504 + assert data["matched"] == [] 505 + assert data["unmatched"] == [] 480 506 481 507 482 508 def test_get_journal_principal(speakers_env):
+6 -4
apps/speakers/workspace.html
··· 488 488 489 489 function renderSegmentList() { 490 490 if (segments.length === 0) { 491 - segmentList.innerHTML = '<div class="spk-empty">No segments with speakers found for this day</div>'; 491 + segmentList.innerHTML = '<div class="spk-empty">No segments with embeddings found for this day</div>'; 492 492 return; 493 493 } 494 494 ··· 496 496 <div class="spk-segment${selectedSegment?.key === seg.key ? ' active' : ''}" data-key="${seg.key}"> 497 497 <div class="spk-segment-time"> 498 498 ${seg.start} - ${seg.end} 499 - <span class="spk-speaker-count">${seg.speaker_count} speakers</span> 499 + ${seg.speaker_count > 0 ? `<span class="spk-speaker-count">${seg.speaker_count} speakers</span>` : ''} 500 500 </div> 501 501 <div class="spk-segment-meta">${formatDuration(seg.duration)}</div> 502 502 <div class="spk-segment-sources"> ··· 584 584 const container = document.getElementById('spkSpeakers'); 585 585 if (!container) return; 586 586 587 - fetch(`/app/speakers/api/speakers/${day}/${seg.key}`) 587 + fetch(`/app/speakers/api/speakers/${day}/${seg.stream}/${seg.key}`) 588 588 .then(r => r.json()) 589 589 .then(data => { 590 590 if (data.error) { ··· 648 648 const container = document.getElementById('spkSentences'); 649 649 container.innerHTML = '<div class="spk-empty">Loading...</div>'; 650 650 651 - fetch(`/app/speakers/api/sentences/${day}/${seg.key}/${source}`) 651 + fetch(`/app/speakers/api/sentences/${day}/${seg.stream}/${seg.key}/${source}`) 652 652 .then(r => r.json()) 653 653 .then(data => { 654 654 if (data.error) { ··· 936 936 body: JSON.stringify({ 937 937 entity_name: entityName, 938 938 day, 939 + stream: selectedSegment.stream, 939 940 segment_key: selectedSegment.key, 940 941 source, 941 942 sentence_id: sentenceId, ··· 964 965 type, 965 966 name, 966 967 day, 968 + stream: selectedSegment.stream, 967 969 segment_key: selectedSegment.key, 968 970 source, 969 971 sentence_id: sentenceId,