Add curator Influences tool with import wizard, CRUD, and graph.

CSV/JSON/XLSX mapping wizard expands artist-level rows to all paintings, blocks duplicate file/data imports via content and payload hashes, and documents the workflow in influence-import.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-16 18:54:26 +03:00
co-authored by Cursor
parent 62d7ebbe6a
commit 48bd17e985
21 changed files with 3402 additions and 15 deletions
+79
View File
@@ -0,0 +1,79 @@
# Influence links import & curator tool
Curator tool to **list / add / delete** influence edges, **visualize** an artist neighborhood, and **import** CSV / JSON / XLSX files through a mapping wizard.
Entry: header **Influences** (curator session). Data lives in `painting_influence_sources` (legacy `painting_influences` mirrored for painting→painting).
---
## Expansion rule (artist-level files)
Input workbooks such as [`Inputs/artist_influences_web_sources.xlsx`](../Inputs/artist_influences_web_sources.xlsx) are **artist-centric**. On import:
- **Influenced by** tokens → attach as sources on **all paintings** of the subject artist
- **Influenced** tokens that resolve to an artist → reverse link: subject artist becomes a source on **all paintings** of the influenced artist (same as PainterPalette `Influencedon`)
Unresolved names (artists / movements / paintings not in the DB) are **skipped** with warnings — no auto-create stubs.
---
## Wizard column roles
| Role | Meaning |
|------|---------|
| `subject_artist` | Artist the row is about (required) |
| `subject_painting` | Optional work hint (contextual; expansion still uses all works) |
| `influenced_by` | Who/what influenced the subject (`;` / `,` separated) |
| `influenced` | Who the subject influenced |
| `notes` / `reference` / `source_url` | Citation metadata (URLs scraped from reference text) |
| `ignore` | Skip column |
### Presets
| Preset | Typical headers |
|--------|-----------------|
| Web sources | `Artist`, `Painting`, `Influenced by`, `Influenced`, `Reference (source + link)` |
| Story of Art | Same Title Case (+ chapter reference column) |
| Art influences | `artist`, `painting`, `influenced_by`, `influenced`, `reference` |
| Custom | Map any columns manually |
Token classification order: **artist → movement → painting title** (under subject artist, then global).
Committed edges use `confidence=curated`, `discovered_via=import-wizard`.
### Duplicate file / data guard
Each successful commit stores SHA-256 fingerprints in `curator_audit_log` (`influence.import` details):
- `contentHash` — raw file bytes
- `payloadHash` — normalized mapped rows (same data under another filename still matches)
On parse/preview, if either hash matches a prior import, the UI warns and **blocks commit** unless the curator checks **Import anyway (force)**. Individual edges remain unique via DB `ON CONFLICT` either way.
---
## API (curator)
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/influences` | List (`artistId`, `paintingId`, `q`, `limit`, `offset`) |
| `GET` | `/api/influences/graph?artistId=` | Nodes + edges for SVG neighborhood |
| `POST` | `/api/influences` | Create one edge |
| `PATCH` | `/api/influences/:id` | Update metadata / remap source |
| `DELETE` | `/api/influences/:id` | Delete (+ legacy mirror) |
| `POST` | `/api/influences/import/parse` | `{ filename, contentBase64, sheet? }` → columns, hashes, `alreadyImported` |
| `POST` | `/api/influences/import/preview` | `{ rows, mapping, contentHash?, payloadHash? }` → proposals + duplicate check |
| `POST` | `/api/influences/import/commit` | `{ proposals, fileName?, contentHash?, payloadHash?, force? }``409 ALREADY_IMPORTED` unless `force` |
Audit: `influence.create` / `update` / `delete` / `import` in `curator_audit_log`.
---
## CLI still available
- `npm run dev:update-influences` — curated [`scripts/art-influences-data.js`](../scripts/art-influences-data.js)
- `npm run dev:import-painter-palette` — PainterPalette CSV
The wizard is the interactive path for ad-hoc spreadsheets under `Inputs/`.
See also [API.md](API.md), [DB_structure.md](DB_structure.md), [Plans.md](Plans.md).