{"record":{"id":"948a2e336cbdd132","repo":"koala73/worldmonitor","slug":"provide-ticker-or-name","errorCode":null,"errorMessage":"Provide ticker or name","messagePattern":"Provide ticker or name","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/intelligence/v1/get-company-enrichment.ts","lineNumber":49,"sourceCode":"  fetchFinnhubCompanyAndEarnings,\n} from './_company-shared';\n\nconst MAX_RECENT_FILINGS = 15;\n\nexport async function getCompanyEnrichment(\n  _ctx: ServerContext,\n  req: GetCompanyEnrichmentRequest,\n): Promise<GetCompanyEnrichmentResponse> {\n  const domain = req.domain?.trim().toLowerCase();\n  const name = req.name?.trim();\n  const ticker = req.ticker?.trim();\n\n  // v1 compatibility: domain attribution remains disabled. Existing callers\n  // that still send the deprecated field keep receiving the safe empty stub.\n  if (domain && !ticker) return legacyDomainStub(domain, name);\n\n  if (!name && !ticker) {\n    throw new ValidationError([{ field: 'ticker', description: 'Provide ticker or name' }]);\n  }\n\n  const resolution = await resolveCompany({ ticker, name });\n  if (resolution.status !== 'ok') {\n    // \"not_found\" is a real, cacheable answer; \"registry_unavailable\" is a\n    // lookup failure and must not be cached as one.\n    return unresolved(ticker, name, resolution.status === 'registry_unavailable');\n  }\n  const resolved = resolution.company;\n\n  // Finnhub profile + earnings share one gate on cold miss (30/min budget).\n  const [submissions, finnhub, news] = await Promise.all([\n    fetchSecSubmissions(resolved.cik),\n    fetchFinnhubCompanyAndEarnings(resolved.ticker),\n    fetchCompanyNewsMentions(resolved.ticker, resolved.name),\n  ]);\n  const profile = finnhub.profile;\n  const earnings = finnhub.earnings;","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/intelligence/v1/get-company-enrichment.ts#L31-L67","documentation":"getCompanyEnrichment throws ValidationError (field 'ticker') when, after trimming, both req.name and req.ticker are empty. Note the v1 compatibility branch: a request with only domain (deprecated field) returns the safe empty legacyDomainStub and does NOT throw — this error strictly means 'neither ticker nor name'.","triggerScenarios":"Calling /api/intelligence/v1/get-company-enrichment with an empty body; passing whitespace-only strings for both fields; a form/UI that submits before the user enters a company; passing only the deprecated domain field expecting this error and instead getting the stub.","commonSituations":"Client-side validation missing so empty searches reach the API; dynamic query building that drops both identifiers after a filter step; scripts iterating a list where some rows have neither ticker nor name.","solutions":["Require ticker or name in the client before calling","Prefer ticker when available — resolution by ticker is the most reliable path","Trim inputs client-side and skip the call entirely when both are empty rather than relying on the 400"],"exampleFix":"// before\nconst enriched = await getCompanyEnrichment({ ticker: form.ticker }); // form.ticker may be ''\n\n// after\nconst ticker = form.ticker?.trim();\nconst name = form.name?.trim();\nif (!ticker && !name) return showInputError('Enter a ticker or company name');\nconst enriched = await getCompanyEnrichment({ ticker, name });","handlingStrategy":"validation","validationCode":"const ticker = req.ticker?.trim();\nconst name = req.name?.trim();\nif (!ticker && !name) {\n  throw new ClientError('Provide a ticker or company name');\n}","typeGuard":"function isMissingIdentifierViolation(body: unknown): boolean {\n  const v = (body as { violations?: { field?: string; description?: string }[] })?.violations;\n  return Array.isArray(v) && v.some((x) => x.field === 'ticker' && x.description === 'Provide ticker or name');\n}","tryCatchPattern":"try {\n  await getCompanyEnrichment({ ticker, name });\n} catch (e) {\n  if (e instanceof HttpError && e.status === 400 && isMissingIdentifierViolation(e.body)) {\n    return promptForCompany();\n  }\n  throw e;\n}","preventionTips":["Make ticker-or-name a required client-side rule, not a server round-trip","Trim inputs before submitting so whitespace-only values never leave the client","Note domain-only requests return an empty stub (v1 contract) — do not rely on this error for domain searches"],"tags":["intelligence","validation","missing-parameter"],"backgroundTag":"missing-required-argument","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}