Automate issuer overlay creation from ticker searches

This commit is contained in:
2026-03-19 20:44:58 -04:00
parent 17de3dd72d
commit 391d6d34ce
79 changed files with 4746 additions and 695 deletions

View File

@@ -0,0 +1,146 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Database } from "bun:sqlite";
import { __dbInternals } from "@/lib/server/db";
import type {
IssuerOverlayDefinition,
IssuerOverlayDiagnostics,
IssuerOverlayStats,
} from "@/lib/server/db/schema";
let tempDir: string | null = null;
let sqliteClient: Database | null = null;
let overlayRepo: typeof import("./issuer-overlays") | null = null;
function resetDbSingletons() {
const globalState = globalThis as typeof globalThis & {
__fiscalSqliteClient?: Database;
__fiscalDrizzleDb?: unknown;
__financialIngestionSchemaStatus?: unknown;
};
globalState.__fiscalSqliteClient?.close();
globalState.__fiscalSqliteClient = undefined;
globalState.__fiscalDrizzleDb = undefined;
globalState.__financialIngestionSchemaStatus = undefined;
}
function sampleDefinition(): IssuerOverlayDefinition {
return {
version: "fiscal-v1",
ticker: "AAPL",
pack: "core",
mappings: [
{
surface_key: "revenue",
statement: "income",
allowed_source_concepts: ["aapl:ServicesRevenue", "aapl:ProductRevenue"],
allowed_authoritative_concepts: [],
},
],
};
}
function sampleDiagnostics(): IssuerOverlayDiagnostics {
return {
pack: "core",
sampledSnapshotIds: [11, 12],
acceptedMappings: [
{
qname: "aapl:ServicesRevenue",
surface_key: "revenue",
statement: "income",
reason: "local_name_match",
source_snapshot_ids: [11, 12],
},
],
rejectedMappings: [],
};
}
function sampleStats(): IssuerOverlayStats {
return {
pack: "core",
sampledSnapshotCount: 2,
sampledSnapshotIds: [11, 12],
acceptedMappingCount: 1,
rejectedMappingCount: 0,
publishedRevisionNumber: null,
};
}
describe("issuer overlay repo", () => {
beforeAll(async () => {
tempDir = mkdtempSync(join(tmpdir(), "fiscal-issuer-overlay-"));
const env = process.env as Record<string, string | undefined>;
env.DATABASE_URL = `file:${join(tempDir, "repo.sqlite")}`;
env.NODE_ENV = "test";
resetDbSingletons();
sqliteClient = new Database(join(tempDir, "repo.sqlite"), { create: true });
sqliteClient.exec("PRAGMA foreign_keys = ON;");
__dbInternals.ensureLocalSqliteSchema(sqliteClient);
const globalState = globalThis as typeof globalThis & {
__fiscalSqliteClient?: Database;
__fiscalDrizzleDb?: unknown;
};
globalState.__fiscalSqliteClient = sqliteClient;
globalState.__fiscalDrizzleDb = undefined;
overlayRepo = await import("./issuer-overlays");
});
afterAll(() => {
sqliteClient?.close();
resetDbSingletons();
if (tempDir) {
rmSync(tempDir, { recursive: true, force: true });
}
});
beforeEach(() => {
sqliteClient?.exec("DELETE FROM issuer_overlay;");
sqliteClient?.exec("DELETE FROM issuer_overlay_revision;");
});
it("creates an empty overlay row on ensure", async () => {
if (!overlayRepo) {
throw new Error("overlay repo not initialized");
}
const overlay = await overlayRepo.ensureIssuerOverlayRow("aapl");
expect(overlay?.ticker).toBe("AAPL");
expect(overlay?.status).toBe("empty");
expect(overlay?.active_revision).toBeNull();
});
it("publishes and deduplicates overlay revisions by content hash", async () => {
if (!overlayRepo) {
throw new Error("overlay repo not initialized");
}
const first = await overlayRepo.publishIssuerOverlayRevision({
ticker: "AAPL",
definition: sampleDefinition(),
diagnostics: sampleDiagnostics(),
stats: sampleStats(),
});
const second = await overlayRepo.publishIssuerOverlayRevision({
ticker: "AAPL",
definition: sampleDefinition(),
diagnostics: sampleDiagnostics(),
stats: sampleStats(),
});
const overlay = await overlayRepo.getIssuerOverlay("AAPL");
const revisions = await overlayRepo.listIssuerOverlayRevisions("AAPL");
expect(first.published).toBe(true);
expect(second.published).toBe(false);
expect(overlay?.active_revision?.id).toBe(first.revision.id);
expect(revisions).toHaveLength(1);
});
});