edgar-cli 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +0,0 @@
1
- import { CommandContext, CommandResult } from '../core/runtime.js';
2
- export declare function runFactsGet(params: {
3
- id: string;
4
- taxonomy?: 'us-gaap' | 'dei';
5
- concept?: string;
6
- unit?: string;
7
- latest?: boolean;
8
- }, context: CommandContext): Promise<CommandResult>;
@@ -1,125 +0,0 @@
1
- import { CLIError, ErrorCode } from '../core/errors.js';
2
- import { companyFactsUrl } from '../sec/endpoints.js';
3
- import { resolveEntity } from '../sec/ticker-map.js';
4
- function buildConceptSummary(taxonomyFacts) {
5
- return Object.entries(taxonomyFacts)
6
- .map(([concept, payload]) => {
7
- const units = Object.keys(payload.units ?? {});
8
- return {
9
- concept,
10
- label: payload.label ?? null,
11
- unit_count: units.length,
12
- units
13
- };
14
- })
15
- .sort((a, b) => a.concept.localeCompare(b.concept));
16
- }
17
- function pickLatest(points) {
18
- if (points.length === 0) {
19
- return null;
20
- }
21
- const sorted = [...points].sort((a, b) => {
22
- const aKey = a.filed ?? a.end ?? '';
23
- const bKey = b.filed ?? b.end ?? '';
24
- return bKey.localeCompare(aKey);
25
- });
26
- return sorted[0] ?? null;
27
- }
28
- function selectTaxonomy(allFacts, concept, taxonomy) {
29
- if (taxonomy) {
30
- if (!allFacts[taxonomy]) {
31
- throw new CLIError(ErrorCode.NOT_FOUND, `Taxonomy ${taxonomy} not found`);
32
- }
33
- return taxonomy;
34
- }
35
- const preferred = ['us-gaap', 'dei'];
36
- for (const tax of preferred) {
37
- if (allFacts[tax]?.[concept]) {
38
- return tax;
39
- }
40
- }
41
- const anyTaxonomy = Object.keys(allFacts).find((tax) => Boolean(allFacts[tax]?.[concept]));
42
- if (anyTaxonomy) {
43
- return anyTaxonomy;
44
- }
45
- throw new CLIError(ErrorCode.NOT_FOUND, `Concept ${concept} not found in company facts`);
46
- }
47
- export async function runFactsGet(params, context) {
48
- const entity = await resolveEntity(params.id, context.secClient, { strictMapMatch: false });
49
- const payload = await context.secClient.fetchSecJson(companyFactsUrl(entity.cik));
50
- const allFacts = payload.facts ?? {};
51
- if (!params.concept) {
52
- if (params.taxonomy) {
53
- const taxonomyFacts = allFacts[params.taxonomy];
54
- if (!taxonomyFacts) {
55
- throw new CLIError(ErrorCode.NOT_FOUND, `Taxonomy ${params.taxonomy} not found`);
56
- }
57
- return {
58
- data: {
59
- cik: entity.cik,
60
- entityName: payload.entityName,
61
- taxonomy: params.taxonomy,
62
- concept_count: Object.keys(taxonomyFacts).length,
63
- concepts: buildConceptSummary(taxonomyFacts)
64
- }
65
- };
66
- }
67
- const taxonomySummary = Object.fromEntries(Object.entries(allFacts).map(([taxonomy, taxonomyFacts]) => [
68
- taxonomy,
69
- {
70
- concept_count: Object.keys(taxonomyFacts).length
71
- }
72
- ]));
73
- return {
74
- data: {
75
- cik: entity.cik,
76
- entityName: payload.entityName,
77
- taxonomies: taxonomySummary
78
- }
79
- };
80
- }
81
- const concept = params.concept;
82
- const taxonomy = selectTaxonomy(allFacts, concept, params.taxonomy);
83
- const conceptData = allFacts[taxonomy]?.[concept];
84
- if (!conceptData) {
85
- throw new CLIError(ErrorCode.NOT_FOUND, `Concept ${concept} not found in taxonomy ${taxonomy}`);
86
- }
87
- const rawUnits = conceptData.units ?? {};
88
- let selectedUnits;
89
- if (params.unit) {
90
- if (!rawUnits[params.unit]) {
91
- throw new CLIError(ErrorCode.NOT_FOUND, `Unit ${params.unit} not found for ${taxonomy}:${concept}`);
92
- }
93
- selectedUnits = {
94
- [params.unit]: rawUnits[params.unit]
95
- };
96
- }
97
- else {
98
- selectedUnits = rawUnits;
99
- }
100
- if (params.latest) {
101
- const latestByUnit = Object.fromEntries(Object.entries(selectedUnits).map(([unitName, points]) => [unitName, pickLatest(points)]));
102
- return {
103
- data: {
104
- cik: entity.cik,
105
- entityName: payload.entityName,
106
- taxonomy,
107
- concept,
108
- label: conceptData.label ?? null,
109
- description: conceptData.description ?? null,
110
- latest: latestByUnit
111
- }
112
- };
113
- }
114
- return {
115
- data: {
116
- cik: entity.cik,
117
- entityName: payload.entityName,
118
- taxonomy,
119
- concept,
120
- label: conceptData.label ?? null,
121
- description: conceptData.description ?? null,
122
- units: selectedUnits
123
- }
124
- };
125
- }
@@ -1,14 +0,0 @@
1
- import { CommandContext, CommandResult } from '../core/runtime.js';
2
- export declare function runFilingsList(params: {
3
- id: string;
4
- form?: string;
5
- from?: string;
6
- to?: string;
7
- queryLimit?: number;
8
- offset?: number;
9
- }, context: CommandContext): Promise<CommandResult>;
10
- export declare function runFilingsGet(params: {
11
- id: string;
12
- accession: string;
13
- format: 'url' | 'html' | 'text' | 'markdown';
14
- }, context: CommandContext): Promise<CommandResult>;
@@ -1,198 +0,0 @@
1
- import TurndownService from 'turndown';
2
- import { gfm } from '@joplin/turndown-plugin-gfm';
3
- import { CLIError, ErrorCode } from '../core/errors.js';
4
- import { filingDocumentUrl, submissionsUrl } from '../sec/endpoints.js';
5
- import { dateInRange, normalizeAccession } from '../sec/normalizers.js';
6
- import { resolveEntity } from '../sec/ticker-map.js';
7
- function buildMarkdownConverter() {
8
- const service = new TurndownService({
9
- headingStyle: 'atx',
10
- hr: '---',
11
- bulletListMarker: '-',
12
- codeBlockStyle: 'fenced',
13
- fence: '```',
14
- emDelimiter: '*',
15
- strongDelimiter: '**',
16
- linkStyle: 'inlined'
17
- });
18
- service.use(gfm);
19
- service.remove(['script', 'style', 'noscript', 'iframe', 'canvas']);
20
- return service;
21
- }
22
- const markdownConverter = buildMarkdownConverter();
23
- function stripInlineXbrlHeaders(content) {
24
- return content
25
- .replace(/<ix:header[\s\S]*?<\/ix:header>/gi, '')
26
- .replace(/<ix:hidden[\s\S]*?<\/ix:hidden>/gi, '')
27
- .replace(/<ix:resources[\s\S]*?<\/ix:resources>/gi, '');
28
- }
29
- function splitMarkdownTableCells(line) {
30
- const trimmed = line.trim();
31
- const withoutLeadingPipe = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed;
32
- const withoutTrailingPipe = withoutLeadingPipe.endsWith('|')
33
- ? withoutLeadingPipe.slice(0, -1)
34
- : withoutLeadingPipe;
35
- return withoutTrailingPipe.split('|').map((cell) => cell.trim());
36
- }
37
- function isMarkdownTableSeparatorLine(line) {
38
- const cells = splitMarkdownTableCells(line);
39
- if (cells.length === 0) {
40
- return false;
41
- }
42
- return cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s+/g, '')));
43
- }
44
- function collapseLayoutTables(markdown) {
45
- const lines = markdown.split('\n');
46
- const output = [];
47
- for (let idx = 0; idx < lines.length; idx += 1) {
48
- const line = lines[idx];
49
- if (!line.trimStart().startsWith('|')) {
50
- output.push(line);
51
- continue;
52
- }
53
- const tableBlock = [line];
54
- while (idx + 1 < lines.length && lines[idx + 1].trimStart().startsWith('|')) {
55
- idx += 1;
56
- tableBlock.push(lines[idx]);
57
- }
58
- const hasSeparator = tableBlock.some(isMarkdownTableSeparatorLine);
59
- if (!hasSeparator) {
60
- output.push(...tableBlock);
61
- continue;
62
- }
63
- const dataRows = tableBlock.filter((row) => !isMarkdownTableSeparatorLine(row));
64
- const nonEmptyCellCounts = dataRows.map((row) => splitMarkdownTableCells(row).filter((cell) => cell.length > 0).length);
65
- const maxNonEmptyCells = Math.max(...nonEmptyCellCounts, 0);
66
- const avgNonEmptyCells = nonEmptyCellCounts.reduce((sum, count) => sum + count, 0) /
67
- Math.max(nonEmptyCellCounts.length, 1);
68
- const isLayoutTable = maxNonEmptyCells <= 1 || avgNonEmptyCells <= 1.2;
69
- if (!isLayoutTable) {
70
- output.push(...tableBlock);
71
- continue;
72
- }
73
- const flattenedRows = dataRows
74
- .map((row) => splitMarkdownTableCells(row).filter((cell) => cell.length > 0).join(' '))
75
- .map((row) => row.replace(/\s+/g, ' ').trim())
76
- .filter((row) => row.length > 0);
77
- if (flattenedRows.length > 0) {
78
- output.push(...flattenedRows, '');
79
- }
80
- }
81
- return output.join('\n').replace(/\n{3,}/g, '\n\n').trim();
82
- }
83
- function zipRecentFilings(cik, recent) {
84
- if (!recent) {
85
- return [];
86
- }
87
- const accessionNumbers = recent.accessionNumber ?? [];
88
- const forms = recent.form ?? [];
89
- const filingDates = recent.filingDate ?? [];
90
- const reportDates = recent.reportDate ?? [];
91
- const primaryDocuments = recent.primaryDocument ?? [];
92
- const rowCount = accessionNumbers.length;
93
- const rows = [];
94
- for (let idx = 0; idx < rowCount; idx += 1) {
95
- const accession = accessionNumbers[idx];
96
- if (!accession) {
97
- continue;
98
- }
99
- const primaryDocument = primaryDocuments[idx] ?? null;
100
- const filingUrl = primaryDocument && primaryDocument.length > 0
101
- ? filingDocumentUrl({
102
- cik,
103
- accession,
104
- primaryDocument
105
- })
106
- : null;
107
- rows.push({
108
- accession,
109
- form: forms[idx] ?? null,
110
- filingDate: filingDates[idx] ?? null,
111
- reportDate: reportDates[idx] ?? null,
112
- primaryDocument,
113
- filingUrl
114
- });
115
- }
116
- return rows;
117
- }
118
- function extractMarkdownFromHtml(content) {
119
- const sanitizedHtml = stripInlineXbrlHeaders(content);
120
- const markdown = markdownConverter
121
- .turndown(sanitizedHtml)
122
- .replace(/\u00a0/g, ' ')
123
- .replace(/\r/g, '')
124
- .replace(/[ \t]+\n/g, '\n')
125
- .replace(/\n[ \t]+/g, '\n')
126
- .replace(/\n{3,}/g, '\n\n')
127
- .trim();
128
- return collapseLayoutTables(markdown);
129
- }
130
- export async function runFilingsList(params, context) {
131
- const entity = await resolveEntity(params.id, context.secClient, { strictMapMatch: false });
132
- const submissions = await context.secClient.fetchSecJson(submissionsUrl(entity.cik));
133
- const rows = zipRecentFilings(entity.cik, submissions.filings?.recent);
134
- const normalizedForm = params.form?.toUpperCase();
135
- const filteredRows = rows.filter((row) => {
136
- if (normalizedForm && (row.form ?? '').toUpperCase() !== normalizedForm) {
137
- return false;
138
- }
139
- if (!row.filingDate) {
140
- return !params.from && !params.to;
141
- }
142
- return dateInRange(row.filingDate, params.from, params.to);
143
- });
144
- const offset = params.offset ?? 0;
145
- const queryLimit = params.queryLimit ?? filteredRows.length;
146
- const pagedRows = filteredRows.slice(offset, offset + queryLimit);
147
- return {
148
- data: pagedRows,
149
- metaUpdates: {
150
- query_total_count: filteredRows.length,
151
- query_returned_count: pagedRows.length,
152
- query_truncated: offset + pagedRows.length < filteredRows.length,
153
- query_offset: offset
154
- }
155
- };
156
- }
157
- export async function runFilingsGet(params, context) {
158
- const accession = normalizeAccession(params.accession);
159
- const entity = await resolveEntity(params.id, context.secClient, { strictMapMatch: false });
160
- const submissions = await context.secClient.fetchSecJson(submissionsUrl(entity.cik));
161
- const rows = zipRecentFilings(entity.cik, submissions.filings?.recent);
162
- const match = rows.find((row) => row.accession === accession);
163
- if (!match) {
164
- throw new CLIError(ErrorCode.NOT_FOUND, `Accession ${accession} not found in recent submissions for ${params.id}`);
165
- }
166
- if (!match.primaryDocument || !match.filingUrl) {
167
- throw new CLIError(ErrorCode.NOT_FOUND, `No primary document found for accession ${accession}`);
168
- }
169
- if (params.format === 'url') {
170
- return {
171
- data: {
172
- accession: match.accession,
173
- form: match.form,
174
- filingDate: match.filingDate,
175
- reportDate: match.reportDate,
176
- primaryDocument: match.primaryDocument,
177
- url: match.filingUrl
178
- }
179
- };
180
- }
181
- const content = await context.secClient.fetchSecText(match.filingUrl);
182
- if (params.format === 'html') {
183
- return {
184
- data: {
185
- accession: match.accession,
186
- url: match.filingUrl,
187
- content
188
- }
189
- };
190
- }
191
- return {
192
- data: {
193
- accession: match.accession,
194
- url: match.filingUrl,
195
- content: extractMarkdownFromHtml(content)
196
- }
197
- };
198
- }
@@ -1,28 +0,0 @@
1
- import { CommandContext, CommandResult } from '../core/runtime.js';
2
- type ResearchProfile = 'core' | 'events' | 'financials';
3
- export declare function parseResearchProfile(value: string): ResearchProfile;
4
- export declare function runResearchSync(params: {
5
- id: string;
6
- profile: ResearchProfile;
7
- cacheDir?: string;
8
- refresh?: boolean;
9
- }, context: CommandContext): Promise<CommandResult>;
10
- export declare function runResearchAsk(params: {
11
- query: string;
12
- docs: string[];
13
- manifestPath?: string;
14
- topK: number;
15
- chunkLines: number;
16
- chunkOverlap: number;
17
- }, context: CommandContext): Promise<CommandResult>;
18
- export declare function runResearchAskById(params: {
19
- id: string;
20
- query: string;
21
- profile: ResearchProfile;
22
- cacheDir?: string;
23
- refresh?: boolean;
24
- topK: number;
25
- chunkLines: number;
26
- chunkOverlap: number;
27
- }, context: CommandContext): Promise<CommandResult>;
28
- export {};