PRD-028: Position Tracker¶
- Status: Draft
- Authors: Marko
- Target Release: TBD
- Related RFCs:
docs/rfc/RFC-072-canonical-identity-layer-cross-layer-bridge.md-- CIL identity layer, bridge artifact, Flagship 1 definition, and query Pattern A (position_arc)docs/rfc/RFC-062-gi-kg-viewer-v2.md-- viewer SPA shell and tab modeldocs/rfc/RFC-069-graph-exploration-toolkit.md-- graph chrome (Cytoscape)docs/rfc/RFC-049-grounded-insight-layer-core.md-- GIL Insight and Quote nodesdocs/rfc/RFC-055-knowledge-graph-layer-core.md-- KG Episode and Entity nodesdocs/rfc/RFC-061-semantic-corpus-search.md-- FAISS search and chunk-to-Insight lift (Phase 5)- Related PRDs:
docs/prd/PRD-017-grounded-insight-layer.md-- GIL artifact foundationdocs/prd/PRD-019-knowledge-graph-layer.md-- KG artifact foundationdocs/prd/PRD-026-topic-entity-view.md-- topic-first navigation; cross-linked from Position Tracker topic selectordocs/prd/PRD-027-enriched-search.md-- enriched search entry point; speaker names in enriched sources can open Position Tracker via Person Landingdocs/prd/PRD-029-person-profile.md-- owns the shared Person Landing component that hosts both Person Profile and Position Tracker; provides identity header, topic overview, and cross-navigation- Related UX specs:
docs/uxs/UXS-009-position-tracker.md-- visual contract for Position Tracker panel layout, timeline, quote cards, degradation statesdocs/uxs/UXS-001-gi-kg-viewer.md-- shared design system (tokens, typography, states)- Related Architecture:
docs/architecture/gi/ontology.mddocs/architecture/kg/ontology.md
Summary¶
The Position Tracker answers: "How has person X's thinking on topic Y evolved across episodes?"
It is a dedicated viewer panel where person:{slug} + topic:{slug} are the
subject. The user navigates to a person, selects a topic, and sees a chronological
arc of grounded Insights with verbatim quotes and timestamps -- one entry per
episode, ordered by publish date. The difference between a quote search and a
Position Tracker is narrative structure over time: not "here are 5 things Satya
Nadella said about AI" but "here is how his thinking shifted from 2023 to 2025,
grounded in his own words."
No LLM and no database are required. All data comes from the CIL bridge artifact, GIL Insights and Quotes, and KG episode metadata.
Background¶
RFC-072 defines the Position Tracker as Flagship 1 of the Canonical Identity Layer.
The CIL bridge makes cross-episode person + topic queries possible by scanning small
bridge.json files instead of loading every gi.json in the corpus. The query
pattern (position_arc) is implemented in cil_queries.py and exposed via
GET /api/persons/{person_id}/positions?topic={topic_id} (GitHub #527).
Today the API returns structured data but there is no viewer surface. The user must call the API directly or use the CLI. This PRD adds the viewer panel, tightens the API response to include display names and summary fields, and defines acceptance criteria for the complete product experience.
PRD-029 (Person Profile) is the companion flagship. The Person Profile shows all topics a person discusses; clicking a topic group in the profile navigates to the Position Tracker for that person + topic combination.
Goals¶
- Make
person:{slug}+topic:{slug}a navigable concept in the viewer -- the user can see how a person's position on a topic has evolved across episodes. - Present a chronological arc of grounded Insights with verbatim quotes, timestamps, and episode attribution.
- Enable filtering by
insight_typeso the user can focus on claims, recommendations, or all types. - Provide multiple entry points: graph Person node click, search result speaker click, and a dedicated person browse.
- Cross-link to PRD-026 Topic Entity View from the topic selector.
- Work entirely from core artifacts (bridge, GIL, KG) -- no LLM, no database, no new extraction.
Non-Goals¶
- Not automated position-change detection or stance summarisation -- out of scope.
- Not automated contradiction detection or stance comparison across persons -- out of scope.
- Not a cross-corpus view -- single corpus only.
- Not audio playback -- timestamps are displayed as text (e.g. "14:23 -- 14:35"), not as playable clips.
- Not a replacement for the graph view -- the Cytoscape Person node and its edges remain. Position Tracker is a complementary surface.
- Not a new top-level tab -- it opens in the right rail panel or a dedicated route, depending on entry point.
Personas¶
- User: A single person operating the podcast scraper tool. They explore their corpus to understand how guests' positions evolve, prepare for interviews, or research public figures' stated views over time.
User Stories¶
- As a user, I can click a person in the graph and select a topic so that I see a chronological arc of their stated positions on that topic across all episodes.
- As a user, I can see verbatim quotes with timestamps for each position so that I can verify the attribution and find the exact moment in the episode.
- As a user, I can filter the arc to show only claims (or recommendations, or all types) so that I can focus on the signal that matters for my research.
- As a user, I can click a topic in the selector to navigate to the Topic Entity View (PRD-026) so that I can see the full corpus picture for that concept.
Functional Requirements¶
FR1: API Layer¶
The Position Tracker API builds on the existing CIL endpoints. Requirements marked "existing" are already implemented; requirements marked "new" require implementation.
- FR1.1 (existing):
GET /api/persons/{person_id}/positions?topic={topic_id}returns aCilPositionArcResponsewith oneCilArcEpisodeBlockper episode where the person discusses the topic, ordered bypublish_date. - FR1.2 (existing):
GET /api/persons/{person_id}/topicsreturns a list of all topic IDs this person discusses (for the topic selector). - FR1.3 (new): The
CilPositionArcResponsemust includeperson_display_nameandtopic_display_nameresolved from the bridgeidentitiesarray. The viewer must not have to resolve IDs to display names client-side. - FR1.4 (new): Each
CilArcEpisodeBlockmust includepodcast_titleresolved from the KG Episode node, so the viewer can show which show the episode belongs to. - FR1.5 (new): The
CilPositionArcResponsemust include summary fields:position_count(total Insights across all episodes),episode_count, anddate_range(earliestandlatestpublish dates). - FR1.6 (new): The
CilIdListResponsefor/persons/{id}/topicsmust includedisplay_names: dict[str, str]mapping each topic ID to its display name from the bridge, so the topic selector can show human-readable labels. - FR1.7 (existing): The
insight_typesquery parameter on the positions endpoint filters Insights by type. Default isclaimonly;allor*disables the filter.
FR2: Viewer Panel -- Position Tracker¶
- FR2.1: The Position Tracker is one view within the shared Person Landing component (owned by PRD-029). It opens from three entry points:
- Clicking a Person node in the Cytoscape graph (right rail panel).
- Clicking a speaker name in a search result card (including lifted results).
- A dedicated person browse (accessible from the viewer navigation).
All entry points navigate to the Person Landing, which defaults to the Person
Profile tab (PRD-029). The user toggles to Position Tracker from there. When the
entry point is topic-specific (e.g. a topic link from PRD-026), the Position
Tracker tab is preselected with that topic active.
- FR2.2: Person header section shows:
- Person display name (text-lg font-semibold).
- Canonical person:{slug} ID (muted, text-xs, monospace).
- Appearance count (total episodes where this person appears in the corpus).
- Date range of first and last appearance.
- FR2.3: Topic selector:
- A searchable dropdown populated from GET /api/persons/{id}/topics with display
names (FR1.6).
- Selecting a topic loads the position arc from
GET /api/persons/{id}/positions?topic={id}.
- A "View topic" link next to the selected topic navigates to the PRD-026 Topic
Entity View for that topic.
- FR2.4: Insight type filter:
- A segmented control with options: "Claims" (default), "Recommendations", "All".
- Changing the filter re-fetches the arc with the corresponding insight_types
parameter.
- FR2.5: Timeline visualization:
- Episodes are displayed as cards in a vertical timeline, ordered by publish_date
(most recent first).
- Each episode card shows: episode title, podcast title, publish date, and the
number of matching Insights.
- A connecting line between episode cards visualises the chronological progression.
- FR2.6: Insight cards within each episode:
- Insight text.
- insight_type badge (e.g. "claim", "recommendation", "observation") using intent
tokens.
- position_hint indicator: a small bar or number showing the Insight's relative
position within the episode (0.0 = early, 1.0 = late).
- Confidence score (if available).
- Grounding status badge: "grounded" (green/success) or "ungrounded"
(warning).
- FR2.7: Quote cards within each Insight:
- Verbatim quote text in a blockquote style.
- Timestamp display as text (e.g. "20:00 -- 20:15"), formatted from
timestamp_start_ms and timestamp_end_ms.
- Episode attribution (episode title, publish date).
- FR2.8: Cross-navigation:
- "View in graph" button navigates to the Graph tab and focuses on this person's
node.
- "Open profile" button navigates to the Person Profile (PRD-029) for this person.
FR3: Empty and Degraded States¶
These are acceptance criteria -- each must have defined UI behavior and test coverage.
- FR3.1: Person has 0 Insights (appears in KG but no GIL attribution):
- The person header (FR2.2) still shows with metadata from KG (name, episode count).
- The topic selector is empty or shows "No topics found."
- The arc area shows: "No grounded insights found for this person. Insights appear when the pipeline runs with GIL extraction enabled."
- FR3.2: Person has Insights but 0 grounded Quotes (all Insights are ungrounded):
- Insight cards display with an "ungrounded" badge (
warningtoken). - The quote section within each Insight is hidden (no empty quote placeholder).
- A note below the Insights: "These insights are not grounded in verbatim quotes. Grounding improves with diarization and GIL extraction quality."
- FR3.3: Selected topic has 0 Insights:
- The topic appears in the selector (it exists in the bridge).
- Selecting it shows: "No insights on this topic for [person name]. Try selecting a different topic or viewing all types."
- FR3.4: Lift fails (char offset mismatch on search entry point):
- The search result card shows the raw transcript chunk without a speaker link.
- No Position Tracker navigation is offered from that card.
- The
lift_statsin the search response indicates the lift failure. - FR3.5: No
bridge.jsonfor some episodes: - Episodes without a bridge file are silently excluded from the arc.
- If all episodes lack bridge files, the arc area shows: "No cross-layer data available. Run the pipeline with bridge generation enabled."
- The person header still shows KG-derived metadata if available.
Data Flow¶
Entry point (graph / search / browse)
|
v
GET /api/persons/{id}/topics --> topic selector populated
|
v (user selects topic)
GET /api/persons/{id}/positions?topic={id}&insight_types=claim
|
v
CIL query: scan bridge.json files across corpus
|
v
Filter: episodes where person + topic both in bridge GI identities
|
v
Load gi.json per episode: ABOUT + SUPPORTED_BY + SPOKEN_BY edges
|
v
Assemble arc: Insights + Quotes, ordered by publish_date
|
v
Viewer renders timeline with Insight and Quote cards
API Response Shape¶
The enriched CilPositionArcResponse (FR1.3 -- FR1.5):
{
"path": "/path/to/corpus",
"person_id": "person:satya-nadella",
"person_display_name": "Satya Nadella",
"topic_id": "topic:ai-safety",
"topic_display_name": "AI Safety",
"position_count": 4,
"episode_count": 2,
"date_range": {
"earliest": "2023-06-15",
"latest": "2025-02-20"
},
"episodes": [
{
"episode_id": "episode:abc123",
"publish_date": "2023-06-15T00:00:00Z",
"podcast_title": "Lex Fridman Podcast",
"insights": [
{
"id": "insight:a1b2c3d4",
"text": "AI safety is important but should not slow down innovation",
"insight_type": "claim",
"position_hint": 0.15,
"grounded": true,
"confidence": 0.88,
"supporting_quotes": [
{
"text": "We need to move fast. Safety is a priority, but paralysis is not safety.",
"timestamp_start_ms": 1200000,
"timestamp_end_ms": 1215000
}
]
}
]
},
{
"episode_id": "episode:def456",
"publish_date": "2025-02-20T00:00:00Z",
"podcast_title": "Hard Fork",
"insights": [
{
"id": "insight:e5f6g7h8",
"text": "AI safety requires mandatory external audits before deployment",
"insight_type": "claim",
"position_hint": 0.85,
"grounded": true,
"confidence": 0.91,
"supporting_quotes": [
{
"text": "I've changed my mind. We need third-party audits. Full stop.",
"timestamp_start_ms": 840000,
"timestamp_end_ms": 852000
}
]
}
]
}
]
}
Success Criteria¶
- A user can click a Person node in the graph, select a topic, and see the full position arc in the right rail without leaving the viewer session.
- Each Insight in the arc is accompanied by at least one verbatim quote with a human-readable timestamp.
- The
insight_typefilter correctly narrows the arc to the selected types. - The topic selector shows human-readable display names, not raw IDs.
- Cross-navigation to Topic Entity View (PRD-026) and Person Profile (PRD-029) works from the Position Tracker panel.
- All five empty/degraded states (FR3.1 -- FR3.5) render correctly with honest messaging and no broken UI.
- Playwright E2E coverage for: populated arc, empty person, empty topic, and insight type filtering.
Dependencies¶
- RFC-072 (Phases 1--4): CIL identity, bridge artifact,
position_arcquery pattern, and CIL API endpoints. - PRD-026 (Topic Entity View): cross-linked from the topic selector.
- PRD-029 (Person Profile): companion flagship; "Open profile" button navigates there. Owns the shared Person Landing component.
Constraints and Assumptions¶
Constraints:
- Must run on Apple M4 Pro with 48 GB RAM (local-first tool).
- Arc assembly must complete in < 3 seconds for a corpus of 200 episodes.
- No database -- all data read from filesystem artifacts.
Assumptions:
- The user has run the pipeline with GIL extraction and bridge generation enabled.
bridge.jsonfiles exist for the episodes the user wants to explore.- Display names are available in the bridge
identitiesarray.
Design Considerations¶
Timeline Layout¶
- Option A: Horizontal timeline with episode markers on a date axis.
- Pros: Visually shows time gaps between episodes; familiar timeline metaphor.
- Cons: Horizontal scrolling on narrow screens; sparse for few episodes.
- Option B: Vertical card list ordered by date.
- Pros: Works well in a right rail panel; scales to many episodes; no horizontal scroll.
- Cons: Less visually "timeline-like."
- Decision: Vertical card list (Option B) for the right rail entry point. A connecting line between cards provides the timeline metaphor. Full-width mode (from browse entry point) may use a horizontal timeline in a future iteration.
Insight Ranking Within an Episode¶
- Insights within a single episode are ordered by
position_hint(ascending), so the user reads them in the order they appeared in the conversation. This preserves the argumentative progression (setup before conclusion).
Known Limitations¶
- Single-episode corpus: The arc shows one data point. No evolution is possible. The viewer does not show a special state for this -- it simply displays one episode card.
Open Questions¶
- Appearance count source: Should the person header's appearance count come from the bridge scan (episodes where the person has GIL attribution) or from KG (episodes where the person is mentioned)? The bridge count is more accurate for "episodes where this person spoke" but may undercount mentions.
- Cross-show arc: When the corpus contains multiple podcasts, should the arc interleave episodes from different shows or group by show? Interleaving by date is the default; grouping by show is a future option.
Related Work¶
- Issue #527: CIL query API implementation
- PRD-026: Topic Entity View
- PRD-029: Person Profile
- RFC-072: Canonical Identity Layer
- UXS-009:
docs/uxs/UXS-009-position-tracker.md-- visual contract
Release Checklist¶
- [ ] PRD reviewed and approved
- [ ] API enrichments implemented (FR1.3 -- FR1.6)
- [ ] Viewer panel implemented with all entry points (FR2.1 -- FR2.8)
- [ ] Empty/degraded states implemented and tested (FR3.1 -- FR3.5)
- [ ] UXS-009 tokens implemented in viewer styles
- [ ] Playwright E2E coverage for populated, empty, and filter states
- [ ] Documentation updated (SERVER_GUIDE, E2E_SURFACE_MAP)
- [ ] Integration with PRD-026 (Topic Entity View) verified
- [ ] Integration with PRD-029 (Person Profile) verified