# 乾坤先机 API 文档

## 在线获取与 AI 同步

文档随当前应用版本提供，使用部署站点的同一域名访问以下稳定入口：

| 入口 | 用途 |
|---|---|
| [`/docs/api`](/docs/api) | 供浏览器阅读的 API 文档网页 |
| [`/docs/api.md`](/docs/api.md) | 供 AI 和脚本获取的完整 Markdown 原文 |
| [`/docs/analysis-result.schema.json`](/docs/analysis-result.schema.json) | 自动研究任务 `result` 的 JSON Schema |
| [`/llms.txt`](/llms.txt) | AI 文档导航与读取约定 |

这些文档入口无需登录；业务接口仍按本文的认证规则校验。推荐 AI 在每次开始调用接口前主动重新读取同域 `/docs/api.md`，以该站点当前返回的契约为准，避免长期使用对话记录或本地副本。接口变更时维护这一份 `docs/API.md`；线上文档随同次源码部署更新，网页和 Markdown 入口使用同一份内容。提供在线入口不代表 AI 会自动轮询，调用方应主动重新获取。

例如应用地址为 `https://your-domain.example` 时，AI 应先获取 `https://your-domain.example/docs/api.md`，再调用 `https://your-domain.example/api/...`。下文的 `http://localhost:38810` 仅为本地示例，实际调用时替换为部署地址。

## 概述

乾坤先机（Market Events）API 用于管理影响股市的重大事件。每个事件包含基本信息、分析和股票推荐。

**设计理念：** 采用两阶段API设计，将"添加事件"和"分析事件"分为两个独立的功能。

## 认证方式

市场事件 CRUD 和人工分析接口接受 **Bearer Token**、已登录的管理员 Session 或 IP 白名单。自动研究管理接口仅接受管理员 Session；任务回调使用每次尝试专用的任务 Token，不能用通用 API Token 代替。

### 获取 Token

在 `config.ini` 文件中配置：

```ini
[api]
token = your-secret-token-here
```

或通过环境变量设置：

```bash
export STRATEGIST_API_TOKEN="your-secret-token-here"
```

### 使用 Token

第三方调用市场事件接口时，在 HTTP 请求头中包含 Bearer Token：

```
Authorization: Bearer your-secret-token-here
```

### 示例

```bash
curl -X GET "http://localhost:38810/api/market-events" \
  -H "Authorization: Bearer your-secret-token-here"
```

### IP 白名单免鉴权

在 `config.ini` 的 `[api]` 段配置 `ip_whitelist`，写入其中的地址调用市场事件 CRUD 和人工分析接口时
无需 Bearer Token：

```ini
[api]
ip_whitelist = 90.96.187.0/24, 10.0.0.0/8
```

`90.96.187.0/24` 表示前三段固定、最后一段任意，覆盖 `90.96.187.0` 到
`90.96.187.255`。若只需放行一个 IP，可直接写 `90.96.187.244`。
修改后需重启应用；若使用环境变量 `STRATEGIST_API_IP_WHITELIST`，应修改环境变量中的网段，
它会覆盖 `config.ini` 中的配置。

```bash
curl -X GET "http://localhost:38810/api/market-events"
```

- 支持单个 IP 与 CIDR 网段，多个条目用英文或中文逗号分隔；写错的条目会在启动时直接报错，
  不会静默忽略。
- 判定地址优先取 `X-Forwarded-For` 最左边一段，该头不存在时回退到 TCP 对端地址。
  **该请求头可被客户端伪造**，只有在反向代理会强制覆盖它时才是可信的。
- 白名单只覆盖市场事件 CRUD 和人工分析接口；用户管理、审计日志与自动研究管理接口始终要求登录管理员，不受白名单影响。
- 免鉴权写入的 `created_by` 与审计日志会记成 `ip:<地址>`。
- 白名单优先判定。未命中时，若提供 `Bearer` 请求头则先验证 Token；Token 无效时不会回退到管理员 Session。没有 `Bearer` 请求头时才使用管理员 Session。

### 认证范围

| 接口范围 | 认证要求 |
|---|---|
| `/api/market-events` 及 `/<id>` 的 CRUD、`/<id>/analysis` | API Bearer Token、管理员 Session、白名单 IP 三者之一 |
| `/api/users`、`/api/audit-logs`、自动研究设置/任务/运行记录/状态 | 管理员 Session；API Token 和白名单不能替代 |
| `/api/me` | 已登录的有效用户 Session |
| `/api/analysis-tasks/<id>/result` | 本次尝试的专用任务 Bearer Token |
| `/healthz`、文档入口 | 公开只读，无需认证 |

