spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1"""Shared vocabulary and typed configuration: surfaces, connector categories, schedule tiers, failure classes, event taxonomy,2significance bands, metric names and index weights. Everything here is versioned with the code (spec §21, §35, §61, §178)."""3from __future__ import annotations45from enum import StrEnum67# ------------------------------------------------------------------------------------------------------------ surfaces8910class Surface(StrEnum):11 HOMEPAGE = "homepage"12 ABOUT = "about"13 LEADERSHIP = "leadership"14 PRODUCTS = "products"15 SERVICES = "services"16 SOLUTIONS = "solutions"17 INDUSTRIES = "industries"18 PRICING = "pricing"19 CAREERS = "careers"20 JOBS_BOARD = "jobs_board" # structured ATS board (Greenhouse, Lever, Ashby, SmartRecruiters, Workday, JSON)21 NEWSROOM = "newsroom"22 BLOG = "blog"23 FEED = "feed" # RSS / Atom24 DOCS = "docs"25 DEVELOPER = "developer"26 API = "api"27 CHANGELOG = "changelog"28 INVESTOR_RELATIONS = "investor_relations"29 LOCATIONS = "locations"30 CONTACT = "contact"31 CUSTOMERS = "customers"32 PARTNERS = "partners"33 LEGAL_TERMS = "legal_terms"34 LEGAL_PRIVACY = "legal_privacy"35 SECURITY = "security"36 SUSTAINABILITY = "sustainability"37 RESEARCH = "research"38 STATUS = "status"39 SUPPORT = "support"40 SITEMAP = "sitemap"41 OTHER = "other"424344# Default base interval per surface (seconds) — the scheduler adapts around it (spec §15–16). Tier letters are derived.45SURFACE_BASE_INTERVAL_S: dict[str, int] = {46 Surface.HOMEPAGE: 6 * 3600,47 Surface.NEWSROOM: 3 * 3600,48 Surface.FEED: 2 * 3600,49 Surface.BLOG: 6 * 3600,50 Surface.CAREERS: 12 * 3600,51 Surface.JOBS_BOARD: 6 * 3600,52 Surface.PRICING: 12 * 3600,53 Surface.PRODUCTS: 24 * 3600,54 Surface.SERVICES: 48 * 3600,55 Surface.SOLUTIONS: 48 * 3600,56 Surface.INDUSTRIES: 72 * 3600,57 Surface.LEADERSHIP: 24 * 3600,58 Surface.ABOUT: 72 * 3600,59 Surface.LOCATIONS: 48 * 3600,60 Surface.CONTACT: 7 * 86400,61 Surface.DOCS: 24 * 3600,62 Surface.DEVELOPER: 24 * 3600,63 Surface.API: 24 * 3600,64 Surface.CHANGELOG: 6 * 3600,65 Surface.INVESTOR_RELATIONS: 12 * 3600,66 Surface.CUSTOMERS: 72 * 3600,67 Surface.PARTNERS: 72 * 3600,68 Surface.LEGAL_TERMS: 3 * 86400,69 Surface.LEGAL_PRIVACY: 3 * 86400,70 Surface.SECURITY: 3 * 86400,71 Surface.SUSTAINABILITY: 7 * 86400,72 Surface.RESEARCH: 48 * 3600,73 Surface.STATUS: 6 * 3600,74 Surface.SUPPORT: 7 * 86400,75 Surface.SITEMAP: 24 * 3600,76 Surface.OTHER: 3 * 86400,77}7879# Semantic importance of a surface (0–1): weights change significance and sensor quality (spec §12, §20).80SURFACE_IMPORTANCE: dict[str, float] = {81 Surface.PRICING: 1.0, Surface.JOBS_BOARD: 0.95, Surface.CAREERS: 0.85, Surface.LEADERSHIP: 0.9, Surface.NEWSROOM: 0.9,82 Surface.PRODUCTS: 0.85, Surface.INVESTOR_RELATIONS: 0.85, Surface.CHANGELOG: 0.8, Surface.LOCATIONS: 0.8, Surface.FEED: 0.75,83 Surface.HOMEPAGE: 0.7, Surface.DOCS: 0.65, Surface.API: 0.65, Surface.DEVELOPER: 0.65, Surface.LEGAL_TERMS: 0.7,84 Surface.LEGAL_PRIVACY: 0.6, Surface.SECURITY: 0.6, Surface.PARTNERS: 0.6, Surface.CUSTOMERS: 0.55, Surface.BLOG: 0.55,85 Surface.RESEARCH: 0.55, Surface.SERVICES: 0.55, Surface.SOLUTIONS: 0.5, Surface.INDUSTRIES: 0.45, Surface.ABOUT: 0.5,86 Surface.SUSTAINABILITY: 0.45, Surface.STATUS: 0.5, Surface.CONTACT: 0.3, Surface.SUPPORT: 0.3, Surface.SITEMAP: 0.4, Surface.OTHER: 0.3,87}888990def tier_for_interval(seconds: int) -> str:91 """Schedule tier letter (spec §15): A ≤ 15 min · B ≤ 1 h · C ≤ 6 h · D ≤ 24 h · E > 24 h."""92 if seconds <= 15 * 60:93 return "A"94 if seconds <= 3600:95 return "B"96 if seconds <= 6 * 3600:97 return "C"98 if seconds <= 86400:99 return "D"100 return "E"101102103# ------------------------------------------------------------------------------------------------------------ fetch / failures104105106class FetchMode(StrEnum):107 HTTP = "http"108 BROWSER = "browser"109 FEED = "feed"110 JSON = "json"111 SITEMAP = "sitemap"112113114class FailureClass(StrEnum):115 DNS = "DNS"116 TIMEOUT = "TIMEOUT"117 HTTP_4XX = "HTTP_4XX"118 HTTP_5XX = "HTTP_5XX"119 BOT_CHALLENGE = "BOT_CHALLENGE"120 PARSING = "PARSING"121 SCHEMA = "SCHEMA"122 REDIRECT = "REDIRECT"123 PAGE_REMOVED = "PAGE_REMOVED"124 RATE_LIMIT = "RATE_LIMIT"125 ROBOTS = "ROBOTS"126 BLOCKED_DESTINATION = "BLOCKED_DESTINATION"127 TOO_LARGE = "TOO_LARGE"128 UNKNOWN = "UNKNOWN"129130131# Retry policy per failure class: (backoff multiplier on the sensor interval, failures before the sensor is marked failing)132FAILURE_POLICY: dict[str, tuple[float, int]] = {133 FailureClass.DNS: (3.0, 3),134 FailureClass.TIMEOUT: (1.5, 4),135 FailureClass.HTTP_4XX: (2.0, 3),136 FailureClass.HTTP_5XX: (1.5, 5),137 FailureClass.BOT_CHALLENGE: (4.0, 2),138 FailureClass.PARSING: (2.0, 3),139 FailureClass.SCHEMA: (2.0, 3),140 FailureClass.REDIRECT: (2.0, 2),141 FailureClass.PAGE_REMOVED: (4.0, 2),142 FailureClass.RATE_LIMIT: (3.0, 4),143 FailureClass.ROBOTS: (8.0, 1),144 FailureClass.BLOCKED_DESTINATION: (8.0, 1),145 FailureClass.TOO_LARGE: (4.0, 2),146 FailureClass.UNKNOWN: (2.0, 3),147}148149150class SensorStatus(StrEnum):151 PENDING = "pending"152 ACTIVE = "active"153 PAUSED = "paused"154 FAILING = "failing"155 STALE = "stale"156 BLOCKED = "blocked"157 REDIRECTED = "redirected"158 RETIRED = "retired"159160161class CompanyStatus(StrEnum):162 ACTIVE = "ACTIVE"163 POSSIBLY_INACTIVE = "POSSIBLY_INACTIVE"164 WEBSITE_UNAVAILABLE = "WEBSITE_UNAVAILABLE"165 ACQUIRED = "ACQUIRED"166 DISSOLVED = "DISSOLVED"167 UNKNOWN = "UNKNOWN"168169170class OnboardingStatus(StrEnum):171 PENDING = "pending"172 DISCOVERING = "discovering"173 ACTIVE = "active"174 FAILED = "failed"175 NO_WEBSITE = "no_website"176177178# ------------------------------------------------------------------------------------------------------------ changes / events179180181class ChangeKind(StrEnum):182 NOISE = "noise"183 MINOR = "minor"184 MEANINGFUL = "meaningful"185 MAJOR = "major"186 CRITICAL = "critical"187188189def change_kind(significance: float, *, noise: float = 0.20, meaningful: float = 0.40, major: float = 0.65, critical: float = 0.85) -> ChangeKind:190 if significance < noise:191 return ChangeKind.NOISE192 if significance < meaningful:193 return ChangeKind.MINOR194 if significance < major:195 return ChangeKind.MEANINGFUL196 if significance < critical:197 return ChangeKind.MAJOR198 return ChangeKind.CRITICAL199200201class EventType(StrEnum):202 PRODUCT = "PRODUCT"203 PRICING = "PRICING"204 HIRING = "HIRING"205 LEADERSHIP = "LEADERSHIP"206 LOCATION = "LOCATION"207 FINANCING = "FINANCING"208 MA = "M&A"209 PARTNERSHIP = "PARTNERSHIP"210 STRATEGY = "STRATEGY"211 TECHNOLOGY = "TECHNOLOGY"212 LEGAL = "LEGAL"213 MARKETING = "MARKETING"214 DEVELOPER = "DEVELOPER"215 SECURITY = "SECURITY"216 OPERATIONS = "OPERATIONS"217 SUSTAINABILITY = "SUSTAINABILITY"218 INVESTOR_RELATIONS = "INVESTOR_RELATIONS"219 COMMUNICATION = "COMMUNICATION"220 OTHER = "OTHER"221222223# subtype → (type, default importance). Deterministic extractors emit these; the LLM classifier may only pick from this list.224EVENT_SUBTYPES: dict[str, tuple[EventType, float]] = {225 "PRODUCT_LAUNCH": (EventType.PRODUCT, 0.75),226 "NEW_PRODUCT": (EventType.PRODUCT, 0.7),227 "PRODUCT_REMOVED": (EventType.PRODUCT, 0.6),228 "PRODUCT_RENAME": (EventType.PRODUCT, 0.5),229 "PRODUCT_UPDATE": (EventType.PRODUCT, 0.45),230 "FEATURE_LAUNCH": (EventType.PRODUCT, 0.5),231 "PRICE_INCREASE": (EventType.PRICING, 0.8),232 "PRICE_DECREASE": (EventType.PRICING, 0.75),233 "NEW_PRICING_TIER": (EventType.PRICING, 0.7),234 "PRICING_TIER_REMOVED": (EventType.PRICING, 0.65),235 "PRICING_CHANGE": (EventType.PRICING, 0.65),236 "NEW_JOB": (EventType.HIRING, 0.3),237 "JOB_REMOVED": (EventType.HIRING, 0.25),238 "JOB_COUNT_INCREASE": (EventType.HIRING, 0.55),239 "JOB_COUNT_DECREASE": (EventType.HIRING, 0.55),240 "HIRING_SURGE": (EventType.HIRING, 0.75),241 "HIRING_FREEZE_SIGNAL": (EventType.HIRING, 0.7),242 "AI_HIRING": (EventType.HIRING, 0.5),243 "NEW_EXECUTIVE": (EventType.LEADERSHIP, 0.8),244 "EXECUTIVE_NO_LONGER_LISTED": (EventType.LEADERSHIP, 0.75),245 "EXECUTIVE_TITLE_CHANGE": (EventType.LEADERSHIP, 0.6),246 "LEADERSHIP_CHANGE": (EventType.LEADERSHIP, 0.7),247 "NEW_OFFICE": (EventType.LOCATION, 0.65),248 "NEW_LOCATION": (EventType.LOCATION, 0.6),249 "OFFICE_REMOVED": (EventType.LOCATION, 0.6),250 "COUNTRY_EXPANSION": (EventType.LOCATION, 0.8),251 "FUNDING_ROUND": (EventType.FINANCING, 0.85),252 "IPO": (EventType.FINANCING, 0.95),253 "ACQUISITION": (EventType.MA, 0.9),254 "DIVESTITURE": (EventType.MA, 0.8),255 "MERGER": (EventType.MA, 0.9),256 "NEW_PARTNERSHIP": (EventType.PARTNERSHIP, 0.6),257 "PARTNERSHIP_ENDED": (EventType.PARTNERSHIP, 0.5),258 "BRAND_REPOSITIONING": (EventType.STRATEGY, 0.6),259 "STRATEGY_UPDATE": (EventType.STRATEGY, 0.5),260 "ENTERPRISE_REPOSITIONING": (EventType.STRATEGY, 0.55),261 "TECHNOLOGY_ADOPTION": (EventType.TECHNOLOGY, 0.5),262 "AI_LAUNCH": (EventType.TECHNOLOGY, 0.7),263 "TERMS_CHANGE": (EventType.LEGAL, 0.6),264 "PRIVACY_POLICY_CHANGE": (EventType.LEGAL, 0.55),265 "LEGAL_UPDATE": (EventType.LEGAL, 0.5),266 "REGULATORY": (EventType.LEGAL, 0.6),267 "CAMPAIGN_LAUNCH": (EventType.MARKETING, 0.35),268 "MESSAGING_CHANGE": (EventType.MARKETING, 0.4),269 "API_LAUNCH": (EventType.DEVELOPER, 0.7),270 "API_CHANGE": (EventType.DEVELOPER, 0.5),271 "SDK_RELEASE": (EventType.DEVELOPER, 0.5),272 "DOCUMENTATION_CHANGE": (EventType.DEVELOPER, 0.35),273 "CHANGELOG_ENTRY": (EventType.DEVELOPER, 0.4),274 "SECURITY_INCIDENT": (EventType.SECURITY, 0.85),275 "SECURITY_UPDATE": (EventType.SECURITY, 0.5),276 "OUTAGE": (EventType.OPERATIONS, 0.6),277 "OPERATIONS_UPDATE": (EventType.OPERATIONS, 0.4),278 "SUSTAINABILITY_UPDATE": (EventType.SUSTAINABILITY, 0.4),279 "EARNINGS_RELEASE": (EventType.INVESTOR_RELATIONS, 0.7),280 "INVESTOR_UPDATE": (EventType.INVESTOR_RELATIONS, 0.55),281 "NEWS_RELEASE": (EventType.COMMUNICATION, 0.45),282 "BLOG_POST": (EventType.COMMUNICATION, 0.3),283 "WEBSITE_CHANGE": (EventType.COMMUNICATION, 0.3),284 "HOMEPAGE_REDESIGN": (EventType.MARKETING, 0.45),285 "DOC_CHANGE": (EventType.DEVELOPER, 0.35),286 "OTHER": (EventType.OTHER, 0.3),287}288289# MVP high-confidence events emitted deterministically (spec §158).290MVP_EVENT_SUBTYPES = ("NEW_JOB", "JOB_REMOVED", "JOB_COUNT_INCREASE", "JOB_COUNT_DECREASE", "NEW_PRODUCT", "PRODUCT_REMOVED",291 "PRICING_CHANGE", "PRICE_INCREASE", "PRICE_DECREASE", "NEW_PRICING_TIER", "LEADERSHIP_CHANGE", "NEW_EXECUTIVE",292 "EXECUTIVE_NO_LONGER_LISTED", "NEW_LOCATION", "NEWS_RELEASE", "DOC_CHANGE", "CHANGELOG_ENTRY", "BLOG_POST")293294295class EventStatus(StrEnum):296 ACTIVE = "active"297 RETRACTED = "retracted"298 DUPLICATE = "duplicate"299 REVIEW = "review"300301302def confidence_label(confidence: float) -> str:303 """Spec §50: VERIFIED · HIGH CONFIDENCE · LIKELY · INFERRED · LOW CONFIDENCE."""304 if confidence >= 0.95:305 return "VERIFIED"306 if confidence >= 0.85:307 return "HIGH_CONFIDENCE"308 if confidence >= 0.7:309 return "LIKELY"310 if confidence >= 0.5:311 return "INFERRED"312 return "LOW_CONFIDENCE"313314315# ------------------------------------------------------------------------------------------------------------ metrics316317318class Metric(StrEnum):319 ACTIVITY_SCORE = "activity_score"320 HIRING_MOMENTUM_7D = "hiring_momentum_7d"321 HIRING_MOMENTUM_30D = "hiring_momentum_30d"322 HIRING_MOMENTUM_90D = "hiring_momentum_90d"323 OPEN_JOBS = "open_jobs"324 AI_ADOPTION = "ai_adoption"325 PRODUCT_VELOCITY = "product_velocity"326 GEO_EXPANSION = "geo_expansion"327 DEVELOPER_MOMENTUM = "developer_momentum"328 COMMUNICATION_ACTIVITY = "communication_activity"329 PRICING_ACTIVITY = "pricing_activity"330 LEADERSHIP_ACTIVITY = "leadership_activity"331 CORPORATE_CHANGE_INDEX = "corporate_change_index"332 ANOMALY_SCORE = "anomaly_score"333 HISTORICAL_COVERAGE = "historical_coverage"334335336# Corporate Change Index weights (spec §35) — formula version bumps when weights change.337CCI_FORMULA_VERSION = "cci-v1"338CCI_WEIGHTS: dict[str, float] = {339 Metric.HIRING_MOMENTUM_30D: 0.25,340 Metric.PRODUCT_VELOCITY: 0.20,341 Metric.GEO_EXPANSION: 0.15,342 Metric.LEADERSHIP_ACTIVITY: 0.15,343 Metric.DEVELOPER_MOMENTUM: 0.10,344 Metric.COMMUNICATION_ACTIVITY: 0.10,345 Metric.PRICING_ACTIVITY: 0.05,346}347METRICS_FORMULA_VERSION = "metrics-v1"348349# Observable AI-adoption signals (spec §31): keywords matched in job titles, product names, docs headings. Case-insensitive.350AI_KEYWORDS = ("machine learning", "artificial intelligence", "deep learning", " ai ", "ai/ml", "ml engineer", "llm", "large language",351 "generative", "genai", "gen ai", "nlp", "computer vision", "data scientist", "mlops", "agentic", "copilot",352 "foundation model", "neural", "inference", "rag ", "retrieval-augmented", "prompt engineer", "ai engineer", "ai product")353354# Metric formula parameters (intelligence layer, docs/SCORING.md). Bump METRICS_FORMULA_VERSION when any of these change.355METRIC_PARAMS: dict[str, float] = {356 "activity_window_days": 30, "activity_decay_tau_days": 10.0, "activity_coverage_exp": 0.5, "activity_density_max": 6.0,357 "change_weight_meaningful": 1.0, "change_weight_major": 2.0, "change_weight_critical": 3.0,358 "hiring_min_listings": 3, "velocity_window_days": 90, "velocity_saturation": 6.0, "geo_saturation": 4.0, "geo_country_weight": 3.0,359 "developer_saturation": 6.0, "communication_window_days": 30, "communication_saturation": 8.0, "pricing_saturation": 3.0,360 "leadership_saturation": 3.0, "ai_jobs_weight": 0.5, "ai_events_weight": 0.25, "ai_keywords_weight": 0.25, "ai_events_saturation": 3.0,361 "ai_keywords_saturation": 5.0, "coverage_expected_surfaces": 8, "coverage_obs_weight": 0.5, "coverage_continuity_weight": 0.3,362 "coverage_sources_weight": 0.2, "anomaly_min_samples": 4, "index_trailing_days": 28,363}364365# Confidence assigned to deterministic events by evidence quality (spec §49–50).366EVIDENCE_CONFIDENCE: dict[str, float] = {"ats_json": 0.95, "jsonld": 0.9, "html": 0.8, "text_diff": 0.7}367368# Signal kinds (spec §92) — labelled as signals, never facts.369SIGNAL_KINDS = ("hiring_surge", "hiring_freeze", "launch_buildup", "expansion", "pricing_migration", "developer_push",370 "enterprise_repositioning", "ai_acceleration", "abnormal_activity")371SIGNALS_FORMULA_VERSION = "signals-v1"372373# Words the intelligence layer must never emit in generated titles/summaries (spec §167–168).374FORBIDDEN_WORDING = ("laid off", "layoff", "fired", "shut down", "shutdown", "bankrupt", "collapsed")375376# ------------------------------------------------------------------------------------------------------------ companies377378COMPANY_IMPORTANCE_TIERS = {1: "global", 2: "major", 3: "notable", 4: "long_tail"}379380381class CollectionMethod(StrEnum):382 LIVE = "live"383 BACKFILL = "backfill"384385386__all__ = [387 "AI_KEYWORDS", "CCI_FORMULA_VERSION", "CCI_WEIGHTS", "COMPANY_IMPORTANCE_TIERS", "EVENT_SUBTYPES", "EVIDENCE_CONFIDENCE", "FAILURE_POLICY",388 "FORBIDDEN_WORDING", "METRICS_FORMULA_VERSION", "METRIC_PARAMS", "MVP_EVENT_SUBTYPES", "SIGNALS_FORMULA_VERSION", "SIGNAL_KINDS",389 "SURFACE_BASE_INTERVAL_S", "SURFACE_IMPORTANCE", "ChangeKind", "CollectionMethod",390 "CompanyStatus", "EventStatus", "EventType", "FailureClass", "FetchMode", "Metric", "OnboardingStatus", "SensorStatus", "Surface",391 "change_kind", "confidence_label", "tier_for_interval",392]393