-
Notifications
You must be signed in to change notification settings - Fork 0
Function Index
The master cross-reference for every documented function, class, dataclass, and
path-operation handler in gradient-server. Grouped by module (file path,
alphabetical), every entry carries file:line, its layer tag, a one-line
description, and a link to the Feature page where it is documented
in context.
This page is the single point of truth for where a symbol lives; the Feature
pages explain how it behaves and the SPEC (SPEC.md, §G/§C/§I/§V/§T/§B) is
the forward authority on why. Where docs and code disagree, code wins.
See also: Home · Architecture · Data Models · API Reference · Glossary · Getting Started · design notes Intelligence Layer and Core Library.
Note: Line ranges track the code at the time of writing. The watcher-backed index lags edits by ~1s; treat
file:lineas a starting anchor, not a byte offset.
| Layer | Meaning |
|---|---|
| handler | FastAPI path-operation function bound to an HTTP route. |
| dependency | Injectable (Depends(...)) — session, settings, auth, per-request client. |
| middleware | App-level lifespan / exception handler wired into the ASGI app. |
| service | Domain logic in app/services/**; pure-ish, transport-agnostic, the Core Library candidate surface. |
| job | Scheduler entrypoint or its core (run_*), invoked by APScheduler in lifespan. |
| schema | Dataclass / result type / Pydantic-ish shape (no behavior beyond construction + derived props). |
| util | Pure helper, factory, or config/infra construct. |
Feature pages linked below: Capture, Knowledge Base, Outline, Anki, Tutor / MCP, Platform / Admin — all sections of the consolidated Features page.
A handful of load-bearing functions get the long form; everything else is a one-line index row in the by-module tables below.
Location: app/services/ingest.py:17-19 · Layer: service · Feature(s): Capture
Purpose: Thin dispatcher for POST /api/v1/captures — routes a capture to its registered source adapter by payload.source (the §A plugin-seam dispatch contract).
Signature:
async def ingest_capture(payload: CapturePayload, session: AsyncSession) -> IngestResponseParameters:
| Name | Type | Required | Description |
|---|---|---|---|
payload |
CapturePayload |
yes | Normalized capture envelope; payload.source selects the adapter. |
session |
AsyncSession |
yes | Caller-owned DB transaction; the adapter writes inside it. |
Returns: IngestResponse.
Side effects: Delegates all DB/filesystem writes to the selected adapter.
Errors: UnknownSourceError when source has no registered adapter.
Called by / Calls: Called by post_capture. Calls get_adapter, adapter.ingest → normalize_capture.
Location: app/services/adapters/extension_capture.py:294-368 · Layer: service · Feature(s): Capture
Purpose: Shared source-agnostic normalizer that turns one capture into the {RawCapture, media, Passage, Question, Attempt} row set, stamping payload.source on each row. Every source adapter delegates here verbatim, proving the §A seam takes a new source without touching the entrypoint.
Signature:
async def normalize_capture(payload: CapturePayload, session: AsyncSession) -> IngestResponseParameters:
| Name | Type | Required | Description |
|---|---|---|---|
payload |
CapturePayload |
yes | The capture to normalize; payload.source is stamped onto each persisted row. |
session |
AsyncSession |
yes | Caller-owned transaction; this writes + flushes but does not commit. |
Returns: IngestResponse.
Side effects: Writes RawCapture/Media/Passage/Question/Attempt; writes media bytes to disk; flushes (no commit).
Errors: Propagates UnknownCourseError from _resolve_course_id.
Called by / Calls: Called by ManualAdapter.ingest, UWorldAdapter.ingest, WebQbankAdapter.ingest. Calls _resolve_course_id, _persist_media, _upsert_passage, _resolve_choice, _upsert_question.
Location: app/services/kb/jobs.py:271-386 · Layer: service · Feature(s): Knowledge Base
Purpose: THE grounded categorizer core (run_grounded_tag): tag untagged atomic_facts and needs_categorization questions via recall → grounded pick → calibrate → persist, course-scoped per V-CAP2. See Intelligence Layer for the chain.
Signature:
async def tag_pending(session, *, tagging_client, calibrator_client=None, version=None) -> TagReportParameters:
| Name | Type | Required | Description |
|---|---|---|---|
session |
AsyncSession |
yes | Caller-owned transaction; this flushes per V41 isolation. |
tagging_client |
AsyncOpenAI |
yes | Client for the grounded-pick call. |
calibrator_client |
AsyncOpenAI | None |
no | V69 calibrator client; defaults to the tagging client wiring downstream. |
version |
str | None |
no | Embedding version; defaults to current_version(). |
Returns: TagReport (counts, token accounting, manual-review flags, partial_failure).
Side effects: OpenAI tagging + calibrator calls; INSERT/DELETE question_tags & atomic_fact_tags; denormalizes atomic_facts.node_id; sets questions.needs_categorization=False; flush. V41 per-entity isolation.
Errors: Per-entity failures isolated into report.failures (V41); no fatal raise on a single bad item.
Called by / Calls: Called by run_grounded_tag_job → _do_run_grounded_tag. Calls _tag_one, current_version. Governed by V-L3, V69, V-T2, V-T3, V-CAP2.
Location: app/services/kb/recall.py:448-570 · Layer: service · Feature(s): Knowledge Base
Purpose: Build the V-L3 constrained candidate set merging the three recall meta-paths — C2T (cosine to node embeddings), C2C2T (tags borrowed from similar facts), and T2T (similarity-edge expansion) — deduped by node, with optional prior-tag exemplars. The grounded prompt may pick only from this set.
Signature:
async def retrieve_candidates(session, *, course_id, query_embedding, top_k=10,
edge_expansion=True, content_expansion=True, exemplars_per_node=0,
embedding_version=None, ...) -> RecallResultParameters: (selected — full keyword surface in source)
| Name | Type | Required | Description |
|---|---|---|---|
session |
AsyncSession |
yes | Read-only DB session. |
course_id |
int |
yes | Course scope; recall never crosses courses (V-CAP2). |
query_embedding |
list[float] |
yes | The entity's embedding vector. |
top_k |
int |
no | Cap for the C2T cosine path (default 10). |
edge_expansion / content_expansion
|
bool |
no | Toggle the T2T / C2C2T paths. |
Returns: RecallResult (ordered Candidate list + embedding_version).
Side effects: None — read-only.
Errors: None thrown for empty recall; returns an empty candidate list.
Called by / Calls: Called by _tag_one. Calls _embedding_candidates, _content_candidates, _expand_via_edges, _load_course_node_map, _exemplars_for_node. Governed by V-L3.
Location: app/services/anki/sync.py:77-208 · Layer: service · Feature(s): Anki
Purpose: §T3 core: pull a deck's cards/notes from AnkiConnect, upsert the anki_notes/anki_cards SRS mirror, replace schema_map tag sets, and append revlog rows — returning the {synced_cards, linked_qids, error, reviews_synced} envelope.
Signature:
async def sync_deck(session, client, *, deck_name: str,
outline_lookup: Optional[OutlineLookup] = None) -> SyncSummaryParameters:
| Name | Type | Required | Description |
|---|---|---|---|
session |
AsyncSession |
yes | Transaction for the SRS-mirror writes. |
client |
AnkiConnectClient |
yes | Read-only AnkiConnect surface (V13). |
deck_name |
str |
yes | Deck to sync. |
outline_lookup |
OutlineLookup | None |
no | In-memory path index for AAMC CC tag resolution. |
Returns: SyncSummary.
Side effects: AnkiConnect reads (V13 read-only) + DB upserts (anki_notes, anki_cards, anki_note_tags, anki_card_reviews).
Errors: Unreachable → error='anki_not_running' (V4, soft); empty deck → error='deck_empty_or_misspelled' (V20). No HTTPException.
Called by / Calls: Called by sync_anki route + run_anki_sync_job. Calls client.find_cards, client.cards_info, client.notes_info, OutlineLookup.load, _replace_tags.
Location: app/api/v1/captures.py:17 · Layer: handler · Feature(s): Capture
Purpose: POST /api/v1/captures (200, IngestResponse) — ingest a UWorld (or other-source) question capture from the Chrome extension into the question/attempt/media tables.
Signature:
async def post_capture(payload: CapturePayload, ...) -> IngestResponseParameters: body CapturePayload (extra='forbid'); deps get_session, verify_coach_token.
Returns: IngestResponse.
Side effects: Delegates to ingest_capture (all DB/FS writes downstream).
Errors: 422 from UnknownSourceError/UnknownCourseError (re-raised with str(exc) detail); 422 RequestValidationError on malformed body (logged by the capture-path handler in main.py).
Called by / Calls: ASGI route. Calls ingest_capture.
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_settings |
app/api/deps.py:16-17 |
dependency | Return the singleton Settings for injection. |
Platform / Admin |
get_session |
app/api/deps.py:20-28 |
dependency | Yield an AsyncSession; commit on clean exit, rollback on exception. |
Platform / Admin |
verify_coach_token |
app/api/deps.py:31-39 |
dependency | Constant-time check of X-Coach-Token vs settings.COACH_TOKEN; 401 on miss. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
_anki_status_client |
app/api/v1/admin.py:45-51 |
dependency | Per-request AnkiConnect client for the status probe; closed on teardown (V16). | Platform / Admin |
_openai_status_client |
app/api/v1/admin.py:54-58 |
dependency | OpenAI client for the status probe (max_retries=0, fails fast). |
Platform / Admin |
_notion_status_client |
app/api/v1/admin.py:61-74 |
dependency | Notion AsyncClient for the status probe; None when token unset. |
Platform / Admin |
create_manual_tag (handler) |
app/api/v1/admin.py:119 |
handler |
POST /admin/questions/{question_id}/tags (201) — create a manual override tag. No token/localhost guard. |
Platform / Admin |
list_jobs_payload |
app/api/v1/admin.py:159-169 |
util | Snapshot scheduler jobs as {job_id, next_run_time}; shared by /admin/jobs + /admin/status. |
Platform / Admin |
trigger_job_logic |
app/api/v1/admin.py:172-190 |
util | Validate job_name vs _VALID_JOBS and nudge next_run_time=now; 404/409/503 guard. |
Platform / Admin |
list_jobs (handler) |
app/api/v1/admin.py:194 |
handler |
GET /admin/jobs (200) — scheduler job snapshot. |
Platform / Admin |
trigger_job (handler) |
app/api/v1/admin.py:203 |
handler |
POST /admin/jobs/{job_name}/trigger (202) — run a named job now. |
Platform / Admin |
system_status (handler) |
app/api/v1/admin.py:217 |
handler |
GET /admin/status (200) — Anki/OpenAI/Notion reachability + per-job last-run (T39). |
Platform / Admin |
delete_tag (handler) |
app/api/v1/admin.py:236 |
handler |
DELETE /admin/tags/{tag_id} (204) — manual hard-delete / llm soft-override / else 403. No token/localhost guard. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
_anki_client |
app/api/v1/anki.py:57-63 |
dependency | Per-request AnkiConnectClient for POST /anki/sync (test-overridable). |
Anki |
sync_anki (handler) |
app/api/v1/anki.py:76 |
handler |
POST /anki/sync (200) — one-off deck sync; soft V4 error envelope, never 500. |
Anki |
get_review_queue (handler) |
app/api/v1/anki.py:110 |
handler |
GET /anki/review-queue (200) — due cards + retention + retrievability (T43). |
Anki |
get_cards_for_qid (handler) |
app/api/v1/anki.py:132 |
handler |
GET /anki/cards/by-qid/{qid} (200) — cards carrying the uworld qid tag. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
create_assignment_route (handler) |
app/api/v1/anki_assign.py:48 |
handler |
POST /anki/assignments (201) — create + snapshot resolved card_ids (V52). |
Anki |
list_assignments_route (handler) |
app/api/v1/anki_assign.py:71 |
handler |
GET /anki/assignments (200) — list by unlock time, optional status/window filter. |
Anki |
patch_assignment_route (handler) |
app/api/v1/anki_assign.py:97 |
handler |
PATCH /anki/assignments/{assignment_id} (200) — skipped/completed transition (V51). |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
_get_or_seed |
app/api/v1/anki_load.py:38-48 |
util | Read singleton AnkiLoadConfig (id=1); seed V59 default (200 cards / 60 min) on miss. |
Anki |
get_load_config_route (handler) |
app/api/v1/anki_load.py:56 |
handler |
GET /anki/load-config (200) — read singleton config, auto-seed on miss. |
Anki |
upsert_load_config_route (handler) |
app/api/v1/anki_load.py:68 |
handler |
POST /anki/load-config (200) — upsert id=1 in place (V59). |
Anki |
get_load_adherence_route (handler) |
app/api/v1/anki_load.py:93 |
handler |
GET /anki/load-adherence (200) — V54 plan-adherence payload + reviewed series. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
create_review_route (handler) |
app/api/v1/anki_review.py:30 |
handler |
POST /anki/reviews (201) — pending review for a set of card_ids on a date (V53). |
Anki |
list_reviews_route (handler) |
app/api/v1/anki_review.py:47 |
handler |
GET /anki/reviews (200) — list by review_date, optional status/window filter. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_notes (handler) |
app/api/v1/attempts.py:35 |
handler |
GET /attempts/{attempt_id}/notes (200) — list attempt notes. |
Capture |
add_note (handler) |
app/api/v1/attempts.py:44 |
handler |
POST /attempts/{attempt_id}/notes (201) — create a note, optionally flagged. |
Capture |
remove_note (handler) |
app/api/v1/attempts.py:63 |
handler |
DELETE /attempts/notes/{note_id} (204) — delete a note by id. |
Capture |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
post_capture (handler) |
app/api/v1/captures.py:17 |
handler |
POST /captures (200) — ingest a question capture. (full template above)
|
Capture |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
list_concept_edges (handler) |
app/api/v1/kb_reads.py:54 |
handler |
GET /concept-edges (200) — similarity + manual edge feed (T45, V-E2). |
Knowledge Base |
list_atomic_facts (handler) |
app/api/v1/kb_reads.py:92 |
handler |
GET /atomic-facts (200) — facts by node or PDF source (T46, V-KB1). |
Knowledge Base |
list_notion_pages (handler) |
app/api/v1/kb_reads.py:131 |
handler |
GET /notion/pages (200) — Notion pointer index; never reads Notion back (T47, V-N1/V-N2). |
Knowledge Base |
list_pdf_sources (handler) |
app/api/v1/kb_reads.py:162 |
handler |
GET /pdf-sources (200) — ingested-PDF inbox + per-source fact count (T48). |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
list_courses (handler) |
app/api/v1/outline.py:52 |
handler |
GET /courses (200) — list courses slug-ordered. |
Outline |
create_course (handler) |
app/api/v1/outline.py:65 |
handler |
POST /courses (201) — create a course from slug/name/description. |
Outline |
import_outline (handler) |
app/api/v1/outline.py:87 |
handler |
POST /courses/{course_id}/outline:import (200) — validate-then-materialize (V-O2/V-O3). |
Outline |
read_outline (handler) |
app/api/v1/outline.py:164 |
handler |
GET /courses/{course_id}/outline (200) — materialized node tree. |
Outline |
node_mastery (handler) |
app/api/v1/outline.py:188 |
handler |
GET /outline/nodes/{node_id}/mastery (200) — subtree set-rollup (T44, V-O5). |
Outline |
course_mastery (handler) |
app/api/v1/outline.py:199 |
handler |
GET /outline/courses/{course_id}/mastery (200) — course rollup (T44). |
Outline |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
post_pdf_ingest (handler) |
app/api/v1/pdf.py:33 |
handler |
POST /pdf/ingest (200) — upload + synchronously vision-ingest a lecture PDF. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
post_discriminator (handler) |
app/api/v1/pkm.py:23 |
handler |
POST /pkm/discriminators (200) — persist-only discriminator write (V-M1, V-M3). |
Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_question_by_qid (handler) |
app/api/v1/tutor.py:32 |
handler |
GET /tutor/questions/by-qid/{qid} (200) — full review payload by qid. |
Tutor / MCP |
get_question_by_attempt_id (handler) |
app/api/v1/tutor.py:44 |
handler |
GET /tutor/questions/by-attempt-id/{attempt_id} (200) — review payload by attempt. |
Tutor / MCP |
get_recent_captures (handler) |
app/api/v1/tutor.py:61 |
handler |
GET /tutor/captures/recent (200) — N most-recent captures. |
Tutor / MCP |
get_latest_session_id (handler) |
app/api/v1/tutor.py:70 |
handler |
GET /tutor/sessions/latest (200) — {test_id} of the latest session. |
Tutor / MCP |
get_recent_sessions (handler) |
app/api/v1/tutor.py:78 |
handler |
GET /tutor/sessions/recent (200) — N most-recent sessions. |
Tutor / MCP |
get_session_summary (handler) |
app/api/v1/tutor.py:87 |
handler |
GET /tutor/sessions/{test_id}/summary (200) — per-topic session debrief. |
Tutor / MCP |
get_flagged_attempts (handler) |
app/api/v1/tutor.py:99 |
handler |
GET /tutor/attempts/flagged (200) — flagged-attempt review queue. |
Tutor / MCP |
search_outline_nodes (handler) |
app/api/v1/tutor.py:110 |
handler |
GET /tutor/outline/nodes/search (200) — node name search (T22). |
Tutor / MCP |
get_outline_tree (handler) |
app/api/v1/tutor.py:128 |
handler |
GET /tutor/outline (200) — full outline tree by course slug. |
Tutor / MCP |
get_node_subtree (handler) |
app/api/v1/tutor.py:142 |
handler |
GET /tutor/outline/nodes/{node_id}/subtree (200) — node + descendant subtree. |
Tutor / MCP |
healthcheck (handler) |
app/api/v1/tutor.py:156 |
handler |
GET /tutor/healthz (200) — token-gated DB-touching tutor health check. |
Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
Settings |
app/config.py:16-118 |
util | Pydantic-settings singleton; env + .env + defaults across db/auth/media/openai/anki/notion+pdf/scheduler groups (T40, V-TC1). |
Platform / Admin |
ensure_media_root |
app/config.py:115-118 |
util | Create settings.MEDIA_ROOT if missing and return its absolute path. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
engine / AsyncSessionLocal / Base |
app/database.py:1-11 |
dependency | Module-level async engine, async_sessionmaker (expire_on_commit=False), and DeclarativeBase. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
validate_kb_config |
app/kb_config.py:28-68 |
util | Startup validator for the opt-in KB substrate; WARN-logs missing optionals, never raises (T25, V-KB2). | Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
lifespan |
app/main.py:29-37 |
middleware | ASGI lifespan: validate KB config + start/stop the scheduler. | Platform / Admin |
health_check (handler) |
app/main.py:69 |
handler |
GET /healthz (200) — open root liveness probe, no DB, no auth. |
Platform / Admin |
_capture_validation_handler |
app/main.py:82-98 |
middleware | Global RequestValidationError handler; WARN-logs capture-path 422 detail, returns standard 422. |
Capture |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
run_anki_sync_job |
app/scheduler.py:97-107 |
job | Interval (ANKI_SYNC_INTERVAL_MINUTES); sync_deck for the configured deck (§T4); V4 unreachable = success. |
Platform / Admin |
run_anki_assignment_unlock_job |
app/scheduler.py:180-190 |
job | Interval (ANKI_ASSIGNMENT_UNLOCK_INTERVAL_MINUTES); run_unlock_due (§T63, V51/V55). |
Platform / Admin |
run_anki_assignment_complete_job |
app/scheduler.py:255-265 |
job | Cron (ANKI_ASSIGNMENT_COMPLETE_CRON_*); run_complete_unlocked, DB-only, no Anki (§T64, V51). |
Platform / Admin |
run_anki_review_job |
app/scheduler.py:334-344 |
job | Interval (ANKI_REVIEW_PUSH_INTERVAL_MINUTES); run_review_due (§T76, V53/V55). |
Platform / Admin |
run_pdf_ingest_job |
app/scheduler.py:433-443 |
job | Interval (PDF_INGEST_INTERVAL_MINUTES); poll_inbox (T51, V-KB1, V41); no-op without OPENAI_API_KEY. |
Knowledge Base |
run_notion_sync_job |
app/scheduler.py:537-547 |
job | Interval (NOTION_SYNC_INTERVAL_MINUTES); sync_pending_nodes (T51, V-N1/V-N2, V41); no-op without token/db id. |
Knowledge Base |
run_embed_job |
app/scheduler.py:627-637 |
job | Interval (EMBED_INTERVAL_MINUTES); embed_pending (T50, V-E1, V41); gates tagging; no-op without key. |
Knowledge Base |
run_grounded_tag_job |
app/scheduler.py:725-735 |
job | Interval (GROUNDED_TAG_INTERVAL_MINUTES); tag_pending — THE categorizer (T50, V-L3, V69, V-T2/V-T3, V41). |
Knowledge Base |
start_scheduler |
app/scheduler.py:743-803 |
util | Lifespan boot: register all 8 jobs (7 interval + 1 cron) with replace_existing=True; no-op when disabled. |
Platform / Admin |
stop_scheduler |
app/scheduler.py:806-809 |
util | Lifespan shutdown: scheduler.shutdown(wait=False) if running. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
create_manual_tag |
app/services/admin_tags.py:45-81 |
service | Insert a manual (question_id, node_id, source='manual') tag, NULL confidence (V-T3). |
Platform / Admin |
delete_tag |
app/services/admin_tags.py:84-102 |
service | V-T2 source policy: manual hard-delete, llm soft-override, schema_map forbidden. | Platform / Admin |
QuestionNotFoundError |
app/services/admin_tags.py:25-26 |
service | Raised when create_manual_tag targets a missing question. |
Platform / Admin |
ManualTagValidationError |
app/services/admin_tags.py:29-30 |
service | Raised on an invalid manual-tag payload. | Platform / Admin |
ManualTagConflictError |
app/services/admin_tags.py:33-34 |
service | Raised on uniqueness/check-constraint violation. | Platform / Admin |
TagNotFoundError |
app/services/admin_tags.py:37-38 |
service | Raised when delete_tag targets a missing tag. |
Platform / Admin |
TagDeleteForbiddenError |
app/services/admin_tags.py:41-42 |
service | Raised when a tag's source (e.g. schema_map) is not deletable. | Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
normalize_capture |
app/services/adapters/extension_capture.py:294-368 |
service | Shared source-agnostic normalizer. (full template above) | Capture |
ManualAdapter.ingest |
app/services/adapters/manual.py:24-25 |
service |
source='manual' adapter; delegates to normalize_capture. |
Capture |
UWorldAdapter.ingest |
app/services/adapters/uworld.py:24-25 |
service |
source='uworld' reference adapter; delegates to normalize_capture. |
Capture |
WebQbankAdapter.ingest |
app/services/adapters/web_qbank.py:25-26 |
service |
source='web-qbank' adapter; delegates to normalize_capture. |
Capture |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
wilson_lower |
app/services/analytics.py:38-46 |
service | 95% Wilson score lower bound on success rate (pure; 0.0 with no attempts). | Outline |
compute_node_mastery |
app/services/analytics.py:116-161 |
service | V-O1 subtree set-rollup + per-direct-child breakdown for a node. | Outline |
compute_course_mastery |
app/services/analytics.py:164-207 |
service | Course-total V-O1 set-rollup + per-root-node breakdown. | Outline |
NodeNotFoundError |
app/services/analytics.py:30-31 |
service | Raised when compute_node_mastery node_id is absent. |
Outline |
CourseNotFoundError |
app/services/analytics.py:34-35 |
service | Raised when compute_course_mastery course_id is absent. |
Outline |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
resolve_card_ids |
app/services/anki/assignment.py:272-297 |
service | V52: ordered in-scope suspended card_ids for a cc/topic scope, sliced to max_cards. |
Anki |
create_assignment |
app/services/anki/assignment.py:300-345 |
service | Two-pass create of a pending assignment with a frozen card/note snapshot (V52). | Anki |
mark_skipped |
app/services/anki/assignment.py:365-372 |
service | V51 pending/unlocked → skipped (accounting only, no Anki). | Anki |
mark_completed_manual |
app/services/anki/assignment.py:375-383 |
service | V51 manual pending/unlocked → completed (human override of T64). | Anki |
run_unlock_due |
app/services/anki/assignment.py:421-574 |
job | T63 core: unsuspend snapshot cards, flip to unlocked, chain audit addTags; V55 retry-cap 3. |
Anki |
run_complete_unlocked |
app/services/anki/assignment.py:598-662 |
job | T64 auto-completion: unlocked → completed once every card reviewed after unlock (V51, DB-only). | Anki |
AssignmentUnlockSummary |
app/services/anki/assignment.py:393-399 |
schema | Counts returned by run_unlock_due. |
Anki |
AssignmentCompleteSummary |
app/services/anki/assignment.py:580-584 |
schema | Counts returned by run_complete_unlocked. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
AnkiConnectClient.version |
app/services/anki/client.py:122-124 |
service | Read-only version action (V13 allowlist). |
Anki |
AnkiConnectClient.find_cards |
app/services/anki/client.py:126-130 |
service |
findCards query → native card ids. |
Anki |
AnkiConnectClient.cards_info |
app/services/anki/client.py:132-138 |
service |
cardsInfo scheduler state; [] short-circuit. |
Anki |
AnkiConnectClient.notes_info |
app/services/anki/client.py:140-150 |
service |
notesInfo per-note metadata incl. tags. |
Anki |
AnkiConnectClient.card_reviews |
app/services/anki/client.py:152-164 |
service |
cardReviews revlog rows for incremental sync (V26). |
Anki |
AnkiConnectClient.deck_names |
app/services/anki/client.py:166-173 |
service |
deckNames for the V20 loud-fail log. |
Anki |
AnkiConnectClient.unsuspend_cards |
app/services/anki/client.py:210-224 |
service | V50 write-allowlist: queue −1→0, no SRS mutation; dedup (V64). | Anki |
AnkiConnectClient.add_tags |
app/services/anki/client.py:226-250 |
service | V50 write-allowlist: add audit-trail note tags (note-scoped, V75). | Anki |
AnkiConnectClient.create_filtered_deck |
app/services/anki/client.py:252-274 |
service | V50 write-allowlist: cram deck in ::review::* namespace; dedup (V64). |
Anki |
AnkiConnectClient.aclose |
app/services/anki/client.py:92-95 |
service | Close the lazily-created httpx client. | Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
compute_load_adherence |
app/services/anki/load_adherence.py:194-227 |
service | T66 entry (V54/V60): projected-load metric, headroom %, status label, dense reviewed series. | Anki |
AnkiLoadAdherence |
app/services/anki/load_adherence.py:65-83 |
schema | V54 payload (no recommended_changes per V60); status_label feasible/tight/overload. |
Anki |
ReviewedDay |
app/services/anki/load_adherence.py:57-61 |
schema | One point of the dense reviewed-count series (T43). | Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
list_review_queue |
app/services/anki/queries.py:39-53 |
service | Scheduled cards soonest-first (limit clamped 1..200), tags eager-loaded. | Anki |
get_tag_parse_stats |
app/services/anki/queries.py:56-60 |
service |
{parsed_kind: row_count} over all note tags. |
Anki |
get_tag_card_coverage |
app/services/anki/queries.py:63-71 |
service |
{parsed_kind: distinct_card_count} joined to cards. |
Anki |
get_anki_card_total |
app/services/anki/queries.py:74-76 |
service | Total anki_cards row count. |
Anki |
list_cards_for_qid |
app/services/anki/queries.py:79-87 |
service | Cards whose note carries a matching uworld qid tag. | Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
retention_for_cc |
app/services/anki/retention.py:72-80 |
service | FENCED (T18, V-RB2): empty windowed summary pending node_id subtree port. | Anki |
retention_for_topic |
app/services/anki/retention.py:83-91 |
service | FENCED (T18, V-RB2): empty windowed summary pending port. | Anki |
RetentionSummary |
app/services/anki/retention.py:56-59 |
schema | Windowed retention container (window_days → RetentionWindow). |
Anki |
RetentionWindow |
app/services/anki/retention.py:41-53 |
schema | Per-window pass/fail counts + derived total/retention. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
create_review |
app/services/anki/review.py:78-128 |
service | Two-flush create of a pending review; deck name derived from own PK (V53/T76). | Anki |
run_review_due |
app/services/anki/review.py:140-285 |
job | T76 core: create filtered deck, flip to pushed, chain audit addTags; V55 retry-cap 3. |
Anki |
ReviewBatchSummary |
app/services/anki/review.py:131-137 |
schema | Counts returned by run_review_due. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
retention_by_card |
app/services/anki/review_metrics.py:41-60 |
service | Lifetime true-retention per card in one grouped query (no N+1). | Anki |
retrievability |
app/services/anki/review_metrics.py:63-78 |
util | Forgetting-curve R = 0.9 ** (elapsed/interval), clamped [0,1]; pure. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
state_for_cc |
app/services/anki/state.py:66-69 |
service | FENCED (T18, V-RB2): empty subtree StateCounts pending node_id port. | Anki |
state_for_topic |
app/services/anki/state.py:72-75 |
service | FENCED (T18, V-RB2): empty subtree StateCounts pending port. | Anki |
StateCounts |
app/services/anki/state.py:37-50 |
schema | Per-scope state-count container + derived unlock_pct. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
sync_deck |
app/services/anki/sync.py:77-208 |
service | §T3 deck sync core. (full template above) | Anki |
SyncSummary |
app/services/anki/sync.py:55-60 |
schema | Result returned by sync_deck and exposed to the /anki/sync surface. |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
parse_tag |
app/services/anki/tag_parser.py:64-118 |
util | Classify one AnKing tag → uworld_qid/aamc_cc/aamc_skill/unparsed; resolves CC → node_id (V3/B3, V-T1). | Anki |
ParsedTag |
app/services/anki/tag_parser.py:52-57 |
schema | Immutable classification result; node_id is the only persisted target (V-T1). |
Anki |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
create_note |
app/services/attempt_notes.py:18-37 |
service | Create an AttemptNote (stripped text); raise if attempt absent. |
Tutor / MCP |
list_notes |
app/services/attempt_notes.py:40-52 |
service | All notes for an attempt, newest-first. | Tutor / MCP |
delete_note |
app/services/attempt_notes.py:55-59 |
service | Hard-delete a note by id; raise if absent. | Tutor / MCP |
AttemptNotFoundError |
app/services/attempt_notes.py:10-11 |
service | Raised when create_note attempt_id is absent. |
Tutor / MCP |
NoteNotFoundError |
app/services/attempt_notes.py:14-15 |
service | Raised when delete_note note_id is absent. |
Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
ingest_capture |
app/services/ingest.py:17-19 |
service | §A plugin-seam dispatcher. (full template above) | Capture |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
expected_dim |
app/services/kb/embeddings.py:56-61 |
service | Canonical embedding dimension for a model (or None). | Knowledge Base |
current_version |
app/services/kb/embeddings.py:64-77 |
service | Build the V-E1 embedding version stamp (<model>-v2). |
Knowledge Base |
embed_and_persist |
app/services/kb/embeddings.py:80-136 |
service | Idempotent embed + persist content_embeddings; V-E1 dim guard. |
Knowledge Base |
DimMismatchError |
app/services/kb/embeddings.py:42-46 |
service | V-E1 error on embedding-length disagreement before persistence. | Knowledge Base |
EmbedResult |
app/services/kb/embeddings.py:49-53 |
schema | Persisted/reused row + reuse flag + token usage. | Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
poll_inbox |
app/services/kb/inbox.py:54-119 |
service | Walk inbox/<slug>/*.pdf, run each through ingest_pdf, isolate per-file failures (V41). |
Knowledge Base |
InboxReport |
app/services/kb/inbox.py:34-45 |
schema | Per-run tally with partial_failure for scheduler partial-success (V41). |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
embed_pending |
app/services/kb/jobs.py:154-217 |
service |
run_embed core: embed un-embedded nodes/facts/questions for the version. |
Knowledge Base |
tag_pending |
app/services/kb/jobs.py:271-386 |
service | THE categorizer core. (full template above) | Knowledge Base |
_tag_one |
app/services/kb/jobs.py:225-268 |
service | Inner chain step: load embedding → recall → grounded → persist for one entity. | Knowledge Base |
EmbedReport |
app/services/kb/jobs.py:49-58 |
schema | Tally of embed_pending with partial_failure. |
Knowledge Base |
TagReport |
app/services/kb/jobs.py:61-76 |
schema | Tally of tag_pending incl. token accounting + manual-review flags. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
sync_node_to_notion |
app/services/kb/notion.py:144-200 |
service | Upsert a one-way Notion page per node + append fact blocks (V-N1/V-N2/V-M3). | Knowledge Base |
mirror_discriminator_to_notion |
app/services/kb/notion.py:231-324 |
service | Append a discriminator factor as one backlink block; idempotent (V-M3). | Knowledge Base |
sync_pending_nodes |
app/services/kb/notion.py:339-405 |
service |
run_notion_sync core: mirror every node owning tagged facts; V41 isolation. |
Knowledge Base |
NotionMirrorError |
app/services/kb/notion.py:40-43 |
service | Raised when a discriminator factor cannot be mirrored. | Knowledge Base |
SyncReport |
app/services/kb/notion.py:46-51 |
schema | Result of sync_node_to_notion. |
Knowledge Base |
DiscriminatorSyncReport |
app/services/kb/notion.py:54-60 |
schema | Result of mirror_discriminator_to_notion (skipped=True on V-M3 no-op). |
Knowledge Base |
PendingSyncReport |
app/services/kb/notion.py:327-336 |
schema | Tally of sync_pending_nodes with partial_failure. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
render_pages |
app/services/kb/pdf_ingest.py:118-132 |
service | Default injectable renderer; rasterize each PDF page to PNG via PyMuPDF. | Knowledge Base |
transcribe_page |
app/services/kb/pdf_ingest.py:168-210 |
service | One OpenAI vision call → page text (V-KB3); optional Flex tier. | Knowledge Base |
extract_atomic_facts |
app/services/kb/pdf_ingest.py:263-306 |
service | One strict json_schema call → atomic claims (V-KB4); blank input short-circuits. | Knowledge Base |
ingest_pdf |
app/services/kb/pdf_ingest.py:326-470 |
service | Orchestrate render → vision → extract → persist; SHA-256 idempotent (V-KB1). | Knowledge Base |
RenderedPage |
app/services/kb/pdf_ingest.py:73-76 |
schema | One rasterized page (page number + PNG bytes). | Knowledge Base |
PageTranscription |
app/services/kb/pdf_ingest.py:79-84 |
schema |
transcribe_page result + token accounting. |
Knowledge Base |
FactExtraction |
app/services/kb/pdf_ingest.py:87-92 |
schema |
extract_atomic_facts result + token accounting. |
Knowledge Base |
IngestReport |
app/services/kb/pdf_ingest.py:95-106 |
schema |
ingest_pdf summary (facts/pages/reuse + V-L1 tokens). |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
persist_grounded_tags |
app/services/kb/persist_tags.py:213-258 |
service | Persist calibrated decisions to question/fact tags (V-T2 delete-then-insert, V-T3). | Knowledge Base |
_persist_question |
app/services/kb/persist_tags.py:107-151 |
service | Replace source='llm' question tags with fresh quantized rows (V-T2/V-T3). |
Knowledge Base |
_persist_atomic_fact |
app/services/kb/persist_tags.py:154-210 |
service | Replace source='llm' fact tags + set denormalized atomic_facts.node_id. |
Knowledge Base |
PersistResult |
app/services/kb/persist_tags.py:46-54 |
schema | Rows inserted/replaced + manual-review flag + chosen primary node. | Knowledge Base |
EntityNotFoundError |
app/services/kb/persist_tags.py:42-43 |
service | Raised when the target question/fact id is absent during persistence. | Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
load_embedding |
app/services/kb/recall.py:122-147 |
service | Fetch the version-stamped embedding for an entity (None when absent). | Knowledge Base |
retrieve_candidates |
app/services/kb/recall.py:448-570 |
service | V-L3 constrained candidate set. (full template above) | Knowledge Base |
format_candidates_for_prompt |
app/services/kb/recall.py:578-611 |
service | Render a RecallResult as the byte-stable numbered candidate surface (V-L3). |
Knowledge Base |
_embedding_candidates |
app/services/kb/recall.py:186-242 |
service | C2T path: cosine-rank node embeddings, floored at min_score, capped top_k. |
Knowledge Base |
_content_candidates |
app/services/kb/recall.py:336-445 |
service | C2C2T path: borrow tags from similar tagged facts, gold/silver-weighted. | Knowledge Base |
_expand_via_edges |
app/services/kb/recall.py:245-287 |
service | T2T path: follow concept_edges.kind='similarity' neighbors (manual excluded, V-E2). |
Knowledge Base |
_exemplars_for_node |
app/services/kb/recall.py:290-333 |
service | SRKI: prior calibrated question-tag exemplars for a node. | Knowledge Base |
Candidate |
app/services/kb/recall.py:87-108 |
schema | One retrieved node candidate with recall provenance + exemplars. | Knowledge Base |
Exemplar |
app/services/kb/recall.py:64-85 |
schema | One few-shot example attached to a candidate node. | Knowledge Base |
RecallResult |
app/services/kb/recall.py:111-114 |
schema | Ordered candidate list + embedding version. | Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
cosine |
app/services/kb/similarity.py:35-54 |
util | Pure cosine similarity; 0.0 for zero-norm, raises on dim mismatch. | Knowledge Base |
derive_similarity_edges |
app/services/kb/similarity.py:57-125 |
service | Pairwise-cosine → persist kind='similarity' edges above threshold; never touches manual (V-E2). |
Knowledge Base |
DeriveReport |
app/services/kb/similarity.py:28-32 |
schema | Tally of derive_similarity_edges. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
LlmCacheBase |
app/services/llm/cache.py:26-143 |
service | Shared SQLite-cache base (lifecycle, schema, stats, context-manager). | Knowledge Base |
LlmCacheBase.lookup_cost |
app/services/llm/cache.py:95-101 |
service | Stored original LLM cost for a key (0.0 if missing). | Knowledge Base |
LlmCacheBase.stats |
app/services/llm/cache.py:103-126 |
service | Aggregate entry counts by version/model + total cost saved. | Knowledge Base |
LlmCacheBase.clear |
app/services/llm/cache.py:128-137 |
service | Delete all rows, or only one extractor_version's; returns rows removed. | Knowledge Base |
LlmCacheBase.get |
app/services/llm/cache.py:139-140 |
service | Abstract per-cache content-hash lookup (subclass implements). | Knowledge Base |
LlmCacheBase.put |
app/services/llm/cache.py:142-143 |
service | Abstract per-cache content-hash write (subclass implements). | Knowledge Base |
LlmCacheBase.close |
app/services/llm/cache.py:83-87 |
service | Close the SQLite connection (backs context-manager exit). | Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
grade_yes_no |
app/services/llm/calibrator.py:90-153 |
service | V69 core: Yes/No grade with logprobs → [0,1] confidence (reasoning off). | Knowledge Base |
calibrate_tag |
app/services/llm/calibrator.py:171-197 |
service | Calibrate one (question, candidate_tag) pair via the discriminator framing. | Knowledge Base |
CalibrationResult |
app/services/llm/calibrator.py:45-53 |
schema | V69 grade result: confidence, manual_review (<0.5), Yes/No logprobs. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
build_openai_client |
app/services/llm/client.py:15-27 |
service | Central AsyncOpenAI factory; V41 retry budget (≥5), optional base URL; the V16 patch seam. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
generate_grounded_tags |
app/services/llm/grounded.py:241-373 |
service | Grounded-pick step: pick tags ONLY from recall candidates (V-L3/V44/V45), then V69 re-score. | Knowledge Base |
build_pick_schema |
app/services/llm/grounded.py:105-150 |
service | V44/V45 strict json_schema; node_index enum [1..N] enforces V-L3 at the grammar level. |
Knowledge Base |
build_system_prompt |
app/services/llm/grounded.py:153-166 |
service | Compose preamble + numbered candidate list (positions = returned indices). | Knowledge Base |
GroundedTag |
app/services/llm/grounded.py:56-72 |
schema | One calibrated tag decision; calibrated_confidence is the sole trusted confidence (V69). |
Knowledge Base |
GroundedResult |
app/services/llm/grounded.py:75-84 |
schema | Output of generate_grounded_tags: decisions + models + tokens + warnings. |
Knowledge Base |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
media_path |
app/services/media_store.py:28-31 |
service | Canonical absolute path MEDIA_ROOT/{hash[:2]}/{hash}{ext}. |
Platform / Admin |
relative_media_path |
app/services/media_store.py:34-37 |
service | Relative path stored in media.local_path. |
Platform / Admin |
write_media |
app/services/media_store.py:68-78 |
service | Decode base64 + atomically write media bytes; idempotent. | Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
validate_outline_schema |
app/services/outline/importer.py:220-229 |
service | Pure whole-upload-or-reject validator → ValidatedOutline or all-problems error (V-O2). |
Outline |
materialize_outline |
app/services/outline/importer.py:232-295 |
service | Upsert course, wipe nodes, insert validated tree depth-ordered (V-O3). | Outline |
OutlineImportError |
app/services/outline/importer.py:40-41 |
service | Raised when materialization fails after validation. | Outline |
OutlineSchemaValidationError |
app/services/outline/importer.py:44-50 |
service | Validation-failure with .errors carrying every problem for one 422. |
Outline |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
normalize_typographic_punctuation |
app/services/outline/lookup.py:33-41 |
service | Map curly quotes/apostrophes → ASCII for exact-path lookups. | Outline |
OutlineLookup.load |
app/services/outline/lookup.py:75-105 |
service | Build a path-indexed lookup for one course; raise if unseeded. | Outline |
OutlineLookup.__init__ |
app/services/outline/lookup.py:60-73 |
service | Construct by-id / roots-by-name / children-by-name indices. | Outline |
OutlineLookup.course_id |
app/services/outline/lookup.py:107-109 |
service | Property: the course_id this lookup was built for. | Outline |
OutlineLookup.node_id_by_path |
app/services/outline/lookup.py:111-152 |
service | Resolve a >>-path to node_id; None on missing/ambiguous (V-O4). |
Outline |
OutlineLookup.node |
app/services/outline/lookup.py:154-155 |
service | In-memory _Node for a node_id, or None. |
Outline |
OutlineLookup.path_of |
app/services/outline/lookup.py:157-166 |
service | Inverse of node_id_by_path: render the >>-path for a node_id. |
Outline |
OutlineNotSeededError |
app/services/outline/lookup.py:44-45 |
service | Raised when load runs against a missing/unseeded course. |
Outline |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
subtree_node_ids |
app/services/outline_subtree.py:31-34 |
service |
{root_id} + descendants via recursive CTE (V-O1 subtree set). |
Outline |
subtree_node_ids_many |
app/services/outline_subtree.py:37-44 |
service | Union of subtrees over many roots; empty list → empty set. | Outline |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
probe_anki |
app/services/system_status.py:48-55 |
service | Reachability probe via AnkiConnect version(); never raises. |
Platform / Admin |
probe_openai |
app/services/system_status.py:58-67 |
service | Reachability probe via models.retrieve; configured=False when key unset. |
Platform / Admin |
probe_notion |
app/services/system_status.py:70-79 |
service | Token-validity probe via users.me() (bot identity only, V-N1). |
Platform / Admin |
job_last_runs |
app/services/system_status.py:82-96 |
service | Latest TaskRun per job via Postgres DISTINCT ON. |
Platform / Admin |
build_jobs_health |
app/services/system_status.py:111-126 |
service | Merge scheduler snapshot with last-run rollup, snapshot order (pure). | Platform / Admin |
collect_system_status |
app/services/system_status.py:129-151 |
service | Single testable entry assembling the full /admin/status payload (V16 injectable). |
Platform / Admin |
ServiceHealth |
app/services/system_status.py:35-46 |
service | Dataclass: one dependency's configured/reachable/detail. |
Platform / Admin |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_recent_captures |
app/services/tutor/captures.py:13-94 |
service | N most-recent attempts + question + resolved node topics + notes (clamp 1..50). | Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
write_discriminator_factor |
app/services/tutor/discriminators.py:48-118 |
service | Append-only, deduped on (question_id, factor_text) (V-M3); persist-only (V-M1). |
Tutor / MCP |
QuestionNotFoundError |
app/services/tutor/discriminators.py:27-28 |
service | Raised when the target question_id is absent. | Tutor / MCP |
NodeNotFoundError |
app/services/tutor/discriminators.py:31-32 |
service | Raised when a provided node_id is absent. | Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_flagged_attempts |
app/services/tutor/flags.py:20-65 |
service | Recent flag-for-review attempts + qid/preview/note/dashboard URL (clamp 1..100). | Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
healthcheck |
app/services/tutor/health.py:14-58 |
service | DB reachability + attempt counts + recommender_ready; never raises (degrades). |
Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
resolve_node_labels |
app/services/tutor/outline.py:84-120 |
service | node_ids → {name, path, kind} walking parents (V-O5, V-O3 cross-course). |
Tutor / MCP |
search_nodes |
app/services/tutor/outline.py:123-197 |
service | Domain-blind substring node search, optional course filter (V-M1). | Tutor / MCP |
get_outline_tree |
app/services/tutor/outline.py:200-239 |
service | Course header + flat node list ordered (depth, position, id). | Tutor / MCP |
get_subtree |
app/services/tutor/outline.py:242-281 |
service | Root node + descendant ids via recursive CTE (V-O1); no metric rollup (V-M1). | Tutor / MCP |
CourseNotFoundError |
app/services/tutor/outline.py:38-39 |
service | Raised when a course_slug filter matches no course. | Tutor / MCP |
NodeNotFoundError |
app/services/tutor/outline.py:42-43 |
service | Raised by get_subtree when node_id is absent. |
Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_question |
app/services/tutor/questions.py:92-96 |
service | Full tutor review payload by external qid. | Tutor / MCP |
get_question_by_attempt_id |
app/services/tutor/questions.py:99-106 |
service | Same payload resolved from an attempt_id. | Tutor / MCP |
_build_question_payload |
app/services/tutor/questions.py:20-89 |
service | Private helper assembling the review shape (tags/features/history/distribution). | Tutor / MCP |
QuestionNotFoundError |
app/services/tutor/questions.py:12-13 |
service | Raised when the question cannot be found. | Tutor / MCP |
AttemptNotFoundError |
app/services/tutor/questions.py:16-17 |
service | Raised when the attempt_id does not exist. | Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
get_latest_session_id |
app/services/tutor/sessions.py:17-26 |
service |
uworld_test_id of the most recently attempted session (or None). |
Tutor / MCP |
get_recent_sessions |
app/services/tutor/sessions.py:29-65 |
service | N most-recent sessions grouped by test id with accuracy + timestamps (clamp 1..50). | Tutor / MCP |
get_session_summary |
app/services/tutor/sessions.py:68-174 |
service | Full debrief: totals, per-node by_topic, top topics, flags, notes (V-O1). |
Tutor / MCP |
SessionNotFoundError |
app/services/tutor/sessions.py:13-14 |
service | Raised when no attempt exists for the test_id. | Tutor / MCP |
| Function | file:line |
Layer | Description | Feature |
|---|---|---|---|---|
serve (handler) |
app/web/media.py:12-27 |
handler |
GET /media/{file_path} — serve media from MEDIA_ROOT with a path-traversal guard. |
Knowledge Base |
Every function in the catalog and every path-operation handler appears exactly
once above. Handlers are listed under their owning module; their behavioral
contracts (status codes, body models, error mapping) live in
API Reference. Job cores (run_*) and their scheduler wrappers
are both indexed — the wrapper in app/scheduler.py (layer job) and the
service core under app/services/** (layer service/job).
Reference
Features
Design Notes