Add dark inference pipeline
This commit is contained in:
parent
3164167bee
commit
ea61c3b013
9 changed files with 699 additions and 1 deletions
|
|
@ -8,6 +8,8 @@ export const STREAM_EQUITY_QUOTES = "EQUITY_QUOTES";
|
|||
export const SUBJECT_EQUITY_QUOTES = "equities.quotes";
|
||||
export const STREAM_EQUITY_JOINS = "EQUITY_JOINS";
|
||||
export const SUBJECT_EQUITY_JOINS = "equities.joins";
|
||||
export const STREAM_INFERRED_DARK = "INFERRED_DARK";
|
||||
export const SUBJECT_INFERRED_DARK = "dark.inferred";
|
||||
export const STREAM_FLOW_PACKETS = "FLOW_PACKETS";
|
||||
export const SUBJECT_FLOW_PACKETS = "flow.packets";
|
||||
export const STREAM_CLASSIFIER_HITS = "CLASSIFIER_HITS";
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
EquityPrintSchema,
|
||||
EquityQuoteSchema,
|
||||
EquityPrintJoinSchema,
|
||||
InferredDarkEventSchema,
|
||||
FlowPacketSchema,
|
||||
OptionNBBOSchema,
|
||||
OptionPrintSchema
|
||||
|
|
@ -15,6 +16,7 @@ import type {
|
|||
EquityPrint,
|
||||
EquityQuote,
|
||||
EquityPrintJoin,
|
||||
InferredDarkEvent,
|
||||
FlowPacket,
|
||||
OptionNBBO,
|
||||
OptionPrint
|
||||
|
|
@ -42,6 +44,13 @@ import {
|
|||
toEquityPrintJoinRecord,
|
||||
type EquityPrintJoinRecord
|
||||
} from "./equity-print-joins";
|
||||
import {
|
||||
inferredDarkTableDDL,
|
||||
INFERRED_DARK_TABLE,
|
||||
fromInferredDarkRecord,
|
||||
toInferredDarkRecord,
|
||||
type InferredDarkRecord
|
||||
} from "./inferred-dark";
|
||||
import {
|
||||
FLOW_PACKETS_TABLE,
|
||||
flowPacketsTableDDL,
|
||||
|
|
@ -120,6 +129,14 @@ export const ensureEquityPrintJoinsTable = async (
|
|||
});
|
||||
};
|
||||
|
||||
export const ensureInferredDarkTable = async (
|
||||
client: ClickHouseClient
|
||||
): Promise<void> => {
|
||||
await client.exec({
|
||||
query: inferredDarkTableDDL()
|
||||
});
|
||||
};
|
||||
|
||||
export const ensureFlowPacketsTable = async (
|
||||
client: ClickHouseClient
|
||||
): Promise<void> => {
|
||||
|
|
@ -202,6 +219,18 @@ export const insertEquityPrintJoin = async (
|
|||
});
|
||||
};
|
||||
|
||||
export const insertInferredDark = async (
|
||||
client: ClickHouseClient,
|
||||
event: InferredDarkEvent
|
||||
): Promise<void> => {
|
||||
const record = toInferredDarkRecord(event);
|
||||
await client.insert({
|
||||
table: INFERRED_DARK_TABLE,
|
||||
values: [record],
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
};
|
||||
|
||||
export const insertFlowPacket = async (
|
||||
client: ClickHouseClient,
|
||||
packet: FlowPacket
|
||||
|
|
@ -367,6 +396,23 @@ const normalizeEquityPrintJoinRow = (row: unknown): EquityPrintJoinRecord | null
|
|||
};
|
||||
};
|
||||
|
||||
const normalizeInferredDarkRow = (row: unknown): InferredDarkRecord | null => {
|
||||
if (!row || typeof row !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const record = row as Record<string, unknown>;
|
||||
return {
|
||||
source_ts: coerceNumber(record.source_ts) as number,
|
||||
ingest_ts: coerceNumber(record.ingest_ts) as number,
|
||||
seq: coerceNumber(record.seq) as number,
|
||||
trace_id: String(record.trace_id ?? ""),
|
||||
type: String(record.type ?? ""),
|
||||
confidence: Number(coerceNumber(record.confidence) ?? 0),
|
||||
evidence_refs_json: String(record.evidence_refs_json ?? "[]")
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeFlowPacketRow = (row: unknown): FlowPacketRecord | null => {
|
||||
if (!row || typeof row !== "object") {
|
||||
return null;
|
||||
|
|
@ -497,6 +543,24 @@ export const fetchRecentEquityPrintJoins = async (
|
|||
return EquityPrintJoinSchema.array().parse(joins);
|
||||
};
|
||||
|
||||
export const fetchRecentInferredDark = async (
|
||||
client: ClickHouseClient,
|
||||
limit: number
|
||||
): Promise<InferredDarkEvent[]> => {
|
||||
const safeLimit = clampLimit(limit);
|
||||
const result = await client.query({
|
||||
query: `SELECT * FROM ${INFERRED_DARK_TABLE} ORDER BY source_ts DESC, seq DESC LIMIT ${safeLimit}`,
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
|
||||
const rows = await result.json<unknown[]>();
|
||||
const records = rows
|
||||
.map(normalizeInferredDarkRow)
|
||||
.filter((record): record is InferredDarkRecord => record !== null);
|
||||
const events = records.map(fromInferredDarkRecord);
|
||||
return InferredDarkEventSchema.array().parse(events);
|
||||
};
|
||||
|
||||
export const fetchRecentFlowPackets = async (
|
||||
client: ClickHouseClient,
|
||||
limit: number
|
||||
|
|
@ -649,3 +713,26 @@ export const fetchEquityPrintJoinsAfter = async (
|
|||
const joins = records.map(fromEquityPrintJoinRecord);
|
||||
return EquityPrintJoinSchema.array().parse(joins);
|
||||
};
|
||||
|
||||
export const fetchInferredDarkAfter = async (
|
||||
client: ClickHouseClient,
|
||||
afterTs: number,
|
||||
afterSeq: number,
|
||||
limit: number
|
||||
): Promise<InferredDarkEvent[]> => {
|
||||
const safeLimit = clampLimit(limit);
|
||||
const safeAfterTs = clampCursor(afterTs);
|
||||
const safeAfterSeq = clampCursor(afterSeq);
|
||||
|
||||
const result = await client.query({
|
||||
query: `SELECT * FROM ${INFERRED_DARK_TABLE} WHERE (source_ts, seq) > (${safeAfterTs}, ${safeAfterSeq}) ORDER BY source_ts ASC, seq ASC LIMIT ${safeLimit}`,
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
|
||||
const rows = await result.json<unknown[]>();
|
||||
const records = rows
|
||||
.map(normalizeInferredDarkRow)
|
||||
.filter((record): record is InferredDarkRecord => record !== null);
|
||||
const events = records.map(fromInferredDarkRecord);
|
||||
return InferredDarkEventSchema.array().parse(events);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ export * from "./flow-packets";
|
|||
export * from "./equity-prints";
|
||||
export * from "./equity-quotes";
|
||||
export * from "./equity-print-joins";
|
||||
export * from "./inferred-dark";
|
||||
export * from "./option-prints";
|
||||
export * from "./option-nbbo";
|
||||
|
|
|
|||
66
packages/storage/src/inferred-dark.ts
Normal file
66
packages/storage/src/inferred-dark.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import type { InferredDarkEvent } from "@islandflow/types";
|
||||
|
||||
export const INFERRED_DARK_TABLE = "inferred_dark";
|
||||
|
||||
export type InferredDarkRecord = {
|
||||
source_ts: number;
|
||||
ingest_ts: number;
|
||||
seq: number;
|
||||
trace_id: string;
|
||||
type: string;
|
||||
confidence: number;
|
||||
evidence_refs_json: string;
|
||||
};
|
||||
|
||||
export const inferredDarkTableDDL = (): string => {
|
||||
return `
|
||||
CREATE TABLE IF NOT EXISTS ${INFERRED_DARK_TABLE} (
|
||||
source_ts UInt64,
|
||||
ingest_ts UInt64,
|
||||
seq UInt64,
|
||||
trace_id String,
|
||||
type String,
|
||||
confidence Float64,
|
||||
evidence_refs_json String
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY (source_ts, seq)
|
||||
`;
|
||||
};
|
||||
|
||||
export const toInferredDarkRecord = (event: InferredDarkEvent): InferredDarkRecord => {
|
||||
return {
|
||||
source_ts: event.source_ts,
|
||||
ingest_ts: event.ingest_ts,
|
||||
seq: event.seq,
|
||||
trace_id: event.trace_id,
|
||||
type: event.type,
|
||||
confidence: event.confidence,
|
||||
evidence_refs_json: JSON.stringify(event.evidence_refs)
|
||||
};
|
||||
};
|
||||
|
||||
const safeStringArray = (value: string): string[] => {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map((entry) => String(entry));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const fromInferredDarkRecord = (record: InferredDarkRecord): InferredDarkEvent => {
|
||||
return {
|
||||
source_ts: record.source_ts,
|
||||
ingest_ts: record.ingest_ts,
|
||||
seq: record.seq,
|
||||
trace_id: record.trace_id,
|
||||
type: record.type,
|
||||
confidence: record.confidence,
|
||||
evidence_refs: safeStringArray(record.evidence_refs_json)
|
||||
};
|
||||
};
|
||||
33
packages/storage/tests/inferred-dark.test.ts
Normal file
33
packages/storage/tests/inferred-dark.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
fromInferredDarkRecord,
|
||||
inferredDarkTableDDL,
|
||||
INFERRED_DARK_TABLE,
|
||||
toInferredDarkRecord
|
||||
} from "../src/inferred-dark";
|
||||
|
||||
const event = {
|
||||
source_ts: 100,
|
||||
ingest_ts: 120,
|
||||
seq: 1,
|
||||
trace_id: "dark:absorbed:join-1",
|
||||
type: "absorbed_block",
|
||||
confidence: 0.62,
|
||||
evidence_refs: ["equityjoin:print-1"]
|
||||
};
|
||||
|
||||
describe("inferred-dark storage helpers", () => {
|
||||
it("includes the correct table name in the DDL", () => {
|
||||
const ddl = inferredDarkTableDDL();
|
||||
expect(ddl).toContain(INFERRED_DARK_TABLE);
|
||||
expect(ddl).toContain("CREATE TABLE IF NOT EXISTS");
|
||||
});
|
||||
|
||||
it("round-trips inferred dark records", () => {
|
||||
const record = toInferredDarkRecord(event);
|
||||
const restored = fromInferredDarkRecord(record);
|
||||
expect(restored.evidence_refs).toEqual(event.evidence_refs);
|
||||
expect(restored.type).toBe(event.type);
|
||||
expect(restored.confidence).toBeCloseTo(event.confidence, 4);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue