SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
21.9 KB · 839 lines typescript
Raw Blame History
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