# data-ana 数据分析服务 > 版本:1.0(P4 完整版) > 阶段:D6 智能洞察领域(仲裁已对齐,v2 实现版) > 端口:HTTP 3006(Gateway 直连降级)+ gRPC 50055(主入口,BFF/ai 调用) > 日期:2026-07-10 ## 职责 数据分析限界上下文(Python/FastAPI 微服务),消费 core-edu / content / iam / ai 的 CDC 事件, 构建 ClickHouse 学情宽表,计算知识点掌握度,评估预警,为前端提供 4 端 Dashboard 查询服务。 ### 核心能力 - **5 张宽表**:student_dashboard_view / student_errors / mastery_snapshot / attendance_logs / ai_usage_log - **CDC 链路**:Debezium → Kafka → data-ana 消费 → ClickHouse(at-least-once + 手动 commit + ReplacingMergeTree 去重) - **掌握度算法**:WEIGHTED_MOVING_AVG(w_i=0.6^i)+ FORGETTING_CURVE(半衰期 30 天)取 max - **预警评估**:5 类预警(LOW_MASTERY / CRITICAL_LOW / SCORE_DROP / ABSENT_FREQUENT / TREND_DECLINE)+ Redis 25h 去重 - **DataScope 过滤**:6 级(SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL),iam gRPC + 角色降级 - **gRPC 12 RPC**:AnalyticsService(含 server-streaming SubscribeMasteryUpdate)+ HealthService - **HTTP 14 端点**:3 基础 + 11 业务(ActionState 信封 + degraded 标记) ## 架构图 ```mermaid flowchart TB subgraph Entry["入口层"] HTTP[FastAPI HTTP
:3006 /analytics/* + /healthz + /readyz] GRPC[grpc.aio Server
:50055 AnalyticsService 12 RPC] end subgraph Service["应用服务层"] S1[AnalyticsService
4 Dashboard + 班级/趋势查询] S2[MasteryService
掌握度计算 + 事件发布] S3[WarningService
5 类预警 + Redis 去重] end subgraph Repo["数据访问层"] R1[ClickHouseRepository
FINAL/argMax + DataScope WHERE] R2[KafkaProducer
mastery.updated 派生事件] R3[IamClient
gRPC GetEffectiveDataScope] R4[RedisClient
DataScope 缓存 + 幂等] end subgraph Consumer["CDC 消费者(后台任务)"] C1[CdcConsumer
手动 commit + 7 类事件路由] C2[ExamCache
LRU 10000 exam_id→metadata] end subgraph Storage["存储/总线"] CH[(ClickHouse
5 宽表)] KAFKA[(Kafka
edu-cdc.* + edu.insight.mastery.updated)] IAM[iam gRPC :50052] REDIS[(Redis
data_ana:* 键)] end HTTP --> S1 HTTP --> S3 GRPC --> S1 S1 --> R1 S2 --> R1 S2 --> R2 S3 --> R1 S3 --> R2 R1 --> CH R2 --> KAFKA R3 --> IAM R3 --> R4 R4 --> REDIS C1 --> C2 C1 --> R1 C1 --> R4 C1 --> KAFKA ``` ## 技术栈 - **Python 3.12+** / **FastAPI 0.115+**(HTTP 端点) - **grpcio 1.66+** / **grpcio-health-checking**(gRPC server + HealthService) - **clickhouse-connect 0.7+**(ClickHouse 宽表查询,支持 FINAL/argMax) - **aiokafka 0.11+**(CDC 消费者,手动 commit + 幂等去重) - **redis 5.0+**(DataScope 缓存 + 事件去重 + 预警位图) - **pydantic 2.9+** / **pydantic-settings 2.5+**(模型校验 + 配置) - **structlog 24.4+**(结构化 JSON 日志) - **prometheus-client 0.20+**(/metrics 指标) - **opentelemetry-sdk 1.27+**(OTLP 链路追踪) - **protobuf 5.28+**(buf generate 生成的 stub 运行时) ## 模块结构 ``` src/data_ana/ ├─ __init__.py ├─ main.py # FastAPI 入口(14 端点 + lifespan + ActionState) ├─ config.py # pydantic-settings 配置(env_prefix=DATA_ANA_) ├─ analytics_service.py # 4 Dashboard + 班级/趋势查询编排 ├─ mastery_service.py # 掌握度算法(WMA + ForgettingCurve) ├─ warning_service.py # 5 类预警评估 + Redis 去重 ├─ exam_cache.py # LRU exam_id→metadata 映射(max 10000) ├─ cdc_consumer.py # CDC 消费者(7 类事件 + 手动 commit) ├─ grpc_server.py # 12 RPC + HealthService + SubscribeMasteryUpdate ├─ clickhouse_client.py # ClickHouse 客户端单例(降级模式) ├─ repository/ │ ├─ __init__.py │ ├─ clickhouse_repository.py # 宽表查询 + FINAL/argMax + DataScope WHERE │ ├─ redis_client.py # Redis 异步客户端(cache + dedup + bitmap) │ ├─ iam_client.py # iam gRPC GetEffectiveDataScope + 角色降级 │ └─ kafka_producer.py # mastery.updated / warning.triggered 发布 └─ shared/ ├─ __init__.py ├─ action_state.py # ActionState[T] 统一信封(ok/fail + degraded) ├─ errors.py # ErrorCode 13 类 + DataAnaError └─ permissions.py # 权限点 + DataScopeLevel + build_datascope_where ``` ## 开发 ```bash # 安装依赖 uv sync # 本地开发(HTTP + gRPC + CDC 同时启动) uv run uvicorn src.data_ana.main:app --host 0.0.0.0 --port 3006 --reload # ruff 校验 ruff check src/ # 类型检查(如已配置 mypy) mypy src/ ``` ## 配置项 所有配置使用 `DATA_ANA_` 前缀(pydantic-settings env_prefix)。 ### 基础配置 | 变量 | 默认值 | 说明 | | ------------------------ | --------------------- | ------------------------------------ | | `DATA_ANA_HTTP_PORT` | 3006 | HTTP 端口(Gateway 直连降级) | | `DATA_ANA_GRPC_PORT` | 50055 | gRPC 端口(AnalyticsService 主入口) | | `DATA_ANA_DEV_MODE` | true | 开发模式(关闭签名校验) | | `DATA_ANA_LOG_LEVEL` | info | 日志级别(debug/info/warning/error) | | `DATA_ANA_OTEL_ENDPOINT` | http://localhost:4318 | OpenTelemetry OTLP 端点 | ### 外部依赖(全部可选,未配置则降级模式) | 变量 | 默认值 | 说明 | | ------------------------------ | ------------- | ------------------------------------------ | | `DATA_ANA_CLICKHOUSE_HOST` | "" | ClickHouse 主机(空字符串=降级模式) | | `DATA_ANA_CLICKHOUSE_PORT` | 8123 | ClickHouse HTTP 端口 | | `DATA_ANA_CLICKHOUSE_DATABASE` | edu_analytics | ClickHouse 数据库 | | `DATA_ANA_CLICKHOUSE_USER` | "" | ClickHouse 用户名 | | `DATA_ANA_CLICKHOUSE_PASSWORD` | "" | ClickHouse 密码 | | `DATA_ANA_KAFKA_BROKERS` | "" | Kafka broker 列表(逗号分隔,空=禁用 CDC) | | `DATA_ANA_KAFKA_GROUP_ID` | data-ana-cdc | 消费者组 ID | | `DATA_ANA_REDIS_URL` | "" | Redis URL(空=禁用缓存/去重) | | `DATA_ANA_IAM_GRPC_ENDPOINT` | "" | iam gRPC 端点(空=角色降级) | ### 降级模式说明 - **ClickHouse 未配置/不可达**:查询返回骨架数据(空数组 + degraded=true) - **Kafka 未配置**:CDC 消费者不启动,`/readyz` 仍就绪 - **Redis 未配置/不可达**:缓存失效,每次查询重新计算 DataScope - **iam gRPC 未配置/不可达**:基于 JWT 角色 fallback(teacher→CLASS,student→SELF) - **grpcio 未安装**:gRPC server 不启动,HTTP 仍可用 ## 端点清单 ### 基础端点(3 个) | 方法 | 路径 | 说明 | | ---- | ---------- | ----------------------------------------------------------- | | GET | `/` | 根信息(服务名/版本/端点列表) | | GET | `/healthz` | liveness 检查(进程存活即 ok) | | GET | `/readyz` | readiness 检查(5 依赖状态:clickhouse/cdc/redis/iam/grpc) | | GET | `/metrics` | Prometheus 指标 | ### 业务端点(11 个,全部返回 ActionState[T]) | 方法 | 路径 | 说明 | | ---- | -------------------------------------------------- | ------------------------------- | | GET | `/analytics/class/{class_id}/performance` | 班级成绩分析(平均分/及格率) | | GET | `/analytics/class/{class_id}/mastery-distribution` | 班级掌握度分布(三档) | | GET | `/analytics/student/{student_id}/weakness` | 学生薄弱知识点(mastery < 0.6) | | GET | `/analytics/student/{student_id}/mastery` | 学生掌握度明细 | | GET | `/analytics/student/{student_id}/trend` | 学习趋势(历史成绩曲线) | | GET | `/analytics/student/{student_id}/errorbook` | 学生错题本 | | GET | `/analytics/teacher/dashboard` | 教师仪表盘 | | GET | `/analytics/student/dashboard` | 学生仪表盘 | | GET | `/analytics/parent/dashboard` | 家长仪表盘 | | GET | `/analytics/admin/dashboard` | 管理员仪表盘 | | GET | `/analytics/warnings` | 预警列表 | | POST | `/analytics/warnings/trigger` | 手动触发预警 | ### ActionState 信封格式 ```json { "success": true, "data": { ... }, "details": { "degraded": false, "degraded_reason": "" } } ``` 降级时 `degraded=true`,`data` 返回骨架数据(空数组/零值)。 ## gRPC 契约 ### AnalyticsService(12 RPC,端口 50055) | RPC | 类型 | 说明 | | ---------------------- | ---------------- | ----------------------------------- | | GetClassPerformance | unary | 班级成绩分析 | | GetStudentWeakness | unary | 学生薄弱知识点 | | GetLearningTrend | unary | 学习趋势 | | GetTeacherDashboard | unary | 教师仪表盘 | | GetStudentDashboard | unary | 学生仪表盘 | | GetParentDashboard | unary | 家长仪表盘 | | GetAdminDashboard | unary | 管理员仪表盘 | | GetWarnings | unary | 预警列表 | | TriggerWarning | unary | 手动触发预警 | | GetMasteryDistribution | unary | 班级掌握度分布 | | GetStudentMastery | unary | 学生掌握度明细 | | SubscribeMasteryUpdate | server-streaming | 掌握度更新订阅(P5+ AI 个性化通道) | ### HealthService `grpc.health.v1.Health` 标准 K8s 探针。 ### proto 定义 - `packages/shared-proto/proto/analytics.proto`(12 RPC + 全部消息) - `packages/shared-proto/proto/events.proto`(MasteryEvent + AIUsageEvent) - `packages/shared-proto/proto/iam.proto`(GetEffectiveDataScope) ## CDC 事件消费 ### 消费的 7 类事件 | 事件源 | Topic | 处理 | | ------------------------ | ------------------------------------------------- | ------------------------- | | core-edu.exams | `edu-cdc.next_edu_cloud.core_edu_exams` | 写 exam_cache | | core-edu.grades | `edu-cdc.next_edu_cloud.core_edu_grades` | 写 student_dashboard_view | | core-edu.homework | `edu-cdc.next_edu_cloud.core_edu_homework` | 写 student_dashboard_view | | core-edu.attendance | `edu-cdc.next_edu_cloud.core_edu_attendance` | 写 attendance_logs | | content.knowledge_points | `edu-cdc.next_edu_cloud.content_knowledge_points` | 知识点元数据 | | ai.ai_usage | `edu.insight.ai.usage` | 写 ai_usage_log | ### 幂等保证 - **Kafka producer**:`enable_idempotence=True` + `transactional_id` - **Consumer 去重**:Redis SETNX `data_ana:dedup:{event_id}` TTL 7 天 - **ClickHouse 去重**:ReplacingMergeTree(last_updated) + FINAL/argMax 查询 - **手动 commit**:仅在 ClickHouse 写入成功后 commit(at-least-once 语义) ## 发布的派生数据事件 | 事件 | Topic | 触发条件 | Outbox 豁免 | | ---------------- | ------------------------------------- | -------------- | ----------------------- | | MasteryUpdated | `edu.insight.mastery.updated` | 掌握度计算完成 | ✅(004 §12.2 仲裁) | | WarningTriggered | `edu.insight.mastery.updated`(复用) | 预警阈值触发 | ✅(004 §15.3 #6 仲裁) | > Outbox 豁免理由:派生数据事件,无业务状态变更,重复消费幂等。 ## ClickHouse 宽表 DDL 位置:`scripts/clickhouse-init.sql` | 表名 | 引擎 | ORDER BY | 用途 | | ---------------------- | ----------------------------------- | ----------------------------------------------- | -------------- | | student_dashboard_view | ReplacingMergeTree(last_updated) | (student_id, exam_id, knowledge_point_id) | 学情宽表 | | student_errors | ReplacingMergeTree(last_error_time) | (student_id, question_id) | 错题本 | | mastery_snapshot | MergeTree | (student_id, knowledge_point_id, calculated_at) | 掌握度历史快照 | | ai_usage_log | ReplacingMergeTree(occurred_at) | (request_id,) | AI 用量计费 | | attendance_logs | ReplacingMergeTree(occurred_at) | (student_id, class_id, attendance_date) | 考勤记录 | Mock 数据:`scripts/data-ana-mock-data.sql` ## 掌握度算法 ### 公式 ``` mastery = max(WMA, ForgettingCurve) WMA = Σ(w_i * score_i) / Σ(w_i), w_i = 0.6^i (近 5 次,权重递减) ForgettingCurve = last_mastery * exp(-ln(2) * days_since / 30) ``` ### 分类 | 区间 | label | 说明 | | --------- | ----------- | ------ | | ≥ 0.8 | mastered | 已掌握 | | 0.4 - 0.8 | progressing | 进展中 | | < 0.4 | weak | 薄弱 | ### 降级规则 - 近 5 次成绩不足 5 次 → 降级 SIMPLE_AVG - 无历史数据 → 返回 0.0 + label=weak ## 预警类型 | 类型 | 阈值 | severity | 说明 | | --------------- | ------------- | -------- | -------- | | LOW_MASTERY | mastery < 0.4 | WARN | 掌握度低 | | CRITICAL_LOW | mastery < 0.2 | CRITICAL | 严重低 | | SCORE_DROP | 下降 ≥ 20% | WARN | 成绩下降 | | ABSENT_FREQUENT | ≥ 3 次/周 | WARN | 频繁缺勤 | | TREND_DECLINE | 连续 3 次降 | INFO | 趋势下降 | ### 去重 - Redis key:`data_ana:warning:{target_id}:{type}:{date}` - TTL:25 小时(跨日保证) ## DataScope 过滤 ### 6 级 | 级别 | scope_ids 含义 | | -------- | --------------------- | | SELF | 学生本人 ID | | CLASS | 可见 class_id 列表 | | GRADE | 可见 grade_id 列表 | | SCHOOL | 可见 school_id 列表 | | DISTRICT | 可见 district_id 列表 | | ALL | 无过滤 | ### 解析链 ``` 请求 → Redis 缓存(5min)→ iam gRPC GetEffectiveDataScope → 角色降级 fallback ``` ### 角色降级(iam 不可用时) | JWT 角色 | 降级 scope | | -------- | --------------------- | | teacher | CLASS(自己教的班级) | | student | SELF(本人) | | parent | SELF(本人 + 孩子) | | admin | ALL | ## 部署 ### Docker ```bash # 构建(多阶段:builder + runtime) docker build -t data-ana:1.0.0 services/data-ana/ # 运行(需 ClickHouse/Kafka/Redis 可达) docker run -p 3006:3006 -p 50055:50055 \ -e DATA_ANA_CLICKHOUSE_HOST=clickhouse \ -e DATA_ANA_KAFKA_BROKERS=kafka:9092 \ -e DATA_ANA_REDIS_URL=redis://redis:6379 \ data-ana:1.0.0 ``` ### 健康检查 - HTTP `/healthz`:进程存活即 ok - HTTP `/readyz`:检查 5 依赖(clickhouse/cdc/redis/iam/grpc) - gRPC `grpc.health.v1.Health`:K8s 探针 ## 对外契约 - **gRPC proto**:`packages/shared-proto/proto/analytics.proto` - **HTTP OpenAPI**:启动后访问 `/docs` - **CDC topic 命名**:`edu-cdc.next_edu_cloud.`(Debezium 标准) - **派生事件 topic**:`edu.insight.mastery.updated` / `edu.insight.ai.usage` ## 相关文档 - [02-architecture-design.md](./docs/02-architecture-design.md):模块架构设计 v2(仲裁后) - [01-understanding.md](./docs/01-understanding.md):阶段 1 理解确认书 - [004 架构影响地图 §15.3](../../docs/architecture/004_architecture_impact_map.md):coord 仲裁结论 - [coord-cross-review.md](../../docs/architecture/coord-cross-review.md):交叉审查记录 - [known-issues §2.6](../../docs/troubleshooting/known-issues.md):data-ana 已知问题速查