spb/company-atlas
Public
1/**2 * TypeScript mirror of `docs/API.md` (contract v1). Keep in sync with the FastAPI routers; never widen a shape here to3 * paper over an API gap — document it in `docs/FRONTEND.md` instead.4 */56export type Num = number | string | null;78export type Metric =9 | 'activity_score'10 | 'hiring_momentum_7d'11 | 'hiring_momentum_30d'12 | 'hiring_momentum_90d'13 | 'open_jobs'14 | 'ai_adoption'15 | 'product_velocity'16 | 'geo_expansion'17 | 'developer_momentum'18 | 'communication_activity'19 | 'pricing_activity'20 | 'leadership_activity'21 | 'corporate_change_index'22 | 'anomaly_score'23 | 'historical_coverage';2425export type ConfidenceLabel = 'VERIFIED' | 'HIGH_CONFIDENCE' | 'LIKELY' | 'INFERRED' | 'LOW_CONFIDENCE';2627export interface Page<T> {28 items: T[];29 page: number;30 per_page: number;31 total: number;32 pages: number;33 meta?: Record<string, unknown>;34}3536export interface CompanyRef {37 id: string;38 slug: string;39 display_name: string;40 canonical_domain: string;41 country: string | null;42 logo_url: string | null;43}4445export interface CompanyCounts {46 sensors: number;47 observations: number;48 changes: number;49 events: number;50 jobs_open: number;51}5253export interface CompanyCard {54 id: string;55 slug: string;56 display_name: string;57 legal_name: string | null;58 canonical_domain: string;59 website: string;60 description: string | null;61 industries: string[];62 industry_primary: string | null;63 country: string | null;64 hq_city: string | null;65 hq_region: string | null;66 public_company: boolean;67 ticker: string | null;68 exchange: string | null;69 founded_year: number | null;70 employees_band: string | null;71 logo_url: string | null;72 status: string;73 onboarding_status: string;74 importance: number;75 tier: 1 | 2 | 3 | 4;76 metrics: Partial<Record<Metric, number>>;77 counts: CompanyCounts;78 last_event_at: string | null;79 last_observed_at: string | null;80 sparkline?: number[];81}8283export interface MetricDetail {84 metric: Metric | string;85 value: number;86 confidence: number;87 computed_at: string;88 inputs: Record<string, unknown>;89 formula_version?: string;90}9192export interface Relationship {93 kind: string;94 company: { slug: string; display_name: string } | null;95 to_name: string | null;96 valid_from: string | null;97 valid_to: string | null;98 confidence: number;99}100101export interface CompanyDetail extends CompanyCard {102 aliases: string[];103 domains: { domain: string; kind: string }[];104 relationships: Relationship[];105 metrics_detail: MetricDetail[];106 sensors_by_surface: Record<string, number>;107 coverage: { historical_coverage: number | null; first_observed_at: string | null; days_observed: number; sensor_uptime: number | null };108 signals: Signal[];109 sparklines: { activity_30d: number[]; hiring_90d: number[] };110}111112export interface EventSource {113 source_url: string;114 surface: string | null;115 detected_at: string;116 kind: string;117 sensor_id: string | null;118}119120export type EventOrigin = 'deterministic' | 'llm' | 'hybrid' | 'backfill';121export type EventStatus = 'active' | 'retracted' | 'duplicate' | 'review';122123export interface Event {124 id: string;125 company: CompanyRef;126 event_type: string;127 event_subtype: string;128 importance: number;129 confidence: number;130 confidence_label: ConfidenceLabel | string;131 title: string;132 summary: string | null;133 old_value: string | null;134 new_value: string | null;135 payload: Record<string, unknown>;136 entities: Record<string, unknown>;137 tags: string[];138 detected_at: string;139 effective_at: string | null;140 published_at: string | null;141 source_url: string | null;142 surface: string | null;143 sensor_id: string | null;144 change_id: string | null;145 cluster_id: string | null;146 origin: EventOrigin;147 model_name: string | null;148 prompt_version: string | null;149 status: EventStatus;150 sources?: EventSource[];151}152153export interface EventDetail extends Event {154 sources: EventSource[];155 change: Change | null;156}157158export type SensorTier = 'A' | 'B' | 'C' | 'D' | 'E';159160export interface Sensor {161 id: string;162 company_id: string;163 surface: string;164 connector_id: string;165 url: string;166 canonical_url: string;167 domain: string;168 status: string;169 tier: SensorTier;170 quality_score: number;171 discovery_confidence: number;172 discovery_method: string | null;173 current_interval_s: number;174 next_run_at: string;175 last_run_at: string | null;176 last_success_at: string | null;177 last_change_at: string | null;178 last_status: number | null;179 last_failure_class: string | null;180 consecutive_failures: number;181 observation_count: number;182 snapshot_count: number;183 change_count: number;184 meaningful_change_count: number;185 event_count: number;186 created_at: string;187}188189export interface SensorDetail extends Sensor {190 company: CompanyRef;191 latest_snapshot: Snapshot | null;192}193194export interface Snapshot {195 id: string;196 sensor_id: string;197 version_no: number;198 fetched_at: string;199 title: string | null;200 language: string | null;201 text_length: number | null;202 block_count: number | null;203 extracted_summary: Record<string, number>;204 content_hash: string;205 previous_snapshot_id: string | null;206}207208export interface Block {209 key: string;210 kind: string;211 path: string;212 text: string;213}214215export interface SnapshotDetail extends Snapshot {216 text: string;217 blocks: Block[];218 extracted: Record<string, unknown>;219}220221export interface BlockDelta {222 key: string;223 kind: string;224 path: string;225 before: string | null;226 after: string | null;227 weight: number;228 similarity: number | null;229}230231export interface DiffPayload {232 added: BlockDelta[];233 removed: BlockDelta[];234 modified: BlockDelta[];235 moved: string[];236 counts: Record<string, number>;237 text_delta_ratio: number;238 similarity: number;239 reasons: string[];240}241242export interface Change {243 id: string;244 sensor_id: string;245 surface: string;246 company_id: string;247 detected_at: string;248 significance: number;249 kind: string;250 blocks_added: number;251 blocks_removed: number;252 blocks_modified: number;253 text_delta_ratio: number;254 similarity: number | null;255 snapshot_before: string | null;256 snapshot_after: string;257 diff?: DiffPayload;258 structured_delta?: Record<string, unknown>;259}260261export interface ChangeDetail extends Change {262 diff: DiffPayload;263 structured_delta: Record<string, unknown>;264 events: Event[];265 company?: CompanyRef;266}267268export interface SnapshotDiff {269 before: Snapshot;270 after: Snapshot;271 diff: DiffPayload;272}273274export interface Job {275 id: string;276 title: string;277 department: string | null;278 location_text: string | null;279 city: string | null;280 country: string | null;281 remote: boolean | null;282 employment_type: string | null;283 seniority: string | null;284 url: string | null;285 posted_at: string | null;286 first_seen_at: string;287 last_seen_at: string;288 removed_at: string | null;289 status: 'open' | 'no_longer_listed';290 is_ai: boolean;291}292293export interface JobsSummary {294 open: number;295 new_7d: number;296 removed_7d: number;297 ai_open: number;298 by_country: { country: string; n: number }[];299 by_department: { department: string; n: number }[];300 remote_ratio: number | null;301}302303export interface JobsPage extends Page<Job> {304 meta?: { summary?: JobsSummary } & Record<string, unknown>;305 summary?: JobsSummary;306}307308export interface Person {309 id: string;310 name: string;311 title: string | null;312 role_category: string | null;313 is_executive: boolean;314 first_seen_at: string;315 last_seen_at: string;316 removed_at: string | null;317 status: string;318 source_url: string | null;319}320321export interface Product {322 id: string;323 name: string;324 category: string | null;325 description: string | null;326 url: string | null;327 first_seen_at: string;328 last_seen_at: string;329 removed_at: string | null;330 status: string;331}332333export interface Plan {334 id: string;335 plan_name: string;336 price: number | null;337 price_text: string | null;338 currency: string | null;339 billing_period: string | null;340 unit: string | null;341 features: string[];342 contact_sales: boolean;343 version_no: number;344 valid_from: string;345 valid_to: string | null;346 status: string;347 source_url: string | null;348}349350export interface Location {351 id: string;352 kind: string;353 name: string | null;354 city: string | null;355 region: string | null;356 country: string | null;357 lat: number | null;358 lon: number | null;359 first_seen_at: string;360 last_seen_at: string;361 removed_at: string | null;362 status: string;363 source_url: string | null;364}365366export interface NewsItem {367 id: string;368 title: string;369 url: string;370 summary: string | null;371 category: string | null;372 published_at: string | null;373 first_seen_at: string;374 language: string | null;375}376377export interface MetricPoint {378 day: string;379 value: number;380 confidence: number;381}382383export interface Signal {384 id: string;385 company_id: string | null;386 scope: string;387 scope_key: string | null;388 kind: string;389 strength: number;390 confidence: number;391 title: string;392 explanation: string | null;393 evidence: Record<string, unknown>;394 window_days: number;395 detected_at: string;396 status: string;397}398399// ---------------------------------------------------------------------------------------------------------- platform400export interface Stats {401 companies: number;402 companies_active: number;403 sensors: number;404 sensors_active: number;405 observations: number;406 snapshots: number;407 changes: number;408 meaningful_changes: number;409 events: number;410 jobs_open: number;411 countries: number;412 industries: number;413 observations_today: number;414 changes_today: number;415 events_today: number;416 dataset_started_at: string | null;417 dataset_age_days: number | null;418 oldest_history_days: number | null;419 last_observation_at: string | null;420 archive: { objects: number; bytes: number };421}422423export interface GlobalDaily {424 day: string;425 companies_active: number;426 sensors_active: number;427 observations: number;428 changes: number;429 meaningful_changes: number;430 events: number;431 events_by_type: Record<string, number>;432 jobs_open: number;433 jobs_new: number;434 jobs_removed: number;435 activity_index: number | null;436}437438export interface SystemHealth {439 sensors_online: number;440 sensors_failing: number;441 observations_today: number;442 events_today: number;443 countries_covered: number;444 queue_lag_s: number | null;445 scheduler_last_tick_at: string | null;446 fetch_per_min: number | null;447 success_rate_24h: number | null;448}449450export interface IndustryRow {451 slug: string;452 name: string;453 parent_slug: string | null;454 companies: number;455 events_7d: number;456 events_30d: number;457 hiring_momentum_30d: number | null;458 activity_score: number | null;459 ai_adoption: number | null;460 top_event_types: string[];461}462463export interface IndustryDetail extends IndustryRow {464 description: string | null;465 companies_list?: CompanyCard[];466 events: Event[];467 hiring: { open: number; new_30d: number; removed_30d: number; momentum_30d: number | null };468 series: MetricPoint[];469 countries: { country: string; companies: number }[];470 trending: TrendRow[];471}472/** `/industries/{slug}` returns `companies: CompanyCard[]` while the row uses `companies: number` — see docs/FRONTEND.md. */473export type IndustryDetailRaw = Omit<IndustryDetail, 'companies' | 'companies_list'> & { companies: CompanyCard[] | number };474475export interface CountryRow {476 code: string;477 name: string;478 region: string | null;479 companies: number;480 events_7d: number;481 events_30d: number;482 hiring_momentum_30d: number | null;483 activity_score: number | null;484 industry_mix: { industry: string; companies: number }[];485 lat: number | null;486 lon: number | null;487}488489export interface CountryDetail extends CountryRow {490 companies_list?: CompanyCard[];491 events: Event[];492 movers: CompanyCard[];493 new_entrants: CompanyCard[];494 series: MetricPoint[];495 industries: IndustryRow[];496}497export type CountryDetailRaw = Omit<CountryDetail, 'companies' | 'companies_list'> & { companies: CompanyCard[] | number };498499export interface TrendRow {500 term: string;501 mentions: number;502 companies: number;503 momentum: number | null;504 series: number[];505}506507export interface MapBucket {508 lat: number;509 lon: number;510 country: string;511 city: string | null;512 companies: number;513 events_30d: number;514 jobs_open: number;515 top: { slug: string; display_name: string }[];516}517518export interface ActivityIndex {519 value: number | null;520 baseline: number;521 delta_7d: number | null;522 delta_30d: number | null;523 series: MetricPoint[];524 by_type: Record<string, number>;525 by_country: { key: string; value: number }[];526 by_industry: { key: string; value: number }[];527 formula_version: string;528}529530export interface Pulse {531 stats: Stats;532 live: Event[];533 movers: CompanyCard[];534 hiring: CompanyCard[];535 launches: Event[];536 pricing: Event[];537 ai: CompanyCard[];538 industries: IndustryRow[];539 countries: CountryRow[];540 trending: TrendRow[];541 activity_index: { value: number | null; delta_7d: number | null; series: MetricPoint[] };542 map: MapBucket[];543}544545export type RankingKind = 'most_active' | 'hiring_growth' | 'hiring_decline' | 'product_velocity' | 'ai_active' | 'geo_expansion' | 'developer_momentum' | 'pricing_changes' | 'unusual_activity';546export type RankingWindow = '24h' | '7d' | '30d' | '90d' | '1y';547548export interface RankingItem extends CompanyCard {549 rank: number;550 value: number;551 delta: number | null;552}553export interface Rankings {554 kind: RankingKind | string;555 window: RankingWindow | string;556 items: RankingItem[];557}558559export interface TimelinePayload {560 items: (Event & { day: string })[];561 days: { day: string; count: number }[];562}563564export interface CompanyMetrics {565 current: MetricDetail[];566 series: Partial<Record<Metric, MetricPoint[]>>;567}568569export interface ComparePayload {570 companies: CompanyCard[];571 metrics: Partial<Record<Metric, Record<string, number>>>;572 series: Record<string, MetricPoint[]>;573 events_30d: Record<string, Record<string, number>>;574 jobs: Record<string, { open: number; ai_open: number; new_30d: number }>;575 locations: Record<string, number>;576}577578export interface HistoryPayload {579 sensors: (Sensor & { versions: Snapshot[] })[];580}581582export interface EventTypes {583 types: { event_type: string; subtypes: { event_subtype: string; count_30d: number }[]; count_30d: number }[];584}585export interface EventSummary {586 items: { key: string; count: number; delta_pct: number | null }[];587}588589export interface SearchPayload {590 query: string;591 companies: CompanyCard[];592 events: Event[];593 industries: IndustryRow[];594 countries: CountryRow[];595 people: (Person & { company: CompanyRef })[];596 products: (Product & { company: CompanyRef })[];597 took_ms: number;598}599export interface Suggestion {600 kind: 'company' | 'industry' | 'country' | 'event_type';601 label: string;602 sublabel: string | null;603 href: string;604}605export interface AskPayload {606 interpretation: Record<string, unknown>;607 answer: string;608 companies: CompanyCard[];609 events: Event[];610 sources: string[];611}612613export interface WatchlistPayload {614 items: CompanyCard[];615 events: Event[];616}617export interface AlertCondition {618 event_types?: string[];619 min_importance?: number;620 metrics?: Partial<Record<string, { gt?: number; lt?: number }>>;621}622export interface Alert {623 id: string;624 name: string;625 company: string | null;626 condition: AlertCondition;627 channel: 'web' | 'webhook';628 target: string | null;629 created_at: string;630 status?: string;631}632export interface AlertDelivery {633 id: string;634 alert_id: string;635 alert_name?: string;636 event_id: string | null;637 event?: Event | null;638 channel: string;639 status: string;640 delivered_at: string;641}642643export interface SitemapPayload {644 items: { slug: string; updated_at: string | null }[];645 pages: number;646}647648export interface Methodology {649 metrics: { metric: string; formula_version: string; description: string; inputs: string[] }[];650 significance_bands: Record<string, [number, number]> | { label: string; min: number; max: number }[];651 event_types: string[];652 confidence_labels: Record<string, string> | (string | { label: string; min_confidence?: number | null })[];653}654655// ---------------------------------------------------------------------------------------------------------- admin656export interface AdminOverview {657 companies_by_status: Record<string, number>;658 sensors_by_status: Record<string, number>;659 sensors_by_tier: Record<string, number>;660 queue: { pending: number; running: number; dead: number; oldest_pending_s: number | null };661 llm: { pending: number; done_today: number; failed_today: number; budget_left: number | null };662 failures_24h_by_class: Record<string, number>;663 fetch_rate_1h: number | null;664 change_rate_1h: number | null;665 meaningful_rate_1h: number | null;666 storage: { objects: number; bytes: number };667 workers: { name: string; last_seen_at: string; inflight: number }[];668 cost_today: { fetch: number; browser: number; llm: number };669}670export interface AdminConnector {671 id: string;672 name: string;673 version: string;674 category: string;675 enabled: boolean;676 sensors_active: number;677 sensors_failing: number;678 success_rate_24h: number | null;679 avg_latency_ms: number | null;680 change_rate_24h: number | null;681 errors_24h: number;682 last_run_at: string | null;683}684export type AdminSensor = Sensor & { company: CompanyRef };685export interface AdminFailure {686 id: string;687 sensor_id: string | null;688 company?: CompanyRef | null;689 domain: string | null;690 failure_class: string;691 status_code: number | null;692 message: string | null;693 occurred_at: string;694 retry_at: string | null;695}696export interface AdminQueueItem {697 id: string;698 kind: string;699 status: string;700 priority: number;701 attempts: number;702 scheduled_at: string;703 started_at: string | null;704 finished_at: string | null;705 worker: string | null;706 ref: string | null;707 error: string | null;708}709export interface AdminLlmJob {710 id: string;711 kind: string;712 status: string;713 model: string | null;714 prompt_version: string | null;715 change_id: string | null;716 event_id: string | null;717 tokens_in: number | null;718 tokens_out: number | null;719 cost_estimate: number | null;720 created_at: string;721 finished_at: string | null;722 error: string | null;723}724export interface AdminReview {725 id: string;726 kind: string;727 status: string;728 subject: string;729 ref_id: string | null;730 company?: CompanyRef | null;731 reason: string | null;732 created_at: string;733 resolved_at: string | null;734 resolution: string | null;735}736export interface AdminQuality {737 coverage: { companies_active_pct: number | null; sensors_active_pct: number | null };738 freshness: { sensors_checked_24h_pct: number | null; stale: number };739 duplicate_rate: number | null;740 event_confidence_avg: number | null;741 unknown_surfaces: number;742 failed_sensors: number;743 calibration: { correct: number; duplicate: number; noise: number; misclassified: number };744}745export interface AdminCosts {746 items: { day: string; dimension: string; key: string; units: number; cost_estimate: number }[];747 per_1000_companies: number | null;748 per_million_observations: number | null;749 per_meaningful_event: number | null;750}751export type AdminCompany = CompanyCard;752