Files
backend/CLAUDE.md
T
34047007@qq.com 57134561ea docs: add Git PR workflow to CLAUDE.md
Document the new PR-based workflow with Gitea branch protection.
2026-07-27 08:23:41 +08:00

195 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
```bash
# Start dev environment (backend + frontend)
cd backend && python scripts/seed_data.py && uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
cd frontend && npm install && npm run dev
# Run tests (611 tests, ~3min)
cd backend && python -m pytest tests/ -v --no-cov
# Build frontend (type-check + prod bundle, ~5s)
cd frontend && npm run build
# Database migrations (Alembic)
cd backend && python -m alembic -c alembic/alembic.ini upgrade head # 应用迁移
cd backend && python -m alembic -c alembic/alembic.ini downgrade -1 # 回滚一步
cd backend && python -m alembic -c alembic/alembic.ini history # 查看历史
cd backend && python -m alembic -c alembic/alembic.ini current # 查看当前版本
# 自动生成迁移(修改模型后执行):
cd backend && python -m alembic -c alembic/alembic.ini revision --autogenerate -m "变更说明"
# Docker deployment
docker compose up -d # development(自动运行迁移)
docker compose -f docker-compose.prod.yml up -d # production(自动运行迁移)
# PubMed pipeline (real data)
curl -X POST localhost:8000/api/v1/admin/pipeline/run -H "Authorization: Bearer $(admin_token)"
# FTP baseline import (first-time full data)
cd backend && python scripts/pubmed_baseline.py --dir /path/to/pubmed/baseline/ --demo
# 批量刷新被引次数(PubMed elink,免费,3 req/s。全库约N分钟/N秒)
curl -X POST localhost:8000/api/v1/admin/pipeline/refresh-citations -H "Authorization: Bearer $(admin_token)"
curl -X POST "localhost:8000/api/v1/admin/pipeline/refresh-citations?limit=500" -H "Authorization: Bearer $(admin_token)"
```
## Architecture Overview
### Database Migrations (Alembic)
Alembic 管理数据库 schema 版本。初始迁移 `alembic/versions/e56e408b2208_initial_schema.py` 包含所有 32 个模型的建表语句。
**工作流:**
1. 修改 Python 模型(增/删/改字段)
2. 运行 `alembic revision --autogenerate -m "描述"` 自动生成迁移脚本
3. 检查生成的脚本,确认无误
4. 运行 `alembic upgrade head` 应用到数据库
**部署:** Docker Compose 启动时会自动执行 `alembic upgrade head`
**模型修改后务必同步文档:** 每次通过 migration 增/删/改字段后,必须同步更新 `docs/03-数据库设计.md` 中对应表的 `CREATE TABLE` 定义和索引,保持与模型代码一致。
### Multi-Tenant Isolation
Two levels: `ContextVar` (application) + PostgreSQL RLS (database, production only). Every tenant-scoped request sets `tenant_ctx` via `get_current_user` (not middleware — avoids connection pool races). The JWT access token carries `tid` (tenant_id) and `is_superuser`. Admin routes (`/admin/*`) enforce `require_superuser` via router-level dependency — demo users get 403.
### Middleware Stack (inside→out)
`CORS → RateLimit → PerformanceMonitor → SecurityHeaders → CSRFProtection → CSRFCookie → Trace`
Rate limiter caches plan quotas 5 minutes. CSRF exempts Bearer tokens and `/auth/*`, `/public/*`, `/captcha/*`.
### Backend Layer Pattern
```
api/v1/ → schemas/ → services/ → models/ (DB)
core/ (security, permissions, tenant_ctx, middleware)
```
Services never import from api/. Models never import from services/. `core/plans.py` defines Free/Pro/Team/Enterprise feature matrix — use `get_tenant_plan()` to check feature access.
### Database
39 SQLAlchemy 2.0 models. All `TIMESTAMPTZ` columns use `server_default=func.now()`. Pure date fields (pub_date, approval_date) use `DATE`.
`user_feed` is the only table needing partitioning — `PARTITION BY RANGE (pushed_at)` monthly.
### Feed Engine
`generate_feeds_for_literature()`: new article → match tags against active subscriptions → compute priority (must_read/recommended/related) → insert `user_feed`. Called after every PubMed pipeline run.
### Frontend Route Architecture
Three layouts: `PublicLayout` (no auth, no sidebar), `AuthLayout` (centered card), `AppLayout` (sidebar + header). Admin uses its own `AdminLayout`. All pages lazy-loaded via `() => import(...)`. Auth guard in `router.beforeEach()` checks `auth.isAuthenticated`.
### PubMed Pipeline
**FTP 每日更新文件为主数据源**2026-07-25 决策)。取代旧 E-utilities API 多路搜索策略。
日常运行:
1. **FTP 下载** `pubmed25updateNNNN.xml.gz`(每日 ~10MB,含全部新增/修改/删除记录)
2. **解析**`_extract_article()`(复用 `pubmed_baseline.py` 的 lxml 解析器)
3. **过滤**`_is_oncology()` 按 MeSH 肿瘤科筛选(复用 `pubmed_baseline.py`
4. **处理**`<PubmedArticle>``_update_lit_from_article()` upsert`<DeleteCitation>` → 标记 `retracted=True`
5. **打标**: MeSH UI → `global_tags` lookup → `global_literature_tags` INSERT
6. **检查点**`pipeline_runs.processed_date` 记录已处理的 EDAT 日期
降级:FTP 不可用时,回退到 `pubmed_api.py`NCBI E-utilities3 req/s)的 `reldate=1&datetype=edat` 查询。
### ARQ Scheduled Tasks (`backend/app/tasks/worker.py`)
定时任务使用 ARQ (Redis-backed),所有时间均为 **UTC**
| 任务 | 函数 | Cron (UTC) | 北京时间 | 说明 |
|---|---|---|---|---|
| 每日 FTP 增量 | `daily_ftp_update` | `03:07` 每天 | 11:07 | FTP 下载更新文件,处理新增/修改/删除(取代精搜+宽搜+retagger |
| 引用更新 | `daily_citation_update` | `05:13` 每天 | 13:13 | 刷新最近文献被引次数 |
| 摘要邮件 | `daily_digest_task` | `22:30` 每天 | 06:30 (次日) | 每日摘要推送 |
> **旧任务已移除:** `daily_pubmed_pipeline`MAJR 精搜)、`weekly_broad_pipeline`Title/Abstract 宽搜)和 `mesh_retagger.py`(回查)已被 `daily_ftp_update` 完全取代。FTP 每日更新文件包含所有新/改/删记录,覆盖 MeSH in-process→medline 过渡,不再需要多路搜索和回查。
手动触发:
```bash
# FTP 增量更新
curl -X POST localhost:8000/api/v1/admin/pipeline/run -H "Authorization: Bearer $(admin_token)"
# 批量刷新被引次数
curl -X POST localhost:8000/api/v1/admin/pipeline/refresh-citations -H "Authorization: Bearer $(admin_token)"
curl -X POST "localhost:8000/api/v1/admin/pipeline/refresh-citations?limit=500" -H "Authorization: Bearer $(admin_token)"
```
### Citation Counts (PubMed elink)
每条文献入库时**自动查询被引次数**(`pubmed_api.py:fetch_citedby_counts()`,通过 NCBI elink 免费接口,200 篇/req)。每日定时任务 `daily_citation_update` 刷新最近文献的被引。管理后台也支持手动全量刷新。
**技术要点:**
- 接口:`elink.fcgi?dbfrom=pubmed&linkname=pubmed_pubmed_citedin&id=PMID1,PMID2,...`
- 限速:无 API Key 3 req/s,有 key 10 req/s
- 批量 200 篇/次,全库 1 万篇约需 15-20 分钟免费模式
- 更新 `global_literature.cited_by_count` + `updated_at`(所以 🔄 徽标会联动)
- 新文献引用数可能为 0(刚发表,随后每次定时刷新自动更新)
**NLM 行为要点:**
- `DateRevised` **不可靠**——NLM 明确说不应依赖它判断 revision(如 MeSH 年更大批改记录但不一定赋值)。判 new/revised 的唯一稳妥方式是 PMID 撞库
- `_update_lit_from_article()` 对已存在 PMID 直接覆盖所有字段,不做"是否变了"的检查
- 每年 11 月中–12 月中是 MeSH 年更期,indexed 记录暂停灌入,只发 in-process/publisher。此时宽搜补充尤为重要
### PMC OA TDM (Full Text)
为未来深度学习 NLP(药物-靶点关系抽取、临床试验结构化提取)做数据准备。
**PMC OA 覆盖:** 约 15% 的 PubMed 文献有 PMC 全文,但高影响力期刊 OA 比例远高于此,对 oncology 领域数据挖掘足够。
**数据流:** PubMed efetch 已返回 `pmc_id` + `is_oa``pmc_oa.py` 用 OA Service API 下载 XML → `jats_parser.py` 解析成结构化 sections。
**存储策略:** 存 parsed sections JSON`full_text_sections` 字段),不存原始 XML。sections 结构:
```json
{"sections": [{"heading": "Introduction", "text": "..."}, {"heading": "Methods", "text": "..."}],
"chemicals": [{"name": "Trastuzumab", "registry_number": "..."}],
"references": [{"pmid": 12345, "title": "..."}],
"tables": [{"caption": "Table 1", "data": "..."}]}
```
**实现(Phase 1):** `app/services/pmc_oa.py` — OA Service API 客户端;`app/services/jats_parser.py` — JATS XML 解析;`GlobalLiterature.full_text_sections` JSON 字段(Alembic 迁移);在 `_run_pipeline()` 中新增 OA 全文抓取阶段。Phase 2 回填现有 OA 文献。Phase 3 按需上 NLP 模型。
**技术要点:**
- API`https://www.ncbi.nlm.nih.gov/pmc/utils/oa/oa.fcgi?id=PMCID` → XML 响应
- PMCID ↔ PMID 映射已在 PubMed efetch 阶段完成(`pmc_id` 字段)
- JATS XML 按 `<sec>` 标签组织,`<sec-title>` + 段落文本即可提取结构化 sections
- `<chemical>` 标签含 Registry Number,可对接 PubChem
- 限速:共用 NCBI E-utilities 池(3 req/s 无 key10 req/s 有 key
-`_run_pipeline()` 集成:在打标 + Feed 生成后,对 `is_oa=True` 且无 `full_text_sections` 的文献执行
### Key Design Decisions
- **No multi-specialty in one deployment.** One codebase, one YAML config, one Docker stack per specialty. Oncology is the first.
- **Personal → Team upgrade is zero-data-migration.** Personal user's `tenant_id` stays the same; only `is_personal` flips and `plan_type` changes.
- **SQLite in dev, PostgreSQL in prod.** SQLAlchemy generic types enable this.
- **Dev mode password reset** returns the reset link directly in API response (no SMTP needed).
- **`user["sub"]` is a string.** Always convert to `uuid.UUID()` before passing to SQLAlchemy queries.
- **搜索功能必须与 PubMed 完全一致。** 这是硬性要求,不是"未来优化"。所有 PubMed 字段标签必须全量支持,已存储数据的立即接通搜索路径,缺失数据的补充 XML 抽取和存储。不允许任何字段退化到纯文本搜索。
## Git Workflow (PR)
代码托管在自建 Gitea: http://123.207.9.209:3000/scilit/backend。**严禁直接 push 到 main 分支**Gitea 端已设置 branch protection)。所有改动必须走 Pull Request。
### ClaudeCode 工作流
1. 从 main 新建分支:`git checkout -b feat/xxx``fix/xxx`
2. 在分支上修改代码、commit
3. Push 分支:`git push origin 分支名`
4. 用户访问 Gitea 创建 PRReview 后 Merge
### 分支命名规范
- `feat/xxx` — 新功能
- `fix/xxx` — 修复
- `refactor/xxx` — 重构
- `docs/xxx` — 文档
- `chore/xxx` — 杂项
### 仓库地址
- SSH: `git@gitea:scilit/backend.git`
- Web: http://123.207.9.209:3000/scilit/backend