feat(sec-lint): lint+backfill system for SEC data gaps (B1-B4)
- SecLintAdapter implements SourceFetch, runs via shared queue/drain loop - Two new SourceKinds: sec-lint-holders, sec-lint-insiders (weekly schedules) - No-op cache handlers so drain->cache.set doesn't throw on lint keys - tRPC admin.queueLint(symbol, kind) — run lint for one symbol, returns LintResult - tRPC admin.queueLintAll(kind) — backfill ALL watched symbols at once - tRPC admin.dataQualityList() — query data_quality rows (filterable by symbol/kind) - InstitutionalDashboard: 'Lint holders' button + status badge in detail panel header - Admin queue page: Data Quality section with per-row status badges, 'Lint all' buttons - DEFAULT_RATE_MS includes 167ms (~6 req/s) for lint kinds matching EDGAR limiter
This commit is contained in:
+38
-2
@@ -135,6 +135,7 @@ export interface Form13fHolding {
|
||||
|
||||
export interface Form13fResult {
|
||||
holdings: Form13fHolding[];
|
||||
total: number;
|
||||
accession: string;
|
||||
}
|
||||
|
||||
@@ -174,8 +175,13 @@ export const api = {
|
||||
trpcQuery<{ meta: FilerMeta; cik: string }>("edgar.filer_cik_meta", { cik }),
|
||||
full_text_search: (q: string) =>
|
||||
trpcQuery<FilingsSearchResult>("edgar.full_text_search", { q }),
|
||||
form13f_holdings: (cik: string, accession: string) =>
|
||||
trpcQuery<{ holdings: Form13fResult }>("edgar.form13f_holdings", { cik, accession }),
|
||||
form13f_holdings: (cik: string, accession: string, opts?: { limit?: number; offset?: number }) =>
|
||||
trpcQuery<Form13fResult>("edgar.form13f_holdings", {
|
||||
cik,
|
||||
accession,
|
||||
...(opts?.limit != null ? { limit: opts.limit } : {}),
|
||||
...(opts?.offset != null ? { offset: opts.offset } : {}),
|
||||
}),
|
||||
form4_tx: (cik: string, accession: string) =>
|
||||
trpcQuery<{ transactions: Form4Result }>("edgar.form4_tx", { cik, accession }),
|
||||
},
|
||||
@@ -249,9 +255,39 @@ export const api = {
|
||||
queueRetrySource: (sourceKind: string) => trpcMutate<{ cleared: number }>("admin.queueRetrySource", { sourceKind }),
|
||||
auditLog: (limit = 50, offset = 0, actor?: string, action?: string) =>
|
||||
trpcQuery<Array<{ id: string; actor: string; action: string; target: string | null; ts: string; meta: string | null }>>("admin.auditLog", { limit, offset, ...(actor ? { actor } : {}), ...(action ? { action } : {}) }),
|
||||
queueLint: (symbol: string, kind: 'sec-lint-holders' | 'sec-lint-insiders') =>
|
||||
trpcMutate<LintResult>("admin.queueLint", { symbol, kind }),
|
||||
queueLintAll: (input: { kind: 'sec-lint-holders' | 'sec-lint-insiders' }) =>
|
||||
trpcMutate<{ total: number; results: LintResult[] }>("admin.queueLintAll", input),
|
||||
dataQualityList: (input?: { symbol?: string; kind?: 'institution_filings' | 'insider_transactions' }) =>
|
||||
trpcQuery<DataQualityRow[]>("admin.dataQualityList", input ?? {}),
|
||||
},
|
||||
};
|
||||
|
||||
export interface LintResult {
|
||||
symbol: string;
|
||||
kind: string;
|
||||
discoveredCount: number;
|
||||
storedCount: number;
|
||||
missingCount: number;
|
||||
backfilled: number;
|
||||
stale: number;
|
||||
status: 'ok' | 'gaps_found' | 'stale' | 'error';
|
||||
detail: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DataQualityRow {
|
||||
symbol: string;
|
||||
kind: string;
|
||||
last_checked_at: string;
|
||||
stored_count: number;
|
||||
discovered_count: number;
|
||||
missing_count: number;
|
||||
stale: number;
|
||||
status: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export interface WatchlistEntry {
|
||||
symbol: string;
|
||||
added_at: string;
|
||||
|
||||
Reference in New Issue
Block a user