# parent-bff GraphQL Schema(SDL 契约,对齐 02-architecture-design.md §4.2) # # 本文件是 parent-bff GraphQL 端点的契约唯一源(single source of truth)。 # - 前端 parent-portal 引用本文件生成 TypeScript 类型 # - 后端 parent-bff 引用本文件加载为 GraphQLSchema # - CI 强制检测 breaking change(与 main 分支对比) # # 仲裁依据: # - U3:GraphQL P2 立即引入(parent-bff P4 直接用 GraphQL,不走 REST 过渡) # - C1:错误码前缀 BFF_PARENT_ # - U4:BFF 豁免 @RequirePermission,仅校验 x-user-id + ChildGuard # - 02 §9 #5:depth ≤ 7 / cost ≤ 1000 # # 版本:v2(32 Query + 6 Mutation,对齐 parent-portal 全部 operations) scalar DateTime scalar JSON # ============ Legacy Types ============ type Parent { id: ID! email: String! name: String! avatar: String roles: [String!]! permissions: [String!] dataScope: DataScope! schoolId: ID } enum DataScope { SELF CHILDREN CLASS GRADE SCHOOL DISTRICT ALL } type ViewportItem { key: String! label: String! route: String! icon: String sortOrder: String! requiredPermission: String } type Child { id: ID! name: String! grade: String! class: ClassInfo! lastGrade: Grade grades(page: Int = 1, pageSize: Int = 20, subject: String): GradePage! homework: [Homework!]! exams: [Exam!]! analytics: ChildAnalytics! } type ClassInfo { id: ID! name: String! gradeId: String! } type Grade { id: ID! examId: ID! examTitle: String! subject: String! score: Float! rank: Int gradedAt: DateTime! } type GradePage { items: [Grade!]! pagination: Pagination! } type Pagination { page: Int! pageSize: Int! total: Int! } type Homework { id: ID! title: String! classId: ID! dueDate: DateTime! status: HomeworkStatus! submittedAt: DateTime } enum HomeworkStatus { NOT_SUBMITTED SUBMITTED GRADED OVERDUE } type Exam { id: ID! title: String! classId: ID! status: ExamStatus! examDate: DateTime! publishedAt: DateTime } enum ExamStatus { DRAFT PUBLISHED IN_PROGRESS GRADING SCORED ARCHIVED } type ChildAnalytics { childId: ID! weakness: [WeaknessTopic!]! trend: [TrendPoint!]! classRank: Int classAverage: Float } type WeaknessTopic { knowledgePointId: ID! name: String! subject: String! masteryRate: Float! } type TrendPoint { date: DateTime! score: Float! subject: String } type Notification { id: ID! type: NotificationType! title: String! content: String! read: Boolean! childId: ID createdAt: DateTime! } enum NotificationType { SYSTEM EXAM HOMEWORK GRADE ATTENDANCE ANNOUNCEMENT } type NotificationPreferences { channels: [NotificationChannel!]! eventTypes: NotificationEventTypes! } enum NotificationChannel { APP SMS EMAIL WECHAT } type NotificationEventTypes { gradeReleased: Boolean! homeworkGraded: Boolean! examPublished: Boolean! attendanceAlert: Boolean! schoolAnnouncement: Boolean! } type DashboardData { parent: Parent children: [Child!]! viewports: [ViewportItem!]! unreadNotifications: Int! degraded: Boolean! } # ============ Extended Types(v2 新增) ============ type ChildBrief { id: ID! name: String! grade: String! classId: ID className: String avatar: String } type ChildSummary { childId: ID! avgScore: Float classRank: Int classSize: Int attendanceRate: Float pendingHomeworkCount: Int! recentGradeTrend: Float recentScores: [ChildGrade!]! upcomingEvents: [UpcomingEvent!]! } type UpcomingEvent { id: ID! type: String! title: String! dueDate: DateTime! } type ChildDetail { childId: ID! basicInfo: ChildBasicInfo! todaySchedule: [JSON!]! weeklySchedule: [JSON!]! homeworkSummary: HomeworkSummary! gradeSummary: GradeSummary! examResults: ExamResultSummary! } type ChildBasicInfo { name: String! avatar: String grade: String! className: String! schoolName: String! relation: String! } type HomeworkSummary { pendingCount: Int! overdueCount: Int! submittedCount: Int! gradedCount: Int! } type GradeSummary { avgScore: Float! classRank: Int classSize: Int trend: Float } type ExamResultSummary { upcoming: Int! completed: Int! avgScore: Float } type ChildGrade { examId: ID! examName: String! examDate: DateTime! subject: String! studentScore: Float! classAverage: Float classMax: Float classMin: Float gradeLevel: String } type ChildHomework { id: ID! title: String! subject: String! className: String! assignedDate: DateTime! dueDate: DateTime! status: String! score: Float maxScore: Float feedback: String } type ChildExam { id: ID! name: String! subject: String! status: String! startsAt: DateTime! expiresAt: DateTime durationSeconds: Int questionCount: Int totalScore: Float submittedAt: DateTime } type AttendanceRecord { id: ID! date: DateTime! status: String! checkInTime: DateTime checkOutTime: DateTime note: String } type ExamResult { examId: ID! childId: ID! score: Float rank: Int subjectScores: [SubjectScore!]! feedback: String } type SubjectScore { subject: String! score: Float! fullScore: Float! } type ChildClass { id: ID! name: String! homeroomTeacher: String studentCount: Int grade: String! year: String } type ReportCard { childId: ID! academicYearId: ID! semester: Int! subjects: [ReportCardSubject!]! overallComment: String classRank: Int } type ReportCardSubject { subject: String! score: Float! grade: String! teacherComment: String } type GrowthArchive { childId: ID! subject: String dataPoints: [GrowthDataPoint!]! } type GrowthDataPoint { date: DateTime! category: String! title: String! description: String! evidence: String } type WeaknessItem { id: ID! knowledgePoint: String! masteryLevel: Float! subject: String! recommendation: String } type ChildTrend { childId: ID! period: TrendPeriod! dataPoints: [TrendDataPoint!]! } type TrendDataPoint { date: DateTime! score: Float! subject: String } type LearningPathItem { id: ID! title: String! subject: String! order: Int! masteryLevel: Float! resources: [String!]! } type ErrorBookStats { childId: ID! totalCount: Int! newCount: Int! learningCount: Int! masteredCount: Int! dueReviewCount: Int! masteredRate: Float! } type WrongQuestion { id: ID! questionId: ID! subject: String! content: String! wrongAnswer: String! correctAnswer: String! addedAt: DateTime! status: String! } type WeakKp { id: ID! knowledgePoint: String! subject: String! masteryLevel: Float! recommendation: String } type MasterySummary { childId: ID! overallMastery: Float! subjectMastery: [SubjectMastery!]! totalKps: Int! masteredKps: Int! } type SubjectMastery { subject: String! mastery: Float! totalKps: Int! masteredKps: Int! } type DiagnosticReport { id: ID! childId: ID! subject: String! reportDate: DateTime! summary: String! recommendations: [String!]! } type PracticeStats { childId: ID! totalSessions: Int! completedSessions: Int! totalQuestionsAnswered: Int! overallAccuracy: Float! } type PracticeSession { id: ID! childId: ID! subject: String! startedAt: DateTime! completedAt: DateTime questionCount: Int! correctCount: Int! accuracy: Float! } type CoursePlan { id: ID! childId: ID! subject: String! title: String! startDate: DateTime! endDate: DateTime! progress: Float! } type CoursePlanDetail { id: ID! childId: ID! subject: String! title: String! startDate: DateTime! endDate: DateTime! progress: Float! lessons: [CoursePlanLesson!]! } type CoursePlanLesson { id: ID! title: String! date: DateTime! completed: Boolean! } type LessonPlan { id: ID! childId: ID! subject: String! title: String! date: DateTime! teacherName: String! } type LessonPlanDetail { id: ID! childId: ID! subject: String! title: String! date: DateTime! teacherName: String! objectives: [String!]! content: String! homework: String } type ElectiveCourse { id: ID! childId: ID! name: String! subject: String! teacher: String! schedule: String! selected: Boolean! } type LeaveRequestItem { id: ID! childId: ID! childName: String className: String type: String! startDate: DateTime! endDate: DateTime! reason: String! status: String! submittedAt: DateTime! reviewedAt: DateTime reviewerName: String reviewComment: String } type AcademicYear { id: ID! name: String! startDate: DateTime! endDate: DateTime! isCurrent: Boolean! } type MyNotification { id: ID! childId: ID eventType: String! title: String! body: String! read: Boolean! createdAt: DateTime! actionUrl: String pinned: Boolean! } type MyNotificationPreferences { parentId: ID! preferences: JSON! defaults: NotificationPreferencesDefaults! updatedAt: DateTime } type NotificationPreferencesDefaults { channels: [NotificationChannel!]! eventTypes: NotificationEventTypes! } type MarkAsReadResult { id: ID! read: Boolean! } type MarkAllAsReadResult { count: Int! } type SwitchChildResult { childId: ID! childName: String! selectedAt: DateTime! } type ExportChildGradesResult { downloadUrl: String! expiresAt: DateTime! } type UpdateNotificationPreferencesResult { parentId: ID! updatedAt: DateTime! } # ============ Query(32 个) ============ type Query { # Legacy(11 个) dashboard: DashboardData! viewports: [ViewportItem!]! me: Parent! children: [Child!]! child(childId: ID!): Child childGrades( childId: ID! page: Int = 1 pageSize: Int = 20 subject: String ): GradePage! childHomework(childId: ID!): [Homework!]! childExams(childId: ID!): [Exam!]! childAnalytics(childId: ID!, dateRange: DateRangeInput): ChildAnalytics! notifications( unreadOnly: Boolean page: Int = 1 pageSize: Int = 20 ): [Notification!]! notificationPreferences: NotificationPreferences! # Extended(21 个) currentUser: Parent! myChildren: [ChildBrief!]! childSummary(childId: ID!): ChildSummary childDetail(childId: ID!): ChildDetail childAttendance( childId: ID! startDate: DateTime! endDate: DateTime! ): [AttendanceRecord!]! childExamResult(childId: ID!, examId: ID!): ExamResult childClasses(childId: ID!): [ChildClass!]! childReportCard( childId: ID! academicYearId: ID! semester: Int! ): ReportCard childGrowthArchive(childId: ID!, subject: String): GrowthArchive! childWeakness(childId: ID!): [WeaknessItem!]! childTrend(childId: ID!, period: TrendPeriod!): ChildTrend! childLearningPath(childId: ID!): [LearningPathItem!]! childErrorBookStats(childId: ID!): ErrorBookStats! childTopWrongQuestions(childId: ID!, limit: Int): [WrongQuestion!]! childWeakKps(childId: ID!, limit: Int): [WeakKp!]! childMasterySummary(childId: ID!): MasterySummary! childDiagnosticReports(childId: ID!): [DiagnosticReport!]! childPracticeStats(childId: ID!): PracticeStats! childPracticeSessions(childId: ID!, limit: Int): [PracticeSession!]! childCoursePlans(childId: ID!): [CoursePlan!]! childCoursePlanDetail(childId: ID!, planId: ID!): CoursePlanDetail childLessonPlans(childId: ID!, subject: String): [LessonPlan!]! childLessonPlanDetail(childId: ID!, planId: ID!): LessonPlanDetail childElective(childId: ID!): [ElectiveCourse!]! childLeaveRequests(childId: ID!): [LeaveRequestItem!]! academicYears: [AcademicYear!]! myNotifications(unreadOnly: Boolean, limit: Int): [MyNotification!]! myNotificationPreferences: MyNotificationPreferences! } # ============ Mutation(6 个) ============ type Mutation { # Legacy(3 个) selectChild(childId: ID!): SelectChildResult! markNotificationRead(notificationId: ID!): Notification! updateNotificationPreferences( input: UpdateNotificationPreferencesInput! ): NotificationPreferences! # Extended(6 个新 mutation,覆盖前端全部操作) markAsRead(notificationId: ID!): MarkAsReadResult! markAllAsRead: MarkAllAsReadResult! switchChild(childId: ID!): SwitchChildResult! updateMyNotificationPreferences( parentId: ID! preferences: JSON! defaults: JSON ): UpdateNotificationPreferencesResult! createLeaveRequest(input: LeaveRequestInput!): LeaveRequestItem! exportChildGrades(childId: ID!, subject: String): ExportChildGradesResult! } # ============ Inputs ============ input DateRangeInput { start: DateTime! end: DateTime! } enum TrendPeriod { WEEK MONTH SEMESTER YEAR } input UpdateNotificationPreferencesInput { channels: [NotificationChannel!]! eventTypes: NotificationEventTypesInput! } input NotificationEventTypesInput { gradeReleased: Boolean homeworkGraded: Boolean examPublished: Boolean attendanceAlert: Boolean schoolAnnouncement: Boolean } input LeaveRequestInput { childId: ID! type: String! startDate: DateTime! endDate: DateTime! reason: String! } type SelectChildResult { childId: ID! selectedAt: DateTime! audited: Boolean! }