Ionosphere.tv
3
fork

Configure Feed

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

feat: ATScience boundary detection — 22 talks with precise offsets

+76 -35
+45 -20
apps/ionosphere-appview/src/detect-boundaries-v6.ts
··· 573 573 // Get talks from DB 574 574 const db = openDb(); 575 575 const roomMap: Record<string, { rooms: string[]; dates: string[] }> = { 576 - "ATScience": { rooms: ["Performance Theatre", "Bukhman Lounge"], dates: ["2026-03-27"] }, 576 + "ATScience": { rooms: [], dates: ["2026-03-27"], rkeyPrefix: "ats26-" }, 577 577 "Great Hall South": { rooms: ["Great Hall South"], dates: [] }, 578 578 "Room 2301": { rooms: ["Room 2301", "2301 Classroom"], dates: [] }, 579 579 "Performance Theatre": { rooms: ["Performance Theatre"], dates: [] }, 580 580 }; 581 - const room = transcript.room || "Great Hall South"; 581 + // Resolve room from transcript metadata — check stream name for ATScience 582 + let room = transcript.room || "Great Hall South"; 583 + if (!transcript.room && transcript.stream?.includes("ATScience")) room = "ATScience"; 582 584 const config = roomMap[room] || { rooms: [room], dates: [] }; 583 585 if (config.dates.length === 0) { 584 586 const dayMap: Record<number, string> = { 1: "2026-03-28", 2: "2026-03-29" }; 585 587 if (dayMap[transcript.day]) config.dates = [dayMap[transcript.day]]; 586 588 } 587 589 588 - const rp = config.rooms.map(() => "?").join(","); 589 - const dp = config.dates.map(() => "?").join(","); 590 - const dateFilter = config.dates.length > 0 ? `AND substr(datetime(t.starts_at, '-7 hours'), 1, 10) IN (${dp})` : ""; 590 + let talks: Talk[]; 591 + 592 + if ((config as any).rkeyPrefix) { 593 + // ATScience: match by rkey prefix 594 + talks = db.prepare( 595 + `SELECT t.rkey, t.title, t.starts_at, t.ends_at, 596 + GROUP_CONCAT(s.name) as speaker_names 597 + FROM talks t 598 + LEFT JOIN talk_speakers ts ON t.uri = ts.talk_uri 599 + LEFT JOIN speakers s ON ts.speaker_uri = s.uri 600 + WHERE t.rkey LIKE ? 601 + GROUP BY t.rkey 602 + ORDER BY t.starts_at ASC` 603 + ).all(`${(config as any).rkeyPrefix}%`).map((t: any) => ({ 604 + ...t, 605 + scheduledDuration: Math.max(300, (new Date(t.ends_at).getTime() - new Date(t.starts_at).getTime()) / 1000), 606 + })) as Talk[]; 607 + } else { 608 + const rp = config.rooms.map(() => "?").join(","); 609 + const dp = config.dates.map(() => "?").join(","); 610 + const dateFilter = config.dates.length > 0 ? `AND substr(datetime(t.starts_at, '-7 hours'), 1, 10) IN (${dp})` : ""; 591 611 592 - const talks = db.prepare( 593 - `SELECT t.rkey, t.title, t.starts_at, t.ends_at, 594 - GROUP_CONCAT(s.name) as speaker_names 595 - FROM talks t 596 - LEFT JOIN talk_speakers ts ON t.uri = ts.talk_uri 597 - LEFT JOIN speakers s ON ts.speaker_uri = s.uri 598 - WHERE t.room IN (${rp}) ${dateFilter} 599 - GROUP BY t.uri 600 - ORDER BY t.starts_at ASC`, 601 - ).all(...config.rooms, ...config.dates).map((t: any) => ({ 602 - ...t, 603 - scheduledDuration: Math.max(300, (new Date(t.ends_at).getTime() - new Date(t.starts_at).getTime()) / 1000), 604 - })) as Talk[]; 612 + talks = db.prepare( 613 + `SELECT t.rkey, t.title, t.starts_at, t.ends_at, 614 + GROUP_CONCAT(s.name) as speaker_names 615 + FROM talks t 616 + LEFT JOIN talk_speakers ts ON t.uri = ts.talk_uri 617 + LEFT JOIN speakers s ON ts.speaker_uri = s.uri 618 + WHERE t.room IN (${rp}) ${dateFilter} 619 + GROUP BY t.rkey 620 + ORDER BY t.starts_at ASC`, 621 + ).all(...config.rooms, ...config.dates).map((t: any) => ({ 622 + ...t, 623 + scheduledDuration: Math.max(300, (new Date(t.ends_at).getTime() - new Date(t.starts_at).getTime()) / 1000), 624 + })) as Talk[]; 625 + } 605 626 606 627 console.log(`Talks (${talks.length}):`); 607 628 for (const t of talks) { ··· 630 651 631 652 // Pass 3: DP 632 653 console.log(`\n=== Pass 3: Selecting ${talks.length - 1} transitions ===\n`); 633 - const firstStart = findFirstTalkStart(gaps, talks, words, segments); 634 - console.log(` First talk starts at: ${fmt(firstStart)}`); 654 + // Allow ground truth override for first talk start via --first-start <seconds> 655 + const firstStartArg = process.argv.indexOf("--first-start"); 656 + const firstStart = firstStartArg >= 0 657 + ? parseFloat(process.argv[firstStartArg + 1]) 658 + : findFirstTalkStart(gaps, talks, words, segments); 659 + console.log(` First talk starts at: ${fmt(firstStart)}${firstStartArg >= 0 ? " (ground truth)" : ""}`); 635 660 636 661 const dpResult = selectTransitionsDP(gaps, talks, firstStart); 637 662
+31 -15
apps/ionosphere-appview/src/tracks.ts
··· 183 183 "Sunday": "2026-03-29", 184 184 }; 185 185 186 - // Which schedule rooms map to which stream 187 - const STREAM_ROOMS: Record<string, string[]> = { 188 - "great-hall-day-1": ["Great Hall South"], 189 - "great-hall-day-2": ["Great Hall South"], 190 - "room-2301-day-1": ["Room 2301"], 191 - "room-2301-day-2": ["Room 2301"], 192 - "performance-theatre-day-1": ["Performance Theatre"], 193 - "performance-theatre-day-2": ["Performance Theatre"], 194 - // ATScience was Friday — talks were in various rooms 195 - "atscience": ["Performance Theatre", "2301 Classroom", "2311 Classroom", "Bukhman Lounge", "Room 2301", "Great Hall South"], 186 + // Stream-to-talk matching: room + date, or rkey prefix for ATScience 187 + const STREAM_MATCH: Record<string, { rooms?: string[]; rkeyPrefix?: string }> = { 188 + "great-hall-day-1": { rooms: ["Great Hall South"] }, 189 + "great-hall-day-2": { rooms: ["Great Hall South"] }, 190 + "room-2301-day-1": { rooms: ["Room 2301"] }, 191 + "room-2301-day-2": { rooms: ["Room 2301"] }, 192 + "performance-theatre-day-1": { rooms: ["Performance Theatre"] }, 193 + "performance-theatre-day-2": { rooms: ["Performance Theatre"] }, 194 + "atscience": { rkeyPrefix: "ats26-" }, 196 195 }; 197 196 198 197 function getTalksForStream(db: Database.Database, slug: string, dayLabel: string): any[] { 198 + const match = STREAM_MATCH[slug]; 199 + if (!match) return []; 200 + 201 + // ATScience: match by rkey prefix (spans multiple rooms on Friday) 202 + if (match.rkeyPrefix) { 203 + return db.prepare( 204 + `SELECT t.rkey, t.title, t.starts_at, t.ends_at, t.duration, 205 + GROUP_CONCAT(s.name) as speaker_names 206 + FROM talks t 207 + LEFT JOIN talk_speakers ts ON t.uri = ts.talk_uri 208 + LEFT JOIN speakers s ON ts.speaker_uri = s.uri 209 + WHERE t.rkey LIKE ? 210 + GROUP BY t.rkey 211 + ORDER BY t.starts_at ASC` 212 + ).all(`${match.rkeyPrefix}%`) as any[]; 213 + } 214 + 215 + // Other streams: match by room + date 199 216 const date = DAY_DATES[dayLabel]; 200 - const rooms = STREAM_ROOMS[slug]; 201 - if (!date || !rooms?.length) return []; 217 + if (!date || !match.rooms?.length) return []; 202 218 203 - const placeholders = rooms.map(() => "?").join(","); 219 + const placeholders = match.rooms.map(() => "?").join(","); 204 220 return db.prepare( 205 221 `SELECT t.rkey, t.title, t.starts_at, t.ends_at, t.duration, 206 222 GROUP_CONCAT(s.name) as speaker_names ··· 208 224 LEFT JOIN talk_speakers ts ON t.uri = ts.talk_uri 209 225 LEFT JOIN speakers s ON ts.speaker_uri = s.uri 210 226 WHERE t.room IN (${placeholders}) AND t.starts_at LIKE ? 211 - GROUP BY t.uri 227 + GROUP BY t.rkey 212 228 ORDER BY t.starts_at ASC` 213 - ).all(...rooms, `${date}%`) as any[]; 229 + ).all(...match.rooms, `${date}%`) as any[]; 214 230 } 215 231 216 232 // --- Public API ---