# 数据库设计

## 日期时间类型规范

**全部使用 `TIMESTAMPTZ`**。固定日期字段（出生日期、发表日期、批准日期、排期日期）用 `DATE`。

```sql
-- ✅ 正确
created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
pub_date    DATE          -- 文献发表日期不精确到时间

-- ❌ 禁止
created_at  TIMESTAMP     -- 不知道时区
created_at  INTEGER       -- Unix时间戳，人不可读，SQL 中日期运算困难
```

---

## ER 关系总图

```
tenants ──1:N── user_tenants ──N:1── users
   │                                    │
   ├── teams ──N:M── team_members       ├── user_subscriptions ── global_tags
   ├── invitations                      ├── user_journal_subscriptions ── global_journals
   ├── export_jobs                      ├── user_feed ── global_literature
   ├── shared_folders                   ├── user_literature ── global_literature
   ├── journal_club_queue               ├── user_notes ── global_literature
   ├── approval_workflows               ├── user_pdf_highlights
   ├── api_keys                         ├── user_folders
   ├── tenant_subscriptions             ├── user_feedback
   ├── subscription_events              └── systematic_reviews ── review_literatures
   ├── payment_orders                   
   └── ai_provider_config               
                                       
global_literature ──N:M── global_literature_tags ──N:M── global_tags
       │                                                       │
       ├── drug_approvals(pmid)                                ├── drug_approvals(cancer_type_tag)
       ├── guideline_evidence                                  ├── guideline_versions(cancer_type_tag)
       ├── review_literatures                                  └── user_subscriptions(tag)
       └── global_journals(issn)

user_journal_subscriptions ──N:1── global_journals

mdt_cases ──1:N── mdt_decisions ──1:N── mdt_evidence
                                      └── global_literature
```

---

## 一、基础架构表

### 1.1 tenants — 租户

```sql
CREATE TABLE tenants (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),    -- 租户ID
    name            VARCHAR(200) NOT NULL,                         -- 租户名称
    slug            VARCHAR(100) UNIQUE NOT NULL,                  -- 唯一标识符（用于URL）
    hospital_name   VARCHAR(200),                                  -- 医院名称
    department_name VARCHAR(200),                                  -- 科室名称
    plan_type       VARCHAR(30) NOT NULL DEFAULT 'free',           -- 套餐类型：free/pro/team/enterprise
    is_personal     BOOLEAN NOT NULL DEFAULT TRUE,                 -- 是否个人租户
    status          VARCHAR(20) NOT NULL DEFAULT 'active',         -- 状态：active/inactive/suspended
    logo_url        VARCHAR(500),                                  -- Logo 图片 URL
    primary_color   VARCHAR(7) DEFAULT '#1a5276',                  -- 主题色（十六进制）
    settings        JSONB NOT NULL DEFAULT '{}',                   -- 租户级 JSON 配置
    max_members     INTEGER NOT NULL DEFAULT 1,                    -- 最大成员数
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),            -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()             -- 更新时间
);
```

### 1.2 users — 用户

```sql
CREATE TABLE users (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 用户ID
    email             VARCHAR(320) NOT NULL UNIQUE,                -- 邮箱（登录账号）
    hashed_password   VARCHAR(128),                                -- 密码哈希（SSO用户可为NULL）
    display_name      VARCHAR(150) NOT NULL,                       -- 显示名称
    title             VARCHAR(100),                                -- 职称/头衔
    avatar_url        VARCHAR(500),                                -- 头像 URL
    phone             VARCHAR(30),                                 -- 手机号
    is_active         BOOLEAN NOT NULL DEFAULT TRUE,               -- 是否激活
    is_superuser      BOOLEAN NOT NULL DEFAULT FALSE,              -- 是否超级管理员
    sso_provider      VARCHAR(20),                                 -- SSO 提供商：wechat/oauth
    sso_subject_id    VARCHAR(255),                                -- SSO 主体 ID
    email_verified    BOOLEAN NOT NULL DEFAULT FALSE,              -- 邮箱是否已验证
    phone_verified    BOOLEAN NOT NULL DEFAULT FALSE,              -- 手机是否已验证
    notify_email      BOOLEAN NOT NULL DEFAULT TRUE,               -- 是否接收邮件通知
    digest_frequency  VARCHAR(10) NOT NULL DEFAULT 'daily',        -- 摘要频率：daily/weekly/never
    last_login_at     TIMESTAMPTZ,                                 -- 最后登录时间
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX idx_users_email ON users(email);
```

用户通知偏好（`notify_email`, `digest_frequency`）和验证状态（`email_verified`, `phone_verified`）存储在 users 表内，不单独建通知表。

### 1.3 user_tenants — 用户-租户关联

```sql
CREATE TABLE user_tenants (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 关联ID
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    role        VARCHAR(20) NOT NULL DEFAULT 'viewer',           -- 角色：viewer/member/admin/owner
    is_default  BOOLEAN NOT NULL DEFAULT FALSE,                  -- 是否为该用户的默认租户
    joined_at   TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 加入时间
    UNIQUE (user_id, tenant_id)
);
CREATE INDEX idx_ut_user ON user_tenants(user_id);
CREATE INDEX idx_ut_user_tenant ON user_tenants(user_id, tenant_id);
```

### 1.4 login_logs — 登录日志

```sql
CREATE TABLE login_logs (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 日志ID
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id   UUID REFERENCES tenants(id) ON DELETE SET NULL,             -- 租户ID（可能为空）
    ip_address  VARCHAR(45),                                     -- 登录IP（支持IPv6）
    user_agent  VARCHAR(500),                                    -- 用户代理字符串
    success     BOOLEAN NOT NULL DEFAULT TRUE,                   -- 是否登录成功
    fail_reason VARCHAR(200),                                    -- 失败原因
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()               -- 登录时间
);
CREATE INDEX idx_ll_user_created ON login_logs(user_id, created_at);
```

---

## 二、文献核心表（全局库，所有租户共享）

### 2.1 global_literature — 全局文献库

