# 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<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-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.<table>`（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 已知问题速查
