SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
18.3 KB · 356 lines typescript
Raw Blame History
1/**2 * WebSensor taxonomy. Event types are open (strings) so the set can grow without3 * migrations, but everything we emit ourselves comes from this list and carries an4 * intrinsic severity used by the importance model.5 */67export const SENSOR_TYPES = [8  "WEBHOOK",9  "WEBSOCKET",10  "SSE",11  "REST_API",12  "GRAPHQL",13  "RSS",14  "ATOM",15  "JSON",16  "XML",17  "SITEMAP",18  "HTML",19  "RENDERED_HTML",20  "PDF_INDEX",21  "GITHUB_RELEASE",22  "GITHUB_REPO",23  "STATUSPAGE",24  "DNS",25  "TLS",26  "HTTP_HEADERS",27  "FILE",28  "CUSTOM_CONNECTOR",29] as const;30export type SensorType = (typeof SENSOR_TYPES)[number];3132export const TIERS = ["S", "A", "B", "C", "D"] as const;33export type Tier = (typeof TIERS)[number];3435/** Base polling interval per tier (seconds). */36export const TIER_BASE_INTERVAL: Record<Tier, number> = {37  S: 60,38  A: 180,39  B: 900,40  C: 3600,41  D: 21600,42};43export const TIER_MIN_INTERVAL: Record<Tier, number> = {44  S: 15,45  A: 60,46  B: 300,47  C: 1800,48  D: 21600,49};50export const TIER_MAX_INTERVAL: Record<Tier, number> = {51  S: 300,52  A: 1800,53  B: 7200,54  C: 21600,55  D: 86400,56};5758export const CATEGORIES = [59  "ai",60  "cloud",61  "developer",62  "cyber",63  "consumer-tech",64  "semiconductors",65  "finance",66  "government",67  "statistics",68  "health",69  "pharma",70  "science",71  "space",72  "automotive",73  "commerce",74  "payments",75  "crypto",76  "enterprise",77  "internet",78  "standards",79  "technology",80  "news",81  "media",82  "politics",83] as const;84export type Category = (typeof CATEGORIES)[number] | string;8586/** Public feed channels → categories that map to them. */87export const FEED_CHANNELS: Record<string, string[]> = {88  ai: ["ai"],89  cyber: ["cyber"],90  finance: ["finance", "payments", "crypto", "commerce"],91  health: ["health", "pharma"],92  government: ["government", "statistics"],93  science: ["science", "space"],94  products: ["consumer-tech", "automotive", "semiconductors", "enterprise"],95  infrastructure: ["cloud", "developer", "internet", "standards"],96  news: ["news", "media"],97};9899export interface EventTypeSpec {100  /** Intrinsic severity 0–100 used as the 25 % component of importance. */101  severity: number;102  label: string;103  /** Does this type usually come with a public announcement? Used for silent-change detection. */104  usuallyAnnounced: boolean;105}106107export const EVENT_TYPES: Record<string, EventTypeSpec> = {108  announcement: { severity: 55, label: "Announcement", usuallyAnnounced: true },109  product_launch: { severity: 80, label: "Product launch", usuallyAnnounced: true },110  product_update: { severity: 55, label: "Product update", usuallyAnnounced: true },111  model_release: { severity: 88, label: "Model release", usuallyAnnounced: true },112  software_release: { severity: 55, label: "Software release", usuallyAnnounced: true },113  pricing_change: { severity: 82, label: "Pricing change", usuallyAnnounced: false },114  availability_change: { severity: 62, label: "Availability change", usuallyAnnounced: false },115  policy_change: { severity: 68, label: "Policy change", usuallyAnnounced: false },116  terms_change: { severity: 66, label: "Terms change", usuallyAnnounced: false },117  documentation_change: { severity: 30, label: "Documentation change", usuallyAnnounced: false },118  API_change: { severity: 62, label: "API change", usuallyAnnounced: false },119  security_advisory: { severity: 86, label: "Security advisory", usuallyAnnounced: true },120  vulnerability: { severity: 90, label: "Vulnerability", usuallyAnnounced: true },121  breach: { severity: 95, label: "Breach", usuallyAnnounced: true },122  incident: { severity: 78, label: "Incident", usuallyAnnounced: true },123  outage: { severity: 90, label: "Outage", usuallyAnnounced: true },124  maintenance: { severity: 35, label: "Maintenance", usuallyAnnounced: true },125  recall: { severity: 85, label: "Recall", usuallyAnnounced: true },126  drug_approval: { severity: 88, label: "Drug approval", usuallyAnnounced: true },127  clinical_trial: { severity: 60, label: "Clinical trial", usuallyAnnounced: true },128  scientific_publication: { severity: 45, label: "Scientific publication", usuallyAnnounced: true },129  regulatory_filing: { severity: 65, label: "Regulatory filing", usuallyAnnounced: true },130  financial_filing: { severity: 68, label: "Financial filing", usuallyAnnounced: true },131  earnings: { severity: 75, label: "Earnings", usuallyAnnounced: true },132  leadership_change: { severity: 72, label: "Leadership change", usuallyAnnounced: true },133  acquisition: { severity: 85, label: "Acquisition", usuallyAnnounced: true },134  funding: { severity: 70, label: "Funding", usuallyAnnounced: true },135  partnership: { severity: 60, label: "Partnership", usuallyAnnounced: true },136  job_expansion: { severity: 40, label: "Job expansion", usuallyAnnounced: false },137  layoffs: { severity: 78, label: "Layoffs", usuallyAnnounced: true },138  new_region: { severity: 62, label: "New region", usuallyAnnounced: true },139  new_country: { severity: 65, label: "New country", usuallyAnnounced: true },140  infrastructure_change: { severity: 50, label: "Infrastructure change", usuallyAnnounced: false },141  api_change: { severity: 62, label: "API change", usuallyAnnounced: false },142  dns_change: { severity: 48, label: "DNS change", usuallyAnnounced: false },143  domain_registration_change: { severity: 45, label: "Domain registration change", usuallyAnnounced: false },144  crawler_policy_change: { severity: 52, label: "Crawler policy change", usuallyAnnounced: false },145  DNS_change: { severity: 45, label: "DNS change", usuallyAnnounced: false },146  certificate_change: { severity: 30, label: "Certificate change", usuallyAnnounced: false },147  repository_release: { severity: 50, label: "Repository release", usuallyAnnounced: true },148  dataset_release: { severity: 55, label: "Dataset release", usuallyAnnounced: true },149  standard_update: { severity: 58, label: "Standard update", usuallyAnnounced: true },150  government_announcement: { severity: 65, label: "Government announcement", usuallyAnnounced: true },151  economic_release: { severity: 80, label: "Economic release", usuallyAnnounced: true },152  legal_change: { severity: 70, label: "Legal change", usuallyAnnounced: true },153  monetary_policy: { severity: 92, label: "Monetary policy", usuallyAnnounced: true },154  page_created: { severity: 35, label: "Page created", usuallyAnnounced: false },155  page_removed: { severity: 40, label: "Page removed", usuallyAnnounced: false },156  content_change: { severity: 25, label: "Content change", usuallyAnnounced: false },157  unknown: { severity: 20, label: "Unknown", usuallyAnnounced: false },158  // ---- 2026-09-11: cyber event classes ----159  zero_day: { severity: 97, label: "Zero-day", usuallyAnnounced: true },160  active_exploitation: { severity: 96, label: "Active exploitation", usuallyAnnounced: true },161  patch_release: { severity: 70, label: "Patch release", usuallyAnnounced: true },162  supply_chain_attack: { severity: 95, label: "Supply-chain attack", usuallyAnnounced: true },163  credential_leak: { severity: 88, label: "Credential leak", usuallyAnnounced: true },164  malware_campaign: { severity: 80, label: "Malware campaign", usuallyAnnounced: true },165  ransomware: { severity: 88, label: "Ransomware", usuallyAnnounced: true },166  // ---- finance ----167  merger: { severity: 85, label: "Merger", usuallyAnnounced: true },168  ipo: { severity: 80, label: "IPO", usuallyAnnounced: true },169  guidance: { severity: 78, label: "Guidance", usuallyAnnounced: true },170  dividend: { severity: 60, label: "Dividend", usuallyAnnounced: true },171  buyback: { severity: 62, label: "Buyback", usuallyAnnounced: true },172  bankruptcy: { severity: 92, label: "Bankruptcy", usuallyAnnounced: true },173  capital_raise: { severity: 70, label: "Capital raise", usuallyAnnounced: true },174  rating_change: { severity: 74, label: "Rating change", usuallyAnnounced: true },175  insider_transaction: { severity: 45, label: "Insider transaction", usuallyAnnounced: true },176  // ---- government / legal ----177  regulatory_action: { severity: 76, label: "Regulatory action", usuallyAnnounced: true },178  lawsuit: { severity: 70, label: "Lawsuit", usuallyAnnounced: true },179  court_decision: { severity: 74, label: "Court decision", usuallyAnnounced: true },180  sanction: { severity: 84, label: "Sanction", usuallyAnnounced: true },181  legislation: { severity: 72, label: "Legislation", usuallyAnnounced: true },182  budget: { severity: 74, label: "Budget", usuallyAnnounced: true },183  procurement: { severity: 45, label: "Procurement", usuallyAnnounced: true },184  appointment: { severity: 62, label: "Appointment", usuallyAnnounced: true },185  emergency_notice: { severity: 90, label: "Emergency notice", usuallyAnnounced: true },186  // ---- health / science ----187  outbreak: { severity: 90, label: "Outbreak", usuallyAnnounced: true },188  drug_warning: { severity: 82, label: "Drug warning", usuallyAnnounced: true },189  device_recall: { severity: 82, label: "Device recall", usuallyAnnounced: true },190  paper_release: { severity: 45, label: "Paper", usuallyAnnounced: true },191  retraction: { severity: 70, label: "Retraction", usuallyAnnounced: true },192  space_mission: { severity: 68, label: "Space mission", usuallyAnnounced: true },193  // ---- transport ----194  accident: { severity: 88, label: "Accident", usuallyAnnounced: true },195  grounding: { severity: 85, label: "Grounding", usuallyAnnounced: true },196  safety_bulletin: { severity: 72, label: "Safety bulletin", usuallyAnnounced: true },197  route_change: { severity: 50, label: "Route change", usuallyAnnounced: true },198  production_delay: { severity: 65, label: "Production delay", usuallyAnnounced: true },199  // ---- product / service ----200  service_launch: { severity: 70, label: "Service launch", usuallyAnnounced: true },201  service_shutdown: { severity: 78, label: "Service shutdown", usuallyAnnounced: false },202  feature_removed: { severity: 66, label: "Feature removed", usuallyAnnounced: false },203  domain_change: { severity: 50, label: "Domain change", usuallyAnnounced: false },204  sports_transaction: { severity: 40, label: "Transaction", usuallyAnnounced: true },205  sports_result: { severity: 25, label: "Result", usuallyAnnounced: true },206  suspension: { severity: 45, label: "Suspension", usuallyAnnounced: true },207  schedule_change: { severity: 35, label: "Schedule change", usuallyAnnounced: true },208};209210export function eventTypeSpec(type: string): EventTypeSpec {211  return EVENT_TYPES[type] ?? EVENT_TYPES.unknown!;212}213214/** Event types whose silent modification is a signal (pricing quietly changed, terms rewritten…). */215export const SILENT_ELIGIBLE_TYPES = new Set([216  "pricing_change",217  "terms_change",218  "policy_change",219  "API_change",220  "api_change",221  "availability_change",222  "documentation_change",223  "service_shutdown",224  "feature_removed",225  "leadership_change",226  "page_removed",227  "crawler_policy_change",228  "product_update",229  "infrastructure_change",230]);231232/** Groups used by filters, category desks and the explore page. */233export const EVENT_GROUPS: Record<string, { label: string; types: string[] }> = {234  security: { label: "Security", types: ["zero_day", "active_exploitation", "vulnerability", "security_advisory", "breach", "supply_chain_attack", "credential_leak", "malware_campaign", "ransomware", "patch_release"] },235  reliability: { label: "Reliability", types: ["outage", "incident", "maintenance", "infrastructure_change", "dns_change", "DNS_change", "certificate_change"] },236  product: { label: "Product & API", types: ["product_launch", "product_update", "model_release", "software_release", "repository_release", "service_launch", "service_shutdown", "feature_removed", "API_change", "api_change", "availability_change", "new_region", "new_country", "documentation_change"] },237  commercial: { label: "Pricing & terms", types: ["pricing_change", "terms_change", "policy_change", "crawler_policy_change"] },238  corporate: { label: "Corporate", types: ["earnings", "guidance", "financial_filing", "acquisition", "merger", "ipo", "funding", "capital_raise", "dividend", "buyback", "bankruptcy", "rating_change", "insider_transaction", "leadership_change", "appointment", "layoffs", "job_expansion", "partnership"] },239  government: { label: "Government & legal", types: ["government_announcement", "regulatory_filing", "regulatory_action", "legislation", "budget", "procurement", "sanction", "lawsuit", "court_decision", "legal_change", "emergency_notice", "monetary_policy", "economic_release"] },240  science: { label: "Science & health", types: ["scientific_publication", "paper_release", "retraction", "dataset_release", "clinical_trial", "drug_approval", "drug_warning", "recall", "device_recall", "outbreak", "space_mission", "standard_update"] },241  transport: { label: "Transport", types: ["accident", "grounding", "safety_bulletin", "route_change", "production_delay"] },242  sports: { label: "Sports", types: ["sports_transaction", "sports_result", "suspension", "schedule_change"] },243  web: { label: "Web", types: ["announcement", "page_created", "page_removed", "content_change", "domain_change", "domain_registration_change", "unknown"] },244};245246export function eventGroupOf(type: string): string {247  for (const [g, spec] of Object.entries(EVENT_GROUPS)) if (spec.types.includes(type)) return g;248  return "web";249}250251/** Semantic classes assigned to every raw change by the semantic diff stage. */252export const CHANGE_CLASSES = ["meaningful", "pricing", "policy", "product", "personnel", "cosmetic", "navigation", "timestamp", "advertisement", "boilerplate"] as const;253export type ChangeClass = (typeof CHANGE_CLASSES)[number];254export const NOISE_CLASSES = new Set<ChangeClass>(["cosmetic", "navigation", "timestamp", "advertisement", "boilerplate"]);255256/** Cluster / breaking states. */257export const CLUSTER_STATES = ["breaking", "developing", "confirmed", "watching", "closed"] as const;258export type ClusterState = (typeof CLUSTER_STATES)[number];259260/** Priority tiers for scheduling (spec §76). Derived from tier + category. */261export const PRIORITY_LABELS: Record<number, string> = { 0: "TIER 0 · critical", 1: "TIER 1 · major", 2: "TIER 2 · specialized", 3: "TIER 3 · low urgency" };262263/** Countries with dedicated desks. ISO 3166-1 alpha-2 (plus EU / INT). */264export const COUNTRIES: Record<string, { name: string; slug: string; flag: string }> = {265  CA: { name: "Canada", slug: "canada", flag: "🇨🇦" },266  US: { name: "United States", slug: "united-states", flag: "🇺🇸" },267  EU: { name: "European Union", slug: "european-union", flag: "🇪🇺" },268  GB: { name: "United Kingdom", slug: "united-kingdom", flag: "🇬🇧" },269  FR: { name: "France", slug: "france", flag: "🇫🇷" },270  DE: { name: "Germany", slug: "germany", flag: "🇩🇪" },271  IT: { name: "Italy", slug: "italy", flag: "🇮🇹" },272  ES: { name: "Spain", slug: "spain", flag: "🇪🇸" },273  JP: { name: "Japan", slug: "japan", flag: "🇯🇵" },274  KR: { name: "South Korea", slug: "south-korea", flag: "🇰🇷" },275  AU: { name: "Australia", slug: "australia", flag: "🇦🇺" },276  IN: { name: "India", slug: "india", flag: "🇮🇳" },277  BR: { name: "Brazil", slug: "brazil", flag: "🇧🇷" },278  MX: { name: "Mexico", slug: "mexico", flag: "🇲🇽" },279  CH: { name: "Switzerland", slug: "switzerland", flag: "🇨🇭" },280  NL: { name: "Netherlands", slug: "netherlands", flag: "🇳🇱" },281  SE: { name: "Sweden", slug: "sweden", flag: "🇸🇪" },282  NO: { name: "Norway", slug: "norway", flag: "🇳🇴" },283  FI: { name: "Finland", slug: "finland", flag: "🇫🇮" },284  DK: { name: "Denmark", slug: "denmark", flag: "🇩🇰" },285  IE: { name: "Ireland", slug: "ireland", flag: "🇮🇪" },286  BE: { name: "Belgium", slug: "belgium", flag: "🇧🇪" },287  AT: { name: "Austria", slug: "austria", flag: "🇦🇹" },288  PT: { name: "Portugal", slug: "portugal", flag: "🇵🇹" },289  PL: { name: "Poland", slug: "poland", flag: "🇵🇱" },290  CN: { name: "China", slug: "china", flag: "🇨🇳" },291  HK: { name: "Hong Kong", slug: "hong-kong", flag: "🇭🇰" },292  TW: { name: "Taiwan", slug: "taiwan", flag: "🇹🇼" },293  SG: { name: "Singapore", slug: "singapore", flag: "🇸🇬" },294  IL: { name: "Israel", slug: "israel", flag: "🇮🇱" },295  AE: { name: "United Arab Emirates", slug: "uae", flag: "🇦🇪" },296  SA: { name: "Saudi Arabia", slug: "saudi-arabia", flag: "🇸🇦" },297  QA: { name: "Qatar", slug: "qatar", flag: "🇶🇦" },298  ZA: { name: "South Africa", slug: "south-africa", flag: "🇿🇦" },299  NG: { name: "Nigeria", slug: "nigeria", flag: "🇳🇬" },300  KE: { name: "Kenya", slug: "kenya", flag: "🇰🇪" },301  AR: { name: "Argentina", slug: "argentina", flag: "🇦🇷" },302  CL: { name: "Chile", slug: "chile", flag: "🇨🇱" },303  CO: { name: "Colombia", slug: "colombia", flag: "🇨🇴" },304  NZ: { name: "New Zealand", slug: "new-zealand", flag: "🇳🇿" },305  RU: { name: "Russia", slug: "russia", flag: "🇷🇺" },306  TR: { name: "Türkiye", slug: "turkiye", flag: "🇹🇷" },307  ID: { name: "Indonesia", slug: "indonesia", flag: "🇮🇩" },308  TH: { name: "Thailand", slug: "thailand", flag: "🇹🇭" },309  VN: { name: "Vietnam", slug: "vietnam", flag: "🇻🇳" },310  MY: { name: "Malaysia", slug: "malaysia", flag: "🇲🇾" },311  PH: { name: "Philippines", slug: "philippines", flag: "🇵🇭" },312  EG: { name: "Egypt", slug: "egypt", flag: "🇪🇬" },313  UA: { name: "Ukraine", slug: "ukraine", flag: "🇺🇦" },314  CZ: { name: "Czechia", slug: "czechia", flag: "🇨🇿" },315  GR: { name: "Greece", slug: "greece", flag: "🇬🇷" },316  LU: { name: "Luxembourg", slug: "luxembourg", flag: "🇱🇺" },317  INT: { name: "International", slug: "international", flag: "🌐" },318};319320export function countryBySlug(slug: string): { code: string; name: string; flag: string } | null {321  const s = slug.toLowerCase();322  for (const [code, c] of Object.entries(COUNTRIES)) if (c.slug === s || code.toLowerCase() === s) return { code, name: c.name, flag: c.flag };323  return null;324}325326export const ENTITY_TYPES = [327  "organization",328  "company",329  "government",330  "agency",331  "product",332  "software",333  "AI_model",334  "API",335  "drug",336  "disease",337  "person",338  "country",339  "city",340  "stock",341  "crypto_asset",342  "repository",343  "standard",344  "technology",345  "dataset",346  "publication",347  "vulnerability",348] as const;349export type EntityType = (typeof ENTITY_TYPES)[number];350351export const EVIDENCE_LABELS = ["OBSERVED", "INFERRED", "CONFIRMED", "UNCONFIRMED"] as const;352export type EvidenceLabel = (typeof EVIDENCE_LABELS)[number];353354export const CONNECTOR_HEALTH = ["UP", "DEGRADED", "ERROR", "DISABLED", "RATE_LIMITED"] as const;355export type ConnectorHealth = (typeof CONNECTOR_HEALTH)[number];356