"use client"; /** * Analytics domain API(ARCHITECTURE.md §5.1 / §5.3 / §9.1 教师域学情分析模块) * * 契约状态:混合(🟡 部分真实 + 部分契约待补) * - learningTrend: ✅ 真实(schema 已就绪,LearningTrend,无参数) * - studentWeakness: ✅ 真实(schema 已就绪,StudentWeakness,无参数) * - analyticsOverview: ❌ schema 无此聚合根字段 → MSW 兜底(@contract-pending) * - studentAnalytics(studentId): ❌ schema 无此根字段 → MSW 兜底(@contract-pending) * * Schema 缺陷处理(data-ana 子图): * - LearningTrend.points 在 schema 中是单数 TrendPoint 类型(应为列表) * - StudentWeakness.weak_points 在 schema 中是单数 WeakPoint 类型(应为列表) * - lib/api 层 TypeScript 类型按业务语义定义为数组,MSW 返回数组形状 * * 契约工单:docs/architecture/issues/contracts/data-ana_contract.md#analytics * 关联:ARCHITECTURE.md §5.3 契约纪律 / §5.4 MSW 兜底 / §5.5 后端已就绪查询 / §9.1 / §11.4 */ import type { FetchPolicy } from "@apollo/client"; import { useWidgetQuery } from "../useWidgetQuery"; import { GET_ANALYTICS_OVERVIEW_DOC, GET_LEARNING_TREND_DOC, GET_STUDENT_ANALYTICS_DOC, GET_STUDENT_WEAKNESS_DOC, } from "./operations/analytics.graphql"; import type { TrendPoint, WeakPoint } from "./dashboard"; import type { UseQueryResult } from "./types"; // ===== 数据类型(对齐 data-ana 子图,snake_case)===== // TrendPoint / WeakPoint 复用 dashboard.ts 的定义(同 schema 字段,避免 barrel 导出冲突) /** 学习趋势(对齐 schema LearningTrend,points 运行时按列表处理) */ export interface LearningTrend { student_id: string; points: TrendPoint[]; } /** 学生薄弱点(对齐 schema StudentWeakness,weak_points 运行时按列表处理) */ export interface StudentWeakness { student_id: string; weak_points: WeakPoint[]; } /** 总览趋势点(@contract-pending 扩展,含 avg_score) */ export interface OverviewTrendPoint { date: string; avg_score: number; } /** 总览薄弱知识点(@contract-pending 扩展) */ export interface OverviewWeakPoint { knowledge_point_id: string; title: string; error_count: number; mastery: number; } /** 班级学情分解(@contract-pending 扩展) */ export interface AnalyticsClassBreakdown { class_id: string; class_name: string; avg_score: number; student_count: number; at_risk_count: number; } /** 学情分析总览(@contract-pending 聚合数据) */ export interface AnalyticsOverview { total_students: number; avg_score: number; avg_mastery: number; at_risk_count: number; weak_points: OverviewWeakPoint[]; trends: OverviewTrendPoint[]; class_breakdown: AnalyticsClassBreakdown[]; } /** 学生近期考试(@contract-pending 扩展) */ export interface StudentRecentExam { exam_id: string; exam_title: string; score: number; total_score: number; date: string; } /** 单生学情分析详情(@contract-pending 聚合数据) */ export interface StudentAnalytics { student_id: string; student_name: string; student_no: string; class_id: string; class_name: string; avg_score: number; class_rank: number; total_students: number; weak_points: WeakPoint[]; trends: TrendPoint[]; recent_exams: StudentRecentExam[]; } // ===== 响应类型 ===== /** learningTrend 查询响应 */ interface LearningTrendResponse { learningTrend: LearningTrend | null; } /** studentWeakness 查询响应 */ interface StudentWeaknessResponse { studentWeakness: StudentWeakness | null; } /** analyticsOverview 查询响应(@contract-pending) */ interface AnalyticsOverviewResponse { analyticsOverview: AnalyticsOverview | null; } /** studentAnalytics 查询响应(@contract-pending) */ interface StudentAnalyticsResponse { studentAnalytics: StudentAnalytics | null; } // ===== 查询选项 ===== export interface AnalyticsQueryOptions { enabled?: boolean; pollInterval?: number; fetchPolicy?: FetchPolicy; } // ===== Hooks ===== /** * 查询学习趋势(✅ 真实 schema,learningTrend)。 * * 关联:ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1 */ export function useLearningTrend( options?: AnalyticsQueryOptions, ): UseQueryResult { const result = useWidgetQuery( GET_LEARNING_TREND_DOC, {}, { enabled: options?.enabled ?? true, fetchPolicy: options?.fetchPolicy, pollInterval: options?.pollInterval, }, ); return { data: result.data?.learningTrend ?? null, loading: result.loading, error: result.error, refetch: result.refetch, }; } /** * 查询学生薄弱知识点(✅ 真实 schema,studentWeakness)。 * * 关联:ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1 */ export function useStudentWeakness( options?: AnalyticsQueryOptions, ): UseQueryResult { const result = useWidgetQuery( GET_STUDENT_WEAKNESS_DOC, {}, { enabled: options?.enabled ?? true, fetchPolicy: options?.fetchPolicy, pollInterval: options?.pollInterval, }, ); return { data: result.data?.studentWeakness ?? null, loading: result.loading, error: result.error, refetch: result.refetch, }; } /** * 查询学情分析总览(@contract-pending,MSW 兜底)。 * * schema 无 analyticsOverview 根字段,由 MSW handlers 返回 mock 数据。 * 用于 /shell/teacher/analytics 总览页。 * * 关联:ARCHITECTURE.md §5.4 / §9.1 总览页 / §11.4 契约工单 */ export function useAnalyticsOverview( options?: AnalyticsQueryOptions, ): UseQueryResult { const result = useWidgetQuery( GET_ANALYTICS_OVERVIEW_DOC, {}, { enabled: options?.enabled ?? true, fetchPolicy: options?.fetchPolicy, pollInterval: options?.pollInterval, }, ); return { data: result.data?.analyticsOverview ?? null, loading: result.loading, error: result.error, refetch: result.refetch, }; } /** * 按 studentId 查询单生学情分析(@contract-pending,MSW 兜底)。 * * schema 无 studentAnalytics(studentId) 根字段,由 MSW handlers 返回 mock 数据。 * 用于 /shell/teacher/analytics/[studentId] 学生详情页。 * * 关联:ARCHITECTURE.md §5.4 / §9.1 详情页 / §11.4 契约工单 */ export function useStudentAnalytics( studentId: string, options?: AnalyticsQueryOptions, ): UseQueryResult { const result = useWidgetQuery< StudentAnalyticsResponse, { studentId: string } >( GET_STUDENT_ANALYTICS_DOC, { studentId }, { ...options, enabled: options?.enabled ?? studentId.length > 0, }, ); return { data: result.data?.studentAnalytics ?? null, loading: result.loading, error: result.error, refetch: result.refetch, }; }