import pandas as pd from fuzzywuzzy import fuzz def match_inventory(entities_str, inventory_list, description=""): """Check entities column AND raw description text for inventory matches.""" matched = [] # Check entities column if entities_str and not pd.isna(entities_str): entities = [e.strip() for e in str(entities_str).split(",") if e.strip()] for inv_item in inventory_list: for entity in entities: if fuzz.partial_ratio(inv_item.lower(), entity.lower()) >= 75: matched.append(inv_item) break # Also check raw description text directly if description: desc_lower = description.lower() for inv_item in inventory_list: if inv_item not in matched: inv_lower = inv_item.lower() # Direct substring match in description if inv_lower in desc_lower: matched.append(inv_item) # Fuzzy match on key words (handles "Log4j" vs "log4j2") elif fuzz.partial_ratio(inv_lower, desc_lower) >= 85: matched.append(inv_item) return list(set(matched)) def compute_context_score(row, inventory_list, base_prob_critical): boost = 1.0 # Pass description to match_inventory for better matching matched = match_inventory( row.get("entities", ""), inventory_list, row.get("description", "") ) if matched: boost += 0.3 * len(matched) if row.get("exploit_available", 0) == 1: boost *= 1.25 if row.get("has_remote", 0) == 1 and row.get("has_unauth", 0) == 1: boost *= 1.15 if str(row.get("attack_vector", "")).upper() == "NETWORK": boost *= 1.10 context_score = min(float(base_prob_critical) * boost, 1.0) return { "context_score": round(context_score, 4), "matched_inventory": matched, "boost_factor": round(boost, 3) }