Add PainterPalette integration, canonical DB schema, and influence UI fixes.

Integrate Inputs/PainterPalette.csv for artist metadata and influence links, add db/schema.sql with server/migrate.js, letterbox influence panel thumbnails, and refresh Titian/Pontormo images.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-22 12:09:47 +03:00
co-authored by Cursor
parent 4eead54062
commit b2cae284ac
41 changed files with 11270 additions and 14 deletions
+5 -2
View File
@@ -145,7 +145,8 @@ Full artist profile for the bio page and 3D gallery entry.
"bio_full": "Full Wikipedia lead section…",
"wikipedia_title": "Claude Monet",
"checkup_checked": false,
"checkup_fixed": false
"checkup_fixed": false,
"palette_metadata": { "nationality": "French", "styles": "Impressionism, …", "source": "PainterPalette.csv", ... }
},
"periods": [ { "id": 1, "name": "Milan Period", "start_year": 1482, "end_year": 1499, ... } ],
"paintings": [ { "id": 10, "title": "...", "year": 1498, "image_path": "...", "thumbnail_path": "...", "wikipedia_title": "...", "has_influence_links": true, "checkup_checked": false, "checkup_fixed": false, ... } ]
@@ -162,7 +163,7 @@ Each painting includes:
Populate biographies with `npm run fetch-artist-bios` (see [data-and-images.md](data-and-images.md)).
Artist objects also include `checkup_checked` and `checkup_fixed` (same semantics as paintings; gold portrait border when reviewed). Run `npm run migrate:artist-checkup-flags` on existing databases.
Artist objects also include `checkup_checked` and `checkup_fixed` (same semantics as paintings; gold portrait border when reviewed). After `npm run import-painter-palette`, **`palette_metadata`** holds PainterPalette enrichment (nationality, styles, occupations, raw influence fields, etc.). Run `npm run migrate:artist-checkup-flags` and `npm run migrate:artist-palette` on existing databases.
---
@@ -361,6 +362,8 @@ Painting detail with influence graph neighbours.
Both **`influencedBy`** and **`influenced`** are read from **`painting_influence_sources`** only (one row per edge). Painting-type sources appear in *Influenced By* when they are predecessors; in *Influenced* when this painting is the source of a later work. Artist and movement sources appear only in *Influenced By*. The legacy `painting_influences` table is not merged into these responses — it mirrors painting edges for scripts only.
In the UI, side-panel thumbnails use the paintings local image with **letterboxing** (`object-fit: contain`) so the full composition is visible. Click a thumbnail to open that works detail view.
---
## `GET /api/paintings/:id/image`
+2 -1
View File
@@ -72,8 +72,9 @@ Finer-grained styles (Impressionism, Cubism, Suprematism, …).
| `century` | INTEGER | Rounded century bucket for seeding limits |
| `checkup_checked` | BOOLEAN NOT NULL DEFAULT false | Portrait reviewed in debug workflow (gold border on bio when true) |
| `checkup_fixed` | BOOLEAN NOT NULL DEFAULT false | Portrait replaced, cleared, or uploaded via debug |
| `palette_metadata` | JSONB | Enrichment from `Inputs/PainterPalette.csv` (`npm run import-painter-palette`) |
Applied by `npm run migrate:artist-checkup-flags` (`db/migrate-artist-checkup-flags.sql`).
Applied by `npm run migrate:artist-checkup-flags` (`db/migrate-artist-checkup-flags.sql`) and `npm run migrate:artist-palette` (`db/migrate-artist-palette.sql`).
### `artist_periods`
+7 -2
View File
@@ -59,7 +59,11 @@ Gallery/
│ ├── fetch-missing-images.js
│ ├── find-duplicate-paintings.js
│ ├── audit-influence-duplicates.js
│ ├── painter-palette-lib.js
│ ├── import-painter-palette.js
│ └── image-fetcher.js
├── Inputs/ # External datasets (e.g. PainterPalette.csv)
├── Output/ # Generated exports (e.g. paintings.csv)
├── data/images/ # Local portraits and paintings (+ thumbs/)
├── db/ # SQL schema and migrations (when present)
├── deploy/ # Production nginx + systemd examples
@@ -272,7 +276,7 @@ Opened from the 3D hall (artist or movement wing — click a frame) or from infl
| Layer | What you see |
|-------|----------------|
| **Detail** | Centre image, *Influenced By* (left) and *Influenced* (right) — painting thumbnails, artist portraits, or movement swatches — position in catalog (e.g. `3 of 12`) |
| **Detail** | Centre image, *Influenced By* (left) and *Influenced* (right) — painting thumbnails (full work visible, letterboxed), artist portraits, or movement swatches — position in catalog (e.g. `3 of 12`) |
| **Art history notes** | Numbered markers on the image (when positioned) plus a note list below — short citations from Gombrich, museum catalogs, Wikipedia, etc. (`painting_annotations` table) |
| **Fullscreen** | Click the centre image; `Escape` or click anywhere to return to detail only |
@@ -292,6 +296,7 @@ Opened from the 3D hall (artist or movement wing — click a frame) or from infl
- **Catalog browsing** ( / arrow keys) walks the current artists works earliest → latest. It does **not** change the back target: after browsing several works, **Back to Gallery** still returns directly to the hall.
- **Influence links** push a new detail layer; **Back** from an influenced work returns to the painting you came from (and from there back to the gallery if applicable).
- Side-panel influence images use **`object-fit: contain`** so tall or wide works are not cropped (dark letterbox background).
- The 3D hall stays mounted in the background while detail is open so nothing is lost on return.
### Debug mode (developer)
@@ -307,7 +312,7 @@ When **Debug mode** is enabled from the home header, painting detail and artist
- **Movement galleries** complement artist halls: full movement corpus in period-themed wings, entered from the flow diagram.
- **Influence-based hall links** connect artists through documented painting relationships, grouped by movement at the exit.
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
- **Influence data** is stored in **`painting_influence_sources`** (directed links from paintings to source paintings, artists, or movements), with optional period fields and citation metadata. Legacy `painting_influences` mirrors painting-to-painting edges for scripts only.
- **Influence data** is stored in **`painting_influence_sources`** (directed links from paintings to source paintings, artists, or movements), with optional period fields and citation metadata. Sources include curated scholarship (`art-influences-data.js`) and **PainterPalette** (`discovered_via = painter-palette`). Legacy `painting_influences` mirrors painting-to-painting edges for scripts only.
## Developer tools (image audit)
+45
View File
@@ -322,6 +322,51 @@ These live in `client/public/` (and `client/dist/` after build).
5. Run `npm run fetch-images -- --artist="…"` or rely on preload / on-demand sync.
6. Add influence rows via `npm run update-influences` / `art-influences-data.js` (writes both `painting_influence_sources` and legacy painting edges).
## PainterPalette external dataset
`Inputs/PainterPalette.csv` is a curated dataset (~10,000 painters) merging WikiArt, Art500k, and Wikidata with cleaned influence fields. The gallery integrates it **for existing DB artists only** (not a full catalog import).
### One-time setup
```bash
npm run migrate:artist-palette # adds artists.palette_metadata JSONB
npm run import-painter-palette # enrich + influence links
```
### What gets imported
| PainterPalette column | Gallery use |
|-----------------------|-------------|
| Nationality, gender, styles, birth/death places, occupations, … | Stored in `artists.palette_metadata` |
| `birth_year`, `death_year` | Fills missing DB years only |
| `Influencedby`, `Teachers` | **Artist** or **movement** sources on that artist's paintings |
| `Influencedon`, `Pupils` | Reverse **artist** sources on the pupil/successor's paintings |
Influence rows are written to `painting_influence_sources` with `discovered_via = painter-palette` and do not duplicate curated Gombrich edges (unique index + `ON CONFLICT DO NOTHING`).
Name matching uses normalized strings plus aliases in `scripts/painter-palette-lib.js` (e.g. `Bronzino``Agnolo Bronzino`, `J. M. W. Turner``J.M.W. Turner`). Museum names, WikiArt tags, and dimension strings are filtered out.
### Commands
```bash
npm run analyze-painter-palette # match report
npm run import-painter-palette -- --dry-run
npm run import-painter-palette -- --metadata-only
npm run import-painter-palette -- --influences-only
```
Re-run `import-painter-palette` after adding gallery artists or updating the CSV; existing palette influence rows are skipped if already present.
## Catalog export
Export the full painting catalog as CSV:
```bash
npm run export-paintings
```
Writes **`Output/paintings.csv`** with columns `artist`, `painting`, `year` (sorted by artist, year, title). The `Output/` folder is git-ignored by convention; regenerate after catalog changes.
## Image fetcher overrides
`scripts/image-fetcher.js` includes hand-maintained overrides for ambiguous Wikipedia titles and direct URLs (e.g. works whose Commons name does not match the article title, or when museum search returns the wrong work). Extend these maps when automated resolution fails:
+7
View File
@@ -168,6 +168,10 @@ Allow inbound **TCP 3520** on the gallery host if clients reach it directly on t
| `npm run migrate:checkup-flags` | `scripts/migrate-checkup-flags.js` | Add `checkup_checked` / `checkup_fixed` on `paintings` |
| `npm run migrate:artist-checkup-flags` | `scripts/migrate-artist-checkup-flags.js` | Add `checkup_checked` / `checkup_fixed` on `artists` (bio debug) |
| `npm run migrate:painting-annotations` | `scripts/migrate-painting-annotations.js` | Create `painting_annotations` table |
| `npm run migrate:artist-palette` | `scripts/migrate-artist-palette.js` | Add `palette_metadata` JSONB on `artists` |
| `npm run import-painter-palette` | `scripts/import-painter-palette.js` | Enrich artists + influence links from `Inputs/PainterPalette.csv` |
| `npm run analyze-painter-palette` | `scripts/analyze-painter-palette.js` | Report CSV ↔ gallery artist name matches |
| `npm run export-paintings` | `scripts/export-paintings-csv.js` | Write `Output/paintings.csv` (artist, painting, year) |
| `npm run update-painting-annotations` | `scripts/update-painting-annotations.js` | Load curated notes from `painting-annotations-data.js` |
| `npm run update-painting-annotations -- --wikipedia` | ↑ | Add intro sentences from each works Wikipedia page |
| `npm run update-painting-annotations -- --wikipedia --wiki-delay=3000` | ↑ | Slower Wikipedia pass when rate-limited (429) |
@@ -190,6 +194,8 @@ These are checked in and maintained:
- `fetch-missing-images.js` — batch image backfill
- `find-duplicate-paintings.js` — duplicate catalog audit
- `audit-influence-duplicates.js` — influence graph duplicate / mirror audit
- `painter-palette-lib.js`, `import-painter-palette.js`, `migrate-artist-palette.js`, `analyze-painter-palette.js` — PainterPalette CSV integration
- `export-paintings-csv.js` — catalog CSV export to `Output/`
- `migrate-checkup-flags.js` — checkup workflow columns (paintings)
- `migrate-artist-checkup-flags.js` — checkup workflow columns (artist portraits)
- `migrate-painting-annotations.js`, `update-painting-annotations.js`, `painting-annotations-data.js` — art-history notes on painting detail
@@ -240,3 +246,4 @@ After clone: copy `.env.example` → `.env`, install dependencies, run `npm run
| **Failed to load movement gallery** / `Cannot GET /api/movements/:id/gallery` | Stale server process missing route | Restart `npm run dev` or `npm run dev:server` after pulling API changes |
| Movement gallery shows generic cream walls | Stale client build | `cd client && npm run build`; hard-refresh browser |
| Windows overlap paintings in movement wing | Stale client | Rebuild client — windows are placed only on side walls in gaps between frames |
| Influence thumbnails cropped on painting detail | Stale client build | `npm run build` — panels use `object-fit: contain` for full image |