Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/cli/groups/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@ describe("query CIK options", () => {
);
}
});

it("rejects an explicitly empty --sic on entities and --portal on crowdfunding", () => {
const program = new Command("sec");
addQueryCommands(program);
const query = program.commands.find((command) => command.name() === "query");
const cases: Array<[string, string]> = [
["entities", "--sic"],
["crowdfunding", "--portal"],
];
for (const [commandName, flag] of cases) {
const command = query!.commands.find((candidate) => candidate.name() === commandName);
const option = command?.options.find((o) => o.long === flag);
expect(option?.parseArg).toBeFunction();
expect(() => option!.parseArg!("", undefined)).toThrow('"" is not a valid number');
}
});
});
19 changes: 13 additions & 6 deletions src/cli/groups/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function addQueryCommands(program: Command): void {
.command("entities [search]")
.description("Search entities in the database")
.option("--cik <cik>", "Filter by CIK", parseIntOption)
.option("--sic <sic>", "Filter by SIC code")
.option("--sic <sic>", "Filter by SIC code", parseIntOption)
.option("--state <state>", "Filter by state")
.option("--limit <n>", "Limit results", parseIntOption, 25)
.option("--offset <n>", "Offset results", parseIntOption, 0)
Expand All @@ -118,7 +118,7 @@ export function addQueryCommands(program: Command): void {
defaults: {
search,
cik: options.cik as number | undefined,
sic: options.sic ? parseInt(options.sic as string, 10) : undefined,
sic: options.sic as number | undefined,
state: options.state as string | undefined,
limit,
offset,
Expand Down Expand Up @@ -222,7 +222,7 @@ export function addQueryCommands(program: Command): void {
.command("crowdfunding [search]")
.description("Search crowdfunding offerings")
.option("--cik <cik>", "Filter by CIK", parseIntOption)
.option("--portal <portal>", "Filter by portal")
.option("--portal <portal>", "Filter by portal", parseIntOption)
.option("--after <date>", "Filter after date")
.option("--before <date>", "Filter before date")
.option("--limit <n>", "Limit results", parseIntOption, 25)
Expand All @@ -238,7 +238,7 @@ export function addQueryCommands(program: Command): void {
defaults: {
search,
cik: options.cik as number | undefined,
portal: options.portal ? parseInt(options.portal as string, 10) : undefined,
portal: options.portal as number | undefined,
after: options.after as string | undefined,
before: options.before as string | undefined,
limit,
Expand Down Expand Up @@ -309,7 +309,7 @@ export function addQueryCommands(program: Command): void {
const format = validateFormat(options.format as string);
const summary = await runWorkflowCli<QueryRegASummaryTaskOutput>([
new QueryRegASummaryTask({
defaults: { cik: cik ? parseIntOption(cik) : undefined },
defaults: { cik: cik !== undefined ? parseIntOption(cik) : undefined },
}),
]);

Expand Down Expand Up @@ -358,10 +358,17 @@ export function addQueryCommands(program: Command): void {
const limit = options.limit as number;
const offset = options.offset as number;
const format = validateFormat(options.format as string);
// Require an all-digit string; parseInt would silently accept
// "123abc" as 123 and produce a plausible-but-wrong CIK. Mirrors
// the same guard on the `xbrl --cik` option below.
const trimmed = cik.trim();
if (!/^\d+$/.test(trimmed)) {
throw new Error(`Invalid CIK "${cik}". Must be a positive integer.`);
}
const result = await runWorkflowCli<QueryResult<unknown>>([
new QueryFactsTask({
defaults: {
cik: parseInt(cik, 10),
cik: Number(trimmed),
name: options.name as string | undefined,
taxonomy: options.taxonomy as string | undefined,
year: options.year as number | undefined,
Expand Down
3 changes: 2 additions & 1 deletion src/commands/accreditedPortal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,10 @@ export function registerAccreditedPortalCommands(program: Command): void {
portalId = portal.portal_id;
}
const result = await backfillFormDAttribution({ portalId });
const scope = portalId ? `portal ${portalId}` : "all portals";
console.log(
`attributed ${result.attributions} filings across ${result.filings} Form D accessions` +
(portalId ? ` (portal ${portalId}, cleared ${result.cleared} prior rows)` : "")
` (${scope}, cleared ${result.cleared} prior rows)`
);
});

Expand Down
14 changes: 13 additions & 1 deletion src/config/registerModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isAbsolute, join } from "node:path";
import type { ModelRecord, ServiceRegistry } from "workglow";
import { getGlobalModelRepository, globalServiceRegistry } from "workglow";
import { SecHftModelDefault, SecModelDefault } from "./Constants";
import { SecCliConfigurationError } from "./EnvToDI";

/**
* Provider discriminators. Mirror the constants the provider packages register
Expand Down Expand Up @@ -341,7 +342,18 @@ function ggufCacheFileName(uri: string): string {
*/
function ggufPathConfig(rawPath: string): Record<string, unknown> {
if (!isRemoteGgufUri(rawPath)) {
return { model_path: isAbsolute(rawPath) ? rawPath : join(ggufModelsDir(), rawPath) };
const resolved = isAbsolute(rawPath) ? rawPath : join(ggufModelsDir(), rawPath);
// A `gguf:` id must point at a `.gguf` weights file. Absent the guard, a
// typo (or the id shape as a whole) can silently coerce node-llama-cpp
// into loading an arbitrary file off disk — the provider takes what the
// record's `model_path` says. The remote-URI branch stays unchecked: the
// download harness always caches under a synthesized `.gguf` filename.
if (!/\.gguf$/i.test(resolved)) {
throw new SecCliConfigurationError(
`gguf:${rawPath} must resolve to a .gguf file (got ${resolved})`
);
}
return { model_path: resolved };
}
const dir = ggufModelsDir();
return {
Expand Down
10 changes: 4 additions & 6 deletions src/resolver/backfillFormDAttribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,10 @@ export async function backfillFormDAttribution(options: {
const attributionRepo = new FormDPortalAttributionRepo();
const signalRepo = new AccreditedPortalSignalRepo();

let cleared = 0;
if (options.portalId !== undefined) {
cleared = await attributionRepo.clearPortal(options.portalId);
} else {
await attributionRepo.clearAll();
}
const cleared =
options.portalId !== undefined
? await attributionRepo.clearPortal(options.portalId)
: await attributionRepo.clearAll();

const signals =
options.portalId !== undefined
Expand Down
10 changes: 7 additions & 3 deletions src/storage/accredited-portal/FormDPortalAttributionRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ export class FormDPortalAttributionRepo implements FormDPortalAttributionRepoOpt
return count;
}

/** Clears the whole table ahead of an unscoped recompute. */
async clearAll(): Promise<void> {
await this.attributionRepository.deleteAll();
/** Clears the whole table ahead of an unscoped recompute. Returns rows cleared. */
async clearAll(): Promise<number> {
const count = await this.attributionRepository.count();
if (count > 0) {
await this.attributionRepository.deleteAll();
}
return count;
}
}