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%
15.3 KB · 393 lines python
Raw Blame History
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