Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
6.4 kB · 221 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222#!/usr/bin/env node// Validates catalog.json against the JSON schemas and reports completeness.
import Ajv from "ajv";import addFormats from "ajv-formats";import { readFileSync } from "fs";import { dirname, join } from "path";import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
function loadJSON(path) { return JSON.parse(readFileSync(join(__dirname, path), "utf-8"));}
// Load schemasconst artworkSchema = loadJSON("schema/artwork.schema.json");const exhibitionSchema = loadJSON("schema/exhibition.schema.json");const writingSchema = loadJSON("schema/writing.schema.json");const catalogSchema = loadJSON("schema/catalog.schema.json");
// Load catalog datalet catalog;try { catalog = loadJSON("data/catalog.json");} catch (e) { console.error("No catalog.json found. Run `node scrape.mjs` first."); process.exit(1);}
// Set up validatorconst ajv = new Ajv({ allErrors: true, strict: false, validateSchema: false });addFormats(ajv);
// Register schemas so $ref worksajv.addSchema(artworkSchema, "artwork.schema.json");ajv.addSchema(exhibitionSchema, "exhibition.schema.json");ajv.addSchema(writingSchema, "writing.schema.json");
const validateCatalog = ajv.compile(catalogSchema);const validateArtwork = ajv.compile(artworkSchema);const validateExhibition = ajv.compile(exhibitionSchema);const validateWriting = ajv.compile(writingSchema);
console.log("Thomas Lawson Catalog Validator");console.log("==============================\n");
// Validate top-level catalogconst catalogValid = validateCatalog(catalog);if (!catalogValid) { console.log("✗ Catalog structure invalid:"); for (const err of validateCatalog.errors) { console.log(` ${err.instancePath} ${err.message}`); }} else { console.log("✓ Catalog structure valid\n");}
// Validate each artworklet artworkErrors = 0;const optionalFields = [ "year", "medium", "artMedium", "artworkSurface", "dimensions", "series", "description", "collection",];const completeness = {};for (const f of optionalFields) completeness[f] = 0;
for (const artwork of catalog.artworks) { const valid = validateArtwork(artwork); if (!valid) { artworkErrors++; console.log(`✗ Artwork "${artwork.title}" (${artwork.id}):`); for (const err of validateArtwork.errors) { console.log(` ${err.instancePath} ${err.message}`); } }
for (const f of optionalFields) { if (artwork[f] !== null && artwork[f] !== undefined) completeness[f]++; }}
const totalArtworks = catalog.artworks.length;if (artworkErrors === 0) { console.log(`✓ All ${totalArtworks} artworks pass schema validation\n`);} else { console.log( `\n${artworkErrors}/${totalArtworks} artworks have validation errors\n`, );}
// Validate each exhibitionlet exhibitionErrors = 0;for (const exhibition of catalog.exhibitions) { const valid = validateExhibition(exhibition); if (!valid) { exhibitionErrors++; console.log(`✗ Exhibition "${exhibition.title}" (${exhibition.id}):`); for (const err of validateExhibition.errors) { console.log(` ${err.instancePath} ${err.message}`); } }}
const totalExhibitions = catalog.exhibitions.length;if (exhibitionErrors === 0) { console.log( `✓ All ${totalExhibitions} exhibitions pass schema validation\n`, );} else { console.log( `${exhibitionErrors}/${totalExhibitions} exhibitions have validation errors\n`, );}
// Validate each writinglet writingErrors = 0;const writings = catalog.writings || [];for (const writing of writings) { const valid = validateWriting(writing); if (!valid) { writingErrors++; console.log(`✗ Writing "${writing.title}" (${writing.id}):`); for (const err of validateWriting.errors) { console.log(` ${err.instancePath} ${err.message}`); } }}
const totalWritings = writings.length;if (totalWritings > 0) { if (writingErrors === 0) { console.log(`✓ All ${totalWritings} writings pass schema validation\n`); } else { console.log( `${writingErrors}/${totalWritings} writings have validation errors\n`, ); }}
// Completeness reportconsole.log("Field Completeness (artworks):");console.log("─".repeat(40));for (const f of optionalFields) { const pct = totalArtworks ? Math.round((completeness[f] / totalArtworks) * 100) : 0; const bar = "█".repeat(Math.round(pct / 5)) + "░".repeat(20 - Math.round(pct / 5)); console.log( ` ${f.padEnd(16)} ${bar} ${pct}% (${completeness[f]}/${totalArtworks})`, );}
// Writing completenessif (totalWritings > 0) { const wFields = ["year", "publication", "url", "author"]; console.log("\nField Completeness (writings):"); console.log("─".repeat(40)); for (const f of wFields) { const count = writings.filter( (w) => w[f] !== null && w[f] !== undefined, ).length; const pct = Math.round((count / totalWritings) * 100); const bar = "█".repeat(Math.round(pct / 5)) + "░".repeat(20 - Math.round(pct / 5)); console.log( ` ${f.padEnd(16)} ${bar} ${pct}% (${count}/${totalWritings})`, ); }}
// Summary statsconsole.log("\nSummary:");console.log(` Artworks: ${totalArtworks}`);console.log(` Exhibitions: ${totalExhibitions}`);console.log(` Writings: ${totalWritings}`);console.log(` Scraped at: ${catalog.scrapedAt}`);console.log(` Version: ${catalog.version}`);
// Sections breakdownconst sections = {};for (const a of catalog.artworks) { sections[a.section] = (sections[a.section] || 0) + 1;}console.log("\n By section:");for (const [section, count] of Object.entries(sections)) { console.log(` ${section}: ${count}`);}
// Series breakdownconst series = {};for (const a of catalog.artworks) { if (a.series) series[a.series] = (series[a.series] || 0) + 1;}if (Object.keys(series).length > 0) { console.log("\n Detected series:"); for (const [name, count] of Object.entries(series)) { console.log(` "${name}": ${count} works`); }}
// Writing categoriesif (totalWritings > 0) { const categories = {}; for (const w of writings) { categories[w.category] = (categories[w.category] || 0) + 1; } console.log("\n By writing category:"); for (const [cat, count] of Object.entries(categories)) { console.log(` ${cat}: ${count}`); }}