88 lines
5.0 KiB
TypeScript
88 lines
5.0 KiB
TypeScript
import seed from "../../../../content/research/ai-companies.seed.json";
|
|
import { createHash } from "node:crypto";
|
|
import { openSiteDatabase } from "./site-database";
|
|
import { validateResearch, type ResearchSnapshot } from "./ai-research";
|
|
|
|
// Each release selects an immutable research revision. New revisions are added
|
|
// transactionally, preserving both earlier research and all authentication rows.
|
|
export function readResearchSnapshot(snapshot = seed as ResearchSnapshot): ResearchSnapshot {
|
|
validateResearch(snapshot);
|
|
const digest = createHash("sha256").update(JSON.stringify(snapshot)).digest("hex");
|
|
const db = openSiteDatabase();
|
|
try {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS research_datasets (
|
|
id TEXT PRIMARY KEY, as_of TEXT NOT NULL, metadata TEXT NOT NULL, content_hash TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS research_categories (
|
|
dataset_id TEXT NOT NULL REFERENCES research_datasets(id),
|
|
id TEXT NOT NULL, payload TEXT NOT NULL, PRIMARY KEY(dataset_id,id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS research_companies (
|
|
dataset_id TEXT NOT NULL, id TEXT NOT NULL, category_id TEXT NOT NULL,
|
|
name TEXT NOT NULL, founded_year INTEGER, team_size INTEGER,
|
|
revenue_status TEXT NOT NULL, payload TEXT NOT NULL,
|
|
PRIMARY KEY(dataset_id,id),
|
|
FOREIGN KEY(dataset_id,category_id) REFERENCES research_categories(dataset_id,id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS research_evidence (
|
|
dataset_id TEXT NOT NULL REFERENCES research_datasets(id),
|
|
id TEXT NOT NULL, url TEXT NOT NULL, payload TEXT NOT NULL,
|
|
PRIMARY KEY(dataset_id,id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS research_observations (
|
|
dataset_id TEXT NOT NULL, id TEXT NOT NULL, company_id TEXT NOT NULL,
|
|
evidence_id TEXT NOT NULL, kind TEXT NOT NULL, amount REAL NOT NULL,
|
|
unit TEXT NOT NULL, record_use TEXT NOT NULL, payload TEXT NOT NULL,
|
|
PRIMARY KEY(dataset_id,id),
|
|
FOREIGN KEY(dataset_id,company_id) REFERENCES research_companies(dataset_id,id),
|
|
FOREIGN KEY(dataset_id,evidence_id) REFERENCES research_evidence(dataset_id,id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS research_reviews (
|
|
dataset_id TEXT NOT NULL REFERENCES research_datasets(id),
|
|
id TEXT NOT NULL, payload TEXT NOT NULL, PRIMARY KEY(dataset_id,id)
|
|
);
|
|
`);
|
|
// The database transaction also serializes first requests from two workers.
|
|
db.exec("BEGIN IMMEDIATE");
|
|
try {
|
|
const exists = db.prepare("SELECT content_hash FROM research_datasets WHERE id=?")
|
|
.get(snapshot.version) as { content_hash: string } | undefined;
|
|
if (exists && exists.content_hash !== digest)
|
|
throw new Error("Research data changed without a new revision identifier");
|
|
if (!exists) {
|
|
const { categories, companies, observations, evidence, reviews, ...metadata } = snapshot;
|
|
db.prepare("INSERT INTO research_datasets VALUES (?,?,?,?)")
|
|
.run(snapshot.version, snapshot.asOf, JSON.stringify(metadata), digest);
|
|
const insertCategory = db.prepare("INSERT INTO research_categories VALUES (?,?,?)");
|
|
for (const row of categories) insertCategory.run(snapshot.version, row.id, JSON.stringify(row));
|
|
const insertCompany = db.prepare("INSERT INTO research_companies VALUES (?,?,?,?,?,?,?,?)");
|
|
for (const row of companies) insertCompany.run(snapshot.version, row.id, row.cat,
|
|
row.name, row.year, row.team, row.revenueStatus, JSON.stringify(row));
|
|
const insertEvidence = db.prepare("INSERT INTO research_evidence VALUES (?,?,?,?)");
|
|
for (const row of evidence) insertEvidence.run(snapshot.version, row.id, row.url, JSON.stringify(row));
|
|
const insertObservation = db.prepare("INSERT INTO research_observations VALUES (?,?,?,?,?,?,?,?,?)");
|
|
for (const row of observations) insertObservation.run(snapshot.version, row.id, row.company,
|
|
row.evidence, row.kind, row.value, row.unit, row.selection, JSON.stringify(row));
|
|
const insertReview = db.prepare("INSERT INTO research_reviews VALUES (?,?,?)");
|
|
for (const row of reviews) insertReview.run(snapshot.version, row.id, JSON.stringify(row));
|
|
}
|
|
db.exec("COMMIT");
|
|
} catch (error) {
|
|
db.exec("ROLLBACK");
|
|
throw error;
|
|
}
|
|
const meta = db.prepare("SELECT metadata FROM research_datasets WHERE id=?").get(snapshot.version) as { metadata: string };
|
|
// All displayed records come from the site database, not the seed arrays.
|
|
const rows = (table: string) => (db.prepare(
|
|
`SELECT payload FROM ${table} WHERE dataset_id=? ORDER BY id`,
|
|
).all(snapshot.version) as { payload: string }[]).map((r) => JSON.parse(r.payload));
|
|
const stored: ResearchSnapshot = { ...JSON.parse(meta.metadata),
|
|
categories: rows("research_categories"), companies: rows("research_companies"),
|
|
observations: rows("research_observations"), evidence: rows("research_evidence"),
|
|
reviews: rows("research_reviews") };
|
|
validateResearch(stored);
|
|
return stored;
|
|
} finally { db.close(); }
|
|
}
|