Refine AI rubric assessments using cohort-aware normalization with 0.5-point increments
Refine rubric assessments produced by /assess:ai-pass by adjusting scores and
justifications based on cohort performance. All adjustments use 0.5-point
increments for clarity and rubric integrity.
Normalize totals toward a median target using a nonnegative-only, additive-capped
uplift K applied in 0.5 steps per criterion, respecting per-criterion maximums
and preserving Canvas rubric validity. Optionally rewrite criterion feedback
(15-20 words in the feedback language from metadata.feedback_language), then
mark as reviewed and approved.
If course_id and assignment_id provided:
.claude/assessments/{{course_id}}_{{assignment_id}}_*.json in the current repositoryIf no arguments provided:
.claude/state/assessment_state.json for last used fileassessments_*.json and use most recent/assess:setup firstLoad the assessment file using the Read tool.
reviewed-only (AI evaluated, not yet submitted)reapply=true)Report counts: eligible, skipped (and reasons).
For the scoped set, compute:
Display a concise summary for instructor context.
Defaults unless overridden by flags:
policy: nonnegative-only (no decreases)algorithm: additive-cappedcap_per_criterion: +1.0 (i.e., at most two 0.5 steps)cap_total: nonefeedback_mode: rewriteapprove: truedry_run: trueTarget selection:
--target (required unless policy=advisory-only)0.5-step enforcement (global):
step_size = 0.5half(x) = floor(x*2)/2Only for algorithm=additive-capped:
{0.0, 0.5, 1.0, ..., cap_per_criterion}headroom_c = Mc - Pcraw_uplift_c = min(K, cap_per_criterion, headroom_c)uplift_c = half(raw_uplift_c)new_points_c = min(Mc, half(Pc + uplift_c))cap_total if provided by clamping the sum of upliftsDisplay dry-run preview:
Refinement Preview (DRY RUN)
===========================
Policy: nonnegative-only
Algorithm: additive-capped
Step size: 0.5
Target median: {target} (feasible max: {feasible})
Chosen K: {K}
Totals (median): {old_med} -> {new_med}
Criterion averages:
- {crit1}: {old1} -> {new1}
- {crit2}: {old2} -> {new2}
- ...
(list all criteria from rubric)
Adjusted: X students
No change: Y students
Skipped: Z students (reasons)
For each adjusted criterion:
new_points_c exactly equals a rating value, use that rating_idnew_points_c; store pointsGlobal validations:
refinement_meta exists and reapply=false, abort with guidanceRead metadata.language_learning, metadata.feedback_language, metadata.level,
and metadata.formality from the assessment JSON.
If feedback_mode=rewrite or append, launch the appropriate agent via Task
per adjusted student to produce criterion-level comments with these constraints:
spanish-pedagogy-expert if language_learning=true, pedagogy-expert if falsePrompt template (per criterion) -- Spanish (es):
Reescribe un comentario breve para la rubrica (15-20 palabras, espanol),
centrado SOLO en este criterio. Manten precision, especificidad y
recomendacion accionable. No menciones ajustes de calificacion ni curva.
Tono: {formality} (casual = tu, formal = usted). Nivel: {level}.
Criterio: {criterion_name}
Puntaje final: {new_points_c}/{max_c}
Descripcion breve del desempeno y rasgos observables: {signals_from_submission}
Prompt template (per criterion) -- English (en):
Rewrite a brief rubric comment (15-20 words, English) focused ONLY on this
criterion. Be precise, specific, and actionable. Do not mention grade
adjustments or curves.
Tone: {formality} (casual = academic casual, formal = standard academic). Level: {level}.
Criterion: {criterion_name}
Final score: {new_points_c}/{max_c}
Brief description of performance and observable traits: {signals_from_submission}
Append mode:
feedback_mode=append, keep the original comment and add a final clause
of 8-12 words consistent with the new score (without mentioning calibration).
Validate that the total per criterion stays within 15-20 words if required;
otherwise just add a concise clause.Validation:
append or none and flag for reviewdry_run=true, exit after previewdry_run=false:
For each adjusted student, use the dauber CLI via Bash:
uv run dauber assess update {assessment_file_path} {user_id} \
--rubric-json '{"criterion_id": {"points": X, "justification": "..."}, ...}' \
--comment "Overall comment text" \
--reviewed \
--approved \
--format json
Notes:
--approved only if approve=true (default)--rubric-json value to prevent shell expansionWrite refinement_meta block to the assessment file via Read + modify + Write:
refinement_meta key with: policy, algorithm, step_size=0.5,
target, K, caps, feasibility, timestamp, version, and per-student/criterion
change summaryDisplay final summary:
Refinement Complete
===================
Adjusted: X
No change: Y
Skipped: Z (reasons)
Totals (median): {old_med} -> {new_med}
Means: {old_mean} -> {new_mean}
Criterion averages:
- {crit1}: {old1} -> {new1}
- {crit2}: {old2} -> {new2}
- ...
(list all criteria from rubric)
Assessment File Updated:
{assessment_file_path}
Status:
All adjusted assessments marked as "reviewed"
{approved_line}
Update .claude/state/assessment_state.json with refinement details to enable
auto-discovery in subsequent commands.
--target or abort with guidanceappend or none and flag--reapply: abort with guidance/assess:setup and /assess:ai-passstep_size=0.5)dry_run repeatedly; use --reapply for subsequent writes/assess:submit to post approved grades to Canvas