Files
Edu/services/data-ana/README.md
SpecialX ca3780aa24 feat(data-ana): 完整实现 data-ana 数据分析服务
包含 CDC consumer、analytics/mastery/warning service、grpc server、repository、ClickHouse DDL 等
2026-07-10 19:09:27 +08:00

17 KiB
Raw Blame History

data-ana 数据分析服务

版本1.0P4 完整版) 阶段D6 智能洞察领域仲裁已对齐v2 实现版) 端口HTTP 3006Gateway 直连降级)+ 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 消费 → ClickHouseat-least-once + 手动 commit + ReplacingMergeTree 去重)
  • 掌握度算法WEIGHTED_MOVING_AVGw_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/ALLiam gRPC + 角色降级
  • gRPC 12 RPCAnalyticsService含 server-streaming SubscribeMasteryUpdate+ HealthService
  • HTTP 14 端点3 基础 + 11 业务ActionState 信封 + degraded 标记)

架构图

flowchart TB
    subgraph Entry["入口层"]
        HTTP[FastAPI HTTP<br/>:3006 /analytics/* + /healthz + /readyz]
        GRPC[grpc.aio Server<br/>:50055 AnalyticsService 12 RPC]
    end

    subgraph Service["应用服务层"]
        S1[AnalyticsService<br/>4 Dashboard + 班级/趋势查询]
        S2[MasteryService<br/>掌握度计算 + 事件发布]
        S3[WarningService<br/>5 类预警 + Redis 去重]
    end

    subgraph Repo["数据访问层"]
        R1[ClickHouseRepository<br/>FINAL/argMax + DataScope WHERE]
        R2[KafkaProducer<br/>mastery.updated 派生事件]
        R3[IamClient<br/>gRPC GetEffectiveDataScope]
        R4[RedisClient<br/>DataScope 缓存 + 幂等]
    end

    subgraph Consumer["CDC 消费者(后台任务)"]
        C1[CdcConsumer<br/>手动 commit + 7 类事件路由]
        C2[ExamCache<br/>LRU 10000 exam_id→metadata]
    end

    subgraph Storage["存储/总线"]
        CH[(ClickHouse<br/>5 宽表)]
        KAFKA[(Kafka<br/>edu-cdc.* + edu.insight.mastery.updated)]
        IAM[iam gRPC :50052]
        REDIS[(Redis<br/>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-checkinggRPC 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

开发

# 安装依赖
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 角色 fallbackteacher→CLASSstudent→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 信封格式

{
  "success": true,
  "data": { ... },
  "details": {
    "degraded": false,
    "degraded_reason": ""
  }
}

降级时 degraded=truedata 返回骨架数据(空数组/零值)。

gRPC 契约

AnalyticsService12 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.proto12 RPC + 全部消息)
  • packages/shared-proto/proto/events.protoMasteryEvent + AIUsageEvent
  • packages/shared-proto/proto/iam.protoGetEffectiveDataScope

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 producerenable_idempotence=True + transactional_id
  • Consumer 去重Redis SETNX data_ana:dedup:{event_id} TTL 7 天
  • ClickHouse 去重ReplacingMergeTree(last_updated) + FINAL/argMax 查询
  • 手动 commit:仅在 ClickHouse 写入成功后 commitat-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 keydata_ana:warning:{target_id}:{type}:{date}
  • TTL25 小时(跨日保证)

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

# 构建多阶段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.HealthK8s 探针

对外契约

  • gRPC protopackages/shared-proto/proto/analytics.proto
  • HTTP OpenAPI:启动后访问 /docs
  • CDC topic 命名edu-cdc.next_edu_cloud.<table>Debezium 标准)
  • 派生事件 topicedu.insight.mastery.updated / edu.insight.ai.usage

相关文档