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

@@ -55,6 +55,10 @@ function createHydrationResult(): TaxonomyHydrationResult {
kpi_row_count: 0,
unmapped_row_count: 0,
material_unmapped_row_count: 0,
residual_primary_count: 0,
residual_disclosure_count: 0,
unsupported_concept_count: 0,
issuer_overlay_match_count: 0,
warnings: ["rust_warning"],
},
xbrl_validation: {

View File

@@ -4,6 +4,7 @@ import type {
TaxonomyHydrationInput,
TaxonomyHydrationResult,
} from "@/lib/server/taxonomy/types";
import type { IssuerOverlayDefinition } from "@/lib/server/db/schema";
import { __parserClientInternals } from "@/lib/server/taxonomy/parser-client";
function streamFromText(text: string) {
@@ -81,6 +82,10 @@ function sampleHydrationResult(): TaxonomyHydrationResult {
kpi_row_count: 0,
unmapped_row_count: 0,
material_unmapped_row_count: 0,
residual_primary_count: 0,
residual_disclosure_count: 0,
unsupported_concept_count: 0,
issuer_overlay_match_count: 0,
warnings: [],
},
xbrl_validation: {
@@ -103,6 +108,22 @@ function sampleInput(): TaxonomyHydrationInput {
};
}
function sampleOverlay(): IssuerOverlayDefinition {
return {
version: "fiscal-v1",
ticker: "AAPL",
pack: "core",
mappings: [
{
surface_key: "revenue",
statement: "income",
allowed_source_concepts: ["aapl:ServicesRevenue"],
allowed_authoritative_concepts: [],
},
],
};
}
const passThroughTimeout = ((handler: TimerHandler, timeout?: number) =>
globalThis.setTimeout(
handler,
@@ -156,6 +177,47 @@ describe("parser client", () => {
expect(result.parser_engine).toBe("fiscal-xbrl");
expect(stdinWrite).toHaveBeenCalledTimes(1);
expect(stdinEnd).toHaveBeenCalledTimes(1);
const [firstCall] = stdinWrite.mock.calls as unknown[][];
const firstWrite = firstCall?.[0];
expect(firstWrite).toBeDefined();
const payload = JSON.parse(
new TextDecoder().decode(firstWrite as unknown as Uint8Array),
) as { issuerOverlay?: IssuerOverlayDefinition | null };
expect(payload.issuerOverlay).toBeNull();
});
it("includes issuer overlay payload when provided", async () => {
const stdinWrite = mock(() => {});
await __parserClientInternals.hydrateFromSidecarImpl(
{
...sampleInput(),
issuerOverlay: sampleOverlay(),
},
{
existsSync: () => true,
spawn: mock(() => ({
stdin: {
write: stdinWrite,
end: () => {},
},
stdout: streamFromText(JSON.stringify(sampleHydrationResult())),
stderr: streamFromText(""),
exited: Promise.resolve(0),
kill: mock(() => {}),
})) as never,
setTimeout: passThroughTimeout,
clearTimeout,
},
);
const [firstCall] = stdinWrite.mock.calls as unknown[][];
const firstWrite = firstCall?.[0];
expect(firstWrite).toBeDefined();
const payload = JSON.parse(
new TextDecoder().decode(firstWrite as unknown as Uint8Array),
) as { issuerOverlay?: IssuerOverlayDefinition | null };
expect(payload.issuerOverlay).toEqual(sampleOverlay());
});
it("throws when the sidecar exits non-zero", async () => {

View File

@@ -32,7 +32,7 @@ function candidateBinaryPaths() {
);
}
export function resolveFiscalXbrlBinary() {
function resolveFiscalXbrlBinary() {
return resolveFiscalXbrlBinaryWithDeps({
existsSync,
});
@@ -93,6 +93,7 @@ async function hydrateFromSidecarImpl(
filingType: input.filingType,
filingUrl: input.filingUrl,
primaryDocument: input.primaryDocument,
issuerOverlay: input.issuerOverlay ?? null,
cacheDir:
process.env.FISCAL_XBRL_CACHE_DIR ??
join(process.cwd(), ".cache", "xbrl"),

View File

@@ -2,6 +2,7 @@ import type {
Filing,
FinancialStatementKind,
MetricValidationResult,
TickerAutomationSource,
} from "@/lib/types";
import type { ComputedDefinition } from "@/lib/generated";
import type {
@@ -10,6 +11,7 @@ import type {
FilingTaxonomyPeriod,
FilingTaxonomySource,
} from "@/lib/server/repos/filing-taxonomy";
import type { IssuerOverlayDefinition } from "@/lib/server/db/schema";
export type TaxonomyAsset = {
asset_type: FilingTaxonomyAssetType;
@@ -20,9 +22,9 @@ export type TaxonomyAsset = {
is_selected: boolean;
};
export type TaxonomyNamespaceMap = Record<string, string>;
type TaxonomyNamespaceMap = Record<string, string>;
export type TaxonomyContext = {
type TaxonomyContext = {
id: string;
entityIdentifier: string | null;
entityScheme: string | null;
@@ -40,7 +42,7 @@ export type TaxonomyContext = {
} | null;
};
export type TaxonomyUnit = {
type TaxonomyUnit = {
id: string;
measure: string | null;
};
@@ -65,7 +67,7 @@ export type TaxonomyFact = {
sourceFile: string | null;
};
export type TaxonomyPresentationConcept = {
type TaxonomyPresentationConcept = {
conceptKey: string;
qname: string;
roleUri: string;
@@ -157,7 +159,7 @@ export type TaxonomyHydrationSurfaceRow = {
formula_key: string | null;
has_dimensions: boolean;
resolved_source_row_keys: Record<string, string | null>;
statement?: "income" | "balance" | "cash_flow";
statement?: "income" | "balance" | "cash_flow" | "equity" | "disclosure";
detail_count?: number;
resolution_method?:
| "direct"
@@ -206,6 +208,10 @@ export type TaxonomyHydrationNormalizationSummary = {
kpi_row_count: number;
unmapped_row_count: number;
material_unmapped_row_count: number;
residual_primary_count: number;
residual_disclosure_count: number;
unsupported_concept_count: number;
issuer_overlay_match_count: number;
warnings: string[];
};
@@ -223,6 +229,12 @@ export type TaxonomyHydrationInput = {
filingType: "10-K" | "10-Q";
filingUrl: string | null;
primaryDocument: string | null;
issuerOverlay?: IssuerOverlayDefinition | null;
};
export type TickerAutomationRequest = {
ticker: string;
source: TickerAutomationSource;
};
export type TaxonomyHydrationResult = {