| import pandas as pd
|
|
|
| from .engines import QSRuleBasedFallbackEngine
|
| from .model import QSSelectiveCalibratedModel
|
|
|
|
|
| def predict_with_cascade(
|
| df_raw: pd.DataFrame,
|
| ml_model: QSSelectiveCalibratedModel,
|
| fallback_engine: QSRuleBasedFallbackEngine,
|
| ) -> pd.DataFrame:
|
| """
|
| Executes the ultimate MLOps Cascade Architecture.
|
|
|
| Workflow:
|
| 1. Run the highly-precise ML Model.
|
| 2. Identify rejected items (routed to 'Others').
|
| 3. Pass ONLY the rejected items to the deterministic Rule Engine.
|
| 4. Merge the results, tag the provenance/source, and re-infer NRM hierarchies.
|
|
|
| Parameters
|
| ----------
|
| df_raw : pd.DataFrame
|
| The raw incoming BoQ data.
|
| ml_model : QSSelectiveCalibratedModel
|
| The loaded, hermetic ML production object.
|
| fallback_engine : QSRuleBasedFallbackEngine
|
| The initialized rule-based regex matcher.
|
|
|
| Returns
|
| -------
|
| pd.DataFrame
|
| The final DataFrame with completely resolved predictions and provenance tagging.
|
| """
|
|
|
|
|
|
|
| df_results = ml_model.predict_full(df_raw)
|
|
|
|
|
|
|
|
|
| others_mask = df_results["Predicted_Category"] == ml_model.others_label
|
|
|
|
|
| df_results["Prediction_Source"] = "ML_Model"
|
|
|
|
|
| if not others_mask.any():
|
| return df_results
|
|
|
| ml_model._logger.info(f"Fallback triggered for {others_mask.sum()} items.")
|
|
|
|
|
|
|
|
|
|
|
| rejected_descriptions = df_results.loc[others_mask, "description"]
|
| rule_predictions = fallback_engine.predict(rejected_descriptions)
|
|
|
|
|
|
|
|
|
|
|
| df_results.loc[others_mask, "Predicted_Category"] = rule_predictions
|
| df_results.loc[others_mask, "Prediction_Source"] = "Rule_Engine"
|
|
|
|
|
| df_results.loc[
|
| others_mask & (rule_predictions != "Unclassified"), "Prediction_Confidence"
|
| ] = 1.0
|
| df_results.loc[
|
| others_mask & (rule_predictions == "Unclassified"), "Prediction_Confidence"
|
| ] = 0.0
|
|
|
|
|
|
|
|
|
|
|
| rescued_mask = others_mask & (df_results["Predicted_Category"] != "Unclassified")
|
|
|
| if rescued_mask.any():
|
|
|
| rescued_hierarchies = df_results.loc[rescued_mask, "Predicted_Category"].apply(
|
| lambda cat: ml_model.infer_nrm_hierarchy(cat)
|
| )
|
| rescued_df = pd.DataFrame(
|
| list(rescued_hierarchies), index=rescued_hierarchies.index
|
| )
|
|
|
|
|
| for level in ["ge", "group_element", "e", "element"]:
|
| pred_col = f"Predicted_{level}"
|
|
|
|
|
| if pred_col in df_results.columns:
|
| df_results.loc[rescued_mask, pred_col] = rescued_df[level]
|
|
|
| elif level in df_results.columns:
|
| df_results.loc[rescued_mask, level] = rescued_df[level]
|
|
|
| return df_results
|
|
|