Files
Art-gallery/Documentation/DB_structure.md
T
Danila KhodjaefandCursor 4c6acd5a3a 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>
2026-06-20 20:45:55 +03:00

192 lines
7.9 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.
# Art Gallery — database structure
PostgreSQL schema for the virtual gallery. Canonical DDL is intended to live in `db/schema.sql` when checked in; this document describes the logical model either way.
Connection settings come from `.env` (see [setup.md](setup.md)).
## Overview
| Item | Typical value |
|------|----------------|
| Engine | PostgreSQL 14+ |
| Database | `Gallery` |
| App user | `gallery` |
| Time fields | Integer years (negative = BCE) |
## Entity relationship
```mermaid
erDiagram
historical_eras ||--o{ art_movements : contains
art_movements ||--o{ artists : groups
artists ||--o{ artist_periods : has
artists ||--o{ paintings : created
artist_periods ||--o{ paintings : groups
paintings ||--o{ painting_influences : influenced_by
paintings ||--o{ painting_influences : influences
paintings ||--o{ painting_influence_sources : influenced_by
artists ||--o{ painting_influence_sources : artist_source
art_movements ||--o{ painting_influence_sources : movement_source
```
## Tables
### `historical_eras`
Broad chronological buckets (Ancient, Medieval, Renaissance, …).
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `name` | VARCHAR(100) | Display label |
| `start_year`, `end_year` | INTEGER | Inclusive range |
| `start_definite`, `end_definite` | BOOLEAN | `false` → render as gradient edge on timeline |
| `description` | TEXT | Tooltip / sidebar copy |
| `sort_order` | INTEGER | Display order |
### `art_movements`
Finer-grained styles (Impressionism, Cubism, Suprematism, …).
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `name` | VARCHAR(150) | |
| `start_year`, `end_year` | INTEGER | |
| `start_definite`, `end_definite` | BOOLEAN | Same visual semantics as eras |
| `era_id` | FK → `historical_eras` | Optional parent era |
| `description` | TEXT | |
| `color` | VARCHAR(20) | Hex colour for movement stream in the flow diagram |
### `artists`
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `name` | VARCHAR(200) | |
| `birth_year`, `death_year` | INTEGER | Nullable; used for timeline portrait placement |
| `movement_id` | FK → `art_movements` | Primary movement |
| `portrait_path` | VARCHAR(500) | Relative to `data/images/` |
| `bio_short`, `bio_full` | TEXT | Wikipedia lead section (`npm run fetch-artist-bios`) |
| `wikipedia_title` | VARCHAR(300) | Source page title |
| `century` | INTEGER | Rounded century bucket for seeding limits |
### `artist_periods`
Phases within an artists career (e.g. “Blue Period”, “Roman Period”).
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `artist_id` | FK → `artists` | ON DELETE CASCADE |
| `name` | VARCHAR(200) | |
| `start_year`, `end_year` | INTEGER | |
| `description` | TEXT | |
| `sort_order` | INTEGER | Wall order in 3D gallery |
### `paintings`
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `artist_id` | FK → `artists` | ON DELETE CASCADE |
| `period_id` | FK → `artist_periods` | Optional grouping |
| `title` | VARCHAR(300) | |
| `year`, `year_end` | INTEGER | Creation date(s) |
| `description` | TEXT | |
| `image_path` | VARCHAR(500) | Full-size local file |
| `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`
Directed edges: *this painting* was influenced by *that painting*.
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `painting_id` | FK → `paintings` | The work being explained |
| `influenced_by_painting_id` | FK → `paintings` | The earlier / source work |
| `notes` | TEXT | Curator summary |
| `source` | VARCHAR(500) | General attribution |
| `aspects` | TEXT | What was borrowed (composition, colour, …) |
| `quote` | TEXT | Short citation |
| `source_author` | VARCHAR(200) | e.g. Gombrich, Janson |
| `source_url` | VARCHAR(500) | Reference link |
Unique constraint on `(painting_id, influenced_by_painting_id)`.
Used by the 3D gallery exit panel: predecessors are artists of `influenced_by_painting_id` works; successors are artists of paintings influenced by this artists works (see [API.md](API.md#get-apiartistsidnavigation)). Legacy table; new polymorphic links live in `painting_influence_sources`.
### `painting_influence_sources`
Polymorphic influence links: *this painting* was influenced by a **painting**, **artist**, or **movement**.
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `painting_id` | FK → `paintings` | The work being explained |
| `source_type` | VARCHAR(20) | `painting`, `artist`, or `movement` |
| `source_painting_id` | FK → `paintings` | When `source_type = painting` |
| `source_artist_id` | FK → `artists` | When `source_type = artist` |
| `source_movement_id` | FK → `art_movements` | When `source_type = movement` |
| `period_note` | VARCHAR(240) | Human-readable period label |
| `period_start_year` | INTEGER | Optional span start |
| `period_end_year` | INTEGER | Optional span end |
| `notes` | TEXT | Curator summary |
| `source` | VARCHAR(500) | General attribution |
| `aspects` | TEXT | What was borrowed |
| `quote` | TEXT | Short citation |
| `source_author` | VARCHAR(200) | e.g. Gombrich, Janson |
| `source_url` | VARCHAR(500) | Reference link |
| `discovered_via` | VARCHAR(120) | e.g. `wikipedia`, `wikidata`, `web:metmuseum.org` |
| `confidence` | VARCHAR(20) | `curated` (default) or `discovered` |
Unique index on `(painting_id, source_type, source_painting_id, source_artist_id, source_movement_id)` with COALESCE for null FKs.
Used by painting detail API (`influencedBy`). Painting-type rows also feed `has_influence_links` and hall navigation (with legacy `painting_influences`).
## Indexes
- `artists(movement_id)`, `artists(century)`
- `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)`
## First-time setup
The `gallery` database user needs `CREATE` on schema `public` for migrations. If tables cannot be created, run admin grants as PostgreSQL superuser before `npm run migrate`:
```sql
GRANT CREATE ON SCHEMA public TO gallery;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO gallery;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO gallery;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO gallery;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO gallery;
```
Then:
```bash
npm run migrate
npm run seed
npm run fetch-artist-bios
npm run expand-catalog
```
## Data conventions
- **Year zero** is not used; BCE years are negative integers.
- **Image paths** are relative to `IMAGE_DIR` (default `./data/images`), e.g. `portraits/Claude_Monet.jpg`, `paintings/thumbs/Raphael_The_School_of_Athens_thumb.jpg`.
- **Seeding cap**: curated ingest targets at most ~100 artists per century to keep the catalog manageable.
- **Catalog expansion**: `scripts/famous-paintings-data.js` plus `npm run expand-catalog` raises thin artist catalogs to at least six notable paintings (`MIN_PAINTINGS`, default 6).
- **Biographies**: `bio_short` and `bio_full` are populated by `npm run fetch-artist-bios` from English Wikipedia lead sections; `wikipedia_title` on the artist row is the source article.