spb/autoscholar
Public
1import { createHash } from "node:crypto";2import { mkdir, writeFile } from "node:fs/promises";3import { dirname, join, resolve } from "node:path";4import { fileURLToPath } from "node:url";5import { eq } from "drizzle-orm";6import { closeDb, getDb } from "./client.js";7import { paperSequences, papers, paperTopics, paperVersions, series, topics } from "./schema/index.js";89const DISCIPLINES: Array<[string, string, string]> = [10 ["economics", "Economics", "Micro, macro, econometrics, finance-adjacent economics."],11 ["finance", "Finance", "Asset pricing, corporate finance, market microstructure, financial econometrics."],12 ["mathematics", "Mathematics", "Pure and applied mathematics, formal results and proofs."],13 ["statistics", "Statistics", "Statistical theory, methodology and computation."],14 ["computer-science", "Computer Science", "Algorithms, systems, machine learning and AI."],15 ["physics", "Physics", "Theoretical and computational physics."],16 ["chemistry", "Chemistry", "Computational and theoretical chemistry."],17 ["biology", "Biology", "Computational biology and public-data analyses (human review required)."],18 [19 "medicine",20 "Medicine & Health",21 "Literature synthesis and public-data health research (human review required).",22 ],23 ["engineering", "Engineering", "Methods, simulations and systems engineering."],24 [25 "earth-sciences",26 "Earth & Environmental Sciences",27 "Climate, geoscience and environmental data analyses.",28 ],29 ["social-sciences", "Social Sciences", "Sociology, political science, demography."],30 ["psychology", "Psychology", "Reviews and public-data analyses (human review required)."],31 ["humanities", "Humanities", "History, philosophy, textual and interpretive inquiry."],32 ["law", "Law", "Doctrinal and empirical legal studies (human review required)."],33 ["education", "Education", "Education research and policy."],34 ["interdisciplinary", "Interdisciplinary", "Studies spanning several disciplines."],35];36const SUBFIELDS: Array<[string, string, string]> = [37 ["economics", "econometrics", "Econometrics"],38 ["economics", "macroeconomics", "Macroeconomics"],39 ["economics", "real-estate-economics", "Real Estate Economics"],40 ["finance", "asset-pricing", "Asset Pricing"],41 ["finance", "market-microstructure", "Market Microstructure"],42 ["computer-science", "machine-learning", "Machine Learning"],43 ["computer-science", "large-language-models", "Large Language Models"],44 ["mathematics", "probability", "Probability"],45 ["mathematics", "optimization", "Optimization"],46 ["statistics", "bayesian-statistics", "Bayesian Statistics"],47];4849export async function seedTopicsAndSeries(db = getDb()): Promise<void> {50 for (const [i, [slug, name, description]] of DISCIPLINES.entries()) {51 await db52 .insert(topics)53 .values({ slug, name, description, sortOrder: i })54 .onConflictDoUpdate({ target: topics.slug, set: { name, description, sortOrder: i } });55 }56 for (const [parentSlug, slug, name] of SUBFIELDS) {57 const [parent] = await db58 .select({ id: topics.id })59 .from(topics)60 .where(eq(topics.slug, parentSlug))61 .limit(1);62 await db63 .insert(topics)64 .values({ slug, name, parentId: parent?.id ?? null, sortOrder: 100 })65 .onConflictDoUpdate({ target: topics.slug, set: { name, parentId: parent?.id ?? null } });66 }67 await db68 .insert(series)69 .values({70 slug: "as-wp",71 name: "AutoScholar Working Papers",72 description:73 "The main working-paper series. Numbers AS-WP-YEAR-NNNN are allocated at publication and never reused.",74 })75 .onConflictDoNothing();76}7778/** Staging only: one clearly labelled synthetic sample so the paper page and PDF viewer can be exercised. Never indexed, never shown in production. */79export async function seedStagingPaper(db = getDb()): Promise<string | null> {80 if (process.env.STAGING_MODE !== "1")81 throw new Error("seedStagingPaper refuses to run unless STAGING_MODE=1");82 const publicId = "AS-WP-2026-0000";83 const [exists] = await db84 .select({ id: papers.id, slug: papers.slug })85 .from(papers)86 .where(eq(papers.publicId, publicId))87 .limit(1);88 if (exists) return exists.slug;89 const { PDFDocument, StandardFonts } = await import("pdf-lib");90 const doc = await PDFDocument.create();91 doc.setTitle("[STAGING SAMPLE] A synthetic working paper for interface testing");92 const font = await doc.embedFont(StandardFonts.TimesRoman);93 const bold = await doc.embedFont(StandardFonts.TimesRomanBold);94 const lines = [95 ["AutoScholar Working Paper AS-WP-2026-0000 — STAGING SAMPLE", bold, 14],96 ["[STAGING SAMPLE] A synthetic working paper for interface testing", bold, 18],97 ["AutoScholar — AI-generated research. Research computed on MacLustr.", font, 11],98 ["AI-generated working paper; not formally peer reviewed.", font, 11],99 ["", font, 11],100 [101 "Abstract. This document contains no research. It exists only so the staging site can render a",102 font,103 11,104 ],105 [106 "paper page, a PDF viewer and download links. It is flagged synthetic and is excluded from production,",107 font,108 11,109 ],110 ["indexing and feeds.", font, 11],111 ] as const;112 for (let p = 0; p < 6; p++) {113 const page = doc.addPage([595, 842]);114 let y = 780;115 if (p === 0)116 for (const [text, f, size] of lines) {117 page.drawText(text, { x: 60, y, size, font: f });118 y -= size + 10;119 }120 page.drawText(`Page ${p + 1} of 6 — staging sample — AutoScholar Working Paper AS-WP-2026-0000`, {121 x: 60,122 y: 40,123 size: 9,124 font,125 });126 for (let i = 0; i < 30; i++) {127 page.drawText(`Placeholder line ${i + 1}. Synthetic content for layout testing only.`, {128 x: 60,129 y: (p === 0 ? 560 : 760) - i * 20,130 size: 10,131 font,132 });133 }134 }135 const pdf = Buffer.from(await doc.save());136 const sha256 = createHash("sha256").update(pdf).digest("hex");137 const root = resolve(process.env.ARTIFACT_ROOT ?? "./data/artifacts");138 const key = `public/${publicId}/v1/manuscript/main.pdf`;139 await mkdir(dirname(join(root, key)), { recursive: true });140 await writeFile(join(root, key), pdf);141 await writeFile(`${join(root, key)}.sha256`, `${sha256} ${key}\n`);142 const abstractTxt =143 "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.";144 const [s] = await db.select({ id: series.id }).from(series).where(eq(series.slug, "as-wp")).limit(1);145 const slug = "as-wp-2026-0000-staging-sample";146 const slugRow = await db.transaction(async (tx) => {147 await tx.insert(paperSequences).values({ year: 2026, last: 0 }).onConflictDoNothing();148 const [p] = await tx149 .insert(papers)150 .values({151 publicId,152 slug,153 title: "[STAGING SAMPLE] A synthetic working paper for interface testing",154 status: "published",155 discipline: "interdisciplinary",156 researchMode: "theoretical",157 seriesId: s?.id ?? null,158 synthetic: true,159 })160 .returning({ id: papers.id });161 const [v] = await tx162 .insert(paperVersions)163 .values({164 paperId: p!.id,165 versionNo: 1,166 runId: "00000000-0000-0000-0000-000000000000",167 title: "[STAGING SAMPLE] A synthetic working paper for interface testing",168 abstract: abstractTxt,169 keywords: ["staging", "synthetic sample", "interface test"],170 metadata: {171 contributions: ["None. This is a synthetic staging sample."],172 methods: "No methods: synthetic staging sample.",173 mainResults: "No results: synthetic staging sample.",174 limitations: "This document is a synthetic staging sample with no scientific content.",175 synthetic: true,176 },177 artifactSet: [178 {179 path: "manuscript/main.pdf",180 kind: "pdf",181 sha256,182 bytes: pdf.length,183 contentType: "application/pdf",184 publicPath: `/papers/${publicId}/v1/manuscript/main.pdf`,185 },186 ],187 pdfPublicPath: `/papers/${publicId}/v1/manuscript/main.pdf`,188 qaSummary: {189 publishable: false,190 warnings: ["synthetic staging sample — not evaluated"],191 policyVersion: "staging",192 },193 provenance: {194 model: "none (staging seed)",195 infrastructure: "none — synthetic staging sample",196 toolchain: { "pdf-lib": "seed" },197 },198 changelog: "Staging sample.",199 })200 .returning({ id: paperVersions.id });201 await tx.update(papers).set({ currentVersionId: v!.id }).where(eq(papers.id, p!.id));202 const [t] = await tx203 .select({ id: topics.id })204 .from(topics)205 .where(eq(topics.slug, "interdisciplinary"))206 .limit(1);207 if (t) await tx.insert(paperTopics).values({ paperId: p!.id, topicId: t.id }).onConflictDoNothing();208 return slug;209 });210 return slugRow;211}212213if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {214 (async () => {215 const db = getDb();216 await seedTopicsAndSeries(db);217 console.log("[seed] topics and series ensured");218 if (process.env.STAGING_MODE === "1")219 console.log("[seed] staging sample paper:", await seedStagingPaper(db));220 else console.log("[seed] STAGING_MODE!=1 → no sample paper");221 await closeDb();222 })().catch(async (e) => {223 console.error("[seed] failed:", e);224 await closeDb();225 process.exit(1);226 });227}228