| import re
|
| import pandas as pd
|
| from typing import Dict
|
|
|
|
|
| class QSRuleBasedFallbackEngine:
|
| """
|
| Deterministic rule-based classifier utilizing expert QS heuristics.
|
|
|
| This engine executes ONLY on items rejected by the primary ML model to safely
|
| route rare classes, nested composite descriptions, and pricing schedules.
|
| It relies on Python's dictionary insertion order to enforce 'Specificity Cascading'.
|
| """
|
|
|
| def __init__(self, rule_map: Dict[str, str]):
|
| """
|
| Initializes the engine and pre-compiles regex patterns for high-speed inference.
|
|
|
| Parameters
|
| ----------
|
| rule_map : Dict[str, str]
|
| A dictionary mapping regex patterns to NRM Categories.
|
| Keys must be ordered from most specific to least specific.
|
| """
|
|
|
| self.rules = {
|
| re.compile(pattern): category for pattern, category in rule_map.items()
|
| }
|
|
|
| def predict(self, descriptions: pd.Series) -> pd.Series:
|
| """
|
| Vectorized application of expert heuristics.
|
|
|
| Parameters
|
| ----------
|
| descriptions : pd.Series
|
| Pandas Series containing the BoQ text descriptions.
|
|
|
| Returns
|
| -------
|
| pd.Series
|
| A Series of predicted NRM categories, or 'Unclassified' if no rules match.
|
| """
|
|
|
| results = pd.Series("Unclassified", index=descriptions.index)
|
|
|
|
|
| clean_desc = descriptions.fillna("").astype(str)
|
|
|
|
|
| for pattern, category in self.rules.items():
|
|
|
| unclassified_mask = results == "Unclassified"
|
|
|
|
|
|
|
| if not unclassified_mask.any():
|
| break
|
|
|
|
|
|
|
| match_mask = clean_desc.apply(lambda x: bool(pattern.search(x)))
|
|
|
|
|
| results.loc[unclassified_mask & match_mask] = category
|
|
|
| return results
|
|
|