## 基础信息

- **Base URL:** `http://localhost:38810/api`
- **市场事件认证:** Bearer Token，或浏览器管理员 Session，或 `ip_whitelist` 命中的来源 IP
- **数据格式:** JSON
- **字符编码:** UTF-8（完全支持中文）

## API 端点

### 1. 创建事件（第一阶段）

创建事件的基本信息（日期、标题、内容）。分析和股票推荐稍后单独添加。

**端点:** `POST /api/market-events`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例（确切日期）:**
```bash
curl -X POST "http://localhost:38810/api/market-events" \
  -H "Authorization: Bearer your-secret-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "event_date": "2026-10-06",
    "title": "央行降息25个基点",
    "content": "中国人民银行今日宣布降息25个基点，旨在刺激经济增长。"
  }'
```

**请求示例（连续日期）:**
```bash
curl -X POST "http://localhost:38810/api/market-events" \
  -H "Authorization: Bearer your-secret-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "event_date": "2026-10-08",
    "event_date_end": "2026-10-10",
    "date_type": "range",
    "title": "中央经济工作会议",
    "content": "为期三天的会议将讨论明年经济政策方向。"
  }'
```

**请求示例（传言事件）:**
```bash
curl -X POST "http://localhost:38810/api/market-events" \
  -H "Authorization: Bearer your-secret-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "event_date": "2026-10-15",
    "date_type": "rumor",
    "title": "传监管层将出台新政",
    "content": "据市场传言，监管层可能在近期出台房地产调控新政策。"
  }'
```

**请求体（确切单日事件）:**
```json
{
  "event_date": "2026-10-06",
  "title": "央行降息25个基点",
  "content": "中国人民银行今日宣布降息25个基点，旨在刺激经济增长。此次降息是今年以来第二次下调利率。"
}
```

**请求体（连续日期事件）:**
```json
{
  "event_date": "2026-10-08",
  "event_date_end": "2026-10-10",
  "date_type": "range",
  "title": "中央经济工作会议召开",
  "content": "为期三天的中央经济工作会议于10月8日至10日在北京召开，部署2027年经济工作重点。"
}
```

**请求体（传言事件）:**
```json
{
  "event_date": "2026-10-15",
  "date_type": "rumor",
  "title": "传监管层将出台新政",
  "content": "据市场传言，监管层可能在近期出台房地产调控新政策，具体时间未定。"
}
```

**请求体（预估事件）:**
```json
{
  "event_date": "2026-11-01",
  "date_type": "estimated",
  "title": "预计11月公布GDP数据",
  "content": "根据惯例，国家统计局预计将在11月中旬公布第三季度GDP数据。"
}
```

**字段说明:**
- `event_date` (string, required): 事件日期，ISO格式 (YYYY-MM-DD)，可以是未来日期
- `event_date_end` (string, optional): 结束日期，仅用于连续事件（date_type='range'）
- `date_type` (string, optional): 日期类型，默认 `exact`
  - `exact`: 确切日期（默认）
  - `range`: 连续事件（需要 event_date_end）
  - `rumor`: 传言事件（日期为预期或传言出现时间）
  - `estimated`: 预估事件（日期为大概时间）
- `title` (string, required): 事件标题，1-200字符
- `content` (string, required): 事件内容详情

**注意:** 创建的事件初始状态为 `pending`（尚无正式报告），添加分析后自动更新为 `analyzed`（已分析）。启用自动研究时，开始日期为今天或未来的事件会同时进入初筛队列；创建成功不代表评分或研究已经完成。

**日期类型使用示例:**
- 确切单日: `{"event_date": "2026-10-08", "date_type": "exact"}`
- 连续事件: `{"event_date": "2026-10-08", "event_date_end": "2026-10-10", "date_type": "range"}`
- 传言事件: `{"event_date": "2026-10-15", "date_type": "rumor"}`
- 预估事件: `{"event_date": "2026-11-01", "date_type": "estimated"}`

