import { defineConnector, parseFeed, raw, stripHtml, type NormalizedBatch } from "@market-atlas/connector-sdk"; import { parseTimestamp } from "@market-atlas/market-model"; const FEED = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcurrent&type=&company=&dateb=&owner=include&start=0&count=100&output=atom"; /** Forms that produce a public FILING_PUBLISHED event; the rest is stored but not surfaced as an event. */ export const MATERIAL_FORMS = new Set(["8-K", "8-K/A", "10-K", "10-K/A", "10-Q", "10-Q/A", "6-K", "20-F", "40-F", "S-1", "S-1/A", "F-1", "424B4", "SC 13D", "SC 13D/A", "SC 13G", "SC 13G/A", "DEF 14A", "DEFM14A", "425", "SC TO-T", "S-4", "NT 10-K", "NT 10-Q"]); /** * SEC EDGAR "latest filings" Atom feed (public, no key; SEC asks for an identifying User-Agent * and ≤ 10 req/s — Market Atlas polls once every 2 minutes). Each entry → one Filing + a * FILING_PUBLISHED event for material forms. */ export const secEdgarFilings = defineConnector({ metadata: { id: "sec-edgar-filings", name: "SEC EDGAR — latest filings feed", version: "1.0.0", sourceId: "sec-edgar", organization: "U.S. Securities and Exchange Commission", sourceType: "RSS", jurisdiction: "US", rightsStatus: "OFFICIAL_OPEN_DATA", realtimeStatus: "REALTIME", expectedLatencyMs: 120_000, supportsStreaming: false, supportsHistorical: false, assetClasses: ["EQUITY"], exchanges: [], homepage: "https://www.sec.gov/cgi-bin/browse-edgar?action=getcurrent", description: "Real-time list of documents filed on EDGAR (all forms). Each filing is stored with accession number, form type, filer name and CIK, and linked to instruments through the SEC company/ticker directory. Material forms (8-K, 10-K/Q, 6-K, S-1, 13D/G…) become FILING_PUBLISHED events.", rightsNotes: "U.S. government data (public domain). SEC fair-access policy: identifying User-Agent, max 10 requests/second.", termsUrl: "https://www.sec.gov/os/accessing-edgar-data", sourceFamily: "sec", enabled: true, }, rateLimits: { "www.sec.gov": 2 }, schedule: { intervalMs: 2 * 60_000 }, async poll(ctx) { const res = await ctx.http.getText(FEED, { timeoutMs: 30_000, headers: { accept: "application/atom+xml, application/xml" } }); if (res.notModified) return []; return [raw("sec-edgar-filings", "sec-edgar", "atom", res.text, { status: res.status })]; }, normalize(r): NormalizedBatch { if (r.kind !== "atom" || typeof r.payload !== "string") return { observations: [] }; const feed = parseFeed(r.payload); const filings: NonNullable = []; for (const item of feed.items) { // Title: "4 - Ault & Company, Inc. (0001734770) (Reporting)" const m = item.title.match(/^(.+?)\s+-\s+(.+?)\s+\((\d{10})\)\s*(?:\((\w+)\))?$/); const formType = (item.categories.find((c) => c.label === "form type")?.term ?? m?.[1] ?? "").trim(); const companyName = (m?.[2] ?? item.title).trim(); const cik = m?.[3] ? m[3].replace(/^0+/, "") : null; const role = m?.[4] ?? null; const accn = item.id?.match(/accession-number=([\d-]+)/)?.[1] ?? item.link?.match(/(\d{10}-\d{2}-\d{6})/)?.[1] ?? null; if (!accn || !formType) continue; const summary = item.summary ? stripHtml(item.summary) : ""; const filedDate = summary.match(/Filed:\s*(\d{4}-\d{2}-\d{2})/)?.[1] ?? null; const filedAt = parseTimestamp(item.updated ?? item.published) ?? (filedDate ? Date.parse(filedDate) : null); if (!filedAt) continue; filings.push({ id: `${accn}:${cik ?? "x"}`, cik, companyName, formType, filedAt, url: item.link ?? `https://www.sec.gov/Archives/edgar/data/${cik ?? ""}/${accn.replace(/-/g, "")}/${accn}-index.htm`, metadata: { accession_number: accn, role, filed_date: filedDate, size: summary.match(/Size:\s*([\d.]+\s*\w+)/)?.[1] ?? null, material: MATERIAL_FORMS.has(formType) && role !== "Reporting" }, }); } return { observations: [], filings, stats: { entries: feed.items.length } }; }, fixturesDir: "fixtures", });