```sql
CREATE TABLE global_literature (
    id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 文献ID（内部）
    pmid                  INTEGER UNIQUE NOT NULL,                     -- PubMed ID 【PubMed: MedlineCitation/PMID】
    title                 TEXT NOT NULL,                               -- 标题 【PubMed: Article/ArticleTitle】
    abstract              TEXT,                                        -- 摘要 【PubMed: Article/Abstract/AbstractText】
    authors               JSONB NOT NULL DEFAULT '[]',                 -- 作者列表 [{family,given,affiliations}] 【PubMed: Article/AuthorList/Author】
    doi                   VARCHAR(500),                                -- DOI 标识符 【PubMed: Article/ELocationID[@EIdType="doi"] 或 ArticleId[@IdType="doi"]】
    journal               VARCHAR(500),                                -- 期刊全名 【PubMed: Article/Journal/Title】
    journal_issn          VARCHAR(20),                                 -- 期刊 ISSN 【PubMed: Article/Journal/ISSN】
    journal_iso           VARCHAR(100),                                -- NLM ISO 缩写（如 "N Engl J Med"）【PubMed: Article/Journal/ISOAbbreviation】
    volume                VARCHAR(50),                                 -- 卷号 【PubMed: Article/Journal/JournalIssue/Volume】
    issue                 VARCHAR(50),                                 -- 期号 【PubMed: Article/Journal/JournalIssue/Issue】
    pages                 VARCHAR(100),                                -- 页码 【PubMed: Article/Pagination/MedlinePgn】
    pub_date              DATE,                                        -- 纸质出版日期（期刊卷期日期，若纯电子刊则为电子日期）【PubMed: Article/Journal/JournalIssue/PubDate 组合 Year+Month+Day】
    pub_year              INTEGER,                                     -- 出版年份 【PubMed: 从 PubDate/Year 提取】
    pub_types             JSONB NOT NULL DEFAULT '[]',                 -- 出版类型列表 【PubMed: Article/PublicationTypeList/PublicationType】
    mesh_headings         JSONB NOT NULL DEFAULT '[]',                 -- MeSH 标引词列表（含 DescriptorName+QualifierName）【PubMed: MeshHeadingList/MeshHeading】
    language              VARCHAR(10) DEFAULT 'en',                    -- 语言 【PubMed: Article/Language】
    raw_xml_hash          VARCHAR(64),                                 -- 原始 XML SHA-256 哈希（去重与增量更新用，非 PubMed 字段）
    pmc_id                VARCHAR(20),                                 -- PMC ID（PMC1234567）【PubMed: ArticleId[@IdType="pmc"]】
    is_oa                 BOOLEAN NOT NULL DEFAULT FALSE,               -- 是否开放获取【根据 pmc_id 或 PMC OA API 判定，非 PubMed 原始字段】
    keywords              JSONB NOT NULL DEFAULT '[]',                 -- 作者关键词 【PubMed: Article/KeywordList/Keyword】
    pubmed_revised        TIMESTAMPTZ,                                 -- PubMed 数据库修订日期（NLM 说明不可靠）【PubMed: MedlineCitation/DateRevised】
    grants                JSONB NOT NULL DEFAULT '[]',                 -- 基金资助信息 【PubMed: Article/GrantList/Grant】
    cited_by_count        INTEGER NOT NULL DEFAULT 0,                  -- 被引次数 【来自 NCBI elink 接口 pubmed_pubmed_citedin，非 PMID 固定字段】
    full_text_sections    JSONB,                                       -- PMC OA 全文结构化段落【来自 PMC OA Service API JATS XML，非 PubMed EFetch】
    full_text_path        VARCHAR(255),                                -- COS 对象存储键（内部）
    trial_reg             JSONB,                                       -- 试验注册号 {nct, eudract, chictr} 【PubMed: Article/DataBankList/DataBank，注册号在 ArticleId[@IdType="ClinicalTrials.gov"] 中也有】
    study_design          JSONB,                                       -- 研究设计分类 {primary: "RCT", sub: "Phase 3", design: "parallel"}（AI 推断，非 PubMed 原始字段）
    pico                  JSONB,                                       -- PICO 结构化抽取（AI 推断，非 PubMed 原始字段）
    citation_status       VARCHAR(20),                                 -- MedlineCitation 状态 【PubMed: MedlineCitation/@Status 属性，如 "MEDLINE"/"PubMed-not-MEDLINE"/"In-Data-Review"】
    date_completed        TIMESTAMPTZ,                                 -- MeSH 标引完成日期 【PubMed: MedlineCitation/DateCompleted】
    retracted             BOOLEAN NOT NULL DEFAULT FALSE,               -- 是否已撤稿【依据 PubMed: Article/PublicationTypeList/PublicationType[.="Retracted Publication"]】
    retraction_details    JSONB,                                       -- 撤稿详情（关联撤稿文献等）【PubMed: CommentsCorrectionsList/CommentsCorrections[@RefType="retractionof"/"retractionin"]】
    license_info          JSONB,                                       -- 许可证信息（来自 PMC OA XML 或出版商元数据，非 PubMed 原始字段）
    pre_extracted_data    JSONB,                                       -- Table 1 预提取数据（AI 抽取，非 PubMed 原始字段）
    is_negative_result    BOOLEAN NOT NULL DEFAULT FALSE,               -- 是否为阴性结果（AI 判定，非 PubMed 原始字段）
    rct_detection         JSONB,                                       -- RCT 自动检测结果 {is_rct, confidence, source}（AI，非 PubMed 原始字段）
    search_tsv            TSVECTOR,                                    -- 全文检索向量（内部 PG tsvector，非 PubMed 字段）
    negative_result_details JSONB,                                     -- 阴性结果详情（AI 抽取，非 PubMed 原始字段）
    ai_summary            JSONB,                                       -- AI 摘要（内部，非 PubMed 字段）
    chemical_list         JSONB NOT NULL DEFAULT '[]',                 -- 化学物质列表 【PubMed: ChemicalList/Chemical】
    gene_symbols          JSONB NOT NULL DEFAULT '[]',                 -- 基因符号列表 【PubMed: SupplMeshList/SupplMeshName；部分来自 Gene Symbol 或 ChemicalList】
    num_refs              INTEGER,                                     -- 参考文献数量（来自 PMC 全文或解析，非 PubMed EFetch 原始字段）
    publication_status    VARCHAR(30),                                 -- 出版状态 【PubMed: PubmedData/PublicationStatus，取值 epublish/ppublish/aheadofprint】
    article_date          DATE,                                        -- 电子出版日期（先于印刷版）【PubMed: Article/ArticleDate】
    databank_list         JSONB NOT NULL DEFAULT '[]',                 -- 数据库引用列表（如 ClinicalTrials.gov）【PubMed: Article/DataBankList/DataBank】
    suppl_mesh_list       JSONB NOT NULL DEFAULT '[]',                 -- 补充 MeSH 词列表（含物质名）【PubMed: SupplMeshList/SupplMeshName】
    source                VARCHAR(30) NOT NULL DEFAULT 'pubmed_ftp',   -- 数据来源（内部追踪用：pubmed_api/pubmed_ftp，非 PubMed 字段）
    created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at            TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX idx_gl_pub_date ON global_literature(pub_date);
CREATE INDEX idx_gl_pub_year ON global_literature(pub_year);
CREATE INDEX idx_gl_journal_issn ON global_literature(journal_issn);
CREATE INDEX idx_gl_journal_iso ON global_literature(journal_iso);
CREATE INDEX idx_gl_created ON global_literature(created_at);
CREATE INDEX idx_gl_pmc_id ON global_literature(pmc_id);
CREATE INDEX idx_gl_search_tsv ON global_literature USING gin(search_tsv);
```

