spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
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}5253/** Where the one-paragraph description comes from — drives the attribution line under it (spec: never unattributed). */54export type DescriptionSource = 'wikipedia' | 'wikidata' | 'homepage' | 'llm';5556/** A monetary fact as stated by its source for one fiscal year — rendered compact with the year, never converted. */57export interface MoneyFact {58 value: number;59 currency: string;60 year: number;61}6263/** Per-field provenance for the enrichment profile (`field` matches a `CompanyProfile` key, e.g. `revenue`, `hq`). */64export interface ProfileSource {65 field: string;66 source: string;67 url: string | null;68 retrieved_at: string;69}7071export interface CompanySocials {72 linkedin?: string;73 x?: string;74 youtube?: string;75 facebook?: string;76 instagram?: string;77 github?: string;78 tiktok?: string;79 crunchbase?: string;80}8182/**83 * Enrichment profile (Wikidata / Wikipedia / homepage / registries). Every field may be null: the UI hides missing facts84 * rather than rendering dashes, and every rendered fact carries its source chip.85 */86export interface CompanyProfile {87 description: string | null;88 description_source: DescriptionSource | null;89 description_url: string | null;90 description_license: string | null;91 logo_url: string | null;92 icon_url: string | null;93 founded_year: number | null;94 legal_form: string | null;95 employees: number | null;96 employees_year: number | null;97 revenue: MoneyFact | null;98 net_income: MoneyFact | null;99 total_assets: MoneyFact | null;100 hq: { city: string | null; region: string | null; country: string | null; address: string | null; lat: number | null; lon: number | null };101 ticker: string | null;102 exchange: string | null;103 isin: string | null;104 lei: string | null;105 sec_cik: string | null;106 public_company: boolean;107 wikipedia_url: string | null;108 wikidata_url: string | null;109 official_website: string | null;110 phone: string | null;111 products: string[];112 industries: string[];113 industry_labels: string[];114 socials: CompanySocials;115 enriched_at: string | null;116 sources: ProfileSource[];117}118119/** Flattened, already-formatted fact with provenance (`/companies/{slug}` → `facts`). */120export interface CompanyFact {121 key: string;122 label: string;123 value: string;124 source: string;125 url: string | null;126 retrieved_at: string;127}128129export interface CompanyCard {130 id: string;131 slug: string;132 display_name: string;133 legal_name: string | null;134 canonical_domain: string;135 website: string;136 description: string | null;137 industries: string[];138 industry_primary: string | null;139 country: string | null;140 hq_city: string | null;141 hq_region: string | null;142 public_company: boolean;143 ticker: string | null;144 exchange: string | null;145 founded_year: number | null;146 employees_band: string | null;147 logo_url: string | null;148 status: string;149 onboarding_status: string;150 importance: number;151 tier: 1 | 2 | 3 | 4;152 metrics: Partial<Record<Metric, number>>;153 counts: CompanyCounts;154 last_event_at: string | null;155 last_observed_at: string | null;156 sparkline?: number[];157 /** Enrichment profile; absent on API builds that predate it and null-filled while enrichment is pending. */158 profile?: CompanyProfile | null;159}160161export interface MetricDetail {162 metric: Metric | string;163 value: number;164 confidence: number;165 computed_at: string;166 inputs: Record<string, unknown>;167 formula_version?: string;168}169170/** Corporate-structure kinds the UI groups explicitly; anything else lands under "Other relationships". */171export type RelationshipKind = 'PARENT_OF' | 'SUBSIDIARY_OF' | 'OWNED_BY' | 'OWNER_OF' | 'ACQUIRED_BY' | 'ACQUIRED' | 'COMPETITOR' | 'PARTNER' | (string & {});172173export interface Relationship {174 kind: RelationshipKind;175 /** Counterpart when it is an atlas company (links to its profile); else only `to_name` is known. */176 company: { slug: string; display_name: string; logo_url?: string | null } | null;177 to_name: string | null;178 valid_from: string | null;179 valid_to: string | null;180 confidence: number;181 provenance: { source: string; property?: string };182}183184export interface CompanyDetail extends CompanyCard {185 aliases: string[];186 domains: { domain: string; kind: string }[];187 relationships: Relationship[];188 /** Sourced key facts (flattened); complements `profile` — the UI merges both and hides nothing that is sourced. */189 facts: CompanyFact[];190 metrics_detail: MetricDetail[];191 sensors_by_surface: Record<string, number>;192 coverage: { historical_coverage: number | null; first_observed_at: string | null; days_observed: number; sensor_uptime: number | null };193 signals: Signal[];194 sparklines: { activity_30d: number[]; hiring_90d: number[] };195}196197export interface EventSource {198 source_url: string;199 surface: string | null;200 detected_at: string;201 kind: string;202 sensor_id: string | null;203}204205export type EventOrigin = 'deterministic' | 'llm' | 'hybrid' | 'backfill';206export type EventStatus = 'active' | 'retracted' | 'duplicate' | 'review';207208export interface Event {209 id: string;210 company: CompanyRef;211 event_type: string;212 event_subtype: string;213 importance: number;214 confidence: number;215 confidence_label: ConfidenceLabel | string;216 title: string;217 summary: string | null;218 old_value: string | null;219 new_value: string | null;220 payload: Record<string, unknown>;221 entities: Record<string, unknown>;222 tags: string[];223 detected_at: string;224 effective_at: string | null;225 published_at: string | null;226 source_url: string | null;227 surface: string | null;228 sensor_id: string | null;229 change_id: string | null;230 cluster_id: string | null;231 origin: EventOrigin;232 model_name: string | null;233 prompt_version: string | null;234 status: EventStatus;235 sources?: EventSource[];236}237238export interface EventDetail extends Event {239 sources: EventSource[];240 change: Change | null;241}242243export type SensorTier = 'A' | 'B' | 'C' | 'D' | 'E';244245export interface Sensor {246 id: string;247 company_id: string;248 surface: string;249 connector_id: string;250 url: string;251 canonical_url: string;252 domain: string;253 status: string;254 tier: SensorTier;255 quality_score: number;256 discovery_confidence: number;257 discovery_method: string | null;258 current_interval_s: number;259 next_run_at: string;260 last_run_at: string | null;261 last_success_at: string | null;262 last_change_at: string | null;263 last_status: number | null;264 last_failure_class: string | null;265 consecutive_failures: number;266 observation_count: number;267 snapshot_count: number;268 change_count: number;269 meaningful_change_count: number;270 event_count: number;271 created_at: string;272}273274export interface SensorDetail extends Sensor {275 company: CompanyRef;276 latest_snapshot: Snapshot | null;277}278279export interface Snapshot {280 id: string;281 sensor_id: string;282 version_no: number;283 fetched_at: string;284 title: string | null;285 language: string | null;286 text_length: number | null;287 block_count: number | null;288 extracted_summary: Record<string, number>;289 content_hash: string;290 previous_snapshot_id: string | null;291}292293export interface Block {294 key: string;295 kind: string;296 path: string;297 text: string;298}299300export interface SnapshotDetail extends Snapshot {301 text: string;302 blocks: Block[];303 extracted: Record<string, unknown>;304}305306export interface BlockDelta {307 key: string;308 kind: string;309 path: string;310 before: string | null;311 after: string | null;312 weight: number;313 similarity: number | null;314}315316export interface DiffPayload {317 added: BlockDelta[];318 removed: BlockDelta[];319 modified: BlockDelta[];320 moved: string[];321 counts: Record<string, number>;322 text_delta_ratio: number;323 similarity: number;324 reasons: string[];325}326327export interface Change {328 id: string;329 sensor_id: string;330 surface: string;331 company_id: string;332 detected_at: string;333 significance: number;334 kind: string;335 blocks_added: number;336 blocks_removed: number;337 blocks_modified: number;338 text_delta_ratio: number;339 similarity: number | null;340 snapshot_before: string | null;341 snapshot_after: string;342 diff?: DiffPayload;343 structured_delta?: Record<string, unknown>;344}345346export interface ChangeDetail extends Change {347 diff: DiffPayload;348 structured_delta: Record<string, unknown>;349 events: Event[];350 company?: CompanyRef;351}352353export interface SnapshotDiff {354 before: Snapshot;355 after: Snapshot;356 diff: DiffPayload;357}358359export interface Job {360 id: string;361 title: string;362 department: string | null;363 location_text: string | null;364 city: string | null;365 country: string | null;366 remote: boolean | null;367 employment_type: string | null;368 seniority: string | null;369 url: string | null;370 posted_at: string | null;371 first_seen_at: string;372 last_seen_at: string;373 removed_at: string | null;374 status: 'open' | 'no_longer_listed';375 is_ai: boolean;376}377378export interface JobsSummary {379 open: number;380 new_7d: number;381 removed_7d: number;382 ai_open: number;383 by_country: { country: string; n: number }[];384 by_department: { department: string; n: number }[];385 remote_ratio: number | null;386}387388export interface JobsPage extends Page<Job> {389 meta?: { summary?: JobsSummary } & Record<string, unknown>;390 summary?: JobsSummary;391}392393export interface Person {394 id: string;395 name: string;396 title: string | null;397 role_category: string | null;398 is_executive: boolean;399 first_seen_at: string;400 last_seen_at: string;401 removed_at: string | null;402 status: string;403 source_url: string | null;404 /** `page` = observed on the monitored leadership page (default when absent); `wikidata` = position statement on Wikidata. */405 source?: 'wikidata' | 'page';406}407408export interface Product {409 id: string;410 name: string;411 category: string | null;412 description: string | null;413 url: string | null;414 first_seen_at: string;415 last_seen_at: string;416 removed_at: string | null;417 status: string;418}419420export interface Plan {421 id: string;422 plan_name: string;423 price: number | null;424 price_text: string | null;425 currency: string | null;426 billing_period: string | null;427 unit: string | null;428 features: string[];429 contact_sales: boolean;430 version_no: number;431 valid_from: string;432 valid_to: string | null;433 status: string;434 source_url: string | null;435}436437export interface Location {438 id: string;439 kind: string;440 name: string | null;441 city: string | null;442 region: string | null;443 country: string | null;444 lat: number | null;445 lon: number | null;446 first_seen_at: string;447 last_seen_at: string;448 removed_at: string | null;449 status: string;450 source_url: string | null;451}452453export interface NewsItem {454 id: string;455 title: string;456 url: string;457 summary: string | null;458 category: string | null;459 published_at: string | null;460 first_seen_at: string;461 language: string | null;462}463464export interface MetricPoint {465 day: string;466 value: number;467 confidence: number;468}469470export interface Signal {471 id: string;472 company_id: string | null;473 scope: string;474 scope_key: string | null;475 kind: string;476 strength: number;477 confidence: number;478 title: string;479 explanation: string | null;480 evidence: Record<string, unknown>;481 window_days: number;482 detected_at: string;483 status: string;484}485486// ---------------------------------------------------------------------------------------------------------- platform487export interface Stats {488 companies: number;489 companies_active: number;490 sensors: number;491 sensors_active: number;492 observations: number;493 snapshots: number;494 changes: number;495 meaningful_changes: number;496 events: number;497 jobs_open: number;498 countries: number;499 industries: number;500 observations_today: number;501 changes_today: number;502 events_today: number;503 dataset_started_at: string | null;504 dataset_age_days: number | null;505 oldest_history_days: number | null;506 last_observation_at: string | null;507 archive: { objects: number; bytes: number };508}509510export interface GlobalDaily {511 day: string;512 companies_active: number;513 sensors_active: number;514 observations: number;515 changes: number;516 meaningful_changes: number;517 events: number;518 events_by_type: Record<string, number>;519 jobs_open: number;520 jobs_new: number;521 jobs_removed: number;522 activity_index: number | null;523}524525export interface SystemHealth {526 sensors_online: number;527 sensors_failing: number;528 observations_today: number;529 events_today: number;530 countries_covered: number;531 queue_lag_s: number | null;532 scheduler_last_tick_at: string | null;533 fetch_per_min: number | null;534 success_rate_24h: number | null;535}536537export interface IndustryRow {538 slug: string;539 name: string;540 parent_slug: string | null;541 companies: number;542 events_7d: number;543 events_30d: number;544 hiring_momentum_30d: number | null;545 activity_score: number | null;546 ai_adoption: number | null;547 top_event_types: string[];548}549550export interface IndustryDetail extends IndustryRow {551 description: string | null;552 companies_list?: CompanyCard[];553 events: Event[];554 hiring: { open: number; new_30d: number; removed_30d: number; momentum_30d: number | null };555 series: MetricPoint[];556 countries: { country: string; companies: number }[];557 trending: TrendRow[];558}559/** `/industries/{slug}` returns `companies: CompanyCard[]` while the row uses `companies: number` — see docs/FRONTEND.md. */560export type IndustryDetailRaw = Omit<IndustryDetail, 'companies' | 'companies_list'> & { companies: CompanyCard[] | number };561562export interface CountryRow {563 code: string;564 name: string;565 region: string | null;566 companies: number;567 events_7d: number;568 events_30d: number;569 hiring_momentum_30d: number | null;570 activity_score: number | null;571 industry_mix: { industry: string; companies: number }[];572 lat: number | null;573 lon: number | null;574}575576export interface CountryDetail extends CountryRow {577 companies_list?: CompanyCard[];578 events: Event[];579 movers: CompanyCard[];580 new_entrants: CompanyCard[];581 series: MetricPoint[];582 industries: IndustryRow[];583}584export type CountryDetailRaw = Omit<CountryDetail, 'companies' | 'companies_list'> & { companies: CompanyCard[] | number };585586export interface TrendRow {587 term: string;588 mentions: number;589 companies: number;590 momentum: number | null;591 series: number[];592}593594export interface MapBucket {595 lat: number;596 lon: number;597 country: string;598 city: string | null;599 companies: number;600 events_30d: number;601 jobs_open: number;602 top: { slug: string; display_name: string }[];603}604605export interface ActivityIndex {606 value: number | null;607 baseline: number;608 delta_7d: number | null;609 delta_30d: number | null;610 series: MetricPoint[];611 by_type: Record<string, number>;612 by_country: { key: string; value: number }[];613 by_industry: { key: string; value: number }[];614 formula_version: string;615}616617export interface Pulse {618 stats: Stats;619 live: Event[];620 movers: CompanyCard[];621 hiring: CompanyCard[];622 launches: Event[];623 pricing: Event[];624 ai: CompanyCard[];625 industries: IndustryRow[];626 countries: CountryRow[];627 trending: TrendRow[];628 activity_index: { value: number | null; delta_7d: number | null; series: MetricPoint[] };629 map: MapBucket[];630}631632export type RankingKind = 'most_active' | 'hiring_growth' | 'hiring_decline' | 'product_velocity' | 'ai_active' | 'geo_expansion' | 'developer_momentum' | 'pricing_changes' | 'unusual_activity';633export type RankingWindow = '24h' | '7d' | '30d' | '90d' | '1y';634635export interface RankingItem extends CompanyCard {636 rank: number;637 value: number;638 delta: number | null;639}640export interface Rankings {641 kind: RankingKind | string;642 window: RankingWindow | string;643 items: RankingItem[];644}645646export interface TimelinePayload {647 items: (Event & { day: string })[];648 days: { day: string; count: number }[];649}650651export interface CompanyMetrics {652 current: MetricDetail[];653 series: Partial<Record<Metric, MetricPoint[]>>;654}655656export interface ComparePayload {657 companies: CompanyCard[];658 metrics: Partial<Record<Metric, Record<string, number>>>;659 series: Record<string, MetricPoint[]>;660 events_30d: Record<string, Record<string, number>>;661 jobs: Record<string, { open: number; ai_open: number; new_30d: number }>;662 locations: Record<string, number>;663}664665export interface HistoryPayload {666 sensors: (Sensor & { versions: Snapshot[] })[];667}668669export interface EventTypes {670 types: { event_type: string; subtypes: { event_subtype: string; count_30d: number }[]; count_30d: number }[];671}672export interface EventSummary {673 items: { key: string; count: number; delta_pct: number | null }[];674}675676export interface SearchPayload {677 query: string;678 companies: CompanyCard[];679 events: Event[];680 industries: IndustryRow[];681 countries: CountryRow[];682 people: (Person & { company: CompanyRef })[];683 products: (Product & { company: CompanyRef })[];684 took_ms: number;685}686export interface Suggestion {687 kind: 'company' | 'industry' | 'country' | 'event_type';688 label: string;689 sublabel: string | null;690 href: string;691}692export interface AskPayload {693 interpretation: Record<string, unknown>;694 answer: string;695 companies: CompanyCard[];696 events: Event[];697 sources: string[];698}699700export interface WatchlistPayload {701 items: CompanyCard[];702 events: Event[];703}704export interface AlertCondition {705 event_types?: string[];706 min_importance?: number;707 metrics?: Partial<Record<string, { gt?: number; lt?: number }>>;708}709export interface Alert {710 id: string;711 name: string;712 company: string | null;713 condition: AlertCondition;714 channel: 'web' | 'webhook';715 target: string | null;716 created_at: string;717 status?: string;718}719export interface AlertDelivery {720 id: string;721 alert_id: string;722 alert_name?: string;723 event_id: string | null;724 event?: Event | null;725 channel: string;726 status: string;727 delivered_at: string;728}729730export interface SitemapPayload {731 items: { slug: string; updated_at: string | null }[];732 pages: number;733}734735export interface Methodology {736 metrics: { metric: string; formula_version: string; description: string; inputs: string[] }[];737 significance_bands: Record<string, [number, number]> | { label: string; min: number; max: number }[];738 event_types: string[];739 confidence_labels: Record<string, string> | (string | { label: string; min_confidence?: number | null })[];740}741742// ---------------------------------------------------------------------------------------------------------- admin743export interface AdminOverview {744 companies_by_status: Record<string, number>;745 sensors_by_status: Record<string, number>;746 sensors_by_tier: Record<string, number>;747 queue: { pending: number; running: number; dead: number; oldest_pending_s: number | null };748 llm: { pending: number; done_today: number; failed_today: number; budget_left: number | null };749 failures_24h_by_class: Record<string, number>;750 fetch_rate_1h: number | null;751 change_rate_1h: number | null;752 meaningful_rate_1h: number | null;753 storage: { objects: number; bytes: number };754 workers: { name: string; last_seen_at: string; inflight: number }[];755 cost_today: { fetch: number; browser: number; llm: number };756}757export interface AdminConnector {758 id: string;759 name: string;760 version: string;761 category: string;762 enabled: boolean;763 sensors_active: number;764 sensors_failing: number;765 success_rate_24h: number | null;766 avg_latency_ms: number | null;767 change_rate_24h: number | null;768 errors_24h: number;769 last_run_at: string | null;770}771export type AdminSensor = Sensor & { company: CompanyRef };772export interface AdminFailure {773 id: string;774 sensor_id: string | null;775 company?: CompanyRef | null;776 domain: string | null;777 failure_class: string;778 status_code: number | null;779 message: string | null;780 occurred_at: string;781 retry_at: string | null;782}783export interface AdminQueueItem {784 id: string;785 kind: string;786 status: string;787 priority: number;788 attempts: number;789 scheduled_at: string;790 started_at: string | null;791 finished_at: string | null;792 worker: string | null;793 ref: string | null;794 error: string | null;795}796export interface AdminLlmJob {797 id: string;798 kind: string;799 status: string;800 model: string | null;801 prompt_version: string | null;802 change_id: string | null;803 event_id: string | null;804 tokens_in: number | null;805 tokens_out: number | null;806 cost_estimate: number | null;807 created_at: string;808 finished_at: string | null;809 error: string | null;810}811export interface AdminReview {812 id: string;813 kind: string;814 status: string;815 subject: string;816 ref_id: string | null;817 company?: CompanyRef | null;818 reason: string | null;819 created_at: string;820 resolved_at: string | null;821 resolution: string | null;822}823export interface AdminQuality {824 coverage: { companies_active_pct: number | null; sensors_active_pct: number | null };825 freshness: { sensors_checked_24h_pct: number | null; stale: number };826 duplicate_rate: number | null;827 event_confidence_avg: number | null;828 unknown_surfaces: number;829 failed_sensors: number;830 calibration: { correct: number; duplicate: number; noise: number; misclassified: number };831}832export interface AdminCosts {833 items: { day: string; dimension: string; key: string; units: number; cost_estimate: number }[];834 per_1000_companies: number | null;835 per_million_observations: number | null;836 per_meaningful_event: number | null;837}838export type AdminCompany = CompanyCard;839