feat(marking): add deterministic exam feedback composer#51
Open
hyperpolymath wants to merge 2 commits into
Open
feat(marking): add deterministic exam feedback composer#51hyperpolymath wants to merge 2 commits into
hyperpolymath wants to merge 2 commits into
Conversation
Rescues the in-progress exam feedback composer from the bot-crash
recovery branch (29d5a0d, recovery/tma-mark2-after-bot-crash).
The composer is pure-stdlib Elixir, runs standalone of the unbuilt
v1 data plane (no CubDB, FHI parser or .docx generator needed), and
provides an emergency tool for short-form exam feedback boxes today.
Inputs
- 3 components (A reflective / B planning / C essay) with ordinal
rating (strong | sound | adequate | weak | serious_issue | missing)
- "good" and "improve" indicator checkboxes per component
- 7 convention/completion toggles
- 6 killer-issue overrides
- overall mark percent (band selection only)
Outputs
- did_well prose, >=120 words, band-aware
- improve prose, >=120 words, band-aware
- band metadata and warning flag for manual-override cases
Route: /exam-feedback-composer (LiveView)
Tests: 5 cases. Verified locally via standalone elixirc + ExUnit; all
pass.
Excluded from this PR (follow-ups):
- per-question subset-mark grid + Logic.Calculator averaging
- MarkingLive cockpit link to the composer
- bouncer / audit / calendar / rubric drive-by edits from the
crash-recovery WIP unrelated to the composer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 65 issues detected
View findings[
{
"reason": "Issue in boj-build.yml",
"type": "missing_timeout_minutes",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in container-policy.yml",
"type": "missing_timeout_minutes",
"file": "container-policy.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
Adds optional per-question marks alongside the existing single
"overall mark %" input. Marks support arithmetic expressions
(matching the legacy Java behaviour) via Logic.Calculator.evaluate/1.
Mark resolution in compose/1
- Manual "Overall mark % (override)" wins if parseable
- Else use the aggregated grid percentage (sum of marks / sum of maxes)
- Else band is :unknown
Composer module
- aggregate_questions/1 public API: %{total, max, percentage} | nil
- Rows are dropped if mark is blank/unparseable or max is non-positive,
so partial entries do not poison the total
- effective_mark_for/1 (private) computes the band-driving percentage
LiveView
- New "Per-question marks (optional)" section above the components
- Live total/max/percentage badge updates on phx-change
- "Overall mark % (override)" field relabelled with "auto from grid"
placeholder
- Robust handling of Phoenix form params shape (questions arrive as
a string-indexed map, normalised back to an ordered list)
Tests (7 new cases, 12 total, all passing)
- aggregate_questions: sum/percentage; expressions; partial-row drop;
nil on empty input
- compose with grid: grid-only path -> band; override wins; empty
grid + empty override -> :unknown
Verified locally via standalone elixirc + ExUnit (12/0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🔍 Hypatia Security ScanFindings: 65 issues detected
View findings[
{
"reason": "Issue in boj-build.yml",
"type": "missing_timeout_minutes",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in container-policy.yml",
"type": "missing_timeout_minutes",
"file": "container-policy.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in dogfood-gate.yml",
"type": "missing_timeout_minutes",
"file": "dogfood-gate.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
29d5a0donrecovery/tma-mark2-after-bot-crash) into a clean, reviewable branch offmain..docxgenerator needed. Usable for marking today at/exam-feedback-composer.10+5) → composer averages → derives band → emits deterministic, band-aware "did well" and "where to improve" prose for the two short-form feedback boxes, with copy buttons.What's in this PR
Commit 1 — feat(marking): add deterministic exam feedback composer
lib/etma_handler/marking/exam_feedback_composer.ex(655 LOC) — pure functional composerlib/etma_handler_web/live/exam_feedback_composer_live.ex(329 LOC) — LiveView UItest/etma_handler/marking/exam_feedback_composer_test.exs(106 LOC, 5 cases)assets/js/app.js—CopyTextLiveView hook (additive only)lib/etma_handler_web/router.ex— one-line additive route registration + missing copyright headerCommit 2 — feat(marking): wire per-question subset-mark grid + Calculator averaging
aggregate_questions/1— sums per-question marks/maxes and computes a percentage. Each cell accepts arithmetic expressions (10+5) viaLogic.Calculator.evaluate/1. Rows with blank/unparseable marks or non-positive max are dropped so partial entries don't poison the total.compose/1mark resolution: manualOverall mark % (override)wins → else aggregated grid percentage → else:unknownband.auto from gridplaceholder.Provenance
The full WIP set (14 files, 1369 insertions) lives on
recovery/tma-mark2-after-bot-crashfrom commit29d5a0d"WIP: preserve bot crash state for tma-mark2". This PR cherry-picks only the composer-related changes onto a clean branch offmain, with router edits limited to a single additive line (noplug :foo→plug(:foo)reformat noise).What this PR still excludes (follow-ups for separate PRs)
MarkingLiveto/exam-feedback-composerbouncer.ex/audit.ex/calendar.ex/rubric.exdrive-by edits that were in the crash-recovery WIP but unrelated to the composerexam_feedback_composer_live.ex~line 208)Test plan
elixirc(no Phoenix deps required)ExUnitrun/exam-feedback-composeragainst a real exam script and confirms the generated prose is editable/usableCopyTexthook fires (clipboard write + visual feedback) in a real browser🤖 Generated with Claude Code