feat: Wired source_videos and body_sections_format into technique detai…
- "backend/routers/techniques.py" GSD-Task: S03/T02
This commit is contained in:
parent
bd0dbb4df9
commit
dc18d0a543
4 changed files with 112 additions and 2 deletions
|
|
@ -45,7 +45,7 @@ This is the data layer foundation — migration file, model changes, and schema
|
||||||
- Estimate: 30m
|
- Estimate: 30m
|
||||||
- Files: alembic/versions/012_multi_source_format.py, backend/models.py, backend/schemas.py
|
- 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')"
|
- 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
|
## Steps
|
||||||
|
|
||||||
|
|
|
||||||
16
.gsd/milestones/M014/slices/S03/tasks/T01-VERIFY.json
Normal file
16
.gsd/milestones/M014/slices/S03/tasks/T01-VERIFY.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
77
.gsd/milestones/M014/slices/S03/tasks/T02-SUMMARY.md
Normal file
77
.gsd/milestones/M014/slices/S03/tasks/T02-SUMMARY.md
Normal 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.
|
||||||
|
|
@ -11,12 +11,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from database import get_session
|
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 (
|
from schemas import (
|
||||||
CreatorInfo,
|
CreatorInfo,
|
||||||
KeyMomentSummary,
|
KeyMomentSummary,
|
||||||
PaginatedResponse,
|
PaginatedResponse,
|
||||||
RelatedLinkItem,
|
RelatedLinkItem,
|
||||||
|
SourceVideoSummary,
|
||||||
TechniquePageDetail,
|
TechniquePageDetail,
|
||||||
TechniquePageRead,
|
TechniquePageRead,
|
||||||
TechniquePageVersionDetail,
|
TechniquePageVersionDetail,
|
||||||
|
|
@ -223,6 +224,9 @@ async def get_technique(
|
||||||
selectinload(TechniquePage.incoming_links).selectinload(
|
selectinload(TechniquePage.incoming_links).selectinload(
|
||||||
RelatedTechniqueLink.source_page
|
RelatedTechniqueLink.source_page
|
||||||
),
|
),
|
||||||
|
selectinload(TechniquePage.source_video_links).selectinload(
|
||||||
|
TechniquePageVideo.source_video
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
|
|
@ -295,12 +299,25 @@ async def get_technique(
|
||||||
version_count_result = await db.execute(version_count_stmt)
|
version_count_result = await db.execute(version_count_stmt)
|
||||||
version_count = version_count_result.scalar() or 0
|
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(
|
return TechniquePageDetail(
|
||||||
**base.model_dump(),
|
**base.model_dump(),
|
||||||
key_moments=key_moment_items,
|
key_moments=key_moment_items,
|
||||||
creator_info=creator_info,
|
creator_info=creator_info,
|
||||||
related_links=related_links,
|
related_links=related_links,
|
||||||
version_count=version_count,
|
version_count=version_count,
|
||||||
|
source_videos=source_videos,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue