import { createHash } from "node:crypto"; import { mkdir, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { eq } from "drizzle-orm"; import { closeDb, getDb } from "./client.js"; import { paperSequences, papers, paperTopics, paperVersions, series, topics } from "./schema/index.js"; const DISCIPLINES: Array<[string, string, string]> = [ ["economics", "Economics", "Micro, macro, econometrics, finance-adjacent economics."], ["finance", "Finance", "Asset pricing, corporate finance, market microstructure, financial econometrics."], ["mathematics", "Mathematics", "Pure and applied mathematics, formal results and proofs."], ["statistics", "Statistics", "Statistical theory, methodology and computation."], ["computer-science", "Computer Science", "Algorithms, systems, machine learning and AI."], ["physics", "Physics", "Theoretical and computational physics."], ["chemistry", "Chemistry", "Computational and theoretical chemistry."], ["biology", "Biology", "Computational biology and public-data analyses (human review required)."], [ "medicine", "Medicine & Health", "Literature synthesis and public-data health research (human review required).", ], ["engineering", "Engineering", "Methods, simulations and systems engineering."], [ "earth-sciences", "Earth & Environmental Sciences", "Climate, geoscience and environmental data analyses.", ], ["social-sciences", "Social Sciences", "Sociology, political science, demography."], ["psychology", "Psychology", "Reviews and public-data analyses (human review required)."], ["humanities", "Humanities", "History, philosophy, textual and interpretive inquiry."], ["law", "Law", "Doctrinal and empirical legal studies (human review required)."], ["education", "Education", "Education research and policy."], ["interdisciplinary", "Interdisciplinary", "Studies spanning several disciplines."], ]; const SUBFIELDS: Array<[string, string, string]> = [ ["economics", "econometrics", "Econometrics"], ["economics", "macroeconomics", "Macroeconomics"], ["economics", "real-estate-economics", "Real Estate Economics"], ["finance", "asset-pricing", "Asset Pricing"], ["finance", "market-microstructure", "Market Microstructure"], ["computer-science", "machine-learning", "Machine Learning"], ["computer-science", "large-language-models", "Large Language Models"], ["mathematics", "probability", "Probability"], ["mathematics", "optimization", "Optimization"], ["statistics", "bayesian-statistics", "Bayesian Statistics"], ]; export async function seedTopicsAndSeries(db = getDb()): Promise { for (const [i, [slug, name, description]] of DISCIPLINES.entries()) { await db .insert(topics) .values({ slug, name, description, sortOrder: i }) .onConflictDoUpdate({ target: topics.slug, set: { name, description, sortOrder: i } }); } for (const [parentSlug, slug, name] of SUBFIELDS) { const [parent] = await db .select({ id: topics.id }) .from(topics) .where(eq(topics.slug, parentSlug)) .limit(1); await db .insert(topics) .values({ slug, name, parentId: parent?.id ?? null, sortOrder: 100 }) .onConflictDoUpdate({ target: topics.slug, set: { name, parentId: parent?.id ?? null } }); } await db .insert(series) .values({ slug: "as-wp", name: "AutoScholar Working Papers", description: "The main working-paper series. Numbers AS-WP-YEAR-NNNN are allocated at publication and never reused.", }) .onConflictDoNothing(); } /** Staging only: one clearly labelled synthetic sample so the paper page and PDF viewer can be exercised. Never indexed, never shown in production. */ export async function seedStagingPaper(db = getDb()): Promise { if (process.env.STAGING_MODE !== "1") throw new Error("seedStagingPaper refuses to run unless STAGING_MODE=1"); const publicId = "AS-WP-2026-0000"; const [exists] = await db .select({ id: papers.id, slug: papers.slug }) .from(papers) .where(eq(papers.publicId, publicId)) .limit(1); if (exists) return exists.slug; const { PDFDocument, StandardFonts } = await import("pdf-lib"); const doc = await PDFDocument.create(); doc.setTitle("[STAGING SAMPLE] A synthetic working paper for interface testing"); const font = await doc.embedFont(StandardFonts.TimesRoman); const bold = await doc.embedFont(StandardFonts.TimesRomanBold); const lines = [ ["AutoScholar Working Paper AS-WP-2026-0000 — STAGING SAMPLE", bold, 14], ["[STAGING SAMPLE] A synthetic working paper for interface testing", bold, 18], ["AutoScholar — AI-generated research. Research computed on MacLustr.", font, 11], ["AI-generated working paper; not formally peer reviewed.", font, 11], ["", font, 11], [ "Abstract. This document contains no research. It exists only so the staging site can render a", font, 11, ], [ "paper page, a PDF viewer and download links. It is flagged synthetic and is excluded from production,", font, 11, ], ["indexing and feeds.", font, 11], ] as const; for (let p = 0; p < 6; p++) { const page = doc.addPage([595, 842]); let y = 780; if (p === 0) for (const [text, f, size] of lines) { page.drawText(text, { x: 60, y, size, font: f }); y -= size + 10; } page.drawText(`Page ${p + 1} of 6 — staging sample — AutoScholar Working Paper AS-WP-2026-0000`, { x: 60, y: 40, size: 9, font, }); for (let i = 0; i < 30; i++) { page.drawText(`Placeholder line ${i + 1}. Synthetic content for layout testing only.`, { x: 60, y: (p === 0 ? 560 : 760) - i * 20, size: 10, font, }); } } const pdf = Buffer.from(await doc.save()); const sha256 = createHash("sha256").update(pdf).digest("hex"); const root = resolve(process.env.ARTIFACT_ROOT ?? "./data/artifacts"); const key = `public/${publicId}/v1/manuscript/main.pdf`; await mkdir(dirname(join(root, key)), { recursive: true }); await writeFile(join(root, key), pdf); await writeFile(`${join(root, key)}.sha256`, `${sha256} ${key}\n`); const abstractTxt = "This staging sample contains no research. It exists only so the staging site can render a paper page, a PDF viewer and download links. It is flagged synthetic, excluded from production, indexing and feeds, and must never be mistaken for an AutoScholar working paper. Everything on this page is placeholder content generated for interface testing."; const [s] = await db.select({ id: series.id }).from(series).where(eq(series.slug, "as-wp")).limit(1); const slug = "as-wp-2026-0000-staging-sample"; const slugRow = await db.transaction(async (tx) => { await tx.insert(paperSequences).values({ year: 2026, last: 0 }).onConflictDoNothing(); const [p] = await tx .insert(papers) .values({ publicId, slug, title: "[STAGING SAMPLE] A synthetic working paper for interface testing", status: "published", discipline: "interdisciplinary", researchMode: "theoretical", seriesId: s?.id ?? null, synthetic: true, }) .returning({ id: papers.id }); const [v] = await tx .insert(paperVersions) .values({ paperId: p!.id, versionNo: 1, runId: "00000000-0000-0000-0000-000000000000", title: "[STAGING SAMPLE] A synthetic working paper for interface testing", abstract: abstractTxt, keywords: ["staging", "synthetic sample", "interface test"], metadata: { contributions: ["None. This is a synthetic staging sample."], methods: "No methods: synthetic staging sample.", mainResults: "No results: synthetic staging sample.", limitations: "This document is a synthetic staging sample with no scientific content.", synthetic: true, }, artifactSet: [ { path: "manuscript/main.pdf", kind: "pdf", sha256, bytes: pdf.length, contentType: "application/pdf", publicPath: `/papers/${publicId}/v1/manuscript/main.pdf`, }, ], pdfPublicPath: `/papers/${publicId}/v1/manuscript/main.pdf`, qaSummary: { publishable: false, warnings: ["synthetic staging sample — not evaluated"], policyVersion: "staging", }, provenance: { model: "none (staging seed)", infrastructure: "none — synthetic staging sample", toolchain: { "pdf-lib": "seed" }, }, changelog: "Staging sample.", }) .returning({ id: paperVersions.id }); await tx.update(papers).set({ currentVersionId: v!.id }).where(eq(papers.id, p!.id)); const [t] = await tx .select({ id: topics.id }) .from(topics) .where(eq(topics.slug, "interdisciplinary")) .limit(1); if (t) await tx.insert(paperTopics).values({ paperId: p!.id, topicId: t.id }).onConflictDoNothing(); return slug; }); return slugRow; } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { (async () => { const db = getDb(); await seedTopicsAndSeries(db); console.log("[seed] topics and series ensured"); if (process.env.STAGING_MODE === "1") console.log("[seed] staging sample paper:", await seedStagingPaper(db)); else console.log("[seed] STAGING_MODE!=1 → no sample paper"); await closeDb(); })().catch(async (e) => { console.error("[seed] failed:", e); await closeDb(); process.exit(1); }); }