**成功响应:** `201 Created`
```json
{
  "ok": true,
  "event": {
    "id": 1,
    "event_date": "2026-10-06",
    "event_date_end": null,
    "date_type": "exact",
    "title": "央行降息25个基点",
    "content": "中国人民银行今日宣布降息25个基点...",
    "analysis": null,
    "status": "pending",
    "stocks": [],
    "created_at": "2026-10-06T13:30:00+08:00",
    "updated_at": "2026-10-06T13:30:00+08:00",
    "created_by": "api"
  }
}
```

**连续事件响应示例:**
```json
{
  "ok": true,
  "event": {
    "id": 2,
    "event_date": "2026-10-08",
    "event_date_end": "2026-10-10",
    "date_type": "range",
    "title": "中央经济工作会议召开",
    "content": "为期三天的中央经济工作会议...",
    "analysis": null,
    "status": "pending",
    "stocks": [],
    "created_at": "2026-10-06T13:35:00+08:00",
    "updated_at": "2026-10-06T13:35:00+08:00",
    "created_by": "api"
  }
}
```

**错误响应:**
- `400 Bad Request`: 字段验证失败
- `401 Unauthorized`: Token 无效，或没有可用的登录凭据且未命中白名单
- `403 Forbidden`: Session 用户不是管理员
- `409 Conflict`: 启用语义去重时检测到相似事件，`code=duplicate_event`
- `503 Service Unavailable`: 使用 Bearer 认证但服务器未配置 API Token

### 2. 添加分析（第二阶段）

为已创建的事件添加分析和股票推荐。这是一个独立的操作，与创建事件分离。

**端点:** `POST /api/market-events/<event_id>/analysis`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例:**
```bash
curl -X POST "http://localhost:38810/api/market-events/1/analysis" \
  -H "Authorization: Bearer your-secret-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "analysis": "此次降息将利好房地产和基建板块...",
    "stocks": [
      {
        "stock_symbol": "600000",
        "stock_name": "浦发银行",
        "recommendation_reason": "降息直接利好银行净息差改善"
      }
    ]
  }'
```

**请求体:**
```json
{
  "analysis": "此次降息将利好房地产和基建板块，预计相关股票将迎来上涨机会。银行板块短期内净息差承压，但中长期有利于信贷扩张。",
  "stocks": [
    {
      "stock_symbol": "600000",
      "stock_name": "浦发银行",
      "recommendation_reason": "降息直接利好银行净息差改善，信贷扩张预期增强"
    },
    {
      "stock_symbol": "000002",
      "stock_name": "万科A",
      "recommendation_reason": "房地产龙头企业，降息降低融资成本，刺激购房需求"
    }
  ]
}
```

**字段说明:**
- `analysis` (string, required): 事件分析文本
- `stocks` (array, required): 股票推荐列表，0-20个，允许空数组
  - `stock_symbol` (string, required): 6位数字A股代码（如600000、000002）
  - `stock_name` (string, required): 股票名称，1-100字符
  - `recommendation_reason` (string, required): 推荐原因

**成功响应:** `200 OK`
```json
{
  "ok": true,
  "event": {
    "id": 1,
    "event_date": "2026-10-06",
    "title": "央行降息25个基点",
    "content": "中国人民银行今日宣布降息25个基点...",
    "analysis": "此次降息将利好房地产和基建板块...",
    "stocks": [
      {
        "id": 1,
        "stock_symbol": "600000",
        "stock_name": "浦发银行",
        "recommendation_reason": "降息直接利好银行净息差改善"
      },
      {
        "id": 2,
        "stock_symbol": "000002",
        "stock_name": "万科A",
        "recommendation_reason": "房地产龙头企业，降息降低融资成本"
      }
    ],
    "created_at": "2026-10-06T13:30:00+08:00",
    "updated_at": "2026-10-06T13:35:00+08:00",
    "created_by": "admin"
  }
}
```

**注意:** 
- 此操作会替换事件的所有现有股票推荐
- 股票顺序按照请求中的顺序保存
- 此操作会取消该事件仍在排队或执行的自动任务，并将报告标记为人工更新；旧自动结果不能覆盖此次更新

### 3. 列出事件

获取所有事件列表，支持分页和搜索。

**端点:** `GET /api/market-events`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例:**
```bash
curl -X GET "http://localhost:38810/api/market-events?status=pending&limit=10" \
  -H "Authorization: Bearer your-secret-token-here"
```

