import json import re from typing import Any, Dict, List, Optional NETWORK_KINDS = {"internet_access", "ip_allocation"} def parse_product_attributes(raw: Any) -> Dict[str, Any]: if raw is None: return {} if isinstance(raw, dict): return raw if isinstance(raw, str): text = raw.strip() if not text: return {} try: parsed = json.loads(text) except json.JSONDecodeError: return {} return parsed if isinstance(parsed, dict) else {} return {} def _parse_speed_from_text(text: str) -> Dict[str, Optional[int]]: match = re.search(r"(\d+)\s*/\s*(\d+)\s*(?:mbit|mbps|gbit|gbps)?", text, re.IGNORECASE) if not match: return {"speed_mbps": None, "download_mbps": None, "upload_mbps": None} download = int(match.group(1)) upload = int(match.group(2)) if re.search(r"(gbit|gbps)", text, re.IGNORECASE): download *= 1000 upload *= 1000 return { "speed_mbps": max(download, upload), "download_mbps": download, "upload_mbps": upload, } def _parse_prefix_from_text(text: str) -> Optional[int]: match = re.search(r"/(\d{1,2})", text) if not match: return None try: prefix = int(match.group(1)) except ValueError: return None return prefix if 0 <= prefix <= 32 else None def build_network_product_profile(product: Dict[str, Any], fallback_text: Optional[str] = None) -> Dict[str, Any]: attributes = parse_product_attributes(product.get("attributes_json")) network = attributes.get("network") if isinstance(attributes.get("network"), dict) else {} text = " ".join( part for part in [ str(product.get("name") or "").strip(), str(product.get("product_name") or "").strip(), str(product.get("description") or "").strip(), str(fallback_text or "").strip(), ] if part ) lowered = text.lower() kind = ( network.get("kind") or attributes.get("network_kind") or product.get("network_kind") or product.get("type") ) if kind not in NETWORK_KINDS: if "/3" in lowered and "ip" in lowered: kind = "ip_allocation" elif re.search(r"/\d{1,2}", lowered) and "ip" in lowered: kind = "ip_allocation" elif "bmcnet" in lowered or "internet" in lowered or "fiber" in lowered: kind = "internet_access" else: kind = None speeds = _parse_speed_from_text(text) speed_mbps = network.get("speed_mbps") or attributes.get("speed_mbps") or speeds["speed_mbps"] download_mbps = network.get("download_mbps") or attributes.get("download_mbps") or speeds["download_mbps"] upload_mbps = network.get("upload_mbps") or attributes.get("upload_mbps") or speeds["upload_mbps"] ip_prefix_length = network.get("ip_prefix_length") or attributes.get("ip_prefix_length") or _parse_prefix_from_text(text) connection_type = network.get("connection_type") or attributes.get("connection_type") return { "kind": kind, "is_network_product": kind in NETWORK_KINDS, "requires_provisioning": kind in NETWORK_KINDS, "speed_mbps": int(speed_mbps) if speed_mbps is not None else None, "download_mbps": int(download_mbps) if download_mbps is not None else None, "upload_mbps": int(upload_mbps) if upload_mbps is not None else None, "ip_prefix_length": int(ip_prefix_length) if ip_prefix_length is not None else None, "connection_type": connection_type, "attributes": attributes, } def summarize_subscription_network_requirements(line_items: List[Dict[str, Any]]) -> Dict[str, Any]: internet_items: List[Dict[str, Any]] = [] ip_items: List[Dict[str, Any]] = [] for item in line_items or []: profile = build_network_product_profile(item, fallback_text=item.get("description")) if not profile["requires_provisioning"]: continue entry = { "subscription_item_id": item.get("id"), "line_no": item.get("line_no"), "product_id": item.get("product_id"), "product_name": item.get("product_name") or item.get("description"), "description": item.get("description"), "quantity": item.get("quantity"), "unit_price": item.get("unit_price"), "line_total": item.get("line_total"), "network_kind": profile["kind"], "speed_mbps": profile["speed_mbps"], "download_mbps": profile["download_mbps"], "upload_mbps": profile["upload_mbps"], "ip_prefix_length": profile["ip_prefix_length"], "connection_type": profile["connection_type"], } if profile["kind"] == "internet_access": internet_items.append(entry) elif profile["kind"] == "ip_allocation": ip_items.append(entry) primary_internet_item = internet_items[0] if internet_items else None return { "requires_provisioning": bool(internet_items or ip_items), "internet_items": internet_items, "ip_items": ip_items, "primary_internet_item": primary_internet_item, "required_ip_prefixes": [item["ip_prefix_length"] for item in ip_items if item.get("ip_prefix_length") is not None], }