sec-edgar-api
Version:
Fetch and parse SEC earnings reports and other filings. Useful for financial analysis.
247 lines (246 loc) • 15.4 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseXbrl = void 0;
var constants_1 = require("../../../util/constants");
var util_xbrl_1 = require("../../../util/util-xbrl");
var FactFiscalCalculator_1 = require("../../ReportRawBuilder/FactFiscalCalculator");
var FactPeriodResolver_1 = require("../../ReportRawBuilder/FactPeriodResolver");
var XBRLParser_1 = require("../XBRLParser/XBRLParser");
function isWithinDays(params) {
var dateA = params.dateA, dateB = params.dateB, days = params.days;
if (dateA === dateB)
return true;
var timeDiff = Math.abs(new Date(dateA).getTime() - new Date(dateB).getTime());
var daysDiff = timeDiff / (1000 * 3600 * 24);
return daysDiff < days;
}
function buildReportsFromFacts(params) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
var filing = params.filing, facts = params.facts, fiscalPeriod = params.fiscalPeriod, fiscalYear = params.fiscalYear, pathSeparator = params.pathSeparator, cikProp = params.cik;
var urlParts = (_a = filing === null || filing === void 0 ? void 0 : filing.url.split('/')) !== null && _a !== void 0 ? _a : [];
var cik = cikProp !== null && cikProp !== void 0 ? cikProp : urlParts[(_b = urlParts.indexOf('data')) !== null && _b !== void 0 ? _b : -1];
var reportFactValues = {
cik: Number(cik),
dateReport: (_c = filing === null || filing === void 0 ? void 0 : filing.reportDate) !== null && _c !== void 0 ? _c : '',
fiscalPeriod: fiscalPeriod,
fiscalYear: fiscalYear,
};
// if there is a split fact, make sure it's from the current fiscal year
var splitFact = facts.find(function (f) { var _a; return f.name.endsWith(constants_1.KEY_SPLIT) && isWithinDays({ dateA: f.filed, dateB: (_a = filing === null || filing === void 0 ? void 0 : filing.reportDate) !== null && _a !== void 0 ? _a : '', days: 90 }); });
var reportFocus = {
cik: Number(cik !== null && cik !== void 0 ? cik : reportFactValues.cik),
dateFiled: (_d = filing === null || filing === void 0 ? void 0 : filing.filingDate) !== null && _d !== void 0 ? _d : '',
dateReport: String((_e = filing === null || filing === void 0 ? void 0 : filing.reportDate) !== null && _e !== void 0 ? _e : reportFactValues.dateReport),
fiscalPeriod: (fiscalPeriod !== null && fiscalPeriod !== void 0 ? fiscalPeriod : reportFactValues.fiscalPeriod),
fiscalYear: Number(fiscalYear !== null && fiscalYear !== void 0 ? fiscalYear : reportFactValues.fiscalYear),
splitDate: (_f = splitFact === null || splitFact === void 0 ? void 0 : splitFact.end) !== null && _f !== void 0 ? _f : null,
splitRatio: (splitFact === null || splitFact === void 0 ? void 0 : splitFact.value) ? Number(splitFact.value) : null,
url: (_g = filing === null || filing === void 0 ? void 0 : filing.url) !== null && _g !== void 0 ? _g : '',
};
var reportByDateRange = {};
var factByName = new Map();
var roundPlacesByName = new Map();
var scaleByName = new Map();
var isFocusFactByDateKey = new Map();
var fiscalsByDateKey = new Map();
var focusPeriodByQuarter = {
1: 3,
2: 6,
3: 9,
4: 12,
};
for (var _i = 0, facts_1 = facts; _i < facts_1.length; _i++) {
var fact = facts_1[_i];
var period = FactPeriodResolver_1.default.getPeriod({ start: fact.start, end: fact.end });
var dateKey = fact.start ? "".concat(fact.start, "_").concat(fact.end, "_").concat(period) : "".concat(fact.end, "_").concat(period);
if (!fiscalsByDateKey.has(dateKey)) {
var fiscalPeriod_1 = (period === 12 && fact.quarter === 4 ? 'FY' : "Q".concat(fact.quarter));
fiscalsByDateKey.set(dateKey, { fiscalYear: fact.fiscalYear, fiscalPeriod: fiscalPeriod_1 });
}
var isSplitFact = fact === splitFact;
var focusPeriod = (_h = focusPeriodByQuarter[fact.quarter]) !== null && _h !== void 0 ? _h : 0;
var isFocusPeriod = period === focusPeriod || period === 0;
var isFocusFact = (_j = isFocusFactByDateKey.get(dateKey)) !== null && _j !== void 0 ? _j : (isFocusPeriod &&
(isWithinDays({ dateA: fact.end, dateB: reportFocus.dateReport, days: 45 }) || isSplitFact));
(_k = reportByDateRange[dateKey]) !== null && _k !== void 0 ? _k : (reportByDateRange[dateKey] = {
cik: reportFocus.cik,
url: (_l = filing === null || filing === void 0 ? void 0 : filing.url) !== null && _l !== void 0 ? _l : '',
splitDate: (_m = splitFact === null || splitFact === void 0 ? void 0 : splitFact.end) !== null && _m !== void 0 ? _m : null,
splitRatio: (splitFact === null || splitFact === void 0 ? void 0 : splitFact.value) ? Number(splitFact.value) : null,
dateFiled: reportFocus.dateFiled,
dateReport: fact.end,
fiscalPeriod: (_o = fiscalsByDateKey.get(dateKey)) === null || _o === void 0 ? void 0 : _o.fiscalPeriod,
startDate: (_p = fact.start) !== null && _p !== void 0 ? _p : '',
endDate: fact.end,
fiscalYear: (_q = fiscalsByDateKey.get(dateKey)) === null || _q === void 0 ? void 0 : _q.fiscalYear,
period: period,
isCurrentPeriod: isFocusFact,
});
if (isFocusFact) {
fact.isCurrentPeriod = true;
}
if (!isSplitFact) {
isFocusFactByDateKey.set(dateKey, isFocusFact);
}
var el = fact;
var scale = Number((_r = el.scale) !== null && _r !== void 0 ? _r : 0) || 0;
var decimals = Number((_s = el.decimals) !== null && _s !== void 0 ? _s : 0) || 0;
var suffix = fact.name.includes(pathSeparator)
? null
: (_t = fact === null || fact === void 0 ? void 0 : fact.segments) === null || _t === void 0 ? void 0 : _t.map(function (_a) {
var dimension = _a.dimension, value = _a.value;
return "".concat(dimension).concat(pathSeparator).concat(value);
}).join(pathSeparator);
var nameKey = suffix ? "".concat(fact.name).concat(pathSeparator).concat(suffix) : fact.name;
var roundPlaces = scale + decimals;
var prevFactKey = "".concat(nameKey, "-").concat(dateKey);
var prevFact = factByName.get(prevFactKey);
var hasValue = Boolean(fact.value);
var hadValue = Boolean(prevFact === null || prevFact === void 0 ? void 0 : prevFact.value);
if (hasValue && hadValue && fact.value !== (prevFact === null || prevFact === void 0 ? void 0 : prevFact.value)) {
var prevRounding = roundPlacesByName.get(prevFactKey);
var prevScale = scaleByName.get(prevFactKey);
var prevFact_1 = factByName.get(prevFactKey);
var prevUnit = (_v = (_u = prevFact_1 === null || prevFact_1 === void 0 ? void 0 : prevFact_1.unit) === null || _u === void 0 ? void 0 : _u.split('_').pop()) === null || _v === void 0 ? void 0 : _v.toLowerCase();
var unit = (_x = (_w = fact.unit) === null || _w === void 0 ? void 0 : _w.split('_').pop()) === null || _x === void 0 ? void 0 : _x.toLowerCase();
var shouldSkip = [
(prevUnit === null || prevUnit === void 0 ? void 0 : prevUnit.length) === 3 && (unit === null || unit === void 0 ? void 0 : unit.length) === 3 && prevUnit !== unit && prevUnit === 'usd',
(prevRounding !== null && prevRounding !== void 0 ? prevRounding : 0) < roundPlaces,
(prevScale !== null && prevScale !== void 0 ? prevScale : 0) < scale,
(prevRounding !== null && prevRounding !== void 0 ? prevRounding : 0) === roundPlaces && (prevScale !== null && prevScale !== void 0 ? prevScale : 0) === scale && prevUnit === unit,
].some(Boolean);
if (shouldSkip)
continue;
}
roundPlacesByName.set(prevFactKey, roundPlaces);
scaleByName.set(prevFactKey, scale);
factByName.set(prevFactKey, fact);
reportByDateRange[dateKey][nameKey] = fact.value;
if (isFocusFact) {
if (prevFact)
prevFact.isUsedInReport = false;
fact.isUsedInReport = true;
reportFocus[nameKey] = fact.value;
}
else {
fact.isUsedInReport = false;
}
}
return { reportFocus: reportFocus, reportByDateRange: reportByDateRange, factsFiltered: Array.from(factByName.values()) };
}
function parseXbrl(params) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
var parser = new XBRLParser_1.default();
var xml = params.xml, _r = params.includeReport, includeReport = _r === void 0 ? true : _r, options = __rest(params, ["xml", "includeReport"]);
var response = parser.parse(xml, options);
var _s = (_b = (_a = response.instance) === null || _a === void 0 ? void 0 : _a.xbrl) !== null && _b !== void 0 ? _b : {}, _t = _s.contexts, contexts = _t === void 0 ? [] : _t, _u = _s.factElements, factElements = _u === void 0 ? [] : _u;
var labelByTaxonomy = (0, util_xbrl_1.getLabelByTaxonomy)((_d = (_c = response.linkbaseLabel) === null || _c === void 0 ? void 0 : _c.xbrl) !== null && _d !== void 0 ? _d : {});
var contextById = new Map();
contexts.forEach(function (context) { return contextById.set(context.id, context); });
var cik = response.header.cik;
var accessionNumber = response.header.accessionNumber;
var accessionNumberNoHyphens = accessionNumber.replace(/-/g, '');
var fiscalCalculator = new FactFiscalCalculator_1.default({
fiscalYearEnd: {
day: Number(response.header.fiscalYearEnd.substring(2)),
month: Number(response.header.fiscalYearEnd.substring(0, 2)),
},
});
var facts = [];
for (var _i = 0, factElements_1 = factElements; _i < factElements_1.length; _i++) {
var fact = factElements_1[_i];
var context = contextById.get(fact.contextRef);
var end = (_f = (_e = context === null || context === void 0 ? void 0 : context.period.endDate) !== null && _e !== void 0 ? _e : context === null || context === void 0 ? void 0 : context.period.instant) !== null && _f !== void 0 ? _f : '';
var start = context === null || context === void 0 ? void 0 : context.period.startDate;
var _v = fiscalCalculator.getFiscalYearQuarter({ dateStr: end }), quarter = _v.quarter, year = _v.year;
var factParsed = {
cik: cik,
end: end,
filed: response.header.filingDate,
name: fact.name,
unit: (_g = fact.unitRef) !== null && _g !== void 0 ? _g : 'pure',
value: isNaN(Number(fact.text)) ? String(fact.text) : Number(fact.text),
accn: accessionNumber,
form: response.header.form,
segments: (_h = context === null || context === void 0 ? void 0 : context.entity.segment) !== null && _h !== void 0 ? _h : [],
start: start,
contextRef: fact.contextRef,
label: (_j = labelByTaxonomy[fact.name]) !== null && _j !== void 0 ? _j : fact.name,
period: FactPeriodResolver_1.default.getPeriod({ start: start, end: end }),
quarter: quarter,
fiscalYear: year,
};
if (factParsed.decimals) {
factParsed.decimals = Number(fact.decimals);
}
if (fact.scale) {
factParsed.scale = Number(fact.scale);
}
facts.push(factParsed);
}
var fiscalPeriodFact = facts.find(function (f) { return f.name === 'dei:DocumentFiscalPeriodFocus'; });
var fiscalYearFact = facts.find(function (f) { return f.name === 'dei:DocumentFiscalYearFocus'; });
var fiscalYear = fiscalYearFact ? Number(fiscalYearFact.value) : 0;
var fiscalPeriod = fiscalPeriodFact ? fiscalPeriodFact.value : 'FY';
if (!fiscalPeriodFact || !fiscalYearFact) {
var reportDate = (_k = response.header.reportDate) !== null && _k !== void 0 ? _k : (_m = (_l = response.instance) === null || _l === void 0 ? void 0 : _l.xbrl.factElements.find(function (f) { return f.name === 'dei:DocumentPeriodEndDate'; })) === null || _m === void 0 ? void 0 : _m.text;
if (!reportDate) {
throw new Error("Report date not found. Unable to determine fiscal year and period. accn: ".concat(accessionNumber));
}
var _w = fiscalCalculator.getFiscalYearQuarter({ dateStr: reportDate !== null && reportDate !== void 0 ? reportDate : '' }), quarter = _w.quarter, year = _w.year;
fiscalYear = year;
fiscalPeriod = (quarter === 4 ? 'FY' : "Q".concat(quarter));
}
var factsForBuilder = includeReport ? facts : [];
var _x = buildReportsFromFacts({
facts: factsForBuilder,
pathSeparator: '>',
fiscalPeriod: (_o = factsForBuilder.find(function (f) { return f.name === 'dei:DocumentFiscalPeriodFocus'; })) === null || _o === void 0 ? void 0 : _o.value,
fiscalYear: Number((_q = (_p = factsForBuilder.find(function (f) { return f.name === 'dei:DocumentFiscalYearFocus'; })) === null || _p === void 0 ? void 0 : _p.value) !== null && _q !== void 0 ? _q : 0),
cik: response.header.cik,
filing: {
acceptanceDateTime: response.header.acceptanceDatetime,
accessionNumber: accessionNumber,
act: response.header.act,
fileNumber: response.header.fileNumber,
filingDate: response.header.filingDate,
form: response.header.form,
filmNumber: response.header.filmNumber,
isInlineXBRL: 1,
isXBRL: 1,
items: '',
primaryDocDescription: '',
primaryDocument: '',
reportDate: response.header.reportDate,
size: 1,
url: "https://www.sec.gov/Archives/edgar/data/".concat(cik, "/").concat(accessionNumberNoHyphens, "/").concat(accessionNumber, ".txt"),
urlPrimaryDocument: '',
},
}), factsFiltered = _x.factsFiltered, reportFocus = _x.reportFocus, reportByDateRange = _x.reportByDateRange;
// Some concepts have members, but do not have a sum. add the sum to the report.
var periodReports = Object.values(reportByDateRange);
return __assign(__assign({}, response), { fiscalYear: fiscalYear, fiscalPeriod: fiscalPeriod, facts: factsFiltered, report: factsFiltered.length > 0 ? reportFocus : null, xml: xml, periodReports: periodReports });
}
exports.parseXbrl = parseXbrl;