**查询参数:**
- `limit` (integer, optional): 每页数量，1-100，默认20
- `offset` (integer, optional): 偏移量，默认0
- `q` (string, optional): 搜索关键词，在标题、内容、分析中搜索
- `status` (string, optional): 筛选状态，可选值: `pending`（待分析）或 `analyzed`（已分析）
- `date_scope` (string, optional): `upcoming`（今天及未来，含尚未结束的连续事件）、`week`（含今天的7天）、`month`（含今天的30天）、`history`（已结束的历史事件）、`all`；默认 `all`
- `order` (string, optional): `date_asc`（由近到远，同日按重要性排序）或 `date_desc`（日期倒序，兼容默认值）
- `outcome` (string, optional): `unprocessed`、`processing`、`researched`、`below_threshold`、`excluded`、`needs_review`、`failed`；分别表示未开始、处理中、已出报告、未达门槛、已排除、待核实、执行失败
- `min_score` (integer, optional): 最低重要性分，0-100；无评分事件不满足此筛选
- `screening_status` (string, optional): `not_screened`、`queued`、`running`、`scored`、`low_score`、`needs_confirmation`、`skipped`、`failed`
- `task_status` (string, optional): 当前输入版本最新任务的原始状态，`queued`、`running`、`completed`、`needs_confirmation`、`skipped`、`failed`、`cancelled`；`retrying` 是任务中心的展示状态，此参数不接受 `retrying`

**示例请求:**
```bash
# 获取所有事件
GET /api/market-events?limit=10&offset=0

# 搜索包含"降息"的事件
GET /api/market-events?q=降息

# 只获取待分析的事件
GET /api/market-events?status=pending

# 只获取已分析的事件
GET /api/market-events?status=analyzed

# 今天及未来，按最近日期升序；这是未来事件视图的推荐查询
GET /api/market-events?date_scope=upcoming&order=date_asc&limit=20&offset=0

# 含今天的未来30天内，需要核实的事件
GET /api/market-events?date_scope=month&order=date_asc&outcome=needs_review
```

**成功响应:** `200 OK`
```json
{
  "ok": true,
  "items": [
    {
      "id": 1,
      "event_date": "2026-10-06",
      "event_date_end": null,
      "date_type": "exact",
      "title": "央行降息25个基点",
      "content": "...",
      "analysis": "...",
      "status": "analyzed",
      "stocks": [],
      "display_date": "2026-10-06",
      "is_ongoing": false,
      "workflow": {
        "importance_score": null,
        "screening_status": "not_screened",
        "reason": "",
        "summary": "",
        "sources": [],
        "uncertainties": [],
        "as_of_time": null,
        "latest_task": null,
        "analysis_stale": false,
        "execution_error": "",
        "analysis_sources": [],
        "assessment": null,
        "outcome": "researched"
      },
      "created_at": "2026-10-06T13:30:00+08:00",
      "updated_at": "2026-10-06T13:35:00+08:00",
      "created_by": "admin"
    }
  ],
  "total": 1,
  "limit": 10,
  "offset": 0,
  "today": "2026-10-20",
  "outcome_counts": {
    "unprocessed": 0,
    "processing": 0,
    "researched": 1,
    "below_threshold": 0,
    "excluded": 0,
    "needs_review": 0,
    "failed": 0,
    "all": 1
  },
  "date_counts": {"2026-10-06": 1}
}
```

后台未来事件视图使用 `date_scope=upcoming&order=date_asc`；API 为兼容已有调用，省略参数时仍为 `date_scope=all&order=date_desc`。所有日期窗口以 `Asia/Shanghai` 的今天为准。连续事件开始日期早于今天、结束日期不早于今天时，`is_ongoing=true`、`display_date` 为今天，筛选和排序也使用该展示日期。自动研究只按开始日期判断：开始日期不早于今天可受理；已在之前开始的连续事件即使仍在进行也不受理。

`total` 是应用所有筛选（包括 `outcome`）后的事件数；`items` 只包含当前分页。`outcome_counts` 遵守日期、搜索、状态、分数等其他筛选，唯独忽略当前 `outcome`，包含七种结果和 `all` 合计。`date_counts` 是应用全部筛选后各 `display_date` 的数量。两项均在分页之前统计，同一天跨页时可显示完整数量；这里的“全局计数”不表示忽略日期或搜索条件。

事件 `workflow.outcome` 与结果筛选含义一致，原有 `status=pending` 仅表示尚无正式报告，不能用来判断任务是否执行；`status=analyzed` 的旧报告也可能因输入变化而过期，应同时检查 `workflow.analysis_stale`。字段值不合法或数值参数无法解析时返回 `400`，响应为 `{"ok":false,"error":"..."}`，此接口不保证带 `code` 字段。

### 4. 获取单个事件

获取指定事件的详细信息，包含所有股票推荐。

**端点:** `GET /api/market-events/<event_id>`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例:**
```bash
curl -X GET "http://localhost:38810/api/market-events/1" \
  -H "Authorization: Bearer your-secret-token-here"
```

**成功响应:** `200 OK`
```json
{
  "ok": true,
  "event": {
    "id": 1,
    "event_date": "2026-10-06",
    "title": "央行降息25个基点",
    "content": "...",
    "analysis": "...",
    "stocks": [],
    "created_at": "2026-10-06T13:30:00+08:00",
    "updated_at": "2026-10-06T13:35:00+08:00",
    "created_by": "admin"
  }
}
```

**错误响应:**
- `404 Not Found`: 事件不存在

### 5. 更新事件基本信息

更新事件的基本信息（日期、标题、内容），保留已有分析和股票推荐。实际内容发生变化时会取消活动自动任务、重置评分并更新输入版本；已有报告会标记为过期，符合自动研究日期条件时重新进入初筛队列。

**端点:** `PATCH /api/market-events/<event_id>`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例:**
```bash
curl -X PATCH "http://localhost:38810/api/market-events/1" \
  -H "Authorization: Bearer your-secret-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "央行降息50个基点（更新）"
  }'
```

**请求体:** （只需包含要更新的字段）
```json
{
  "title": "央行降息25个基点（更新）",
  "content": "更新后的内容..."
}
```

**字段说明:**
- `event_date` (string, optional): 新的事件日期
- `event_date_end` (string, optional): 新的结束日期（仅用于 date_type='range'）
- `date_type` (string, optional): 新的日期类型（exact/range/rumor/estimated）
- `title` (string, optional): 新的标题
- `content` (string, optional): 新的内容

**注意:** 至少需要提供一个非 `null` 字段。更新后的 `date_type=range` 必须有有效的结束日期（可沿用已有值），且结束日期不早于开始日期。`null` 表示不更新；需要清空已有结束日期时可传空字符串，但仅限更新后的日期类型不是 `range`。

**成功响应:** `200 OK`
```json
{
  "ok": true,
  "event": {
    "id": 1,
    "event_date": "2026-10-06",
    "title": "央行降息25个基点（更新）",
    "content": "更新后的内容...",
    "analysis": "...",
    "stocks": [],
    "created_at": "2026-10-06T13:30:00+08:00",
    "updated_at": "2026-10-06T14:00:00+08:00",
    "created_by": "admin"
  }
}
```

### 6. 删除事件

删除事件及其所有关联的股票推荐。

**端点:** `DELETE /api/market-events/<event_id>`

**认证:** API Bearer Token / 管理员 Session / 白名单 IP

**请求示例:**
```bash
curl -X DELETE "http://localhost:38810/api/market-events/1" \
  -H "Authorization: Bearer your-secret-token-here"
```

**成功响应:** `200 OK`
```json
{
  "ok": true
}
```

**错误响应:**
- `404 Not Found`: 事件不存在

**注意:** 删除操作会同时删除所有关联的股票推荐，并取消活动自动任务；任务和运行历史仍保留，任务中心会标记事件已删除。

## 数据验证规则

### 事件字段

- **event_date:** 必须是有效的ISO日期格式（YYYY-MM-DD），可以是未来日期
- **title:** 1-200字符，不能为空
- **content:** 必填，不能为空
- **analysis:** 添加分析时必填，不能为空

### 股票字段

- **stock_symbol:** 必须是6位数字（如600000、000002、688981）
- **stock_name:** 1-100字符，不能为空
- **recommendation_reason:** 必填，不能为空
- **数量限制:** 每个事件最多20只股票，允许空数组；自动深研最多5只

## 错误响应格式

本文业务接口可预期的 JSON 错误至少含 `ok=false` 和 `error`。其中部分验证、事件不存在和任务回调错误还含机器可读的 `code`，调用方应将 `code` 视为可选字段，并结合 HTTP 状态码处理：

```json
{
  "ok": false,
  "error": "错误描述信息",
  "code": "error_code"
}
```

**常见错误标识:**

| HTTP 状态 | `error` 或 `code` | 含义 |
|---|---|---|
| 400 | `code=invalid_event` | 事件写入验证失败；列表参数错误仅返回 `error` 文本 |
| 400 | `code=invalid_analysis` | 自动研究管理参数无效 |
| 401 | `error=not_authenticated` | 缺少有效登录 Session，且没有其他可用认证 |
| 401 | `error=invalid_token` | 通用 API Bearer Token 无效 |
| 403 | `error=admin_required` | 当前 Session 用户不是管理员 |
| 404 | `code=event_not_found` | 事件不存在 |
| 409 | `code=duplicate_event` | 创建事件时检测到语义重复 |
| 409 | `code=event_not_upcoming` | 事件开始日期早于今天，不能发起自动研究 |
| 503 | `error=api_token_not_configured` | 使用 Bearer 认证，但服务器未配置通用 API Token |

认证失败通常没有 `code`，例如 `{"ok":false,"error":"not_authenticated"}`。任务回调还可能返回 `401/409/422`，见下文的专用协议。

## 审计日志

所有修改操作（创建、更新、删除事件，添加分析）都会自动记录审计日志，包含：
- 操作用户
- 操作类型
- 操作详情（事件ID、标题等）
- IP地址
- User Agent
- 时间戳

审计日志可通过 `GET /api/audit-logs` 查询（需要管理员权限）。

## 使用示例

### 完整工作流程

**步骤1: 登录**
```bash
curl -X POST http://localhost:38810/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin&password=admin123" \
  -c cookies.txt
```

**步骤2: 创建事件**
```bash
curl -X POST http://localhost:38810/api/market-events \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{
    "event_date": "2026-10-06",
    "title": "央行降息25个基点",
    "content": "中国人民银行今日宣布降息25个基点，旨在刺激经济增长。"
  }'
```

**步骤3: 添加分析和股票推荐**
```bash
curl -X POST http://localhost:38810/api/market-events/1/analysis \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{
    "analysis": "此次降息将利好房地产和基建板块。",
    "stocks": [
      {
        "stock_symbol": "600000",
        "stock_name": "浦发银行",
        "recommendation_reason": "降息直接利好银行净息差改善"
      }
    ]
  }'
```

**步骤4: 查询所有事件**
```bash
curl "http://localhost:38810/api/market-events?limit=20" -b cookies.txt
```

**步骤5: 搜索事件**
```bash
curl "http://localhost:38810/api/market-events?q=降息" -b cookies.txt
```

**步骤6: 更新事件**
```bash
curl -X PATCH http://localhost:38810/api/market-events/1 \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{"title": "央行降息25个基点（已更新）"}'
```

## 技术特性

- **中文支持:** 完全支持UTF-8中文文本
- **原子性操作:** 股票更新在单个事务中完成，保证数据一致性
- **级联删除:** 删除事件时自动删除关联的股票推荐
- **索引优化:** 事件日期、创建时间、股票代码都有索引，查询效率高
- **顺序保持:** 股票推荐按用户指定的顺序存储和返回

## 数据库架构

### market_events 表
- `id`: 主键
- `event_date`: 事件日期（可为未来）
- `title`: 标题（1-200字符）
- `content`: 内容
- `analysis`: 分析（可为NULL）
- `created_at`: 创建时间
- `updated_at`: 更新时间
- `created_by`: 创建人用户名

### market_event_stocks 表
- `id`: 主键
- `event_id`: 关联事件ID
- `stock_symbol`: 股票代码（6位数字）
- `stock_name`: 股票名称
- `recommendation_reason`: 推荐原因
- `order_index`: 排序索引（保持用户指定顺序）

## Codex Skill 自动评分与研究

