Add checkup review flags, duplicate detection, and corrected painting images.
Introduce checkup_checked/checkup_fixed on paintings with Checkup page filters and API. Fixed paintings auto-mark as reviewed. Add find-duplicates tooling and document debug/checkup workflow. Include Botticelli and Michelangelo image fixes from checkup. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
4683089495
commit
4c6acd5a3a
55 files changed
+750
-39
No files matched your search
@@ -204,6 +204,112 @@ Returns the image bytes with `Cache-Control: public, max-age=86400`, or `404` if
|
||||
|
||||
---
|
||||
|
||||
## Developer image audit
|
||||
|
||||
Routes for the **Checkup** page and **Debug mode** on painting detail. Register `GET /api/paintings/checkup` **before** `GET /api/paintings/:id` so `"checkup"` is not parsed as a painting id.
|
||||
|
||||
### `GET /api/paintings/checkup`
|
||||
|
||||
Full-catalog audit table for the Checkup UI.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"paintings": [
|
||||
{
|
||||
"id": 36,
|
||||
"title": "Trinity",
|
||||
"artist_name": "Andrei Rublev",
|
||||
"year": 1411,
|
||||
"gallery_file": "paintings/Andrei_Rublev_Trinity.jpg",
|
||||
"gallery_preview": "paintings/thumbs/Andrei_Rublev_Trinity_thumb.jpg",
|
||||
"detail_file": "paintings/Andrei_Rublev_Trinity.jpg",
|
||||
"detail_preview": "paintings/thumbs/Andrei_Rublev_Trinity_thumb.jpg",
|
||||
"detail_on_demand": false,
|
||||
"gallery_file_exists": true,
|
||||
"detail_file_exists": true,
|
||||
"checked": false,
|
||||
"fixed": false
|
||||
}
|
||||
],
|
||||
"total": 1201
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `checked` | Reviewed in checkup workflow (`checkup_checked`) |
|
||||
| `fixed` | Image replaced via Fix (`checkup_fixed`) |
|
||||
| `detail_on_demand` | `true` when detail would use `GET /api/paintings/:id/image?size=full` |
|
||||
| `gallery_file_exists` / `detail_file_exists` | Disk check under `IMAGE_DIR` |
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /api/paintings/:id/checkup-flags`
|
||||
|
||||
Update review flags. Body: `{ "checked"?: boolean, "fixed"?: boolean }` — at least one field required.
|
||||
|
||||
Setting `fixed: true` also sets `checked: true`. While `fixed` is true, the API rejects clearing `checked`.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{ "checked": true, "fixed": false }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/paintings/:id/debug-image-search`
|
||||
|
||||
Google-family image search for debug / checkup (Custom Search → Google Arts & Culture → scrape → DuckDuckGo). Used by **Search visible** on the Checkup page and the debug panel on painting detail.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Andrei Rublev Trinity painting",
|
||||
"imageUrl": "https://…",
|
||||
"source": "google-arts",
|
||||
"pageUrl": "https://…"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/paintings/:id/fix-image`
|
||||
|
||||
Download a remote URL and replace the painting’s local full image + thumbnail. Sets `checkup_fixed = true` and `checkup_checked = true`.
|
||||
|
||||
**Body**
|
||||
|
||||
```json
|
||||
{ "imageUrl": "https://…" }
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"imagePath": "paintings/Artist_Title.jpg",
|
||||
"thumbnailPath": "paintings/thumbs/Artist_Title_thumb.jpg",
|
||||
"fixed": true,
|
||||
"checked": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/debug/image-proxy`
|
||||
|
||||
Proxy a remote image URL for debug preview (avoids hotlink / CORS blocks in the browser).
|
||||
|
||||
**Query:** `url` — must be `http://` or `https://`
|
||||
|
||||
Returns image bytes with appropriate `Content-Type`.
|
||||
|
||||
---
|
||||
|
||||
## Frontend helpers
|
||||
|
||||
The React client wraps these endpoints in `client/src/api/client.ts`:
|
||||
@@ -220,3 +326,8 @@ The React client wraps these endpoints in `client/src/api/client.ts`:
|
||||
| `imageUrl(path)` | `/images/<path>` or placeholder |
|
||||
| `galleryImageUrl(painting)` | Local thumb/full only (3D) |
|
||||
| `paintingImageUrl(painting)` | Local file or on-demand API |
|
||||
| `api.getPaintingCheckup()` | `GET /api/paintings/checkup` |
|
||||
| `api.updatePaintingCheckupFlags(id, flags)` | `PATCH /api/paintings/:id/checkup-flags` |
|
||||
| `api.getPaintingDebugImageSearch(id)` | `GET /api/paintings/:id/debug-image-search` |
|
||||
| `api.fixPaintingImage(id, imageUrl)` | `POST /api/paintings/:id/fix-image` |
|
||||
| `debugImageProxyUrl(imageUrl)` | `GET /api/debug/image-proxy?url=…` |
|
||||
@@ -98,6 +98,12 @@ Phases within an artist’s career (e.g. “Blue Period”, “Roman Period”).
|
||||
| `thumbnail_path` | VARCHAR(500) | Smaller variant for lists / 3D |
|
||||
| `wikipedia_title` | VARCHAR(300) | Used by image fetcher |
|
||||
| `sort_order` | INTEGER | |
|
||||
| `checkup_checked` | BOOLEAN NOT NULL DEFAULT false | Reviewed in image checkup workflow (UI label: **Reviewed**) |
|
||||
| `checkup_fixed` | BOOLEAN NOT NULL DEFAULT false | Image corrected via checkup / debug **Fix** |
|
||||
|
||||
When `checkup_fixed` is true, `checkup_checked` is set automatically and cannot be cleared until **Fixed** is off.
|
||||
|
||||
Applied by `npm run migrate:checkup-flags` (`db/migrate-checkup-flags.sql`).
|
||||
|
||||
### `painting_influences`
|
||||
|
||||
@@ -150,7 +156,7 @@ Used by painting detail API (`influencedBy`). Painting-type rows also feed `has_
|
||||
## Indexes
|
||||
|
||||
- `artists(movement_id)`, `artists(century)`
|
||||
- `paintings(artist_id)`, `paintings(period_id)`
|
||||
- `paintings(artist_id)`, `paintings(period_id)`, `paintings(checkup_checked)`, `paintings(checkup_fixed)`
|
||||
- `art_movements(era_id)`, `art_movements(start_year, end_year)`
|
||||
- `painting_influences(painting_id)`, `painting_influences(influenced_by_painting_id)`
|
||||
- `painting_influence_sources(painting_id)`, `painting_influence_sources(source_artist_id)`, `painting_influence_sources(source_movement_id)`
|
||||
|
||||
+20
-1
@@ -36,11 +36,12 @@ Gallery/
|
||||
│ │ ├── data/movement-lineage.ts # Curated movement predecessor links (UI)
|
||||
│ │ └── utils/timelineView.ts # Shared zoom/pan math for timeline + movements
|
||||
│ └── dist/ # Production build (served by API when present)
|
||||
├── scripts/ # Seed, bios, catalog expansion, image fetch
|
||||
├── scripts/ # Seed, bios, catalog expansion, image fetch, checkup tools
|
||||
│ ├── fetch-artist-bios.js
|
||||
│ ├── expand-paintings.js
|
||||
│ ├── famous-paintings-data.js
|
||||
│ ├── fetch-missing-images.js
|
||||
│ ├── find-duplicate-paintings.js
|
||||
│ └── image-fetcher.js
|
||||
├── data/images/ # Local portraits and paintings (+ thumbs/)
|
||||
├── db/ # SQL schema and migrations (when present)
|
||||
@@ -222,6 +223,24 @@ Opened from the 3D hall (click a frame) or from influence thumbnails on another
|
||||
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
|
||||
- **Influence data** is stored as directed links from paintings to sources (another painting, an artist, or a movement), with optional period fields and citation metadata (source author, quote, URL).
|
||||
|
||||
## Developer tools (image audit)
|
||||
|
||||
Optional workflow for curating local image files — not part of the public visitor experience.
|
||||
|
||||
| Feature | Where | Purpose |
|
||||
|---------|--------|---------|
|
||||
| **Debug mode** | Home header toggle (`client/src/utils/debugMode.ts`) | Persists in `localStorage`; enables debug panel on painting detail |
|
||||
| **Checkup page** | Home header → **Checkup** (`CheckupPage.tsx`) | Full-catalog table: gallery vs detail thumbnails, search, fix, review flags |
|
||||
| **Debug panel** | Painting detail (bottom-left, when debug mode on) | Google-family image search preview + **Fix it** for the current work |
|
||||
|
||||
**Checkup columns:** Gallery and Detail thumbnails, Search (reference image), Fix (replace local file), **Reviewed** (`checkup_checked`), **Fixed** (`checkup_fixed`).
|
||||
|
||||
**Search visible** runs image search only for rows currently shown after text/filter — not automatically on page load. Fixing an image sets **Fixed** and **Reviewed**.
|
||||
|
||||
Run `npm run migrate:checkup-flags` once on existing databases. After server code changes, restart `npm run dev` so new routes (e.g. `PATCH …/checkup-flags`) are registered.
|
||||
|
||||
See [API.md](API.md#developer-image-audit) and [data-and-images.md](data-and-images.md#duplicate-paintings).
|
||||
|
||||
## Related docs
|
||||
|
||||
| Document | Contents |
|
||||
|
||||
@@ -36,6 +36,8 @@ File names are sanitised `{Artist}_{Title}.{ext}`. The image service can redisco
|
||||
| `image-fetcher.js` | *(library)* | Wikimedia / museum resolution used by fetch scripts and API |
|
||||
| `regenerate-thumbnails.js` | `npm run regenerate-thumbnails` | Rebuild thumbs from full images via `sharp` |
|
||||
| `audit-painting-images.js` | `npm run audit-painting-images` | Detect thumb/full aspect-ratio mismatches |
|
||||
| `find-duplicate-paintings.js` | `npm run find-duplicates` | Report exact and near-duplicate catalog rows |
|
||||
| `migrate-checkup-flags.js` | `npm run migrate:checkup-flags` | Add `checkup_checked` / `checkup_fixed` columns |
|
||||
|
||||
## Typical workflow
|
||||
|
||||
@@ -305,3 +307,38 @@ Examples already in the repo:
|
||||
- `Charing Cross Bridge` → Derain (not Monet)
|
||||
|
||||
After adding an override, delete any wrong cached file under `data/images/paintings/` and re-run fetch or call the on-demand image endpoint for that painting.
|
||||
|
||||
## Duplicate paintings
|
||||
|
||||
The catalog can contain the same work more than once — usually from a **double import** (identical artist + title + year + image) or **Wikipedia scrape variants** (different article titles for one icon, e.g. Andrei Rublev’s Trinity).
|
||||
|
||||
### Find duplicates
|
||||
|
||||
```bash
|
||||
npm run find-duplicates
|
||||
```
|
||||
|
||||
Runs `scripts/find-duplicate-paintings.js`, which reports:
|
||||
|
||||
| Report | Rule |
|
||||
|--------|------|
|
||||
| **Exact duplicates** | Same `artist_id`, `title`, and `year` |
|
||||
| **Normalized title duplicates** | Same artist, titles differing only in punctuation/spacing |
|
||||
| **Rublev / Trinity cluster** | Known multi-entry example for manual merge |
|
||||
|
||||
As of a recent audit (~1200 paintings): **52 exact duplicate pairs** (52 removable rows), concentrated in **Hieronymus Bosch** (25), **Albrecht Dürer** (14), and **Domenico Ghirlandaio** (13). Duplicate copies typically share the same image file and have **no influence links**, so the higher id in each pair is safe to delete after review.
|
||||
|
||||
**Near-duplicates** (different titles, same work) need curator judgment — e.g. Rublev ids 36, 316, 320, 322, 323 all describe the Trinity icon under different Wikipedia labels; keep id **36** (`Trinity`, wiki `Trinity (Andrei Rublev)`).
|
||||
|
||||
`expand-paintings.js` skips inserts when normalized titles match, but duplicates can still appear if seed and expansion use different title strings or if influence discovery creates works independently.
|
||||
|
||||
## Debug and checkup image fix
|
||||
|
||||
When **Debug mode** is on (home header) or from the **Checkup** page:
|
||||
|
||||
1. **Search** — `GET /api/paintings/:id/debug-image-search` tries Google Custom Search (if `GOOGLE_CSE_API_KEY` + `GOOGLE_CSE_CX` are set in `.env`), Google Arts & Culture, Google Images scrape, then DuckDuckGo (`searchGoogleImagesFirst` in `scripts/image-fetcher.js`).
|
||||
2. **Fix** — `POST /api/paintings/:id/fix-image` downloads the chosen URL via `replacePaintingImageFromUrl` in `server/image-service.js`, regenerates the thumbnail with `sharp`, and sets checkup flags.
|
||||
|
||||
Checkup **Search visible** queues search for filtered rows only (3 concurrent); it does not search the full catalog on load.
|
||||
|
||||
See [API.md](API.md#developer-image-audit) and [basics.md](basics.md#developer-tools-image-audit).
|
||||
@@ -65,6 +65,7 @@ After a fresh seed, run these to match a fully populated local install:
|
||||
npm run fetch-artist-bios # bio_short / bio_full from Wikipedia
|
||||
npm run expand-catalog # famous works for artists below MIN_PAINTINGS
|
||||
npm run update-influences # painting influence graph for detail view + hall exits
|
||||
npm run migrate:checkup-flags # optional: review/fixed flags for Checkup page
|
||||
npm run fetch-images -- --limit=50 # random sample; 10s max per painting (default)
|
||||
npm run fetch-images -- --limit=50 --max-wait=120 # same batch size, longer lookup per work
|
||||
cd client && npm run build && cd ..
|
||||
@@ -112,6 +113,8 @@ Open http://localhost:3001 (or your configured `PORT`).
|
||||
| `npm run fetch-artist-images` | `scripts/fetch-artist-images.js` | Backfill portrait files *(if present)* |
|
||||
| `npm run sync-image-paths` | `scripts/sync-image-paths.js` | Align DB paths with disk *(if present)* |
|
||||
| `npm run migrate:influence-sources` | `scripts/migrate-influence-sources.js` | Create `painting_influence_sources` + backfill legacy edges |
|
||||
| `npm run migrate:checkup-flags` | `scripts/migrate-checkup-flags.js` | Add `checkup_checked` / `checkup_fixed` on `paintings` |
|
||||
| `npm run find-duplicates` | `scripts/find-duplicate-paintings.js` | Report duplicate and near-duplicate painting rows |
|
||||
| `npm run update-influences` | `scripts/update-influences.js` | Insert influence links (painting / artist / movement) from `art-influences-data.js` |
|
||||
| `npm run update-influences -- --fetch-images` | ↑ | Also download images for newly created works |
|
||||
| `npm run update-influences -- --discover` | ↑ | Curated pass + web discovery (Wikipedia, Wikidata, Met, art-history sites) |
|
||||
@@ -127,6 +130,8 @@ These are checked in and maintained:
|
||||
- `update-influences.js` + `art-influences-data.js` — influence graph (paintings, artists, movements)
|
||||
- `influence-discovery.js` + `influence-resolver.js` — web discovery and polymorphic source resolution
|
||||
- `fetch-missing-images.js` — batch image backfill
|
||||
- `find-duplicate-paintings.js` — duplicate catalog audit
|
||||
- `migrate-checkup-flags.js` — checkup workflow columns
|
||||
- `regenerate-thumbnails.js`, `audit-painting-images.js`
|
||||
|
||||
These are referenced in `package.json` but may need to be restored from git history if missing locally: `seed-wikipedia.js`, `fetch-artist-images.js`, `sync-image-paths.js`.
|
||||
@@ -162,3 +167,5 @@ After clone: copy `.env.example` → `.env`, install dependencies, run `npm run
|
||||
| Default Vite page instead of gallery | `client/dist` missing or stale | `cd client && npm run build` |
|
||||
| Permission denied creating tables | `gallery` user lacks CREATE | Run admin grants, then migrate |
|
||||
| Wikipedia API rate limit during fetch | Too many requests in a row | Wait and re-run; scripts retry with backoff |
|
||||
| Checkup **Reviewed** toggle returns 404 | Stale server process missing new routes | Restart `npm run dev` after pulling API changes |
|
||||
| Duplicate works in gallery / timeline | Double import or variant Wikipedia titles | `npm run find-duplicates`; merge or delete spare rows manually |
|
||||
Reference in new issue
Block a user