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:
Investor Flow Build
2026-07-12 20:19:12 -04:00
parent ca385c1960
commit 5ef2b2f060
9 changed files with 1154 additions and 47 deletions
+38 -2
View File
@@ -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;