dbtips-curate 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbtips_curate-0.2.0/PKG-INFO +303 -0
- dbtips_curate-0.2.0/README.md +284 -0
- dbtips_curate-0.2.0/pyproject.toml +49 -0
- dbtips_curate-0.2.0/src/dbtips_curate/__init__.py +1 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/__init__.py +0 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/_common.py +67 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/_output.py +51 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/_widget.py +207 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/dev.py +358 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/entity.py +723 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/history.py +213 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/main.py +70 -0
- dbtips_curate-0.2.0/src/dbtips_curate/cli/op.py +399 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/__init__.py +0 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/config.py +181 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/fs.py +154 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/git.py +80 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/git_history.py +183 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/merge.py +314 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/ops.py +78 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/schema.py +161 -0
- dbtips_curate-0.2.0/src/dbtips_curate/core/worklist.py +166 -0
- dbtips_curate-0.2.0/src/dbtips_curate/mcp_server.py +114 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/_deferred/targetability-design-note.md +170 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate/SKILL.md +400 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-antibodies/SKILL.md +219 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-assayability/SKILL.md +175 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-gene-essentiality/SKILL.md +107 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-ontology/SKILL.md +129 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-orthologs/SKILL.md +123 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-supplementary-materials/SKILL.md +147 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-target-literature/SKILL.md +286 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-target-pathway/SKILL.md +166 -0
- dbtips_curate-0.2.0/src/dbtips_curate/skill/dbtips-curate-target-pipeline/SKILL.md +291 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dbtips-curate
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Three-way merge curation for dbtips pipeline-generated dossier caches. Ships agentskills.io-compatible skills for Claude Code, OpenAI Codex, and Google Antigravity.
|
|
5
|
+
Author: Aganitha
|
|
6
|
+
Author-email: dev@aganitha.ai
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: click (>=8.1,<9.0)
|
|
14
|
+
Requires-Dist: pydantic (>=2.6.0,<3.0.0)
|
|
15
|
+
Requires-Dist: rich (>=13.7.0,<14.0.0)
|
|
16
|
+
Requires-Dist: typer (>=0.15.0,<0.16.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# dbtips-curate
|
|
20
|
+
|
|
21
|
+
Three-way merge curation for dbtips pipeline-generated dossier JSON caches.
|
|
22
|
+
|
|
23
|
+
Scientists hand-curate cache files after pipeline generation (deleting noise,
|
|
24
|
+
adding records from external sources, patching status fields). When the
|
|
25
|
+
pipeline refreshes, those edits get wiped. `dbtips-curate` preserves scientist
|
|
26
|
+
intent as an append-only git-tracked operation log and replays it onto every
|
|
27
|
+
refresh — so the merged served file is always "fresh pipeline data + scientist
|
|
28
|
+
intent" without manual re-curation.
|
|
29
|
+
|
|
30
|
+
## Scope
|
|
31
|
+
|
|
32
|
+
- Sections (section-agnostic engine; add more via config — see
|
|
33
|
+
[docs/adding-sections.md](docs/adding-sections.md)):
|
|
34
|
+
- `/evidence/target-literature/` — `target_disease`, key `PMID`
|
|
35
|
+
- `/target-assessment/antibodies/` — `target`, key `Antibody` + `Provider`
|
|
36
|
+
- `/evidence/target-pathway/` — `target`, key `image_url`
|
|
37
|
+
- `/target-assessment/assayability/` — `target`, key `ChEMBL ID`
|
|
38
|
+
- Entity-type folders: `cached_data_json/target_disease/<target>-<disease>.json`
|
|
39
|
+
and `cached_data_json/target/<target>.json`
|
|
40
|
+
- Implemented ops: `delete`, `add`, `undo`
|
|
41
|
+
- Deferred to v2: `patch`, `accept` CLI commands (merge engine handles both)
|
|
42
|
+
|
|
43
|
+
**Adding a new section?** Read [docs/adding-sections.md](docs/adding-sections.md)
|
|
44
|
+
— it has the data constraints to check, a copy-paste validator, and the
|
|
45
|
+
config-vs-code decision flow.
|
|
46
|
+
|
|
47
|
+
## How it actually works (the model in one paragraph)
|
|
48
|
+
|
|
49
|
+
Every tracked entity has a `curation/<entity-type>/<id>/` folder containing:
|
|
50
|
+
immutable raw snapshots of the section (Base for 3-way merge), an append-only
|
|
51
|
+
`ops.jsonl` (scientist intent), `state.json` (mode + pointers), and per-refresh
|
|
52
|
+
changelogs. Each scientist action is one git commit, so blame works on every
|
|
53
|
+
clinical decision. On every pipeline refresh, `entity ingest` takes a fresh
|
|
54
|
+
snapshot (= Theirs), runs a 3-way merge (Base + Theirs + ops → Merged), and
|
|
55
|
+
splices the merged section back into the served file. Scientist edits survive
|
|
56
|
+
across refreshes; pipeline updates land on top.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
The package is published to public PyPI. Any Python tool runner works.
|
|
61
|
+
|
|
62
|
+
### End-user install (recommended)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# One-shot, no persistent install (great for trying it out):
|
|
66
|
+
uvx dbtips-curate --version
|
|
67
|
+
|
|
68
|
+
# Persistent global install (recommended for daily use):
|
|
69
|
+
uv tool install dbtips-curate
|
|
70
|
+
# or: pipx install dbtips-curate
|
|
71
|
+
# or: pip install dbtips-curate
|
|
72
|
+
|
|
73
|
+
dbtips-curate --version # dbtips-curate/0.2.0
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then bootstrap the skills into your AI coding harnesses:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
dbtips-curate dev bootstrap
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Interactive menu picks Claude / Codex / Antigravity / all. Non-interactive form:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
dbtips-curate dev bootstrap --targets all
|
|
86
|
+
dbtips-curate dev bootstrap --targets claude,codex
|
|
87
|
+
dbtips-curate dev bootstrap --targets claude --project .
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Skill install matrix
|
|
91
|
+
|
|
92
|
+
Every supported harness reads the same [agentskills.io](https://agentskills.io)
|
|
93
|
+
`SKILL.md` format we ship — no conversion.
|
|
94
|
+
|
|
95
|
+
| Harness | Global path | Project path |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Claude Code | `~/.claude/skills/<name>/` | `<project>/.claude/skills/<name>/` |
|
|
98
|
+
| OpenAI Codex | `~/.agents/skills/<name>/` | `<project>/.agents/skills/<name>/` |
|
|
99
|
+
| Google Antigravity | `~/.gemini/config/skills/<name>/` | `<project>/.agents/skills/<name>/` (shared with Codex) |
|
|
100
|
+
|
|
101
|
+
### Dev install (contributors only)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/aganitha/dbtips-curate.git
|
|
105
|
+
cd dbtips-curate
|
|
106
|
+
poetry install
|
|
107
|
+
poetry run dbtips-curate --version
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For the test suite: `poetry install --with dev && poetry run pytest`.
|
|
111
|
+
|
|
112
|
+
> **Version note:** `typer` 0.12.x is incompatible with `click` 8.4+ (released
|
|
113
|
+
> April 2026) — options like `--path` get misparsed as positional args and
|
|
114
|
+
> `--version` stops exiting eagerly. The `typer = "^0.15.0"` pin in
|
|
115
|
+
> `pyproject.toml` sidesteps this. If you see *"Got unexpected extra argument"*
|
|
116
|
+
> or *"Missing command"* on a valid flag, upgrade typer.
|
|
117
|
+
|
|
118
|
+
## One-time setup (per platform repo)
|
|
119
|
+
|
|
120
|
+
From the platform repo root, create the curation folder + per-team config:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
dbtips-curate dev init \
|
|
124
|
+
--path backend/res-immunology-automation/res_immunology_automation/src/scripts/curation
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This drops `.dbtips-curate.toml` next to `cached_data_json/` and registers all
|
|
128
|
+
ten curated section endpoints (literature, antibodies, pathway, assayability,
|
|
129
|
+
target-pipeline, ontology, gene-essentiality, orthologs, supplementary
|
|
130
|
+
materials). The file is committed to git so every scientist gets identical
|
|
131
|
+
config.
|
|
132
|
+
|
|
133
|
+
## Commands at a glance
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
dbtips-curate
|
|
137
|
+
entity list | status | track | snapshot | diff | ingest | records | compare | finalize
|
|
138
|
+
op delete | add | patch | undo
|
|
139
|
+
history ops | report
|
|
140
|
+
dev init | bootstrap | install-skill
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Every command supports `--json` for machine-readable output, exits 0/1/2 for
|
|
144
|
+
success / runtime error / usage error, and follows the contract:
|
|
145
|
+
`{"ok": true, "data": {...}}` on success, `{"ok": false, "error": {"code", "message", "hint"}}` on error.
|
|
146
|
+
|
|
147
|
+
### `entity` — lifecycle
|
|
148
|
+
|
|
149
|
+
| Command | Purpose |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `entity list [--type T]` | All tracked entities of one type with their mode + last refresh |
|
|
152
|
+
| `entity track <id>` | Bootstrap. **Walks git history** of the served file → synthesizes `delete` ops for historical removals; flags historical additions for scientist review; sets mode based on what was found (see modes below). `--skip-history` opts out. |
|
|
153
|
+
| `entity snapshot <id> [--reason ...]` | Capture the current served file's section as an immutable raw snapshot. First snapshot becomes Base for 3-way merge. |
|
|
154
|
+
| `entity status <id>` | Prioritized worklist: conflicts → historical_additions → bootstrap_candidates → resurrected → new from pipeline. Includes `suggested_filters` for greenfield noise triage. |
|
|
155
|
+
| `entity records <id> [--page N --page-size 20] [--full] [--fields F1,F2]` | List current records, paginated. For tabular review in chat — Claude renders as markdown table with `delete` action per row. |
|
|
156
|
+
| `entity diff <id>` | **Read-only preview** of the 3-way merge vs the current served file. Writes nothing. Use before `ingest` if scientist wants to see the table first. |
|
|
157
|
+
| `entity ingest <id>` | Take fresh snapshot → 3-way merge → splice merged section back into served file. Writes changelog + conflicts file. This is what the pipeline calls. |
|
|
158
|
+
| `entity finalize <id> [--no-push]` | Mark current review complete: advance `last_reviewed_raw`, clear bootstrap_candidates, prune old snapshots, git commit. Pushes to remote if `auto_push_on = "finalize"`. |
|
|
159
|
+
|
|
160
|
+
### `op` — scientist actions
|
|
161
|
+
|
|
162
|
+
| Command | Purpose |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `op delete <id> --section <s> --key K=V [--key K2=V2 ...] --reason "..."` | Tombstone a record. Whole-record delete. Compound-key sections pass one `--key` flag per identity field. Per-op git commit. |
|
|
165
|
+
| `op add <id> --section <s> --record-file F --source "..."` | Add a record from an external source. Validates against the section's `required_fields`. Use `-` to read from stdin. |
|
|
166
|
+
| `op patch <id> --section <s> --key K=V [--key K2=V2 ...] --set field=value [--set ...] --reason "..."` | Change one or more fields on an existing record. Values are JSON-decoded when possible (`Phase=3` → int, `Approved=true` → bool). Identity fields cannot be `--set` (rejects with `PATCH_TOUCHES_IDENTITY`). |
|
|
167
|
+
| `op undo <id> --undoes <op_id> --reason "..."` | Cancel a prior op. Endpoint inferred from the target op. Rejects unknown op_ids, undo-of-undo, and double-undo. Original op stays in the log for audit. |
|
|
168
|
+
|
|
169
|
+
All support `--dry-run` to preview what would be logged without writing.
|
|
170
|
+
|
|
171
|
+
### `history` — read-only views
|
|
172
|
+
|
|
173
|
+
| Command | Purpose |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `history ops <id> [--since DATE] [--actor EMAIL]` | The full operation log with optional filters. |
|
|
176
|
+
| `history report <id> [--output FILE] [--open]` | Generate standalone HTML report (all queues + ops table). `--open` launches it in your browser. Good for review meetings or async sharing. |
|
|
177
|
+
|
|
178
|
+
### `dev` — setup + maintenance
|
|
179
|
+
|
|
180
|
+
| Command | Purpose |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `dev init --path DIR` | Create the curation folder + write a default `.dbtips-curate.toml`. |
|
|
183
|
+
| `dev bootstrap [--targets claude,codex,antigravity\|all] [--project DIR]` | Install every skill into the chosen AI coding harnesses. Interactive menu if `--targets` omitted. `--project .` also drops skills into the current repo's workspace-scope skill folder. Idempotent. |
|
|
184
|
+
| `dev install-skill [--target claude\|codex\|antigravity] [--home DIR] [--only NAME]` | Single-target install primitive used by `bootstrap`. Prefer `bootstrap` for end-user setup. |
|
|
185
|
+
|
|
186
|
+
## Mode state machine
|
|
187
|
+
|
|
188
|
+
| Mode | Set by | Meaning |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `greenfield` | `track` with no git history changes | Fresh pipeline output, no prior scientist edits detected |
|
|
191
|
+
| `brownfield-from-git` | `track` when git history shows record-level adds/removes in past commits | Prior scientist edits detected via git history; synthesized delete ops + historical_additions queue surfaced |
|
|
192
|
+
| `brownfield-detected` | `ingest` (auto-promotion) when pipeline output drops Base records that aren't tombstoned | Records preserved as bootstrap_candidates pending scientist confirmation |
|
|
193
|
+
| `brownfield-bootstrap` | Legacy label; behaves identically | (Backward compatible) |
|
|
194
|
+
| `active` | `finalize` after first review session | Steady-state operation |
|
|
195
|
+
|
|
196
|
+
## Filesystem layout
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
backend/.../scripts/
|
|
200
|
+
cached_data_json/ (pipeline writes here, served to clients)
|
|
201
|
+
curation/ (this tool writes here, tracked in git)
|
|
202
|
+
.dbtips-curate.toml (per-team config + section schema registry)
|
|
203
|
+
target_disease/<entity-id>/
|
|
204
|
+
raw/<ts>.json (immutable section snapshots)
|
|
205
|
+
ops.jsonl (append-only scientist operations)
|
|
206
|
+
state.json (mode + last_reviewed_raw + git history summary)
|
|
207
|
+
conflicts/<ts>.json (open conflicts after last merge)
|
|
208
|
+
changelog/<ts>.md (human-readable per-refresh summary)
|
|
209
|
+
merged.json (post-merge section, spliced back into served file)
|
|
210
|
+
reports/<ts>.html (HTML reports — only created on demand)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Three-way merge semantics
|
|
214
|
+
|
|
215
|
+
Each `entity ingest` runs:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Base = snapshot at state.last_reviewed_raw (what scientist last accepted)
|
|
219
|
+
Theirs = fresh snapshot of current served file (what pipeline just produced)
|
|
220
|
+
Ours = apply_ops(Base, ops.jsonl) (scientist intent layered on Base)
|
|
221
|
+
|
|
222
|
+
Merged = three_way_merge(Base, Theirs, Ours)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Per identity-keyed record (PMID for literature):
|
|
226
|
+
|
|
227
|
+
| In Base | In Theirs | Has op | Result |
|
|
228
|
+
|---|---|---|---|
|
|
229
|
+
| ✓ | ✗ | tombstone | silent (both agree to remove) |
|
|
230
|
+
| ✓ | ✗ | none | `removed_by_pipeline` queue — in brownfield modes, **preserved** as bootstrap candidate |
|
|
231
|
+
| ✗ | ✓ | none | `new_from_pipeline` queue — added to merged |
|
|
232
|
+
| ✗ | ✓ | tombstone | dropped from merged, logged as `resurrected_tombstone` |
|
|
233
|
+
| ✓ | ✓ | patch | per-field merge; pipeline-changed + scientist-patched same field → `conflict` (scientist wins by default) |
|
|
234
|
+
| ✗ | ✗ | add | scientist-added record, included in merged |
|
|
235
|
+
|
|
236
|
+
## Pipeline integration (build_dossier.py)
|
|
237
|
+
|
|
238
|
+
Already wired in via a CURATION HOOK block at the top of `build_dossier.py`.
|
|
239
|
+
Two knobs:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
IS_REFRESH: bool = False # True = clear section + redis before pipeline regen
|
|
243
|
+
CURATE_ENABLED: bool = True # False = bypass the hook entirely (rollback)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
With `IS_REFRESH = True`, the hook (in order):
|
|
247
|
+
1. Snapshots the current served file's section (pre-refresh Base)
|
|
248
|
+
2. Clears the section key from served JSON + redis
|
|
249
|
+
3. Lets the pipeline regenerate from scratch
|
|
250
|
+
4. Calls `entity ingest` → 3-way merge → splices merged back into served file
|
|
251
|
+
|
|
252
|
+
With `IS_REFRESH = False` (the safe default), only step 4 runs — keeps
|
|
253
|
+
curation state in sync without forcing regeneration.
|
|
254
|
+
|
|
255
|
+
## Driving from Claude (Claude Code only)
|
|
256
|
+
|
|
257
|
+
After `dev install-skill`, in any Claude Code session:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
curate acvr2a-no-disease
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Claude reads the SKILL.md, then:
|
|
264
|
+
|
|
265
|
+
1. Runs `entity status` and presents the worklist as a markdown table
|
|
266
|
+
2. Surfaces brownfield-from-git evidence (commits walked, historical adds/removes)
|
|
267
|
+
prominently before anything else
|
|
268
|
+
3. Walks decisions one-by-one, calling `op delete` / `op add` with `--json` after explicit confirmation
|
|
269
|
+
4. On `show me records`, renders `entity records --page N --page-size 20` as a paginated markdown table with delete actions per row
|
|
270
|
+
5. On `diff` or `preview`, calls `entity diff` (read-only) and renders the four delta tables
|
|
271
|
+
6. On session end, calls `entity finalize` and summarizes
|
|
272
|
+
|
|
273
|
+
> **Claude Code is required.** Skills work in Claude Code (terminal, desktop
|
|
274
|
+
> app, IDE extensions). The Claude.ai consumer Desktop app would need an MCP
|
|
275
|
+
> server wrapper (the `mcp_server.py` stub exists; `mcp serve` subcommand not
|
|
276
|
+
> built).
|
|
277
|
+
|
|
278
|
+
## Testing
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
make test # 26 tests covering merge engine + CLI end-to-end + git history walk + records pagination
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The fixture in `tests/fixtures/acvr2a-no-disease.json` is a real 5-record slice
|
|
285
|
+
of the production literature section. End-to-end tests run the full lifecycle
|
|
286
|
+
(track → snapshot → ingest → op delete → simulate refresh → ingest → finalize)
|
|
287
|
+
against a temp git repo mirroring the production layout.
|
|
288
|
+
|
|
289
|
+
## Status
|
|
290
|
+
|
|
291
|
+
- **Production-ready for literature curation** on `target_disease` entities
|
|
292
|
+
- 26/26 tests passing
|
|
293
|
+
- Pipeline hook in `build_dossier.py` ready behind the `IS_REFRESH` / `CURATE_ENABLED` flags
|
|
294
|
+
- Scientist-facing skill installed via `dev install-skill`
|
|
295
|
+
|
|
296
|
+
## Roadmap (deferred to v2)
|
|
297
|
+
|
|
298
|
+
- `op patch` (field-level updates) — merge engine handles them, no CLI yet
|
|
299
|
+
- `op accept` (resolve conflicts via choice)
|
|
300
|
+
- MCP server runtime (`mcp serve` subcommand) for Claude.ai consumer Desktop
|
|
301
|
+
- Field-level brownfield detection (needed when expanding to clinical trial endpoints)
|
|
302
|
+
- Other section schemas: `/evidence/search-patent/`, `/market-intelligence/*`, etc.
|
|
303
|
+
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# dbtips-curate
|
|
2
|
+
|
|
3
|
+
Three-way merge curation for dbtips pipeline-generated dossier JSON caches.
|
|
4
|
+
|
|
5
|
+
Scientists hand-curate cache files after pipeline generation (deleting noise,
|
|
6
|
+
adding records from external sources, patching status fields). When the
|
|
7
|
+
pipeline refreshes, those edits get wiped. `dbtips-curate` preserves scientist
|
|
8
|
+
intent as an append-only git-tracked operation log and replays it onto every
|
|
9
|
+
refresh — so the merged served file is always "fresh pipeline data + scientist
|
|
10
|
+
intent" without manual re-curation.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
- Sections (section-agnostic engine; add more via config — see
|
|
15
|
+
[docs/adding-sections.md](docs/adding-sections.md)):
|
|
16
|
+
- `/evidence/target-literature/` — `target_disease`, key `PMID`
|
|
17
|
+
- `/target-assessment/antibodies/` — `target`, key `Antibody` + `Provider`
|
|
18
|
+
- `/evidence/target-pathway/` — `target`, key `image_url`
|
|
19
|
+
- `/target-assessment/assayability/` — `target`, key `ChEMBL ID`
|
|
20
|
+
- Entity-type folders: `cached_data_json/target_disease/<target>-<disease>.json`
|
|
21
|
+
and `cached_data_json/target/<target>.json`
|
|
22
|
+
- Implemented ops: `delete`, `add`, `undo`
|
|
23
|
+
- Deferred to v2: `patch`, `accept` CLI commands (merge engine handles both)
|
|
24
|
+
|
|
25
|
+
**Adding a new section?** Read [docs/adding-sections.md](docs/adding-sections.md)
|
|
26
|
+
— it has the data constraints to check, a copy-paste validator, and the
|
|
27
|
+
config-vs-code decision flow.
|
|
28
|
+
|
|
29
|
+
## How it actually works (the model in one paragraph)
|
|
30
|
+
|
|
31
|
+
Every tracked entity has a `curation/<entity-type>/<id>/` folder containing:
|
|
32
|
+
immutable raw snapshots of the section (Base for 3-way merge), an append-only
|
|
33
|
+
`ops.jsonl` (scientist intent), `state.json` (mode + pointers), and per-refresh
|
|
34
|
+
changelogs. Each scientist action is one git commit, so blame works on every
|
|
35
|
+
clinical decision. On every pipeline refresh, `entity ingest` takes a fresh
|
|
36
|
+
snapshot (= Theirs), runs a 3-way merge (Base + Theirs + ops → Merged), and
|
|
37
|
+
splices the merged section back into the served file. Scientist edits survive
|
|
38
|
+
across refreshes; pipeline updates land on top.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
The package is published to public PyPI. Any Python tool runner works.
|
|
43
|
+
|
|
44
|
+
### End-user install (recommended)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# One-shot, no persistent install (great for trying it out):
|
|
48
|
+
uvx dbtips-curate --version
|
|
49
|
+
|
|
50
|
+
# Persistent global install (recommended for daily use):
|
|
51
|
+
uv tool install dbtips-curate
|
|
52
|
+
# or: pipx install dbtips-curate
|
|
53
|
+
# or: pip install dbtips-curate
|
|
54
|
+
|
|
55
|
+
dbtips-curate --version # dbtips-curate/0.2.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then bootstrap the skills into your AI coding harnesses:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
dbtips-curate dev bootstrap
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Interactive menu picks Claude / Codex / Antigravity / all. Non-interactive form:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
dbtips-curate dev bootstrap --targets all
|
|
68
|
+
dbtips-curate dev bootstrap --targets claude,codex
|
|
69
|
+
dbtips-curate dev bootstrap --targets claude --project .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Skill install matrix
|
|
73
|
+
|
|
74
|
+
Every supported harness reads the same [agentskills.io](https://agentskills.io)
|
|
75
|
+
`SKILL.md` format we ship — no conversion.
|
|
76
|
+
|
|
77
|
+
| Harness | Global path | Project path |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| Claude Code | `~/.claude/skills/<name>/` | `<project>/.claude/skills/<name>/` |
|
|
80
|
+
| OpenAI Codex | `~/.agents/skills/<name>/` | `<project>/.agents/skills/<name>/` |
|
|
81
|
+
| Google Antigravity | `~/.gemini/config/skills/<name>/` | `<project>/.agents/skills/<name>/` (shared with Codex) |
|
|
82
|
+
|
|
83
|
+
### Dev install (contributors only)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/aganitha/dbtips-curate.git
|
|
87
|
+
cd dbtips-curate
|
|
88
|
+
poetry install
|
|
89
|
+
poetry run dbtips-curate --version
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For the test suite: `poetry install --with dev && poetry run pytest`.
|
|
93
|
+
|
|
94
|
+
> **Version note:** `typer` 0.12.x is incompatible with `click` 8.4+ (released
|
|
95
|
+
> April 2026) — options like `--path` get misparsed as positional args and
|
|
96
|
+
> `--version` stops exiting eagerly. The `typer = "^0.15.0"` pin in
|
|
97
|
+
> `pyproject.toml` sidesteps this. If you see *"Got unexpected extra argument"*
|
|
98
|
+
> or *"Missing command"* on a valid flag, upgrade typer.
|
|
99
|
+
|
|
100
|
+
## One-time setup (per platform repo)
|
|
101
|
+
|
|
102
|
+
From the platform repo root, create the curation folder + per-team config:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
dbtips-curate dev init \
|
|
106
|
+
--path backend/res-immunology-automation/res_immunology_automation/src/scripts/curation
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This drops `.dbtips-curate.toml` next to `cached_data_json/` and registers all
|
|
110
|
+
ten curated section endpoints (literature, antibodies, pathway, assayability,
|
|
111
|
+
target-pipeline, ontology, gene-essentiality, orthologs, supplementary
|
|
112
|
+
materials). The file is committed to git so every scientist gets identical
|
|
113
|
+
config.
|
|
114
|
+
|
|
115
|
+
## Commands at a glance
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
dbtips-curate
|
|
119
|
+
entity list | status | track | snapshot | diff | ingest | records | compare | finalize
|
|
120
|
+
op delete | add | patch | undo
|
|
121
|
+
history ops | report
|
|
122
|
+
dev init | bootstrap | install-skill
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Every command supports `--json` for machine-readable output, exits 0/1/2 for
|
|
126
|
+
success / runtime error / usage error, and follows the contract:
|
|
127
|
+
`{"ok": true, "data": {...}}` on success, `{"ok": false, "error": {"code", "message", "hint"}}` on error.
|
|
128
|
+
|
|
129
|
+
### `entity` — lifecycle
|
|
130
|
+
|
|
131
|
+
| Command | Purpose |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `entity list [--type T]` | All tracked entities of one type with their mode + last refresh |
|
|
134
|
+
| `entity track <id>` | Bootstrap. **Walks git history** of the served file → synthesizes `delete` ops for historical removals; flags historical additions for scientist review; sets mode based on what was found (see modes below). `--skip-history` opts out. |
|
|
135
|
+
| `entity snapshot <id> [--reason ...]` | Capture the current served file's section as an immutable raw snapshot. First snapshot becomes Base for 3-way merge. |
|
|
136
|
+
| `entity status <id>` | Prioritized worklist: conflicts → historical_additions → bootstrap_candidates → resurrected → new from pipeline. Includes `suggested_filters` for greenfield noise triage. |
|
|
137
|
+
| `entity records <id> [--page N --page-size 20] [--full] [--fields F1,F2]` | List current records, paginated. For tabular review in chat — Claude renders as markdown table with `delete` action per row. |
|
|
138
|
+
| `entity diff <id>` | **Read-only preview** of the 3-way merge vs the current served file. Writes nothing. Use before `ingest` if scientist wants to see the table first. |
|
|
139
|
+
| `entity ingest <id>` | Take fresh snapshot → 3-way merge → splice merged section back into served file. Writes changelog + conflicts file. This is what the pipeline calls. |
|
|
140
|
+
| `entity finalize <id> [--no-push]` | Mark current review complete: advance `last_reviewed_raw`, clear bootstrap_candidates, prune old snapshots, git commit. Pushes to remote if `auto_push_on = "finalize"`. |
|
|
141
|
+
|
|
142
|
+
### `op` — scientist actions
|
|
143
|
+
|
|
144
|
+
| Command | Purpose |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `op delete <id> --section <s> --key K=V [--key K2=V2 ...] --reason "..."` | Tombstone a record. Whole-record delete. Compound-key sections pass one `--key` flag per identity field. Per-op git commit. |
|
|
147
|
+
| `op add <id> --section <s> --record-file F --source "..."` | Add a record from an external source. Validates against the section's `required_fields`. Use `-` to read from stdin. |
|
|
148
|
+
| `op patch <id> --section <s> --key K=V [--key K2=V2 ...] --set field=value [--set ...] --reason "..."` | Change one or more fields on an existing record. Values are JSON-decoded when possible (`Phase=3` → int, `Approved=true` → bool). Identity fields cannot be `--set` (rejects with `PATCH_TOUCHES_IDENTITY`). |
|
|
149
|
+
| `op undo <id> --undoes <op_id> --reason "..."` | Cancel a prior op. Endpoint inferred from the target op. Rejects unknown op_ids, undo-of-undo, and double-undo. Original op stays in the log for audit. |
|
|
150
|
+
|
|
151
|
+
All support `--dry-run` to preview what would be logged without writing.
|
|
152
|
+
|
|
153
|
+
### `history` — read-only views
|
|
154
|
+
|
|
155
|
+
| Command | Purpose |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `history ops <id> [--since DATE] [--actor EMAIL]` | The full operation log with optional filters. |
|
|
158
|
+
| `history report <id> [--output FILE] [--open]` | Generate standalone HTML report (all queues + ops table). `--open` launches it in your browser. Good for review meetings or async sharing. |
|
|
159
|
+
|
|
160
|
+
### `dev` — setup + maintenance
|
|
161
|
+
|
|
162
|
+
| Command | Purpose |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `dev init --path DIR` | Create the curation folder + write a default `.dbtips-curate.toml`. |
|
|
165
|
+
| `dev bootstrap [--targets claude,codex,antigravity\|all] [--project DIR]` | Install every skill into the chosen AI coding harnesses. Interactive menu if `--targets` omitted. `--project .` also drops skills into the current repo's workspace-scope skill folder. Idempotent. |
|
|
166
|
+
| `dev install-skill [--target claude\|codex\|antigravity] [--home DIR] [--only NAME]` | Single-target install primitive used by `bootstrap`. Prefer `bootstrap` for end-user setup. |
|
|
167
|
+
|
|
168
|
+
## Mode state machine
|
|
169
|
+
|
|
170
|
+
| Mode | Set by | Meaning |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `greenfield` | `track` with no git history changes | Fresh pipeline output, no prior scientist edits detected |
|
|
173
|
+
| `brownfield-from-git` | `track` when git history shows record-level adds/removes in past commits | Prior scientist edits detected via git history; synthesized delete ops + historical_additions queue surfaced |
|
|
174
|
+
| `brownfield-detected` | `ingest` (auto-promotion) when pipeline output drops Base records that aren't tombstoned | Records preserved as bootstrap_candidates pending scientist confirmation |
|
|
175
|
+
| `brownfield-bootstrap` | Legacy label; behaves identically | (Backward compatible) |
|
|
176
|
+
| `active` | `finalize` after first review session | Steady-state operation |
|
|
177
|
+
|
|
178
|
+
## Filesystem layout
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
backend/.../scripts/
|
|
182
|
+
cached_data_json/ (pipeline writes here, served to clients)
|
|
183
|
+
curation/ (this tool writes here, tracked in git)
|
|
184
|
+
.dbtips-curate.toml (per-team config + section schema registry)
|
|
185
|
+
target_disease/<entity-id>/
|
|
186
|
+
raw/<ts>.json (immutable section snapshots)
|
|
187
|
+
ops.jsonl (append-only scientist operations)
|
|
188
|
+
state.json (mode + last_reviewed_raw + git history summary)
|
|
189
|
+
conflicts/<ts>.json (open conflicts after last merge)
|
|
190
|
+
changelog/<ts>.md (human-readable per-refresh summary)
|
|
191
|
+
merged.json (post-merge section, spliced back into served file)
|
|
192
|
+
reports/<ts>.html (HTML reports — only created on demand)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Three-way merge semantics
|
|
196
|
+
|
|
197
|
+
Each `entity ingest` runs:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Base = snapshot at state.last_reviewed_raw (what scientist last accepted)
|
|
201
|
+
Theirs = fresh snapshot of current served file (what pipeline just produced)
|
|
202
|
+
Ours = apply_ops(Base, ops.jsonl) (scientist intent layered on Base)
|
|
203
|
+
|
|
204
|
+
Merged = three_way_merge(Base, Theirs, Ours)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Per identity-keyed record (PMID for literature):
|
|
208
|
+
|
|
209
|
+
| In Base | In Theirs | Has op | Result |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| ✓ | ✗ | tombstone | silent (both agree to remove) |
|
|
212
|
+
| ✓ | ✗ | none | `removed_by_pipeline` queue — in brownfield modes, **preserved** as bootstrap candidate |
|
|
213
|
+
| ✗ | ✓ | none | `new_from_pipeline` queue — added to merged |
|
|
214
|
+
| ✗ | ✓ | tombstone | dropped from merged, logged as `resurrected_tombstone` |
|
|
215
|
+
| ✓ | ✓ | patch | per-field merge; pipeline-changed + scientist-patched same field → `conflict` (scientist wins by default) |
|
|
216
|
+
| ✗ | ✗ | add | scientist-added record, included in merged |
|
|
217
|
+
|
|
218
|
+
## Pipeline integration (build_dossier.py)
|
|
219
|
+
|
|
220
|
+
Already wired in via a CURATION HOOK block at the top of `build_dossier.py`.
|
|
221
|
+
Two knobs:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
IS_REFRESH: bool = False # True = clear section + redis before pipeline regen
|
|
225
|
+
CURATE_ENABLED: bool = True # False = bypass the hook entirely (rollback)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
With `IS_REFRESH = True`, the hook (in order):
|
|
229
|
+
1. Snapshots the current served file's section (pre-refresh Base)
|
|
230
|
+
2. Clears the section key from served JSON + redis
|
|
231
|
+
3. Lets the pipeline regenerate from scratch
|
|
232
|
+
4. Calls `entity ingest` → 3-way merge → splices merged back into served file
|
|
233
|
+
|
|
234
|
+
With `IS_REFRESH = False` (the safe default), only step 4 runs — keeps
|
|
235
|
+
curation state in sync without forcing regeneration.
|
|
236
|
+
|
|
237
|
+
## Driving from Claude (Claude Code only)
|
|
238
|
+
|
|
239
|
+
After `dev install-skill`, in any Claude Code session:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
curate acvr2a-no-disease
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Claude reads the SKILL.md, then:
|
|
246
|
+
|
|
247
|
+
1. Runs `entity status` and presents the worklist as a markdown table
|
|
248
|
+
2. Surfaces brownfield-from-git evidence (commits walked, historical adds/removes)
|
|
249
|
+
prominently before anything else
|
|
250
|
+
3. Walks decisions one-by-one, calling `op delete` / `op add` with `--json` after explicit confirmation
|
|
251
|
+
4. On `show me records`, renders `entity records --page N --page-size 20` as a paginated markdown table with delete actions per row
|
|
252
|
+
5. On `diff` or `preview`, calls `entity diff` (read-only) and renders the four delta tables
|
|
253
|
+
6. On session end, calls `entity finalize` and summarizes
|
|
254
|
+
|
|
255
|
+
> **Claude Code is required.** Skills work in Claude Code (terminal, desktop
|
|
256
|
+
> app, IDE extensions). The Claude.ai consumer Desktop app would need an MCP
|
|
257
|
+
> server wrapper (the `mcp_server.py` stub exists; `mcp serve` subcommand not
|
|
258
|
+
> built).
|
|
259
|
+
|
|
260
|
+
## Testing
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
make test # 26 tests covering merge engine + CLI end-to-end + git history walk + records pagination
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The fixture in `tests/fixtures/acvr2a-no-disease.json` is a real 5-record slice
|
|
267
|
+
of the production literature section. End-to-end tests run the full lifecycle
|
|
268
|
+
(track → snapshot → ingest → op delete → simulate refresh → ingest → finalize)
|
|
269
|
+
against a temp git repo mirroring the production layout.
|
|
270
|
+
|
|
271
|
+
## Status
|
|
272
|
+
|
|
273
|
+
- **Production-ready for literature curation** on `target_disease` entities
|
|
274
|
+
- 26/26 tests passing
|
|
275
|
+
- Pipeline hook in `build_dossier.py` ready behind the `IS_REFRESH` / `CURATE_ENABLED` flags
|
|
276
|
+
- Scientist-facing skill installed via `dev install-skill`
|
|
277
|
+
|
|
278
|
+
## Roadmap (deferred to v2)
|
|
279
|
+
|
|
280
|
+
- `op patch` (field-level updates) — merge engine handles them, no CLI yet
|
|
281
|
+
- `op accept` (resolve conflicts via choice)
|
|
282
|
+
- MCP server runtime (`mcp serve` subcommand) for Claude.ai consumer Desktop
|
|
283
|
+
- Field-level brownfield detection (needed when expanding to clinical trial endpoints)
|
|
284
|
+
- Other section schemas: `/evidence/search-patent/`, `/market-intelligence/*`, etc.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "dbtips-curate"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Three-way merge curation for dbtips pipeline-generated dossier caches. Ships agentskills.io-compatible skills for Claude Code, OpenAI Codex, and Google Antigravity."
|
|
5
|
+
authors = ["Aganitha <dev@aganitha.ai>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "dbtips_curate", from = "src"}]
|
|
8
|
+
# Ensure the packaged skill/ tree (SKILL.md files) ships in the wheel so
|
|
9
|
+
# `dbtips-curate dev bootstrap` can copy them out post-install.
|
|
10
|
+
include = ["src/dbtips_curate/skill/**/*.md"]
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.11"
|
|
14
|
+
# typer 0.12 misparses options against click >= 8.4 (Apr 2026): --path is read
|
|
15
|
+
# as an extra positional and --version stops being an eager terminating flag.
|
|
16
|
+
# Pin to 0.15+ which is compatible with the current click line.
|
|
17
|
+
typer = "^0.15.0"
|
|
18
|
+
click = "^8.1"
|
|
19
|
+
pydantic = "^2.6.0"
|
|
20
|
+
rich = "^13.7.0"
|
|
21
|
+
|
|
22
|
+
[tool.poetry.group.dev.dependencies]
|
|
23
|
+
pytest = "^8.0.0"
|
|
24
|
+
pytest-cov = "^4.1.0"
|
|
25
|
+
|
|
26
|
+
[tool.poetry.scripts]
|
|
27
|
+
dbtips-curate = "dbtips_curate.cli.main:app"
|
|
28
|
+
|
|
29
|
+
[[tool.poetry.source]]
|
|
30
|
+
name = "PyPI"
|
|
31
|
+
priority = "primary"
|
|
32
|
+
|
|
33
|
+
[[tool.poetry.source]]
|
|
34
|
+
name = "prod"
|
|
35
|
+
url = "https://pypi.aganitha.ai/"
|
|
36
|
+
priority = "supplemental"
|
|
37
|
+
|
|
38
|
+
[[tool.poetry.source]]
|
|
39
|
+
name = "dev"
|
|
40
|
+
url = "https://dev-pypi.aganitha.ai/"
|
|
41
|
+
priority = "supplemental"
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
addopts = "-ra -q"
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["poetry-core"]
|
|
49
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
File without changes
|