> 预估值：约 600 万条（PubMed 中肿瘤相关的历史累积），单表不需要分区

**全文搜索：**
> `search_tsv`（`TSVECTOR` 类型，GIN 索引 `ix_gl_search_tsv`）由 PG 触发器自动维护，包含 **title + abstract + authors.family + authors.affiliation**。所有搜索（普通搜索 `field="all"` 和高级搜索字段选择中的作者/机构）均走 tsvector `@@` `plainto_tsquery()`，ILIKE 仅作为兜底（NULL tsvector 记录）。触发器 `trg_global_literature_tsv` 在 INSERT 或 UPDATE title/abstract 时触发重建。千万级无压力。
>
> > **GIN 索引重建说明：** 迁移 `f80dca5baa02`（add_pico_column_to_review_literatures）的 `upgrade()` 误将 `ix_gl_search_tsv` 删除后未重建，导致所有 tsvector 搜索走全表扫描。迁移 `0314f4d28728`（rebuild_search_tsv_gin_index）已修复，紧跟在 `e341edea85e2` 之后。

**日期字段说明：** `pub_date`（印刷/电子出版日期）+ `article_date`（电子出版日期，实际上线时间）+ `date_completed`（MeSH 标引完成日，检索质量控制用）+ `pubmed_revised`（NLM 定义为不可靠）+ `publication_status`（epublish / ppublish / aheadofprint 出版阶段标识）。

**PubMed 对应 XML 结构速查：**

| 表字段 | PubMed XML Path | 说明 |
|---|---|---|
| `pmid` | `MedlineCitation/PMID` | Pubmed ID |
| `title` | `Article/ArticleTitle` | 文章标题 |
| `abstract` | `Article/Abstract/AbstractText` | 多个 AbstractText 拼接，按 Label 区分 |
| `authors` | `Article/AuthorList/Author` | LastName + ForeName + AffiliationInfo |
| `doi` | `ArticleId[@IdType="doi"]` | 优先取 PubmedData 下的 ArticleIdList |
| `journal` | `Article/Journal/Title` | 期刊全名 |
| `journal_issn` | `Article/Journal/ISSN` | 注意区分 Print / Electronic |
| `journal_iso` | `Article/Journal/ISOAbbreviation` | NLM 标准缩写 |
| `volume` | `Article/Journal/JournalIssue/Volume` | 卷 |
| `issue` | `Article/Journal/JournalIssue/Issue` | 期 |
| `pages` | `Article/Pagination/MedlinePgn` | 页码范围 |
| `pub_date` | `Article/Journal/JournalIssue/PubDate` | 仅取 Year/Month/Day 组合 |
| `pub_year` | `PubDate/Year` | 单独提取年份 |
| `pub_types` | `Article/PublicationTypeList/PublicationType` | 可多个，含 "Retracted Publication" |
| `mesh_headings` | `MeshHeadingList/MeshHeading` | DescriptorName(UI) + QualifierName(UI) |
| `language` | `Article/Language` | 2-3 字母 ISO 语言码 |
| `pmc_id` | `ArticleId[@IdType="pmc"]` | 带 "PMC" 前缀 |
| `keywords` | `Article/KeywordList/Keyword` | 作者关键词（非 MeSH） |
| `pubmed_revised` | `MedlineCitation/DateRevised` | 不可靠，不应视为修订依据 |
| `grants` | `Article/GrantList/Grant` | GrantID + Agency + Country |
| `chemical_list` | `ChemicalList/Chemical` | RegistryNumber + NameOfSubstance(UI) |
| `suppl_mesh_list` | `SupplMeshList/SupplMeshName` | 补充概念词 |
| `databank_list` | `Article/DataBankList/DataBank` | DataBankName + AccessionNumberList |
| `trial_reg` | `Article/DataBankList/DataBank` | 从中提取 NCT/EudraCT/ChiCTR |
| `citation_status` | `MedlineCitation/@Status` | "MEDLINE","PubMed-not-MEDLINE","In-Data-Review","PubMed" |
| `date_completed` | `MedlineCitation/DateCompleted` | MeSH 标引完成日 |
| `retraction_details` | `CommentsCorrectionsList/CommentsCorrections[@RefType="retractionof" or "retractionin"]` | 撤稿关联关系 |
| `publication_status` | `PubmedData/PublicationStatus` | "epublish" / "ppublish" / "aheadofprint" |
| `article_date` | `Article/ArticleDate` | 电子出版日期（印刷版之前的日期）|

### 2.2 global_tags — 肿瘤科标签树

```sql
CREATE TABLE global_tags (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),    -- 标签ID
    mesh_ui         VARCHAR(20),                                   -- MeSH 唯一标识符（UI）
    name_zh         VARCHAR(200) NOT NULL,                         -- 中文名称
    name_en         VARCHAR(200),                                  -- 英文名称
    path            VARCHAR(500) NOT NULL,                         -- 标签路径（如 /癌种/肺癌/非小细胞肺癌）
    level           INTEGER NOT NULL DEFAULT 1,                    -- 层级深度
    parent_id       UUID REFERENCES global_tags(id) ON DELETE CASCADE,        -- 父标签ID
    tree_number     VARCHAR(100),                                  -- MeSH 树编号
    tag_category    VARCHAR(30) NOT NULL DEFAULT 'cancer',         -- 标签分类
    sort_order      INTEGER DEFAULT 0,                             -- 排序序号
    icon            VARCHAR(50),                                   -- 图标名
    is_selectable   BOOLEAN NOT NULL DEFAULT TRUE,                 -- 是否可选（叶子节点）
    article_count   INTEGER DEFAULT 0,                             -- 关联文献数
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()             -- 更新时间
);
CREATE INDEX idx_tags_path ON global_tags(path);
CREATE INDEX idx_tags_parent ON global_tags(parent_id);
CREATE INDEX idx_tags_category ON global_tags(tag_category);
CREATE INDEX idx_tags_mesh_ui ON global_tags(mesh_ui) WHERE mesh_ui IS NOT NULL;
```

**tag_category 枚举**：`cancer`(癌种) | `histology`(组织分型) | `gene`(驱动基因) | `treatment`(治疗方式) | `study_type`(研究类型) | `endpoint`(临床终点) | `scenario`(临床场景) | `journal_tier`(期刊等级)

### 2.3 global_literature_tags — 文献标签关联

```sql
CREATE TABLE global_literature_tags (
    literature_id UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE,  -- 文献ID
    tag_id        UUID NOT NULL REFERENCES global_tags(id) ON DELETE CASCADE,        -- 标签ID
    is_major      BOOLEAN NOT NULL DEFAULT FALSE,                 -- 是否为主要主题（MeSH Major Topic）
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),             -- 创建时间
    PRIMARY KEY (literature_id, tag_id)
);
CREATE INDEX idx_glt_tag ON global_literature_tags(tag_id);
```

> 预估值：约 6000 万行（600万×10标签），B-tree 索引足够，不需要分区。两种查询都命中索引。

### 2.4 global_journals — 期刊库

```sql
CREATE TABLE global_journals (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),    -- 期刊ID
    name            VARCHAR(500) NOT NULL,                         -- 期刊名称
    issn            VARCHAR(20),                                   -- 印刷版 ISSN
    eissn           VARCHAR(20),                                   -- 电子版 ISSN
    tier            VARCHAR(5) NOT NULL DEFAULT '4',               -- 期刊等级：1/2/3/4
    impact_factor   DOUBLE PRECISION,                              -- 影响因子
    publisher       VARCHAR(300),                                  -- 出版商
    article_count   INTEGER DEFAULT 0,                             -- 收录文献数
    is_active       BOOLEAN DEFAULT TRUE,                          -- 是否活跃
    priority_score  REAL,                                          -- 综合优先级评分 0-100
    priority_source VARCHAR(20) DEFAULT 'auto',                    -- 优先级来源：auto/manual
    specialty       VARCHAR(50),                                   -- 专科领域：oncology
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),            -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),            -- 更新时间
    UNIQUE (issn),
    UNIQUE (name)
);
CREATE INDEX idx_gj_tier ON global_journals(tier);
CREATE INDEX idx_gj_priority ON global_journals(priority_score DESC);
```


**权重评分规则（priority_score）：**

| 维度 | 规则 | 最高加分 |
|---|---|---|
| Tier 基础分 | 1→80, 2→65, 3→50, 4→20 | 80 |
| IF 加成分 | ≥20→+15, ≥10→+10, ≥5→+5, ≥2→+2 | 15 |
| 收录量加成分 | ≥5000→+10, ≥1000→+5, ≥100→+2 | 10 |
| 专科加成分 | specialty='oncology'→+10 | 10 |

封顶 100 分。`priority_source='manual'` 时自动任务跳过该行。用户关注的期刊查询时 +20。

**specialty 自动判定：** Tier 1-3 → `oncology`；name 包含 oncol/cancer/tumor/neoplas → `oncology`；其余 NULL。

---

## 三、用户交互表

### 3.1 user_subscriptions — 关注领域

```sql
CREATE TABLE user_subscriptions (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 订阅ID
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    tag_id      UUID NOT NULL REFERENCES global_tags(id) ON DELETE CASCADE, -- 标签ID
    match_mode  VARCHAR(10) NOT NULL DEFAULT 'standard',         -- 匹配模式：loose/standard/strict
    is_active   BOOLEAN NOT NULL DEFAULT TRUE,                   -- 是否激活
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 创建时间
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 更新时间
    UNIQUE (user_id, tag_id)
);
CREATE INDEX idx_us_user_active ON user_subscriptions(user_id, is_active);
CREATE INDEX idx_us_tag_active ON user_subscriptions(tag_id, is_active);
```

**match_mode**：`loose`(任一命中) | `standard`(主标签命中，默认) | `strict`(全部命中)

### 3.2 user_feed — 每日推送 ⚠️ 需要分区

```sql
CREATE TABLE user_feed (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- FeedID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,           -- 用户ID
    literature_id   UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    matched_tags    JSONB NOT NULL DEFAULT '[]',                 -- 命中的标签列表
    priority        VARCHAR(20) NOT NULL DEFAULT 'related',      -- 优先级：must_read/recommended/related
    is_read         BOOLEAN NOT NULL DEFAULT FALSE,              -- 是否已读
    read_at         TIMESTAMPTZ,                                 -- 阅读时间
    is_dismissed    BOOLEAN NOT NULL DEFAULT FALSE,              -- 是否已忽略
    pushed_at       TIMESTAMPTZ NOT NULL DEFAULT now()           -- 推送时间
);
CREATE INDEX idx_uf_user_push ON user_feed(user_id, pushed_at);
CREATE INDEX idx_uf_user_priority ON user_feed(user_id, is_dismissed, priority);
CREATE INDEX idx_uf_user_lit ON user_feed(user_id, literature_id);
```

> 预估值：年增数千万行。当前模型未分区，**生产需改为按月分区 + 自动删除旧分区**（保留最近3个月）。查询永远只走最新分区。

### 3.3 user_literature — 个人收藏

```sql
CREATE TABLE user_literature (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 收藏ID
    user_id           UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,           -- 用户ID
    literature_id     UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    folder_id         UUID REFERENCES user_folders(id) ON DELETE SET NULL,             -- 所属收藏夹
    reading_status    VARCHAR(20) NOT NULL DEFAULT 'unread',     -- 阅读状态：unread/reading/read
    rating            INTEGER CHECK (rating >= 1 AND rating <= 5), -- 评分 1-5
    personal_tags     JSONB DEFAULT '[]',                        -- 个人标签
    saved_at          TIMESTAMPTZ NOT NULL DEFAULT now(),        -- 收藏时间
    read_started_at   TIMESTAMPTZ,                               -- 开始阅读时间
    read_finished_at  TIMESTAMPTZ,                               -- 读完时间
    UNIQUE (user_id, literature_id)
);
CREATE INDEX idx_ul_user_saved ON user_literature(user_id, saved_at);
CREATE INDEX idx_ul_literature ON user_literature(literature_id);
```

### 3.4 user_folders — 收藏夹

```sql
CREATE TABLE user_folders (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 收藏夹ID
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    name        VARCHAR(200) NOT NULL,                           -- 收藏夹名称
    description TEXT,                                            -- 描述
    color       VARCHAR(7) DEFAULT '#808080',                    -- 颜色（十六进制）
    is_shared   BOOLEAN NOT NULL DEFAULT FALSE,                  -- 是否共享
    sort_order  INTEGER DEFAULT 0,                               -- 排序序号
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 创建时间
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()               -- 更新时间
);
CREATE INDEX idx_folders_user ON user_folders(user_id);
CREATE INDEX idx_folders_shared ON user_folders(tenant_id, is_shared) WHERE is_shared = TRUE;
```

### 3.5 user_notes — 阅读笔记

```sql
CREATE TABLE user_notes (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 笔记ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,           -- 用户ID
    literature_id   UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    content         TEXT NOT NULL,                               -- 笔记内容
    quote_text      TEXT,                                        -- 引用的原文
    is_private      BOOLEAN NOT NULL DEFAULT TRUE,               -- 是否私密
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX idx_un_user ON user_notes(user_id, created_at);
CREATE INDEX idx_un_literature ON user_notes(literature_id);
```

### 3.6 user_pdf_highlights — PDF批注

```sql
CREATE TABLE user_pdf_highlights (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 批注ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,           -- 用户ID
    literature_id   UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    highlight_type  VARCHAR(20) NOT NULL,                        -- 批注类型：highlight/underline/strikeout
    page_number     INTEGER NOT NULL,                            -- 页码
    position        JSONB NOT NULL,                              -- 位置坐标
    text            TEXT,                                        -- 批注文本
    note            TEXT,                                        -- 批注笔记
    color           VARCHAR(7) DEFAULT '#ffff00',                -- 高亮颜色
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX idx_hl_user_lit ON user_pdf_highlights(user_id, literature_id);
```

### 3.7 user_feedback — 文献反馈

```sql
CREATE TABLE user_feedback (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 反馈ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,        -- 用户ID
    literature_id   UUID REFERENCES global_literature(id) ON DELETE CASCADE,     -- 文献ID（可为NULL）
    feedback_type   VARCHAR(30) NOT NULL,                        -- 反馈类型
    detail          TEXT,                                        -- 反馈详情
    is_resolved     BOOLEAN DEFAULT FALSE,                       -- 是否已处理
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    UNIQUE (user_id, feedback_type)
);
```

> `literature_id` 可为 NULL（反馈不一定针对具体文献，也可以是针对搜索结果或系统功能）。`UNIQUE (user_id, feedback_type)` 确保同一用户不会重复提交同类型反馈。

### 3.8 user_journal_subscriptions — 期刊关注

```sql
CREATE TABLE user_journal_subscriptions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 订阅ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    journal_id      UUID NOT NULL REFERENCES global_journals(id) ON DELETE CASCADE, -- 期刊ID
    is_active       BOOLEAN DEFAULT TRUE,                        -- 是否激活
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX ix_ujs_user_active ON user_journal_subscriptions(user_id, is_active);
CREATE INDEX ix_ujs_journal ON user_journal_subscriptions(journal_id);
```

> 关注/取消关注采用软删除（`is_active`），与 `user_subscriptions` 模式一致。关注后，该期刊的新文献会自动推送到用户 Feed。

### 3.9 user_activity_logs — 用户活动日志

```sql
CREATE TABLE user_activity_logs (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 日志ID
    user_id           UUID REFERENCES users(id) ON DELETE SET NULL,           -- 用户ID
    tenant_id         UUID REFERENCES tenants(id) ON DELETE SET NULL,         -- 租户ID
    activity_type     VARCHAR(50) NOT NULL,                       -- 活动类型
    target_type       VARCHAR(30),                                -- 目标类型（literature/search等）
    target_id         VARCHAR(200),                               -- 目标ID
    detail            JSONB,                                      -- 详情 JSON
    ip_address        VARCHAR(45),                                -- 客户端IP
    user_agent        VARCHAR(500),                               -- 用户代理
    session_id        VARCHAR(100),                               -- 会话ID
    duration_seconds  INTEGER,                                    -- 停留时长（秒）
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now()          -- 创建时间
);
CREATE INDEX ix_ual_user_created ON user_activity_logs(user_id, created_at);
CREATE INDEX ix_ual_type_created ON user_activity_logs(activity_type, created_at);
CREATE INDEX ix_ual_target ON user_activity_logs(target_type, target_id);
```

**activity_type 枚举：** `page_view`(页面访问) | `search`(搜索) | `view_literature`(查看文献) | `save_literature`(收藏) | `export`(导出) | `browse_journals`(浏览期刊) | `click_tag`(点击标签)

> 前端通过 `router.afterEach` 自动追踪 `page_view`；关键交互（搜索、收藏、导出等）通过 `trackAction()` 手动埋点。未登录用户不记录。配合 login_logs（登录安全）和 api_usage_logs（API 调用）构成完整的四维分析体系。

### 3.10 user_dismissed_tags — 用户负偏好标签（不感兴趣学习）

```sql
CREATE TABLE user_dismissed_tags (
    id               UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 记录ID
    user_id          UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,     -- 用户ID
    tag_id           UUID NOT NULL REFERENCES global_tags(id) ON DELETE CASCADE, -- 标签ID
    dismiss_count    INTEGER NOT NULL DEFAULT 1,                  -- 不感兴趣次数
    last_dismissed_at TIMESTAMPTZ NOT NULL DEFAULT now(),         -- 最后不感兴趣时间
    UNIQUE(user_id, tag_id)
);
CREATE INDEX ix_udt_user_count ON user_dismissed_tags(user_id, dismiss_count);
```

**说明：** 用户点"不感兴趣"后，系统提取该文章的标签累计到 `dismiss_count`。当某标签的 `dismiss_count >= 3` 时，Feed 引擎不再为用户推送该标签下的文章。可在管理后台或用户设置中重置。`user_id` + `tag_id` 唯一约束确保每个用户对每个标签只有一条累计记录。

---

## 四、团队协作表

### 4.1 teams + team_members — 团队与成员

```sql
CREATE TABLE teams (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 团队ID
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    name        VARCHAR(200) NOT NULL,                           -- 团队名称
    description TEXT,                                            -- 描述
    created_by  UUID REFERENCES users(id) ON DELETE SET NULL,                -- 创建者
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 创建时间
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()               -- 更新时间
);
CREATE INDEX idx_teams_tenant ON teams(tenant_id);

CREATE TABLE team_members (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 成员ID
    team_id     UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,         -- 团队ID
    user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,         -- 用户ID
    role        VARCHAR(20) NOT NULL DEFAULT 'member',           -- 角色：member/lead
    joined_at   TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 加入时间
    UNIQUE (user_id, team_id)
);
CREATE INDEX idx_tm_user_team ON team_members(user_id, team_id);
CREATE INDEX idx_tm_team ON team_members(team_id);
```

### 4.2 invitations + shared_folders — 邀请与共享

```sql
CREATE TABLE invitations (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 邀请ID
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    inviter_id  UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 邀请人
    email       VARCHAR(320) NOT NULL,                           -- 被邀请人邮箱
    role        VARCHAR(20) NOT NULL DEFAULT 'viewer',           -- 被邀请角色
    token       VARCHAR(200) UNIQUE NOT NULL,                    -- 邀请令牌
    status      VARCHAR(20) NOT NULL DEFAULT 'pending',          -- 状态：pending/accepted/expired
    expires_at  TIMESTAMPTZ NOT NULL,                            -- 过期时间
    accepted_at TIMESTAMPTZ,                                     -- 接受时间
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()               -- 创建时间
);

CREATE TABLE shared_folders (
    folder_id   UUID NOT NULL REFERENCES user_folders(id) ON DELETE CASCADE, -- 收藏夹ID
    target_type VARCHAR(20) NOT NULL,                             -- 共享目标类型：team/user
    target_id   UUID NOT NULL,                                   -- 共享目标ID
    permission  VARCHAR(20) NOT NULL DEFAULT 'view',             -- 权限：view/edit
    created_by  UUID REFERENCES users(id) ON DELETE SET NULL,                -- 共享发起人
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),              -- 创建时间
    PRIMARY KEY (folder_id, target_type, target_id)
);
```

### 4.3 journal_club_queue — Journal Club 文献排队

```sql
CREATE TABLE journal_club_queue (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 排队ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    literature_id   UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    suggested_by    UUID REFERENCES users(id) ON DELETE SET NULL,              -- 建议人
    title           VARCHAR(500),                                -- 讨论标题
    scheduled_date  DATE,                                        -- 排期日期
    presenter_id    UUID REFERENCES users(id) ON DELETE SET NULL,              -- 主讲人
    status          VARCHAR(20) NOT NULL DEFAULT 'queued',       -- 状态：queued/completed/cancelled
    discussion_notes TEXT,                                        -- 讨论纪要
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 更新时间
    UNIQUE (tenant_id, literature_id, scheduled_date)
);
CREATE INDEX idx_jc_tenant ON journal_club_queue(tenant_id, status);
```

---

## 五、业务扩展表

### 5.1 drug_approvals — FDA/NMPA/EMA 药品审批

```sql
CREATE TABLE drug_approvals (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 审批ID
    drug_name           VARCHAR(500) NOT NULL,                       -- 药品商品名
    generic_name        VARCHAR(500) NOT NULL,                       -- 通用名/活性成分
    target              VARCHAR(200),                                -- 靶点/作用机制
    indication          TEXT NOT NULL,                               -- 获批适应症
    cancer_type_tag_id  UUID REFERENCES global_tags(id) ON DELETE SET NULL,       -- 关联癌种标签
    approval_agency     VARCHAR(10) NOT NULL,                        -- 审批机构：FDA/NMPA/EMA
    approval_type       VARCHAR(30),                                 -- 审批类型：regular/accelerated/breakthrough
    approval_date       DATE NOT NULL,                               -- 批准日期
    pivotal_trial_nct   VARCHAR(20),                                 -- 关键试验 NCT 号
    pivotal_trial_pmid  INTEGER,                                     -- 关键试验 PMID（非外键）
    source_url          VARCHAR(1000),                               -- 数据来源 URL
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_da_agency_date ON drug_approvals(approval_agency, approval_date);
```

> `pivotal_trial_pmid` 是普通 INTEGER 而非外键：因为试验可能在文献入库前就已记录，删除文献也不应级联删除审批记录。

### 5.2 guideline_versions + guideline_evidence — 指南版本与证据

```sql
CREATE TABLE guideline_versions (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 指南版本ID
    guideline_source    VARCHAR(10) NOT NULL,                        -- 指南来源：NCCN/ASCO/CSCO
    cancer_type_tag_id  UUID NOT NULL REFERENCES global_tags(id) ON DELETE RESTRICT,  -- 关联癌种标签
    version             VARCHAR(30) NOT NULL,                        -- 版本号
    publish_date        DATE NOT NULL,                              -- 发布日期
    change_summary_zh   TEXT,                                       -- 中文变更摘要
    change_summary_en   TEXT,                                       -- 英文变更摘要
    pdf_url             VARCHAR(1000),                              -- PDF 下载 URL
    source_url          VARCHAR(1000),                              -- 来源 URL
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),         -- 创建时间
    UNIQUE (guideline_source, cancer_type_tag_id, version)
);

CREATE TABLE guideline_evidence (
    id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 证据ID
    guideline_version_id  UUID NOT NULL REFERENCES guideline_versions(id) ON DELETE CASCADE, -- 指南版本ID
    literature_id         UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE,  -- 文献ID
    recommendation        TEXT,                                       -- 推荐意见
    recommendation_level  VARCHAR(10),                                -- 推荐级别：1A/1B/2A/2B/3
    treatment_line        VARCHAR(20),                                -- 治疗线数：first/second/third
    created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),         -- 创建时间
    UNIQUE (guideline_version_id, literature_id)
);
```

> `guideline_versions.cancer_type_tag_id` 使用 `ON DELETE RESTRICT`：指南指向的癌种标签禁止被删除。

---

## 六、审批表（企业版）

### 6.1 approval_workflows — 审批工作流

```sql
CREATE TABLE approval_workflows (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 工作流ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    literature_id   UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE, -- 文献ID
    submitted_by    UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 提交人
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',     -- 状态：pending/approved/rejected
    current_step    INTEGER NOT NULL DEFAULT 1,                 -- 当前步骤
    total_steps     INTEGER NOT NULL DEFAULT 1,                 -- 总步骤数
    comment         TEXT,                                       -- 备注
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),         -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()          -- 更新时间
);

CREATE TABLE approval_steps (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 步骤ID
    workflow_id UUID NOT NULL REFERENCES approval_workflows(id) ON DELETE CASCADE, -- 工作流ID
    step_order  INTEGER NOT NULL,                                -- 步骤序号
    approver_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,             -- 审批人
    status      VARCHAR(20) NOT NULL DEFAULT 'pending',         -- 状态：pending/approved/rejected
    comment     TEXT,                                           -- 审批意见
    decided_at  TIMESTAMPTZ,                                    -- 审批时间
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()              -- 创建时间
);
```

### 6.2 ai_provider_config — AI 供应商配置

```sql
CREATE TABLE ai_provider_config (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),    -- 配置ID
    provider        VARCHAR(50) NOT NULL,                          -- AI 供应商：deepseek/openai/claude
    api_key         VARCHAR(500) NOT NULL,                         -- API Key（加密存储）
    base_url        VARCHAR(500) NOT NULL,                         -- API 基础 URL
    model_name      VARCHAR(100) NOT NULL,                         -- 模型名称
    temperature     INTEGER NOT NULL DEFAULT 3,                    -- 温度参数（0-10，展示为0.0-1.0）
    is_active       BOOLEAN NOT NULL DEFAULT TRUE,                 -- 是否激活
    updated_by      UUID REFERENCES users(id) ON DELETE SET NULL,                -- 最后修改人
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),            -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()             -- 更新时间
);
```

---

## 七、运营与基础设施表

### 7.1 pipeline_runs — 数据管道日志

```sql
CREATE TABLE pipeline_runs (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 运行ID
    run_type            VARCHAR(20) NOT NULL,                        -- 运行类型：daily/weekly/manual
    status              VARCHAR(20) NOT NULL DEFAULT 'running',      -- 状态：running/completed/failed
    files_processed     INTEGER DEFAULT 0,                           -- 处理文件数
    articles_total      INTEGER DEFAULT 0,                           -- 总文献数
    articles_new        INTEGER DEFAULT 0,                           -- 新增文献数
    articles_updated    INTEGER DEFAULT 0,                           -- 更新文献数
    articles_deleted    INTEGER DEFAULT 0,                           -- 删除文献数
    articles_filtered   INTEGER DEFAULT 0,                           -- 过滤文献数
    users_matched       INTEGER DEFAULT 0,                           -- 匹配用户数
    feeds_generated     INTEGER DEFAULT 0,                           -- 生成 Feed 数
    error_log           TEXT,                                        -- 错误日志
    started_at          TIMESTAMPTZ,                                 -- 开始时间
    completed_at        TIMESTAMPTZ,                                 -- 完成时间
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_pr_type ON pipeline_runs(run_type, created_at);
```

### 7.2 tenant_subscriptions — Stripe 订阅

```sql
CREATE TABLE tenant_subscriptions (
    id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 订阅ID
    tenant_id               UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE UNIQUE,  -- 租户ID
    stripe_customer_id      VARCHAR(100),                              -- Stripe 客户ID
    stripe_subscription_id  VARCHAR(100),                              -- Stripe 订阅ID
    plan_id                 VARCHAR(50),                               -- 套餐ID
    status                  VARCHAR(30) NOT NULL DEFAULT 'active',     -- 状态：active/past_due/canceled
    current_period_start    TIMESTAMPTZ NOT NULL,                      -- 当前计费周期开始
    current_period_end      TIMESTAMPTZ NOT NULL,                      -- 当前计费周期结束
    cancel_at_period_end    BOOLEAN DEFAULT FALSE,                     -- 是否到期取消
    trial_end               TIMESTAMPTZ,                               -- 试用结束时间
    created_at              TIMESTAMPTZ NOT NULL DEFAULT now(),        -- 创建时间
    updated_at              TIMESTAMPTZ NOT NULL DEFAULT now()         -- 更新时间
);
CREATE INDEX idx_ts_stripe_sub ON tenant_subscriptions(stripe_subscription_id);

CREATE TABLE subscription_events ( -- 订阅事件日志
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 事件ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    event_type      VARCHAR(30) NOT NULL,                        -- 事件类型
    from_plan       VARCHAR(30),                                 -- 原套餐
    to_plan         VARCHAR(30),                                 -- 目标套餐
    stripe_event_id VARCHAR(100),                                -- Stripe 事件ID
    metadata        JSONB,                                       -- 附加元数据
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_se_tenant ON subscription_events(tenant_id);
```

### 7.3 payment_orders — 支付订单

```sql
CREATE TABLE payment_orders (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 订单ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    plan            VARCHAR(30) NOT NULL,                        -- 套餐：pro/team/enterprise
    amount          INTEGER NOT NULL,                            -- 金额（分）
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',      -- 状态：pending/paid/expired/cancelled
    out_trade_no    VARCHAR(64) UNIQUE NOT NULL,                 -- 商户订单号
    prepay_id       VARCHAR(128),                                -- 预支付ID（微信）
    code_url        VARCHAR(512),                                -- 支付二维码 URL
    pay_time        TIMESTAMPTZ,                                 -- 支付时间
    transaction_id  VARCHAR(64),                                 -- 支付平台交易ID
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
CREATE INDEX idx_po_tenant ON payment_orders(tenant_id);
```

### 7.4 api_keys + api_usage_logs — API密钥与使用日志

```sql
CREATE TABLE api_keys (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 密钥ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 创建者
    name            VARCHAR(200) NOT NULL,                       -- 密钥名称
    key_prefix      VARCHAR(20) NOT NULL,                        -- 密钥前缀（便于识别）
    key_hash        VARCHAR(128) NOT NULL,                       -- 密钥哈希
    scopes          JSONB DEFAULT '[]',                          -- 权限范围
    last_used_at    TIMESTAMPTZ,                                 -- 最后使用时间
    expires_at      TIMESTAMPTZ,                                 -- 过期时间
    is_active       BOOLEAN DEFAULT TRUE,                        -- 是否激活
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_ak_tenant_active ON api_keys(tenant_id, is_active);

CREATE TABLE api_usage_logs (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 日志ID
    api_key_id      UUID REFERENCES api_keys(id) ON DELETE SET NULL,           -- API密钥ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    endpoint        VARCHAR(300) NOT NULL,                       -- 请求端点
    method          VARCHAR(10) NOT NULL,                        -- HTTP 方法
    status_code     INTEGER,                                     -- HTTP 状态码
    response_ms     INTEGER,                                     -- 响应时间（毫秒）
    ip_address      VARCHAR(45),                                 -- 请求IP
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_aul_tenant ON api_usage_logs(tenant_id);
CREATE INDEX idx_aul_tenant_created ON api_usage_logs(tenant_id, created_at);
```

### 7.5 export_jobs — 导出任务

```sql
CREATE TABLE export_jobs (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 任务ID
    user_id         UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,       -- 用户ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    export_format   VARCHAR(10) NOT NULL,                        -- 导出格式：ris/bibtex/csv/pdf
    citation_style  VARCHAR(50),                                 -- 引文格式
    filter_params   JSONB,                                       -- 筛选参数
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',      -- 状态：pending/processing/done/failed
    file_url        VARCHAR(1000),                               -- 文件下载 URL
    file_size_bytes INTEGER,                                     -- 文件大小（字节）
    error_message   TEXT,                                        -- 错误信息
    total_items     INTEGER,                                     -- 导出条目数
    completed_at    TIMESTAMPTZ,                                 -- 完成时间
    expires_at      TIMESTAMPTZ,                                 -- 文件过期时间
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
CREATE INDEX idx_ej_user_status ON export_jobs(user_id, status);
```

### 7.6 system_notifications — 系统通知

```sql
CREATE TABLE system_notifications (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),      -- 通知ID
    title       VARCHAR(300) NOT NULL,                           -- 通知标题
    content     TEXT,                                            -- 通知内容
    target_type VARCHAR(20) NOT NULL DEFAULT 'all',              -- 目标类型：all/tenant/user
    target_id   UUID,                                            -- 目标ID（对应租户或用户）
    is_read     BOOLEAN DEFAULT FALSE,                           -- 是否已读
    read_at     TIMESTAMPTZ,                                     -- 阅读时间
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()               -- 创建时间
);
```

---

## 八、系统评价 / Meta 分析

### 8.1 systematic_reviews — 系统评价项目

```sql
CREATE TABLE systematic_reviews (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 评价项目ID
    user_id             UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,     -- 创建者
    tenant_id           UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,   -- 租户ID
    title               VARCHAR(500) NOT NULL,                       -- 评价标题
    objective           TEXT,                                        -- 研究目的
    protocol            TEXT,                                        -- 研究方案
    inclusion_criteria  JSONB DEFAULT '[]',                          -- 纳入标准 [str]
    exclusion_criteria  JSONB DEFAULT '[]',                          -- 排除标准 [str]
    databases_searched  JSONB DEFAULT '[]',                          -- 检索数据库 [str]
    search_strategy     JSONB DEFAULT '{}',                          -- 检索策略 {pubmed: "..."}
    prisma_stage        VARCHAR(20) DEFAULT 'identification',        -- PRISMA 阶段
    status              VARCHAR(20) DEFAULT 'draft',                 -- 状态：draft/active/completed/archived
    notes               TEXT,                                        -- 备注
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
```

**prisma_stage 流转**：`identification → screening → eligibility → included`

**status**：`draft / active / completed / archived`

### 8.2 review_literatures — 系统评价文献

```sql
CREATE TABLE review_literatures (
    id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 记录ID
    review_id         UUID NOT NULL REFERENCES systematic_reviews(id) ON DELETE CASCADE,  -- 系统评价ID
    literature_id     UUID NOT NULL REFERENCES global_literature(id) ON DELETE CASCADE,   -- 文献ID
    stage             VARCHAR(20) NOT NULL DEFAULT 'screening',    -- 当前阶段
    exclusion_reason  TEXT,                                        -- 排除原因
    notes             TEXT,                                        -- 备注
    quality_score     VARCHAR(10),                                 -- 质量评分：high/moderate/low/unclear
    quality_detail    JSONB DEFAULT '{}',                          -- 质量评估细节 {domain: "low", ...}
    pico              JSONB,                                       -- 评价级 PICO（与文献库隔离）
    created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
```

> `pico` 字段与 `global_literature.pico` 隔离：同一篇文献被多个系统评价引用时，各评价可独立编辑 PICO。

---

## 九、MDT 多学科协作

### 9.1 mdt_cases — MDT 病例

```sql
CREATE TABLE mdt_cases (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 病例ID
    tenant_id       UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,     -- 租户ID
    title           VARCHAR(300) NOT NULL,                       -- 病例标题
    patient_summary TEXT NOT NULL,                               -- 患者摘要
    cancer_type     VARCHAR(100),                                -- 癌种（冗余字段，用于快速筛选）
    status          VARCHAR(20) DEFAULT 'open',                  -- 状态：open/closed
    created_by      UUID REFERENCES users(id) ON DELETE SET NULL,              -- 创建者
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),          -- 创建时间
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 更新时间
);
```

### 9.2 mdt_decisions — MDT 决策

```sql
CREATE TABLE mdt_decisions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 决策ID
    case_id         UUID NOT NULL REFERENCES mdt_cases(id) ON DELETE CASCADE,  -- 关联病例
    conclusion      TEXT NOT NULL,                               -- 决策结论
    meeting_date    TIMESTAMPTZ,                                 -- 会议时间
    participants    JSONB DEFAULT '[]',                          -- 参会人员 [{user_id, display_name, role}]
    decided_by      UUID REFERENCES users(id) ON DELETE SET NULL,              -- 决策者
    decided_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 决策时间
);
```

### 9.3 mdt_evidence — MDT 引用证据

```sql
CREATE TABLE mdt_evidence (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 证据ID
    case_id         UUID NOT NULL REFERENCES mdt_cases(id) ON DELETE CASCADE,   -- 关联病例
    decision_id     UUID REFERENCES mdt_decisions(id) ON DELETE SET NULL,       -- 关联决策
    literature_id   UUID REFERENCES global_literature(id) ON DELETE SET NULL,   -- 关联文献（可为NULL）
    pmid            INTEGER,                                     -- PubMed ID（备用）
    citation        TEXT,                                        -- 引文文本
    relevance_note  TEXT,                                        -- 相关性说明
    added_by        UUID REFERENCES users(id) ON DELETE SET NULL,              -- 添加人
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now()           -- 创建时间
);
```

> `literature_id` 和 `pmid` 双字段：文献可能尚未入库（只有 PMID），此时先存 PMID，待 pipeline 灌入后关联。

---

## 十、全表统计数据

| # | 表名 | 预估数据量 | 说明 |
|---|------|:---:|---|
| 1 | tenants | 百～千 | |
| 2 | users | 千～万 | |
| 3 | user_tenants | 千～万 | |
| 4 | login_logs | 万～百万 | |
| 5 | global_literature | **~600万** | B-tree 索引足够 |
| 6 | global_tags | ~800 | |
| 7 | global_literature_tags | **~6000万** | B-tree 索引足够 |
| 8 | global_journals | ~9000 | priority_score DESC 索引 |
| 9 | user_subscriptions | 千～万 | |
| 10 | user_journal_subscriptions | 千～万 | |
| 11 | user_feed | **年增数千万** | ⚠️ 生产需按月分区，保留3个月 |
| 12 | user_literature | 万～百万 | |
| 13 | user_folders | 千～万 | |
| 14 | user_notes | 万～十万 | |
| 15 | user_pdf_highlights | 万～十万 | |
| 16 | user_feedback | 千 | |
| 17 | teams | 千 | |
| 18 | team_members | 千～万 | |
| 19 | invitations | 千 | |
| 20 | shared_folders | 千 | |
| 21 | journal_club_queue | 千 | |
| 22 | drug_approvals | ~500/年 | |
| 23 | guideline_versions | ~200/年 | |
| 24 | guideline_evidence | 万 | |
| 25 | approval_workflows | 千 | |
| 26 | approval_steps | 千 | |
| 27 | ai_provider_config | ~5 | |
| 28 | pipeline_runs | ~365/年 | |
| 29 | tenant_subscriptions | 百～千 | |
| 29 | subscription_events | 千 | |
| 30 | payment_orders | 千 | |
| 31 | api_keys | 百 | |
| 32 | api_usage_logs | 百万 | 按月分区（可选） |
| 33 | system_notifications | 千 | |
| 34 | systematic_reviews | 百～千 | |
| 35 | review_literatures | 千～万 | |
| 36 | mdt_cases | 百～千 | |
| 37 | mdt_decisions | 百～千 | |
| 38 | mdt_evidence | 千 | |
| 39 | export_jobs | 千 | |
