SPB Git
12.0 KB · 262 lines python
1# ============================================================2# SPID — project taxonomy & lightweight value extractors3#4# Shared by both the rule-based and the LLM extractor so that5# `project_type`, `technology`, money, location and dates are6# normalised the same way regardless of backend.7# ============================================================8from __future__ import annotations910import re1112# ----------------------------------------------------------------------13# Canonical project types.  `signals` are high-precision regex fragments14# (already lower-cased) used by the rule extractor and as the controlled15# vocabulary the LLM is asked to classify into.16# ----------------------------------------------------------------------17PROJECT_TYPES: dict[str, dict] = {18    "manufacturing_expansion": {19        "label": "Manufacturing / Capacity Expansion",20        "signals": [21            r"(expand|increas\w+|add\w*|ramp\w*|scal\w+|new|build\w*|construct\w*|open\w*)"22            r"[^.]{0,60}(manufactur\w+|production|plant|factory|facilit\w+|capacity|assembly|fab\b)",23        ],24    },25    "plant_construction": {26        "label": "Plant / Site Construction",27        "signals": [r"(construct\w*|build\w*|broke ground|commission\w*|new (plant|site|facility))"28                    r"[^.]{0,60}(plant|facility|site|campus|complex|refinery|mill)"],29    },30    "data_center": {31        "label": "Data Center",32        "signals": [r"data\s?cent(er|re)s?", r"hyperscal\w+", r"server farm"],33    },34    "cloud_migration": {35        "label": "Cloud Migration",36        "signals": [r"(migrat\w+|mov\w+|transition\w*)[^.]{0,40}(cloud)",37                    r"cloud (migration|platform|adoption|infrastructure|first)"],38    },39    "ai_initiative": {40        "label": "AI / ML Initiative",41        "signals": [r"\b(artificial intelligence|machine learning|generative ai|gen ?ai|deep learning|"42                    r"large language model|\bllm\b|neural network)\b",43                    r"\bai[- ](powered|driven|enabled|capabilit\w+|platform|model|tools?)\b"],44    },45    "digital_transformation": {46        "label": "Digital Transformation",47        "signals": [r"digital (transformation|transition|initiative|strateg\w+|platform)",48                    r"digitali[sz]ation"],49    },50    "erp_implementation": {51        "label": "ERP / Systems Implementation",52        "signals": [r"\b(erp|sap|oracle|workday|s/?4 ?hana)\b[^.]{0,40}(implement\w+|deploy\w+|migrat\w+|system|platform)",53                    r"(implement\w+|deploy\w+|upgrad\w+)[^.]{0,40}\berp\b",54                    r"enterprise resource planning"],55    },56    "rd_program": {57        "label": "R&D Program",58        "signals": [r"research and development", r"\br&d\b",59                    r"(develop\w+|invest\w+)[^.]{0,40}(next[- ]generation|new technolog\w+|new product)"],60    },61    "product_launch": {62        "label": "New Product / Launch",63        "signals": [r"(launch\w*|introduc\w+|releas\w+|roll\w*[- ]?out|unveil\w+)"64                    r"[^.]{0,50}(product|platform|service|offering|solution|model|line)",65                    r"new product (line|launch|introduction)"],66    },67    "drug_pipeline": {68        "label": "Drug / Clinical Pipeline",69        "signals": [r"(phase\s?(i{1,3}|1|2|3)\b)", r"clinical trial", r"\bfda (approval|clearance|submission)\b",70                    r"(drug|therap\w+|candidate|molecule|pipeline)[^.]{0,40}(develop\w+|trial|approval)"],71    },72    "ma_integration": {73        "label": "M&A / Integration",74        "signals": [r"(acqui\w+|merg\w+|takeover|business combination)",75                    r"integration of [a-z]", r"purchase of [a-z]"],76    },77    "geographic_expansion": {78        "label": "Geographic Expansion",79        "signals": [r"(expand\w*|enter\w*|launch\w*|establish\w*)[^.]{0,50}"80                    r"(market|region|countr\w+|geograph\w+|international|overseas|abroad)",81                    r"market entry"],82    },83    "capex_program": {84        "label": "Capital Expenditure Program",85        "signals": [r"capital (expenditure|expenditures|investment|spending|program|project|budget)",86                    r"\bcapex\b", r"invest\w+[^.]{0,30}(\$|usd)"],87    },88    "supply_chain": {89        "label": "Supply Chain Initiative",90        "signals": [r"supply chain", r"(reshor\w+|nearshor\w+|onshor\w+|offshor\w+)",91                    r"(distribution|logistics|fulfillment|warehouse|inventory)[^.]{0,40}"92                    r"(network|center|optimi[sz]\w+|expansion|investment)"],93    },94    "automation": {95        "label": "Automation / Robotics",96        "signals": [r"automat\w+", r"robot\w+", r"(industrial|warehouse) automation"],97    },98    "sustainability": {99        "label": "Sustainability / ESG",100        "signals": [r"sustainabilit\w+", r"\besg\b", r"net[- ]zero", r"carbon (neutral|reduction|footprint|emissions?)",101                    r"decarboni[sz]\w+", r"emission reduction"],102    },103    "energy_transition": {104        "label": "Energy Transition / Renewables",105        "signals": [r"renewable (energy|power)", r"(solar|wind|hydrogen|geothermal|battery storage)[^.]{0,40}"106                    r"(project|facility|farm|plant|capacity|investment)",107                    r"clean energy", r"energy transition"],108    },109    "infrastructure": {110        "label": "Infrastructure Investment",111        "signals": [r"infrastructure (investment|project|upgrade|expansion|spending|build\w*)",112                    r"(network|grid|pipeline|broadband|fiber) (expansion|build\w*|upgrade|deployment)"],113    },114    "cost_reduction": {115        "label": "Cost Reduction / Restructuring",116        "signals": [r"(cost (reduction|savings|cutting)|restructur\w+|efficiency (program|initiative)|"117                    r"productivity (program|initiative)|streamlin\w+|optimi[sz]ation (program|initiative))"],118    },119    "partnership": {120        "label": "Strategic Partnership",121        "signals": [r"(strategic (partnership|alliance|collaboration)|joint venture|\bjv\b|"122                    r"(partner\w+|collaborat\w+|team\w* up)[^.]{0,40}(with))"],123    },124    "technology_deployment": {125        "label": "Technology Deployment",126        "signals": [r"(deploy\w+|implement\w+|roll\w*[- ]?out|adopt\w+|install\w+)[^.]{0,50}"127                    r"(technolog\w+|system|platform|software|solution|tool)"],128    },129}130131# ----------------------------------------------------------------------132# Technology vocabulary -> canonical tag133# ----------------------------------------------------------------------134TECHNOLOGY_TERMS: dict[str, str] = {135    r"\b(artificial intelligence|machine learning|generative ai|gen ?ai|deep learning|llm|neural)\b": "AI/ML",136    r"\bcloud\b": "Cloud",137    r"\b(erp|sap|oracle|workday|s/?4 ?hana|peoplesoft)\b": "ERP",138    r"\b(robot\w+|automation|cobots?)\b": "Robotics/Automation",139    r"\b(semiconductor|chip|wafer|fab|node\b|nanometer|foundry)\b": "Semiconductors",140    r"\b(solar|wind|renewable|hydrogen|geothermal)\b": "Renewable Energy",141    r"\b(battery|batteries|energy storage|lithium)\b": "Battery/Storage",142    r"\b(ev|electric vehicle|charging)\b": "Electric Vehicles",143    r"\b(blockchain|crypto|digital asset|tokeni[sz])\b": "Blockchain",144    r"\b(5g|broadband|fiber|fibre|network infrastructure)\b": "Connectivity",145    r"\b(data\s?cent(er|re)|hyperscale|server)\b": "Data Center",146    r"\b(cyber ?security|encryption|zero trust)\b": "Cybersecurity",147    r"\b(iot|internet of things|sensors?|edge computing)\b": "IoT/Edge",148    r"\b(quantum)\b": "Quantum",149    r"\b(biolog\w+|gene|mrna|crispr|cell therapy|molecul\w+)\b": "Biotech",150    r"\b(saas|software[- ]as[- ]a[- ]service|platform)\b": "Software/SaaS",151}152153# ----------------------------------------------------------------------154# Status keywords -> canonical status155# ----------------------------------------------------------------------156STATUS_TERMS: list[tuple[str, str]] = [157    (r"\b(completed|commission\w+|operational|in service|came online|opened|finalized)\b", "completed"),158    (r"\b(under construction|underway|in progress|ongoing|ramping|being (built|implemented))\b", "in_progress"),159    (r"\b(announc\w+|plan\w+|intend\w+|expect\w+|will (build|invest|launch|open)|propos\w+|"160     r"anticipat\w+|target\w+|by (20\d\d)|slated)\b", "planned"),161]162163# ----------------------------------------------------------------------164# Geography165# ----------------------------------------------------------------------166US_STATES = {167    "alabama","alaska","arizona","arkansas","california","colorado","connecticut","delaware",168    "florida","georgia","hawaii","idaho","illinois","indiana","iowa","kansas","kentucky",169    "louisiana","maine","maryland","massachusetts","michigan","minnesota","mississippi",170    "missouri","montana","nebraska","nevada","new hampshire","new jersey","new mexico",171    "new york","north carolina","north dakota","ohio","oklahoma","oregon","pennsylvania",172    "rhode island","south carolina","south dakota","tennessee","texas","utah","vermont",173    "virginia","washington","west virginia","wisconsin","wyoming",174}175COUNTRIES = {176    "united states","u.s.","usa","canada","mexico","china","india","japan","germany","france",177    "united kingdom","u.k.","ireland","brazil","singapore","south korea","korea","taiwan",178    "vietnam","thailand","malaysia","indonesia","australia","spain","italy","netherlands",179    "switzerland","sweden","poland","israel","saudi arabia","united arab emirates","uae",180    "philippines","czech republic","hungary","turkey","egypt","south africa","argentina","chile",181}182183_MONEY_RE = re.compile(184    r"(?:US)?\$\s?(\d[\d,]*\.?\d*)\s?(billion|bn|million|mm|mn|m\b|thousand|k\b|trillion)?",185    re.IGNORECASE,186)187_MULT = {188    "trillion": 1e12, "billion": 1e9, "bn": 1e9, "million": 1e6, "mm": 1e6, "mn": 1e6,189    "m": 1e6, "thousand": 1e3, "k": 1e3,190}191_YEAR_RE = re.compile(r"\b(?:fiscal\s+|fy\s?)?(20[0-3]\d|19[89]\d)\b", re.IGNORECASE)192_QUARTER_RE = re.compile(r"\bQ([1-4])\s?(20\d\d)\b", re.IGNORECASE)193_TECH_COMPILED = [(re.compile(p, re.IGNORECASE), tag) for p, tag in TECHNOLOGY_TERMS.items()]194_STATUS_COMPILED = [(re.compile(p, re.IGNORECASE), s) for p, s in STATUS_TERMS]195_CITY_STATE_RE = re.compile(r"\bin\s+([A-Z][a-zA-Z.]+(?:\s[A-Z][a-zA-Z.]+){0,2}),\s+([A-Z][a-zA-Z]+)")196197198def extract_money(text: str) -> tuple[float | None, str | None]:199    """Return (usd_amount, raw_span) for the largest explicit dollar figure."""200    best_val, best_raw = None, None201    for m in _MONEY_RE.finditer(text):202        try:203            num = float(m.group(1).replace(",", ""))204        except ValueError:205            continue206        unit = (m.group(2) or "").lower().strip()207        val = num * _MULT.get(unit, 1.0)208        if best_val is None or val > best_val:209            best_val, best_raw = val, m.group(0).strip()210    return best_val, best_raw211212213def extract_locations(text: str) -> list[str]:214    low = text.lower()215    found: list[str] = []216    for st in US_STATES:217        if re.search(rf"\b{re.escape(st)}\b", low):218            found.append(st.title())219    for co in COUNTRIES:220        if re.search(rf"\b{re.escape(co)}\b", low):221            found.append(co.upper() if len(co) <= 4 else co.title())222    for m in _CITY_STATE_RE.finditer(text):223        found.append(f"{m.group(1)}, {m.group(2)}")224    # de-dup preserving order225    seen, out = set(), []226    for f in found:227        k = f.lower()228        if k not in seen:229            seen.add(k); out.append(f)230    return out[:6]231232233def extract_years(text: str) -> list[int]:234    yrs = {int(y) for y in _YEAR_RE.findall(text)}235    for m in _QUARTER_RE.finditer(text):236        yrs.add(int(m.group(2)))237    return sorted(yrs)238239240def extract_technologies(text: str) -> list[str]:241    tags = []242    for rx, tag in _TECH_COMPILED:243        if rx.search(text) and tag not in tags:244            tags.append(tag)245    return tags246247248def detect_status(text: str) -> str:249    for rx, status in _STATUS_COMPILED:250        if rx.search(text):251            return status252    return "mentioned"253254255# pre-compile project-type signals256TYPE_SIGNALS = {257     ptype: [re.compile(sig, re.IGNORECASE) for sig in spec["signals"]]258    for ptype, spec in PROJECT_TYPES.items()259}260TYPE_LABELS = {k: v["label"] for k, v in PROJECT_TYPES.items()}261ALL_TYPES = list(PROJECT_TYPES.keys())262
Files · main
Menu

Theme