Skip to content

Score the mock exam per domain: the CCA-F blueprint weighting is applied at seed time and invisible at grade time #63

Description

@jeremylongshore

What

Question has no domain field, so a learner who finishes the 60-question CCA-F mock gets a single number and no idea which domain sank them.

The curriculum knows the domain for every one of its 696 questions. The seeder uses that knowledge to build a blueprint-weighted exam. Then the information is thrown away, and grading can't use it.

Why this matters more than it looks

scripts/seed_cca_courses.py hardcodes the official CCA-F split and assembles the mock to match it:

Domain Weight
agentic — Agentic Architecture 27%
claudecode — Claude Code Workflows 20%
prompt — Prompt Engineering 20%
tools — Tool Design & MCP 18%
context — Context Management 15%

_weighted_exam_series() even partitions Rick Hightower's 480-question pool into three non-overlapping full-length weighted exams. Real work went into making these exam-shaped.

But grading is flat:

# now_lms/vistas/evaluations.py:150
def calculate_score(attempt) -> float:
    total_questions = len(attempt.evaluation.questions)
    correct_answers = sum(_answer_is_correct(answer) for answer in attempt.answers)
    return (correct_answers / total_questions) * 100

And evaluation_result.html renders exactly: score, pass/fail, and per-question correct/incorrect with the explanation. There is no domain anywhere in it.

So the weighting is applied at build time and invisible at grade time. A learner scoring 68% — four points under the 72% pass mark — has no way to know whether to reread Agentic Architecture or Context Management. That diagnostic breakdown is most of what makes prep material worth sitting.

Where it's lost

The seeder maps banks onto the LMS like this:

Bank field Lands as
text Question.text
rationale + source Question.explanation
options[] + answerIndex QuestionOption rows, is_correct
domain, domainKey, domainName nothing — dropped
id (bank id) nothing — dropped
$ grep -c domain now_lms/db/__init__.py
0

Domains do survive as course structure — one CursoSeccion per domain, each with its lesson and practice quiz. That part works well. What's missing is per-question metadata, which is what a mixed-domain exam needs.

Suggested shape

  1. Question.domain_key — nullable String(64), plus domain_name String(200) (or resolve names from a lookup; nullable either way so upstream's own questions are unaffected). Guarded Alembic revision, same pattern as your 20260731_120500 prior-credentials migration.
  2. Seeder writes it — the values are already in hand at _add_evaluation(); it's a field assignment, not new logic.
  3. Grade per domain — group attempt.answers by question.domain_key, emit {domain_name: (correct, total, pct)} alongside the flat score. The flat score stays authoritative for pass/fail so nothing about certification changes.
  4. Show it — a breakdown table on evaluation_result.html, ideally with the blueprint weight beside each domain so a learner sees that Agentic Architecture is 27% of the real exam.

Backfill is optional. Existing seeded questions would have domain_key = NULL and the breakdown just doesn't render for them; a reseed picks it up. Please don't block on backfilling.

Also worth considering (your call, not a requirement)

Storing the bank id too would make a question traceable to its source file — useful when a bank gets corrected and we need to find every course that shipped the old wording.

Acceptance

  • A learner finishing the CCA-F mock sees a per-domain breakdown, not just a number.
  • Flat score and pass/fail are unchanged — passing_score=72.0 still decides certification.
  • Questions without a domain (upstream's own) render exactly as they do today.
  • Migration is guarded and reversible.

Context

  • Seeder: scripts/seed_cca_courses.py (632 lines, idempotent on Curso.codigo) — reads the private intent-solutions-io/intent-curriculum via CCA_CONTENT_DIR.
  • Curriculum: 696 questions across 5 banks, 0 ID collisions, every question carrying all 9 fields including domain/domainKey/domainName. The data is clean and ready — only the destination schema is missing.

Related: #37 (ADR-6 short-answer question type) also extends the question model, so it's worth checking whether the two migrations should be sequenced.

  • Jeremy Longshore
    intentsolutions.io

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions