Skip to content

Commit d90f4e7

Browse files
committed
Print proof steps in validation logs in frontend format
- Add validate/format-proof-steps.ts (statementToString + reason names) - run-validate.mjs uses formatProofStep for proof task output Made-with: Cursor
1 parent 65d572d commit d90f4e7

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

run-validate.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const DEFAULT_LOG_FILE = resolve(process.cwd(), 'logs', 'user_actions.log');
2929

3030
const replay = require('./validate/replay.js');
3131
const tasks = require('./validate/tasks.js');
32+
const { formatProofStep } = require('./validate/format-proof-steps.js');
3233

3334
const PROOF_TASKS = new Set(['prove-opposite-rays', 'prove-perpendicular-bisector']);
3435

@@ -96,6 +97,14 @@ async function main() {
9697
}
9798
}
9899

100+
if (proofSteps?.length) {
101+
console.log('Proof steps:');
102+
proofSteps.forEach((step, i) => {
103+
console.log(' ' + formatProofStep(step, i));
104+
});
105+
console.log('');
106+
}
107+
99108
const result = tasks.runTask(taskId, snapshot, { proofSteps, taskStatement });
100109

101110
console.log(result.message);

validate/format-proof-steps.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Format proof steps for validation logs in the same way the student sees them in the frontend.
3+
* Mirrors client/proof/statements.ts statementToString and client/proof/reasons.ts REASONS names.
4+
*/
5+
6+
import type { ProofStepLogEntry } from './types.js';
7+
8+
/** Human-readable string for a statement (same as client/proof/statements statementToString). */
9+
function statementToString(s: { kind: string; pointNames: string[]; constant?: number }): string {
10+
const n = s.pointNames;
11+
switch (s.kind) {
12+
case 'points-distinct':
13+
return `${n[0]}${n[1]}`;
14+
case 'point-on-line':
15+
return `${n[0]} ∈ line ${n[1]}${n[2]}`;
16+
case 'point-on-segment':
17+
return `${n[0]}${n[1]}${n[2]}`;
18+
case 'point-on-ray':
19+
return `${n[0]} ∈ ray ${n[1]}${n[2]}`;
20+
case 'line-equals-line':
21+
return `line ${n[0]}${n[1]} = line ${n[2]}${n[3]}`;
22+
case 'rays-form-line':
23+
return `ray ${n[0]}${n[1]} ∪ ray ${n[2]}${n[1]} = line ${n[0]}${n[1]}`;
24+
case 'unique-line':
25+
return `∃₁ line through ${n[0]}, ${n[1]}`;
26+
case 'intersection-equals':
27+
return `${n[0]} = line ${n[1]}${n[2]} ∩ line ${n[3]}${n[4]}`;
28+
case 'intersection-segment-equals':
29+
return `${n[1]}${n[0]}${n[2]}${n[3]} = ${n[0]}`;
30+
case 'angle-equals-constant':
31+
return `∠${n[0]}${n[1]}${n[2]} = ${(s as { constant?: number }).constant ?? '·'}°`;
32+
case 'angle-equals-angle':
33+
return `∠${n[0]}${n[1]}${n[2]} = ∠${n[3]}${n[4]}${n[5]}`;
34+
case 'angle-less-than-angle':
35+
return `∠${n[0]}${n[1]}${n[2]} < ∠${n[3]}${n[4]}${n[5]}`;
36+
case 'angle-greater-than-angle':
37+
return `∠${n[0]}${n[1]}${n[2]} > ∠${n[3]}${n[4]}${n[5]}`;
38+
case 'angle-leq-angle':
39+
return `∠${n[0]}${n[1]}${n[2]} ≤ ∠${n[3]}${n[4]}${n[5]}`;
40+
case 'angle-geq-angle':
41+
return `∠${n[0]}${n[1]}${n[2]} ≥ ∠${n[3]}${n[4]}${n[5]}`;
42+
case 'segment-equals-segment':
43+
return `${n[0]}${n[1]} = ${n[2]}${n[3]}`;
44+
case 'segment-perpendicular':
45+
return `${n[0]}${n[1]}${n[2]}${n[3]}`;
46+
case 'triangles-congruent':
47+
return ${n[0]}${n[1]}${n[2]} ≅ Δ${n[3]}${n[4]}${n[5]}`;
48+
case 'triangle-angles-sum-180':
49+
return `angles of Δ${n[0]}${n[1]}${n[2]} sum to 180°`;
50+
case 'point-midpoint-of-segment':
51+
return `${n[0]} is midpoint of ${n[1]}${n[2]}`;
52+
default:
53+
return String(s.kind);
54+
}
55+
}
56+
57+
/** Reason id -> display name (same as client/proof/reasons REASONS[].name). */
58+
const REASON_NAMES: Record<string, string> = {
59+
given: 'Given in task statement',
60+
definition: 'New Definition',
61+
'perpendicular-definition': 'Perpendicular definition',
62+
'line-determination': 'Incidence axiom I1 (two points determine a unique line)',
63+
'point-on-line-same-line': 'By I1 (point on line: line AC = line AB)',
64+
'opposite-rays-form-line': 'Theorem: opposite rays lie on one line',
65+
reflexivity: 'Reflexivity (AB = AB or ∠A = ∠A)',
66+
'reflexivity-segment': 'Reflexivity (AB = AB or ∠A = ∠A)',
67+
'transitivity-equals': 'Transitivity (=)',
68+
'transitivity-less': 'Transitivity (<)',
69+
'transitivity-greater': 'Transitivity (>)',
70+
'transitivity-leq': 'Transitivity (≤)',
71+
'transitivity-geq': 'Transitivity (≥)',
72+
sas: 'Triangle congruence (SAS)',
73+
asa: 'Triangle congruence (ASA)',
74+
aas: 'Triangle congruence (AAS)',
75+
sss: 'Triangle congruence (SSS)',
76+
hl: 'Triangle congruence (HL)',
77+
'from-congruence': 'From congruence (CPCTC): corresponding sides equal',
78+
'sum-triangle-angles': 'Sum of triangle angles',
79+
thales: 'Thales theorem',
80+
};
81+
82+
/**
83+
* Format one proof step as shown in the frontend: "1. statement (Reason name) [from: Given, Step 2]"
84+
*/
85+
export function formatProofStep(step: ProofStepLogEntry, index: number): string {
86+
const statement = step.outcome ? statementToString(step.outcome) : '?';
87+
const reasonName = (step.reasonId && REASON_NAMES[step.reasonId]) || step.reasonId || '?';
88+
const refs = Array.isArray(step.prerequisiteRefs) && step.prerequisiteRefs.length;
89+
const prereqStr = refs
90+
? ` [from: ${step.prerequisiteRefs.map((r) => (r === 'given' ? 'Given' : `Step ${r}`)).join(', ')}]`
91+
: '';
92+
return `${index + 1}. ${statement} (${reasonName})${prereqStr}`;
93+
}

0 commit comments

Comments
 (0)