From 9412ba3cf64badafb63ce7d9df1441666d34066e Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Sun, 27 Sep 2026 16:08:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=A5=91=E7=BA=A6):=20=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E9=A2=98=E5=8F=B7=E7=BB=9F=E4=B8=80=E5=8F=AB=20proble?= =?UTF-8?q?mDisplayId=EF=BC=8C=E9=A2=98=E5=8D=95=20id=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=8F=AB=20problemSetId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。 - 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成 problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items / 今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象 或数字 id,这几处是字符串,最容易看错 - 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成 ?problemDisplayId=(提交列表、流程图列表、三个统计接口) - 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存, 统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets 路径、前端路由参数一致。drizzle 的列键跟表名走,没动 - aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。 ai_analysis.data 里存的历史快照仍是 user,没有代码读回它 - 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改 Co-Authored-By: Claude Opus 5.5 --- apps/api/src/routes/admin/problemset.ts | 6 ++--- apps/api/src/routes/content.ts | 2 +- apps/api/src/routes/flowchart.ts | 6 ++--- apps/api/src/routes/problemset.ts | 8 +++---- apps/api/src/routes/submission-statistics.ts | 10 ++++----- apps/api/src/routes/submission.ts | 8 +++---- apps/api/src/services/learning-stats.ts | 4 ++-- apps/web/src/admin/contest/helper.vue | 2 +- .../components/ProblemManagement.vue | 2 +- apps/web/src/oj/achievement/index.vue | 6 ++--- apps/web/src/oj/api.ts | 14 ++++++------ .../problem/components/ProblemSubmission.vue | 2 +- apps/web/src/oj/problemset/detail.vue | 2 +- apps/web/src/oj/store/ai.ts | 2 +- .../submission/components/TodayStatistics.vue | 10 ++++++--- apps/web/src/oj/submission/list.vue | 22 +++++++++---------- apps/web/src/oj/user/message.vue | 4 ++-- .../src/shared/components/StatisticsPanel.vue | 12 +++++----- apps/web/src/utils/types.ts | 2 +- packages/contract/src/admin.ts | 6 ++--- packages/contract/src/ai.ts | 2 +- packages/contract/src/flowchart.ts | 2 +- packages/contract/src/problemset.ts | 8 +++---- packages/contract/src/submission.ts | 20 ++++++----------- 24 files changed, 80 insertions(+), 82 deletions(-) diff --git a/apps/api/src/routes/admin/problemset.ts b/apps/api/src/routes/admin/problemset.ts index 2168ded..4b76065 100644 --- a/apps/api/src/routes/admin/problemset.ts +++ b/apps/api/src/routes/admin/problemset.ts @@ -293,9 +293,9 @@ adminProblemSetRoutes.get( ({ item, problem }) => ({ id: item.id, - problemsetId: item.problemsetId, + problemSetId: item.problemsetId, problemId: item.problemId, - displayId: problem.displayId, + problemDisplayId: problem.displayId, title: problem.title, difficulty: problem.difficulty, order: item.order, @@ -456,7 +456,7 @@ async function badgesWithCount(badges: BadgeRow[]) { (badge) => ({ id: badge.id, - problemsetId: badge.problemsetId, + problemSetId: badge.problemsetId, name: badge.name, description: badge.description, icon: badge.icon, diff --git a/apps/api/src/routes/content.ts b/apps/api/src/routes/content.ts index 2123726..38fcb53 100644 --- a/apps/api/src/routes/content.ts +++ b/apps/api/src/routes/content.ts @@ -169,7 +169,7 @@ contentRoutes.get("/messages", requireAuth, async (c) => { language: submission.language, statisticInfo: asRecord(submission.statisticInfo), // 展示用题号而非数字主键,站内信页面拿它拼 /problem/<题号> - problem: displayId, + problemDisplayId: displayId, showLink: true, } satisfies EmbeddedSubmission, }) satisfies Message, diff --git a/apps/api/src/routes/flowchart.ts b/apps/api/src/routes/flowchart.ts index 167a9d1..57de5f8 100644 --- a/apps/api/src/routes/flowchart.ts +++ b/apps/api/src/routes/flowchart.ts @@ -244,7 +244,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => { const user = c.get("user")! const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 }) const offset = queryInteger(c.req.query("offset"), 0, { min: 0 }) - const displayId = c.req.query("problemId")?.trim() + const displayId = c.req.query("problemDisplayId")?.trim() const username = c.req.query("username")?.trim() const grade = c.req.query("grade") // 与代码提交列表同一套口径(submission.ts 的 GET /submissions):关掉 @@ -298,7 +298,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => { ({ id: flowchart.id, username, - problem: problem.displayId, + problemDisplayId: problem.displayId, problemTitle: problem.title, status: flowchart.status, createTime: flowchart.createTime, @@ -349,7 +349,7 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => { if (start) filters.push(sql`${schema.flowchartSubmission.createTime} >= ${start}`) - const displayId = c.req.query("problemId")?.trim() + const displayId = c.req.query("problemDisplayId")?.trim() if (displayId) { const [problem] = await db .select({ id: schema.problem.id }) diff --git a/apps/api/src/routes/problemset.ts b/apps/api/src/routes/problemset.ts index 7e95227..c1c617f 100644 --- a/apps/api/src/routes/problemset.ts +++ b/apps/api/src/routes/problemset.ts @@ -81,7 +81,7 @@ function badgeData( ) { return { id: badge.id, - problemsetId: badge.problemsetId, + problemSetId: badge.problemsetId, name: badge.name, description: badge.description, icon: badge.icon, @@ -306,7 +306,7 @@ problemsetRoutes.get("/problem-sets/:id/problems", optionalAuth, async (c) => { ({ link, problemId, displayId, title, difficulty }) => ({ id: link.id, - problemsetId: link.problemsetId, + problemSetId: link.problemsetId, problem: { id: problemId, _id: displayId, title, difficulty }, order: link.order, isRequired: link.isRequired, @@ -435,7 +435,7 @@ problemsetRoutes.get("/users/:username/badges", optionalAuth, async (c) => { userId: userBadge.userId, badge: badgeData(badge), earnedTime: userBadge.earnedTime, - problemset: { id: problemSet.id, title: problemSet.title }, + problemSet: { id: problemSet.id, title: problemSet.title }, }) satisfies UserBadge, ), ) @@ -572,7 +572,7 @@ problemsetRoutes.get( ({ progress, user: progressUser, realName }) => ({ id: progress.id, - problemsetId: progress.problemsetId, + problemSetId: progress.problemsetId, user: sampleUser(progressUser, realName), joinTime: progress.joinTime, completeTime: progress.completeTime, diff --git a/apps/api/src/routes/submission-statistics.ts b/apps/api/src/routes/submission-statistics.ts index 3418276..4dc73e2 100644 --- a/apps/api/src/routes/submission-statistics.ts +++ b/apps/api/src/routes/submission-statistics.ts @@ -151,7 +151,7 @@ submissionStatisticsRoutes.get( count: row.value, })), problems: problemRows.map((row) => ({ - problem: row.displayId, + problemDisplayId: row.displayId, problemTitle: row.title, count: row.value, acceptedCount: row.accepted, @@ -240,7 +240,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) { number, { id: string - problem: string + problemDisplayId: string result: JudgeStatusValue error: string | null } @@ -276,7 +276,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) { for (const row of rows) { byUser.set(row.user_id, { id: row.id, - problem: row.problem, + problemDisplayId: row.problem, result: row.result, error: row.error, }) @@ -412,7 +412,7 @@ async function statisticsScope(c: { if (range.start) filters.push(sql`${schema.submission.createTime} >= ${range.start}`) - const displayIds = parseDisplayIds(c.req.query("problemId") ?? "") + const displayIds = parseDisplayIds(c.req.query("problemDisplayId") ?? "") if (displayIds.length > STATISTICS_MAX_PROBLEMS) { return { ok: false, @@ -682,7 +682,7 @@ submissionStatisticsRoutes.get( id: schema.submission.id, result: schema.submission.result, createTime: schema.submission.createTime, - problem: schema.problem.displayId, + problemDisplayId: schema.problem.displayId, problemTitle: schema.problem.title, }) .from(schema.submission) diff --git a/apps/api/src/routes/submission.ts b/apps/api/src/routes/submission.ts index 81c6378..11d72bb 100644 --- a/apps/api/src/routes/submission.ts +++ b/apps/api/src/routes/submission.ts @@ -627,7 +627,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => { ) { return success(c, { results: [], total: 0 } satisfies SubmissionList) } - const displayId = c.req.query("problemId")?.trim() + const displayId = c.req.query("problemDisplayId")?.trim() const myself = c.req.query("myself") === "1" ? user : null // 「只看自己」盖过用户名 const username = myself ? undefined : c.req.query("username")?.trim() @@ -673,7 +673,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => { ({ submission, problem }) => ({ id: submission.id, - problem: problem.displayId, + problemDisplayId: problem.displayId, problemTitle: problem.title, showLink: user ? canViewSubmission(user, submission, problem, null, joinTimes) @@ -708,7 +708,7 @@ submissionRoutes.get( const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 }) const offset = queryInteger(c.req.query("offset"), 0, { min: 0 }) const user = c.get("user") - const displayId = c.req.query("problemId")?.trim() + const displayId = c.req.query("problemDisplayId")?.trim() const myself = c.req.query("myself") === "1" ? user : null const username = myself ? undefined : c.req.query("username")?.trim() const result = c.req.query("result") @@ -757,7 +757,7 @@ submissionRoutes.get( ({ submission, problem }) => ({ id: submission.id, - problem: problem.displayId, + problemDisplayId: problem.displayId, problemTitle: problem.title, showLink: user ? canViewSubmission(user, submission, problem, contest) diff --git a/apps/api/src/services/learning-stats.ts b/apps/api/src/services/learning-stats.ts index d5d5fd2..e141b1b 100644 --- a/apps/api/src/services/learning-stats.ts +++ b/apps/api/src/services/learning-stats.ts @@ -314,7 +314,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) { const problemIds = firstAc.map((item) => item.problemId) if (!problemIds.length) return { - user: user.username, + username: user.username, className: user.className, start, end, @@ -415,7 +415,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) { b.latestSubmissionTime.localeCompare(a.latestSubmissionTime), ) return { - user: user.username, + username: user.username, className: user.className, start, end, diff --git a/apps/web/src/admin/contest/helper.vue b/apps/web/src/admin/contest/helper.vue index 956a046..382db3c 100644 --- a/apps/web/src/admin/contest/helper.vue +++ b/apps/web/src/admin/contest/helper.vue @@ -134,7 +134,7 @@ async function viewSubmission(item: HelperItem) { // 查询该用户在该竞赛该题目的 AC 提交 const res = await getSubmissions({ username: item.username, - problemId: item.problemDisplayId, + problemDisplayId: item.problemDisplayId, contestId: props.contestID, result: "0", // ACCEPTED language: "", diff --git a/apps/web/src/admin/problemset/components/ProblemManagement.vue b/apps/web/src/admin/problemset/components/ProblemManagement.vue index 1df5980..4339f3e 100644 --- a/apps/web/src/admin/problemset/components/ProblemManagement.vue +++ b/apps/web/src/admin/problemset/components/ProblemManagement.vue @@ -27,7 +27,7 @@ defineEmits() - + 来自题单 - {{ b.problemset.title }} + {{ b.problemSet.title }} diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts index 16bd7f1..cd3685a 100644 --- a/apps/web/src/oj/api.ts +++ b/apps/web/src/oj/api.ts @@ -174,20 +174,20 @@ export function adminRejudge(id: string) { export function getSubmissionStatisticsItems( duration: { start?: string; end: string }, username: string, - problemID?: string, + problemDisplayId?: string, ) { return api.get("submissions/statistics/items", { - params: { ...duration, problemId: problemID, username }, + params: { ...duration, problemDisplayId, username }, }) } export function getSubmissionStatistics( duration: { start?: string; end: string }, - problemID?: string, + problemDisplayId?: string, username?: string, ) { return api.get("submissions/statistics", { - params: { ...duration, problemId: problemID, username }, + params: { ...duration, problemDisplayId, username }, }) } @@ -410,7 +410,7 @@ export function getFlowchartSubmission(id: string) { export function getFlowchartSubmissions(params: { username?: string - problemId?: string + problemDisplayId?: string myself?: string offset?: number limit?: number @@ -422,11 +422,11 @@ export function getFlowchartSubmissions(params: { export function getFlowchartStatistics( duration: { start?: string; end: string }, - problemID?: string, + problemDisplayId?: string, username?: string, ) { return api.get("flowcharts/statistics", { - params: { ...duration, problemId: problemID, username }, + params: { ...duration, problemDisplayId, username }, }) } diff --git a/apps/web/src/oj/problem/components/ProblemSubmission.vue b/apps/web/src/oj/problem/components/ProblemSubmission.vue index 54d9eb9..d51c34c 100644 --- a/apps/web/src/oj/problem/components/ProblemSubmission.vue +++ b/apps/web/src/oj/problem/components/ProblemSubmission.vue @@ -126,7 +126,7 @@ async function listSubmissions() { ...query, myself: "1", offset, - problemId: (route.params.problemID as string) ?? "", + problemDisplayId: (route.params.problemID as string) ?? "", contestId: (route.params.contestID as string) ?? "", }) submissions.value = res.results diff --git a/apps/web/src/oj/problemset/detail.vue b/apps/web/src/oj/problemset/detail.vue index 0c2810f..679380f 100644 --- a/apps/web/src/oj/problemset/detail.vue +++ b/apps/web/src/oj/problemset/detail.vue @@ -49,7 +49,7 @@ async function loadUserBadges() { const res = await getUserBadges() userBadges.value = res.filter( - (badge: UserBadgeType) => badge.badge.problemsetId === problemSetId.value, + (badge: UserBadgeType) => badge.badge.problemSetId === problemSetId.value, ) } diff --git a/apps/web/src/oj/store/ai.ts b/apps/web/src/oj/store/ai.ts index a764908..503c539 100644 --- a/apps/web/src/oj/store/ai.ts +++ b/apps/web/src/oj/store/ai.ts @@ -16,7 +16,7 @@ export const useAIStore = defineStore("ai", () => { const rangeEnd = ref("") const durationData = ref([]) const detailsData = reactive({ - user: "", + username: "", start: "", end: "", grade: "", diff --git a/apps/web/src/oj/submission/components/TodayStatistics.vue b/apps/web/src/oj/submission/components/TodayStatistics.vue index 625ad64..408a483 100644 --- a/apps/web/src/oj/submission/components/TodayStatistics.vue +++ b/apps/web/src/oj/submission/components/TodayStatistics.vue @@ -148,14 +148,18 @@ onMounted(async () => { - 最近一次:{{ item.failure?.problem }} · + 最近一次:{{ item.failure?.problemDisplayId }} · {{ statusName(item.failure?.result) }}
{{
@@ -252,17 +252,17 @@ function groupByProblem(list: SubmissionStatisticsItems["items"]) {
   const groups = new Map<
     string,
     {
-      problem: string
+      problemDisplayId: string
       problemTitle: string
       items: SubmissionStatisticsItems["items"]
     }
   >()
   for (const item of list) {
-    const group = groups.get(item.problem)
+    const group = groups.get(item.problemDisplayId)
     if (group) group.items.push(item)
     else
-      groups.set(item.problem, {
-        problem: item.problem,
+      groups.set(item.problemDisplayId, {
+        problemDisplayId: item.problemDisplayId,
         problemTitle: item.problemTitle,
         items: [item],
       })
@@ -313,7 +313,7 @@ const columns: DataTableColumn[] = [
                 h(
                   NTag,
                   { size: "small", bordered: false },
-                  () => group.problem,
+                  () => group.problemDisplayId,
                 ),
                 h(
                   NText,
diff --git a/apps/web/src/utils/types.ts b/apps/web/src/utils/types.ts
index f0c58a6..631b5ce 100644
--- a/apps/web/src/utils/types.ts
+++ b/apps/web/src/utils/types.ts
@@ -275,7 +275,7 @@ export interface SubmissionListPayload {
   result?: string
   username?: string
   contestId?: string
-  problemId?: string
+  problemDisplayId?: string
   language: LANGUAGE | ""
   today?: "1" | "0"
   page: number
diff --git a/packages/contract/src/admin.ts b/packages/contract/src/admin.ts
index b77fa4a..61f86ce 100644
--- a/packages/contract/src/admin.ts
+++ b/packages/contract/src/admin.ts
@@ -421,9 +421,9 @@ export const updateProblemSetStatusRequestSchema = z.object({
 
 export const adminProblemSetProblemSchema = z.object({
   id: z.number().int(),
-  problemsetId: z.number().int(),
+  problemSetId: z.number().int(),
   problemId: z.number().int(),
-  displayId: z.string(),
+  problemDisplayId: z.string(),
   title: z.string(),
   difficulty: z.string(),
   order: z.number().int(),
@@ -450,7 +450,7 @@ export const updateProblemInSetRequestSchema = z.object({
 
 export const adminProblemSetBadgeSchema = z.object({
   id: z.number().int(),
-  problemsetId: z.number().int(),
+  problemSetId: z.number().int(),
   name: z.string(),
   description: z.string(),
   icon: z.string(),
diff --git a/packages/contract/src/ai.ts b/packages/contract/src/ai.ts
index f8094f9..4ed8829 100644
--- a/packages/contract/src/ai.ts
+++ b/packages/contract/src/ai.ts
@@ -59,7 +59,7 @@ export const activityBucketSchema = z.object({
 })
 
 export const aiDetailSchema = z.object({
-  user: z.string(),
+  username: z.string(),
   className: z.string().nullable(),
   start: z.string(),
   end: z.string(),
diff --git a/packages/contract/src/flowchart.ts b/packages/contract/src/flowchart.ts
index 790d2b0..c40e556 100644
--- a/packages/contract/src/flowchart.ts
+++ b/packages/contract/src/flowchart.ts
@@ -45,7 +45,7 @@ export const flowchartSubmissionSchema = z.object({
 export const flowchartListItemSchema = z.object({
   id: z.string(),
   username: z.string(),
-  problem: z.string(),
+  problemDisplayId: z.string(),
   problemTitle: z.string(),
   status: flowchartStatusSchema,
   createTime: z.string(),
diff --git a/packages/contract/src/problemset.ts b/packages/contract/src/problemset.ts
index 1428719..de838e9 100644
--- a/packages/contract/src/problemset.ts
+++ b/packages/contract/src/problemset.ts
@@ -13,7 +13,7 @@ export const problemSetUserProgressSummarySchema = z.object({
 
 export const problemSetBadgeSchema = z.object({
   id: z.number().int(),
-  problemsetId: z.number().int(),
+  problemSetId: z.number().int(),
   name: z.string(),
   description: z.string(),
   icon: z.string(),
@@ -58,7 +58,7 @@ export const problemSetProblemItemSchema = z.object({
 
 export const problemSetProblemSchema = z.object({
   id: z.number().int(),
-  problemsetId: z.number().int(),
+  problemSetId: z.number().int(),
   problem: problemSetProblemItemSchema,
   order: z.number().int(),
   isRequired: z.boolean(),
@@ -79,7 +79,7 @@ export const completedProblemSchema = z.object({
 
 export const problemSetProgressSchema = z.object({
   id: z.number().int(),
-  problemsetId: z.number().int(),
+  problemSetId: z.number().int(),
   user: sampleUserSchema,
   joinTime: z.string(),
   completeTime: z.string().nullable(),
@@ -107,7 +107,7 @@ export const userBadgeSchema = z.object({
   userId: z.number().int(),
   badge: problemSetBadgeSchema,
   earnedTime: z.string(),
-  problemset: z.object({ id: z.number().int(), title: z.string() }),
+  problemSet: z.object({ id: z.number().int(), title: z.string() }),
 })
 
 export type ProblemSet = z.infer
diff --git a/packages/contract/src/submission.ts b/packages/contract/src/submission.ts
index 01359e2..ec5360b 100644
--- a/packages/contract/src/submission.ts
+++ b/packages/contract/src/submission.ts
@@ -191,20 +191,14 @@ export const submissionDetailSchema = z.object({
  * 将来有人「顺手」把空值改成真值就不会变成泄露,因为这里压根没有这些字段。
  */
 export const embeddedSubmissionSchema = submissionDetailSchema
-  // problemDisplayId 也要去掉:下面的 problem 就是它,同一个值留两份,
-  // 而路由只填了 problem —— 这里漏 omit 的那阵子,凡是收到过站内信的人
-  // 打开消息页都是 500(parse 抛在缺失的 problemDisplayId 上,列表为空时才碰巧不炸)。
+  // 留下 problemDisplayId(展示用题号)而不是数字主键:站内信页面拿它拼
+  // `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
   .omit({
     info: true,
     contestId: true,
     problemId: true,
-    problemDisplayId: true,
     caseSummary: true,
   })
-  // 旧 SubmissionSafeModelSerializer 里 problem 是
-  // `SlugRelatedField(slug_field="_id")`,即**展示用题号**而非数字主键。
-  // 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
-  .extend({ problem: z.string() })
 
 /**
  * 判题进度推送。**只带前端真正要用的东西**:靠 submissionId 认领、靠 result /
@@ -224,7 +218,7 @@ export const submissionUpdateSchema = z.object({
 
 export const submissionListItemSchema = z.object({
   id: z.string(),
-  problem: z.string(),
+  problemDisplayId: z.string(),
   problemTitle: z.string(),
   showLink: z.boolean(),
   createTime: z.string(),
@@ -277,7 +271,7 @@ export const attemptedStudentSchema = unacceptedStudentSchema.extend({
     .object({
       id: z.string(),
       /** 题目的展示编号,用来告诉老师错在哪道题 */
-      problem: z.string(),
+      problemDisplayId: z.string(),
       result: judgeStatusSchema,
       error: z.string().nullable(),
     })
@@ -325,7 +319,7 @@ export const submissionStatisticsItemsSchema = z.object({
    * 展开某个学生时列出他这段时间的提交。**带上题目**:一节课里学生往往在好几道题
    * 之间来回跳,一串只有编号的按钮看不出他卡在哪一道 —— 前端按题目分组展示。
    *
-   * 字段名沿用 submissionListItemSchema 的口径:`problem` 是展示用题号(problem._id),
+   * 字段名沿用 submissionListItemSchema 的口径:`problemDisplayId` 是展示用题号(problem._id),
    * `problemTitle` 是标题。
    */
   items: z.array(
@@ -333,7 +327,7 @@ export const submissionStatisticsItemsSchema = z.object({
       id: z.string(),
       result: judgeStatusSchema,
       createTime: z.string(),
-      problem: z.string(),
+      problemDisplayId: z.string(),
       problemTitle: z.string(),
     }),
   ),
@@ -405,7 +399,7 @@ export const todaySubmissionStatisticsSchema = z.object({
    */
   problems: z.array(
     z.object({
-      problem: z.string(),
+      problemDisplayId: z.string(),
       problemTitle: z.string(),
       count: z.number().int(),
       acceptedCount: z.number().int(),