Skip to content

feat: add BootstrapSubmissions command and task for processing SEC submissions - #62

Merged
sroussey merged 7 commits into
mainfrom
bootstrap
Mar 3, 2026
Merged

feat: add BootstrapSubmissions command and task for processing SEC submissions#62
sroussey merged 7 commits into
mainfrom
bootstrap

Conversation

@sroussey

@sroussey sroussey commented Mar 2, 2026

Copy link
Copy Markdown
Contributor
  • Introduced a new command bootstrap-submissions to bootstrap submissions from pre-downloaded files in the SEC_RAW_DATA_FOLDER.
  • Created BootstrapSubmissionsTask to handle the logic for reading CIK files, filtering unprocessed submissions, and executing fetch and store operations.
  • Updated command index to include the new BootstrapSubmissions command.

…bmissions

- Introduced a new command `bootstrap-submissions` to bootstrap submissions from pre-downloaded files in the SEC_RAW_DATA_FOLDER.
- Created `BootstrapSubmissionsTask` to handle the logic for reading CIK files, filtering unprocessed submissions, and executing fetch and store operations.
- Updated command index to include the new BootstrapSubmissions command.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CLI entrypoint to bootstrap SEC “company submissions” from already-downloaded JSON files under SEC_RAW_DATA_FOLDER/submissions, processing only CIKs that have not yet been recorded in the processed-submissions table.

Changes:

  • Introduces BootstrapSubmissionsTask to scan the local submissions cache, filter out already-processed CIKs, and run fetch+store per remaining CIK.
  • Adds a new bootstrap-submissions CLI command wiring to run the task.
  • Registers the new command in the commands index.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/task/submissions/BootstrapSubmissionsTask.ts New task that enumerates cached CIK submission files and processes unprocessed ones via the existing fetch/store pipeline.