自动研究由独立 worker 调度，Skill 通过[任务结果回调](#任务结果回调)入库。安装说明在仓库文件 `docs/EVENT_ANALYSIS.md`；自动研究的结果 Schema 可直接读取 [`/docs/analysis-result.schema.json`](/docs/analysis-result.schema.json)，内容来自仓库的 `skills/moushi-event-analysis/references/output-schema.json`。

### 管理接口

以下接口要求已登录的管理员 Session，不接受采集 Token 或 IP 白名单代替管理员身份：

| 接口 | 请求 / 返回 |
|---|---|
| `GET /api/analysis-settings` | 返回 `importance_threshold`（默认60）、当前 `screening_prompt` / `analysis_prompt`，以及默认提示词和只读执行规则 |
| `PATCH /api/analysis-settings` | 部分更新 `importance_threshold`、`screening_prompt`、`analysis_prompt`；门槛为整数0—100，每段提示词为非空字符串、最多30000字符；非法字段或值整次拒绝 |
| `POST /api/market-events/<id>/analysis-tasks` | `{"action":"rescreen"}`、`{"action":"retry"}` 或 `{"action":"analyze","force":true}`；返回202和任务，不重复创建活动任务 |
| `GET /api/market-events/<id>/analysis-runs` | `items` 返回最近100次运行尝试，`tasks` 返回任务记录（包括尚未开始或直接跳过的任务）；含状态、时间及错误摘要，不含凭据 |
| `GET /api/analysis-tasks` | 任务中心：`q`、`stage=screen/analyze`、`status`、`limit`（1—100，默认50）、`offset`；按最近更新时间倒序，返回 `items/total/counts` |
| `GET /api/analysis-status` | 全局任务计数、最近完成记录，以及执行服务状态、心跳、暂停原因和当前任务 |

管理员可在“系统配置”（`/admin/settings`）直接查看和修改初筛、股票分析的完整研究提示词。GET/PATCH 均返回 `default_screening_prompt`、`default_analysis_prompt`、`max_prompt_length`、`execution_instructions`、`skill_instructions`，分别供恢复默认及查看固定执行约束、Skill 共用规则。提示词保存在数据库，worker 每次启动任务前读取；保存后不需要重启 worker，正在执行的任务保持原配置。仅更新提示词不会重新分流、重跑或改写已有分析；历史事件需要手动重新初筛或重新研究。只有修改门槛才复用有效评分重新分流。

管理员研究提示词替代对应阶段的默认研究规范；共用规则、未来事件核验、五项评分字段和上限、最多5只股票、结果 Schema 与任务回调仍由执行器和 Skill 约束。修改需使用管理员 Session；采集 Token、任务 Token 或 IP 白名单不能修改提示词。

事件列表额外支持 `min_score`（0—100）、`screening_status`、`task_status`；与原有筛选共同作用于分页和 `total`。
事件列表和详情增加 `workflow`，包含 `importance_score`、`screening_status`、`reason`、`summary`、
`sources`、`uncertainties`、`as_of_time`、`latest_task`、`analysis_stale`、`outcome`、`execution_error`，以及当前自动报告的 `analysis_sources` / `assessment`。创建接口的即时响应不含 `workflow`；需要完整任务状态时再查询列表或详情。

当前自动深研报告的 `stocks[]` 还返回逐股说明，列表与详情保持一致：

| 字段 | 内容 |
|---|---|
| `recommendation_reason` | 用户直接看到的潜在受益理由：标明“经营受益”或“预期映射”，解释事件如何联系公司真实业务，说明关键条件；预期映射不等于已确认订单或利润 |
| `business_connection` | 展开的经营或预期传导路径，区分公司已核实的产品、业务、项目或权益与条件推断；只有相应证据支持时才讨论订单、收入利润等经营改善 |
| `evidence` | `[{"fact":"已核实的公司业务事实","source_url":"https://example.com/disclosure","strength":"strong"}]`；强度为 `strong/medium/weak` |
| `risks` | 该公司受益路径的成立条件、范围限制和失效风险字符串数组 |

详细说明从当前已发布且输入版本一致的研究记录恢复，不新增数据库列；已有完整自动结果无需重跑即可返回。较新但尚未发布的结果不会替换当前依据。输入变化或人工更新分析后，不把旧自动报告的业务证据拼到新内容上；既有手工接口仍接收原来的三个股票字段，手工记录至少直接展示其 `recommendation_reason`，缺失详情时不编造说明。

初筛状态包括 `not_screened`、`queued`、`running`、`scored`、`low_score`、`needs_confirmation`、`skipped`、`failed`；
任务状态包括 `queued`、`running`、`completed`、`needs_confirmation`、`skipped`、`failed`、`cancelled`。
事件原有 `pending/analyzed` 状态保持兼容。低分属于正常初筛完成，待确认没有评分。

重要性分衡量未来预期催化与风险研究价值，重大利空也可高分，未揭晓结果不是证据不足。五项字段、权重和60分门槛不变。深研报告可以包含受压行业和双向影响，但 `stocks` 仍只列潜在受益股、允许为空；分数不因没有受益股而机械降低。现有字符串字段承载影响性质，不新增JSON字段；历史结果保持原样。

任务中心用 `display_status` 表示面向用户的执行状态：`queued/running/retrying/completed/needs_confirmation/skipped/failed/cancelled`；`GET /api/analysis-tasks` 的 `status` 参数也按这些展示状态筛选。自动重试期间显示 `retrying`，重试耗尽显示 `failed`，业务证据不足显示 `needs_confirmation`；兼容识别旧记录中以 `needs_confirmation` 保存的技术失败。原始 `status` 保留。任务包含入队、开始、结束、更新时间和 `next_retry_at`，以及 `attempts/error/execution_error`、最近一次尝试耗时 `duration_seconds`、`result_summary/result_reason/importance_score`。任务列表额外返回 `event_title/event_date/event_deleted/can_retry`。

任务列表的 `total` 遵守当前筛选，`counts` 则始终按全库任务统计、不受 `q/status/stage` 或分页影响，包含各展示状态及 `total`。事件列表的 `outcome_counts` 按事件统计，口径见“列出事件”，两者不能互相替代。

`workflow.reason` 保留初筛业务理由，`workflow.execution_error` 单独说明执行异常。`GET /api/analysis-status` 返回 `enabled/worker/counts/last_completed_at/recent_completed`；`recent_completed` 是最近五条终态任务，可能包括待确认、跳过、失败和取消。`worker.status` 为 `online/paused/offline/unknown`：未收到执行服务心跳时显示未知，心跳超过 `max(120秒, 3×轮询间隔)` 显示离线；关闭自动分析时显示暂停。`worker` 同时含 `heartbeat_at/reason/current_task_id`。Web 的 `/healthz` 不代表后台执行服务在线。

### 任务结果回调

提交前读取当前站点的[结果 JSON Schema](/docs/analysis-result.schema.json)，该 Schema 约束下列请求体中的 `result` 对象。

`POST /api/analysis-tasks/<task_id>/result` 使用该次尝试专用的 `Authorization: Bearer <task-token>`。
请求体为 `{"attempt_id":"32位十六进制标识","result":{...}}`。Cookie、采集 Token 和 IP 白名单均不能代替任务凭据。

`result.stage` 为 `screen` 或 `analyze`；`outcome` 为 `completed`、`needs_confirmation`、`skipped` 或 `failed`。
初筛分项上限依次为25/20/20/20/15，服务端求和后按门槛创建深研任务；深研允许0—5只股票。
完成结果必须带来源，`event_status=upcoming`，`verification_status` 为 `confirmed` 或 `partially_confirmed`；已开始的事件不能作为完成结果提交。服务端日期资格检查按开始日期不早于 `Asia/Shanghai` 的今天执行，研究端仍须核实事件实际状态。缺证据不以低分提交。

`as_of_time` 必须是带时区的 RFC3339 时间，不得超前服务器时间超过300秒，也不得早于本次尝试开始时间60秒以上。来源 URL 仅接受不含用户名或密码的 HTTP(S) 地址。

成功返回 `{"ok":true,"task":{...},"run_id":1,"importance_score":60}`。
同次尝试重复提交相同内容返回原确认；不同内容或旧任务返回409，格式错误返回422，凭据无效返回401。
历史、当前分析与任务终态原子提交，CLI正常退出不能代替回调成功。

公开只读自检 `GET /healthz` 返回 `{"ok":true,"service":"strategist"}`。

## 版本历史

- **v1.1** (2026-10-18): 新增联网初筛、60分门槛、Skill API回写、独立worker、管理任务与运行历史；人工分析允许空股票数组。

- **v1.0** (2026-10-06): 初始版本
  - 实现两阶段API设计（创建事件 + 添加分析）
  - 支持完整的CRUD操作
  - 中文文本完全支持
  - A股代码验证
  - 审计日志记录

## 联系方式

如有问题或建议，请联系开发团队。
