Files
OJ2/packages/contract/src/flowchart.ts
T
xuyueandClaude Opus 5.5 d2fc0382ec feat(流程图): 没走对的图,AI 对照答案说出错在哪个框、该怎么改,并把那个框标红
原来只有程序拼出的现象(「输出 a」输出的是 2,应该是 8),指的是出结果的框,
错的根子常在前面,学生和老师都看不出该改哪里。跑完以后再请 AI 看题目、图、
没走对的几组和老师的答案,写「哪里错了 / 可以这样改」两句,指出真正要改的框;
提交列表右栏的图和题目页画布上标红它。等级照旧只由跑出来的结果定,
点评失败或像代码就不要,退回只显示逐组结果。老提交不补。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:52:05 +08:00

187 lines
6.8 KiB
TypeScript

import { z } from "zod"
import { paginatedSchema } from "./common"
export const flowchartStatusSchema = z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
])
export const createFlowchartRequestSchema = z.object({
problemId: z.number().int().positive(),
mermaidCode: z
.string()
.trim()
.min(1)
.max(50_000)
.refine(
(value) => value.split("\n").filter((line) => line.trim()).length <= 200,
"Flowchart is too complex",
),
flowchartData: z.record(z.string(), z.unknown()).default({}),
})
export const flowchartSubmissionSchema = z.object({
id: z.string(),
username: z.string(),
problemId: z.number().int(),
mermaidCode: z.string(),
flowchartData: z.record(z.string(), z.unknown()),
status: flowchartStatusSchema,
createTime: z.string(),
aiScore: z.number().nullable(),
aiGrade: z.string().nullable(),
aiFeedback: z.string().nullable(),
aiSuggestions: z.string().nullable(),
aiCriteriaDetails: z.record(z.string(), z.unknown()),
aiProvider: z.string(),
aiModel: z.string(),
processingTime: z.number().nullable(),
evaluationTime: z.string().nullable(),
})
export const flowchartListItemSchema = z.object({
id: z.string(),
username: z.string(),
problemDisplayId: z.string(),
problemTitle: z.string(),
status: flowchartStatusSchema,
createTime: z.string(),
aiScore: z.number().nullable(),
aiGrade: z.string().nullable(),
aiProvider: z.string(),
aiModel: z.string(),
processingTime: z.number().nullable(),
evaluationTime: z.string().nullable(),
showLink: z.boolean(),
})
export const flowchartListSchema = paginatedSchema(flowchartListItemSchema).extend({
/** 题号框里查不到的题号,口径同 submissionListSchema.unknownProblems */
unknownProblems: z.array(z.string()),
})
export const createFlowchartResponseSchema = z.object({
submissionId: z.string(),
status: z.literal("pending"),
})
/**
* 流程图画到这两档算这道题做完:题单进度、课堂条、课堂看板、「你画的流程图」、结果页签的 ✓
* 都按它(设计文档 2026-09-28-problem-page-redesign 第 3 节决定 4、6)。前后端只有这一份
*/
export const FLOWCHART_PASS_GRADES = ["S", "A"] as const
export function isFlowchartPass(grade: string | null | undefined) {
return (FLOWCHART_PASS_GRADES as readonly string[]).includes(grade ?? "")
}
/** 自己在一道题上评完的历次分数,早的在前;hidden = 被题单闸门藏起来的次数 */
export const flowchartScoresSchema = z.object({
scores: z.array(
z.object({
id: z.string(),
score: z.number(),
grade: z.string(),
createTime: z.string(),
}),
),
hidden: z.number().int(),
})
export const flowchartStatisticsSchema = z.object({
totalCount: z.number().int(),
avgScore: z.number(),
gradeDistribution: z.record(z.string(), z.number().int()),
criteriaAverages: z.record(z.string(), z.object({ avg: z.number(), max: z.number() })),
personCount: z.number().int(),
completedCount: z.number().int(),
wordFrequencies: z.array(z.object({ word: z.string(), count: z.number().int() })),
/**
* 2026-10 起「走例子」评的图里最常见的错:每张图取第一组没走对的原因(「『一级果』多输出了
* 一行『一级果』」),同一句话数一下,最多 8 条。新评法几乎没有评语,词云拼不出来,
* 而同一个错在一个班里往往原样出现几十次 —— 老师要看的就是它
*/
commonMistakes: z.array(z.object({ text: z.string(), count: z.number().int() })),
// 与提交统计共用「未完成学生」的形状,见 submission.ts 的 unacceptedStudentSchema
dataUnaccepted: z.array(z.object({ username: z.string(), realName: z.string() })),
/**
* 每人**最好的一次**(分数最高的那张),统计页的「每人最好的一次」表用。
* 只含普通学生;按最好成绩从低到高排,没拿到 A 的排前面 —— 老师要先看的是他们
*/
people: z.array(
z.object({
username: z.string(),
realName: z.string(),
bestScore: z.number().nullable(),
bestGrade: z.string().nullable(),
count: z.number().int(),
/** 拿到 A / S 的题数(查几道题时要每道都拿到才算完成) */
goodProblems: z.number().int(),
}),
),
/** people 超过上限被截了(截掉的是成绩最好的那些) */
peopleTruncated: z.boolean(),
/** 查了几道题(没填题号为 0,此时拿到一道 A / S 就算完成) */
problemCount: z.number().int(),
})
/**
* 2026-10 起流程图的评法:AI 拿题目的例子和几组测试数据,在学生的图上从「开始」走到「结束」,
* 逐组看输出对不对(不再打逻辑 40 / 完整 30 / 规范 20 / 清晰 10 四项分)。这份报告存在
* `ai_criteria_details` 里,靠 `version: 2` 和老的四项分区分开 —— 老提交照旧按四项分显示。
*
* path / wrongAt 里是 Mermaid 的节点编号(n0 是开始),前端按同一套顺序对回画布上的框。
*/
export interface FlowchartCaseResult {
/** 「例子 1」「测试 6」 */
name: string
input: string
expected: string
/** 照着图走出来的输出,多行用换行连起来 */
output: string
ok: boolean
path: string[]
wrongAt: string | null
why: string
}
export interface FlowchartCaseReport {
version: 2
passed: number
total: number
cases: FlowchartCaseResult[]
/**
* 没走对时 AI 对着老师的答案指出的「真正要改的那个框」(Mermaid 编号)。和每组的 wrongAt
* 不同:wrongAt 是程序找到的出结果的框,错的根子常常在它前面(判断条件写反了之类)。
* 「哪里错 / 怎么改」两句存在提交的 ai_feedback / ai_suggestions 里。2026-10-19 之前的报告没有它
*/
focus?: string | null
}
export const flowchartUpdateSchema = z.object({
type: z.enum([
"flowchart_evaluation_completed",
"flowchart_evaluation_failed",
"flowchart_evaluation_update",
]),
submissionId: z.string(),
score: z.number().optional(),
grade: z.string().optional(),
feedback: z.string().optional(),
suggestions: z.string().optional(),
criteriaDetails: z.unknown().optional(),
error: z.string().optional(),
})
export type FlowchartUpdate = z.infer<typeof flowchartUpdateSchema>
export type FlowchartStatistics = z.infer<typeof flowchartStatisticsSchema>
export type FlowchartSubmission = z.infer<typeof flowchartSubmissionSchema>
export type FlowchartListItem = z.infer<typeof flowchartListItemSchema>
export type FlowchartList = z.infer<typeof flowchartListSchema>
export type FlowchartScores = z.infer<typeof flowchartScoresSchema>
export type CreateFlowchartResponse = z.infer<typeof createFlowchartResponseSchema>
export type CreateFlowchartRequest = z.input<typeof createFlowchartRequestSchema>
export type FlowchartStatus = z.infer<typeof flowchartStatusSchema>