-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFetchCompanyFactsTask.ts
More file actions
120 lines (106 loc) · 4.03 KB
/
Copy pathFetchCompanyFactsTask.ts
File metadata and controls
120 lines (106 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* @license
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
* SPDX-License-Identifier: Apache-2.0
*/
import { Static, Type } from "typebox";
import { DataPortSchemaObject, IExecuteContext, Task, TaskAbortedError, TaskError } from "workglow";
import { SecCachedFetchTask } from "../fetch/SecCachedFetchTask";
import { CompanyFacts, Factoid, FactoidSchema, normalizeFp } from "../../sec/facts/CompanyFacts";
import { TypeSecCik } from "../../sec/submissions/EnititySubmissionSchema";
import { secDate, TypeOptionalSecDate } from "../../util/parseDate";
// NOTE: company facts are mutable, so we need to pass in a date to break the cache
const FetchCompanyFactsTaskInput = () =>
Type.Object({
cik: TypeSecCik(),
date: TypeOptionalSecDate(),
});
export type FetchCompanyFactsTaskInput = Static<ReturnType<typeof FetchCompanyFactsTaskInput>>;
const FetchCompanyFactsTaskOutput = () =>
Type.Object({
cik: TypeSecCik(),
facts: Type.Array(FactoidSchema),
date: TypeOptionalSecDate(),
});
export type FetchCompanyFactsTaskOutput = Static<ReturnType<typeof FetchCompanyFactsTaskOutput>>;
class SecFetchCompanyFactsTask extends SecCachedFetchTask<FetchCompanyFactsTaskInput> {
static readonly type = "SecFetchCompanyFactsTask";
static readonly category = "Hidden";
static readonly immutable = false;
public static inputSchema() {
return FetchCompanyFactsTaskInput() as DataPortSchemaObject;
}
inputToFileName(input: FetchCompanyFactsTaskInput): string {
return `companyfacts/CIK${input.cik.toString().padStart(10, "0")}.json`;
}
inputToUrl(input: FetchCompanyFactsTaskInput): string {
const date = input.date ? secDate(input.date) : undefined;
return `https://data.sec.gov/api/xbrl/companyfacts/CIK${input.cik
.toString()
.padStart(10, "0")}.json${date ? `?date=${date}` : ""}`;
}
}
/**
* Task for fetching the daily index of SEC filings and parsing it into a list of CIKs to update
*/
export class FetchCompanyFactsTask extends Task<
FetchCompanyFactsTaskInput,
FetchCompanyFactsTaskOutput
> {
static readonly type = "FetchCompanyFactsTask";
static readonly category = "SEC";
static readonly cacheable = true;
public static inputSchema() {
return FetchCompanyFactsTaskInput();
}
public static outputSchema() {
return FetchCompanyFactsTaskOutput();
}
private _secFetch?: SecFetchCompanyFactsTask;
async execute(
input: FetchCompanyFactsTaskInput,
context: IExecuteContext
): Promise<FetchCompanyFactsTaskOutput> {
const cik = input.cik;
if (!cik) {
return { facts: [], cik: 0, date: input.date ? secDate(input.date) : undefined };
}
this._secFetch ??= context.own(new SecFetchCompanyFactsTask(input));
this._secFetch.setDefaults(input);
const secData = await this._secFetch.run();
const companyFacts = secData.json as unknown as CompanyFacts | undefined;
const facts = companyFacts?.facts;
if (!facts || typeof facts !== "object") {
throw new TaskError(`Company facts JSON for CIK ${cik} has no 'facts' object`);
}
// linearize the facts
const factsArray: Factoid[] = [];
Object.entries(facts).forEach(([grouping, names]) => {
Object.entries(names).forEach(([name, info]) => {
Object.entries(info.units).forEach(([unit, summaries]) => {
for (const summary of summaries) {
if (context.signal?.aborted) {
throw new TaskAbortedError();
}
factsArray.push({
cik,
grouping,
name,
filed_date: summary.filed,
form: summary.form,
val_unit: unit,
frame: summary.frame || null,
accession_number: summary.accn,
start_date: summary.start || null,
end_date: summary.end,
val: summary.val,
fy: summary.fy,
fp: normalizeFp(summary.fp),
});
}
});
});
});
return { cik, facts: factsArray, date: input.date ? secDate(input.date) : undefined };
}
}