feat: Wired source_videos and body_sections_format into technique detai…

- "backend/routers/techniques.py"

GSD-Task: S03/T02
This commit is contained in:
jlightner 2026-04-03 01:19:32 +00:00
parent bd0dbb4df9
commit dc18d0a543
4 changed files with 112 additions and 2 deletions

View file

@ -45,7 +45,7 @@ This is the data layer foundation — migration file, model changes, and schema
- Estimate: 30m
- Files: alembic/versions/012_multi_source_format.py, backend/models.py, backend/schemas.py
- Verify: cd backend && python -c "from models import TechniquePageVideo, TechniquePage; assert hasattr(TechniquePage, 'body_sections_format'); print('models OK')" && python -c "from schemas import SourceVideoSummary, TechniquePageDetail; print('schemas OK')"
- [ ] **T02: Wire source_videos into technique detail endpoint** — Update the technique detail endpoint to eagerly load source_video_links and build the source_videos list on the response. Verify end-to-end with an API call.
- [x] **T02: Wired source_videos and body_sections_format into technique detail API response with eager-loaded association table** — Update the technique detail endpoint to eagerly load source_video_links and build the source_videos list on the response. Verify end-to-end with an API call.
## Steps

View file

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"taskId": "T01",
"unitId": "M014/S03/T01",
"timestamp": 1775178991056,
"passed": true,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd backend",
"exitCode": 0,
"durationMs": 5,
"verdict": "pass"
}
]
}

View file

@ -0,0 +1,77 @@
---
id: T02
parent: S03
milestone: M014
provides: []
requires: []
affects: []
key_files: ["backend/routers/techniques.py"]
key_decisions: []
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "Alembic migration 012 ran clean. API response for existing technique shows body_sections_format: "v1" and source_videos: []. Tested via both Docker internal curl and external ub01:8096 URL."
completed_at: 2026-04-03T01:19:28.974Z
blocker_discovered: false
---
# T02: Wired source_videos and body_sections_format into technique detail API response with eager-loaded association table
> Wired source_videos and body_sections_format into technique detail API response with eager-loaded association table
## What Happened
---
id: T02
parent: S03
milestone: M014
key_files:
- backend/routers/techniques.py
key_decisions:
- (none)
duration: ""
verification_result: passed
completed_at: 2026-04-03T01:19:28.974Z
blocker_discovered: false
---
# T02: Wired source_videos and body_sections_format into technique detail API response with eager-loaded association table
**Wired source_videos and body_sections_format into technique detail API response with eager-loaded association table**
## What Happened
Updated get_technique() in backend/routers/techniques.py to eagerly load source_video_links relationship chained to source_video via selectinload. Built source_videos list from association table rows with content_type enum handling. Added imports for TechniquePageVideo and SourceVideoSummary. Deployed to ub01, ran Alembic migration 012, and verified API response includes both new fields with correct defaults.
## Verification
Alembic migration 012 ran clean. API response for existing technique shows body_sections_format: "v1" and source_videos: []. Tested via both Docker internal curl and external ub01:8096 URL.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd backend && python -c "from models import TechniquePageVideo, TechniquePage; assert hasattr(TechniquePage, 'body_sections_format'); print('models OK')"` | 0 | ✅ pass | 500ms |
| 2 | `cd backend && python -c "from schemas import SourceVideoSummary, TechniquePageDetail; print('schemas OK')"` | 0 | ✅ pass | 400ms |
| 3 | `ssh ub01 'docker exec chrysopedia-api alembic upgrade head'` | 0 | ✅ pass | 2000ms |
| 4 | `ssh ub01 'curl -s http://ub01:8096/api/v1/techniques/parallel-stereo-processing-keota | python3 -m json.tool | grep -E body_sections_format|source_videos'` | 0 | ✅ pass | 1000ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `backend/routers/techniques.py`
## Deviations
None.
## Known Issues
None.

View file

@ -11,12 +11,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from database import get_session
from models import Creator, KeyMoment, RelatedTechniqueLink, SourceVideo, TechniquePage, TechniquePageVersion
from models import Creator, KeyMoment, RelatedTechniqueLink, SourceVideo, TechniquePage, TechniquePageVersion, TechniquePageVideo
from schemas import (
CreatorInfo,
KeyMomentSummary,
PaginatedResponse,
RelatedLinkItem,
SourceVideoSummary,
TechniquePageDetail,
TechniquePageRead,
TechniquePageVersionDetail,
@ -223,6 +224,9 @@ async def get_technique(
selectinload(TechniquePage.incoming_links).selectinload(
RelatedTechniqueLink.source_page
),
selectinload(TechniquePage.source_video_links).selectinload(
TechniquePageVideo.source_video
),
)
)
result = await db.execute(stmt)
@ -295,12 +299,25 @@ async def get_technique(
version_count_result = await db.execute(version_count_stmt)
version_count = version_count_result.scalar() or 0
# Build source video list from association table
source_videos = [
SourceVideoSummary(
id=link.source_video.id,
filename=link.source_video.filename,
content_type=link.source_video.content_type.value if hasattr(link.source_video.content_type, 'value') else str(link.source_video.content_type),
added_at=link.added_at,
)
for link in page.source_video_links
if link.source_video is not None
]
return TechniquePageDetail(
**base.model_dump(),
key_moments=key_moment_items,
creator_info=creator_info,
related_links=related_links,
version_count=version_count,
source_videos=source_videos,
)