src/commands/BootstrapSubmissions.ts New commander CLI command that runs the bootstrap task.
src/commands/index.ts Adds the new command to the CLI command registry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +75 to +78
await wf.run({
cik: unprocessedCiks,
date: unprocessedCiks.map(() => ""),
});

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wf.run passes date: "" for each CIK. FetchSubmissionsTask uses TypeOptionalSecDate which validates the sec-date format when the field is present, so an explicit empty string can fail schema validation before the task executes. Since date is optional here, omit it from the map inputs (or pass undefined/make it optional in the map item type) so bootstrapping doesn't inject an invalid date value.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +95
async function fetchAndStoreSubmission(
input: { cik: number; date: string },
ctx: IExecuteContext
): Promise<{ success: boolean }> {
const pipeline = ctx.own(pipe([new FetchSubmissionsTask(), new StoreSubmissionsTask()]));
try {
await pipeline.run(input);
} catch (e) {
await processUpdateProcessing(input.cik, false);
}
return { success: true };

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchAndStoreSubmission is effectively a copy of the helper in UpdateAllSubmissionsTask. Consider extracting this into a shared helper (e.g., in src/task/submissions/), or reusing an exported function, to avoid the two implementations drifting (concurrency, error handling, processing updates) over time.

Copilot uses AI. Check for mistakes.
@sroussey

sroussey commented Mar 2, 2026

Copy link
Copy Markdown
Contributor Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

@sroussey I've opened a new pull request, #63, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits March 2, 2026 18:50
…empty date in BootstrapSubmissionsTask (#63)

* Initial plan

* fix: extract shared fetchAndStoreSubmission helper and fix invalid empty date in BootstrapSubmissionsTask

Co-authored-by: sroussey <127349+sroussey@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sroussey <127349+sroussey@users.noreply.github.com>
Add BootstrapDownload command to download and extract SEC bulk ZIP archives
(submissions.zip, companyfacts.zip) and BootstrapCompanyFacts command to
process extracted company facts files into the database. Extract shared
fetchAndStoreCompanyFacts helper and refactor UpdateAllCompanyFactsTask to
use it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ackage.json

- Bumped versions of all @workglow/* packages to 0.0.109 for consistency across the project.
- Updated task cleanup methods in tests to be asynchronous for improved reliability.
- Added exit process call after stopping task queues in sec.ts for graceful shutdown.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const processedFactsRepo = globalServiceRegistry.get(PROCESSED_FACTS_REPOSITORY_TOKEN);
await processedFactsRepo.put({
cik: input.cik,
last_processed: todayYYYYdMMdDD(),

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the catch block last_processed is always set to todayYYYYdMMdDD(), even when a date was provided on input. On the success path, StoreCompanyFactsTask currently records last_processed as input.date (when present), so the meaning of last_processed differs depending on success vs failure and can change update/skip behavior. Recommend making the value consistent (e.g., input.date ?? today...) for both paths.

Suggested change
last_processed: todayYYYYdMMdDD(),
last_processed: input.date ?? todayYYYYdMMdDD(),

Copilot uses AI. Check for mistakes.
loop.pipe(fetchAndStoreCompanyFacts);
loop.endMap();
await wf.run({
cik: unprocessedCiks,

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BootstrapCompanyFactsTask runs the map with only cik values (no date). Because StoreCompanyFactsTask only records processed facts when input.date is present, bootstrapped companies may never be marked as processed. Either pass a date here (e.g., today) or adjust the store/processing-record logic so bootstraps also persist processed status.

Suggested change
cik: unprocessedCiks,
cik: unprocessedCiks,
date: new Date().toISOString().slice(0, 10),

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +55
const rawDataFolder = globalServiceRegistry.get(SEC_RAW_DATA_FOLDER);
const targetDir = resolve(rawDataFolder, input.targetFolder);
mkdirSync(targetDir, { recursive: true });

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BootstrapDownloadTask resolves targetDir from user-provided input.targetFolder without validating it. If this task is ever called with untrusted input, values like ../... or an absolute path could write/extract outside SEC_RAW_DATA_FOLDER. Consider validating targetFolder (no path separators / no .. / not absolute) and/or asserting that the resolved path starts with rawDataFolder before proceeding.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +84
const proc = Bun.spawn(["unzip", "-o", zipPath, "-d", targetDir], {
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;

if (exitCode !== 0) {
throw new Error(`unzip exited with code ${exitCode}`);
}

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This task shells out to the external unzip binary. On environments where unzip isn't installed (or behaves differently), bootstrap-download will fail even though Bun/Node are present. Consider extracting with a JS ZIP library (or Bun APIs if available), or at least detect missing unzip and throw a clearer error with remediation steps.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/BootstrapDownload.ts Outdated
Comment on lines +28 to +29
console.error(`Invalid type "${type}". Must be submissions, companyfacts, or all.`);
process.exit(1);

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using process.exit(1) inside the commander action prevents the normal CLI shutdown path in src/sec.ts (e.g., stopQueues()) from running. Prefer throwing an error (so commander prints usage) or setting process.exitCode = 1 and returning, so the top-level cleanup still happens.

Suggested change
console.error(`Invalid type "${type}". Must be submissions, companyfacts, or all.`);
process.exit(1);
throw new Error(`Invalid type "${type}". Must be submissions, companyfacts, or all.`);

Copilot uses AI. Check for mistakes.
Comment thread src/sec.ts Outdated

getTaskQueueRegistry().stopQueues();
await getTaskQueueRegistry().stopQueues();
process.exit(0);

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling process.exit(0) forces an immediate shutdown and can skip pending async cleanup (stdout/stderr flush, telemetry, etc.) even after stopQueues() resolves. If the goal is just to ensure the process terminates, consider setting process.exitCode = 0 (or omitting this entirely) and letting the event loop drain, unless there's a known hang that requires a hard exit.

Suggested change
process.exit(0);
process.exitCode = 0;

Copilot uses AI. Check for mistakes.
Comment thread src/commands/BootstrapSubmissions.ts Outdated
@@ -0,0 +1,23 @@
/**
* @license
* Copyright 2025 Steven Roussey <sroussey@gmail.com>

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The license header here says Copyright 2025, while the other newly added bootstrap commands/tasks in this PR use 2026. If these files are all being introduced together, consider aligning the year for consistency.

Suggested change
* Copyright 2025 Steven Roussey <sroussey@gmail.com>
* Copyright 2026 Steven Roussey <sroussey@gmail.com>

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +26
try {
await pipeline.run(input);
} catch (e) {
const processedFactsRepo = globalServiceRegistry.get(PROCESSED_FACTS_REPOSITORY_TOKEN);
await processedFactsRepo.put({
cik: input.cik,
last_processed: todayYYYYdMMdDD(),
success: false,

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchAndStoreCompanyFacts only writes to PROCESSED_FACTS_REPOSITORY_TOKEN on the error path. When this function is used by BootstrapCompanyFactsTask, inputs are passed without a date, so the success path relies on StoreCompanyFactsTask (which only updates the processed repo when input.date is set). Result: successfully bootstrapped CIKs never get marked processed (so they re-run every time), while failed CIKs do get marked (and will be skipped on subsequent bootstraps). Consider recording processed status in a finally (success + failure), using last_processed: input.date ?? todayYYYYdMMdDD() (or updating StoreCompanyFactsTask to always persist processed status).

Suggested change
try {
await pipeline.run(input);
} catch (e) {
const processedFactsRepo = globalServiceRegistry.get(PROCESSED_FACTS_REPOSITORY_TOKEN);
await processedFactsRepo.put({
cik: input.cik,
last_processed: todayYYYYdMMdDD(),
success: false,
let success = false;
try {
await pipeline.run(input);
success = true;
} catch (e) {
success = false;
} finally {
const processedFactsRepo = globalServiceRegistry.get(PROCESSED_FACTS_REPOSITORY_TOKEN);
await processedFactsRepo.put({
cik: input.cik,
last_processed: input.date ?? todayYYYYdMMdDD(),
success,

Copilot uses AI. Check for mistakes.
@sroussey

sroussey commented Mar 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

@sroussey I've opened a new pull request, #64, to work on those changes. Once the pull request is ready, I'll request review from you.

@sroussey
sroussey merged commit 3be1835 into main Mar 3, 2026
1 of 2 checks passed
@sroussey
sroussey deleted the bootstrap branch March 3, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants