Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/api/concepts.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,24 @@ class StructuredExplanationResponse(BaseModel):
prerequisite_note: str | None = None


class ChunkPreviewResponse(BaseModel):
source_id: str
title: str
preview: str
relevance_score: float = 0.0


class ExplanationResponse(BaseModel):
concept_id: str
concept_name: str
explanation: str
structured: StructuredExplanationResponse | None = None
source_ids: list[str]
chunk_count: int
source_titles: dict[str, str] = {}
dossier_grounded: bool = False
chunk_previews: list[ChunkPreviewResponse] = []
explanation_source: str = "dossier+retrieval"


class PracticeRequest(BaseModel):
Expand Down
55 changes: 52 additions & 3 deletions backend/api/curriculum.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Curriculum API - read-only track and module retrieval."""
"""Curriculum API - track, module, and resource retrieval."""

import logging

Expand All @@ -11,13 +11,16 @@
CurriculumModuleSummary,
CurriculumTrackDetail,
CurriculumTrackSummary,
TrackResource,
)
from backend.services.learning.curriculum_store import (
DEFAULT_USER_ID,
get_concept_progress,
get_module,
get_module_progress,
get_track,
list_module_resources,
list_track_resources,
list_tracks,
update_concept_progress,
)
Expand Down Expand Up @@ -54,6 +57,19 @@ class ModuleProgressResponse(BaseModel):
not_started: int


class ModuleResourcesResponse(BaseModel):
track_id: str
module_id: str
resources: list[TrackResource]
total: int


class TrackResourcesResponse(BaseModel):
track_id: str
resources: list[TrackResource]
total: int


@router.get("/tracks", response_model=TrackListResponse)
def list_curriculum_tracks(published_only: bool = Query(True)):
"""List all curriculum tracks."""
Expand Down Expand Up @@ -81,12 +97,16 @@ def get_curriculum_track(track_id: str):

@router.get("/tracks/{track_id}/modules/{module_id}", response_model=CurriculumModuleDetail)
def get_curriculum_module(track_id: str, module_id: str):
"""Get a module with ordered concept refs and readiness state."""
"""Get a module with ordered concept refs, readiness state, and resources."""
module = get_module(track_id, module_id)
if not module:
raise HTTPException(status_code=404, detail="Module not found")
return CurriculumModuleDetail(
**{**module, "concepts": [ConceptRef(**c) for c in module["concepts"]]}
**{
**module,
"concepts": [ConceptRef(**c) for c in module["concepts"]],
"resources": [TrackResource(**r) for r in module["resources"]],
}
)


Expand All @@ -106,6 +126,35 @@ def get_curriculum_module_progress(
return ModuleProgressResponse(**{k: v for k, v in progress.items() if k != "concepts"})


@router.get(
"/tracks/{track_id}/modules/{module_id}/resources",
response_model=ModuleResourcesResponse,
)
def get_module_resources_endpoint(track_id: str, module_id: str):
"""List all resources for a specific module."""
resources = list_module_resources(track_id, module_id)
return ModuleResourcesResponse(
track_id=track_id,
module_id=module_id,
resources=[TrackResource(**r) for r in resources],
total=len(resources),
)


@router.get(
"/tracks/{track_id}/resources",
response_model=TrackResourcesResponse,
)
def get_track_resources_endpoint(track_id: str):
"""List all resources for a track."""
resources = list_track_resources(track_id)
return TrackResourcesResponse(
track_id=track_id,
resources=[TrackResource(**r) for r in resources],
total=len(resources),
)


@router.put("/progress/{concept_id}", response_model=ConceptProgressResponse)
def update_progress(concept_id: str, body: ProgressUpdateRequest):
"""Update learning progress for a concept."""
Expand Down
Loading
Loading