import re # --------------------------------------------------------------------------- # Reconstructed from master.ipynb (cells 182-189). # extract_attributes(desc) -> dict of parsed spec fields # attribute_score(attrs1, attrs2) -> weighted similarity in [0, 1] # --------------------------------------------------------------------------- PATTERNS = { # Item Type "ITEM": r"^([A-Z]+,[A-Z]+)", # Application "TYPE": r"TYPE(?:/APPLICATION)?[:=]\s*(.+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Sizes "SIZE": r"SIZE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", "RUN SIZE": r"RUN SIZE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", "BRANCH SIZE": r"BRANCH SIZE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Pressure "RATING": r"RATING(?:/SCHEDULE)?[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", "PRESSURE CLASS": r"CLASS[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Schedule "SCHEDULE": r"SCH(?:EDULE)?\.?\s*([0-9A-Z\-]+)", # Material "MATERIAL": r"MATERIAL[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Grade "GRADE": r"GRADE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Specification "SPECIFICATION": r"SPECIFICATION[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Standards "STANDARD": r"(ASME\s*B[0-9.\-]+|ASTM\s*A[0-9A-Z\-]+|API\s*[0-9A-Z\-]+|ANSI\s*B[0-9.\-]+|USAS\s*B[0-9.\-]+)", # Connection "CONNECTION": r"CONNECTION[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # End Connection "END CONNECTION": r"END CONNECTION[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Radius "RADIUS": r"RADIUS[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Degree "DEGREE": r"DEGREE[:=]\s*([0-9]+)", # Face "FACE": r"FACE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Ends "ENDS": r"ENDS[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Bore "BORE": r"BORE[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Thickness "THICKNESS": r"THICKNESS[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Length "LENGTH": r"LENGTH[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Diameter "DIAMETER": r"DIAMETER[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Manufacturer "MANUFACTURER": r"MANUFACTURER(?: CODE)?[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Thread "THREAD": r"THREAD(?: TYPE)?[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Seal "SEAL": r"SEAL[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Seat "SEAT": r"SEAT[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Disc "DISC": r"DISC[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Stem "STEM": r"STEM[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Body "BODY": r"BODY[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", # Trim "TRIM": r"TRIM[:=]\s*([^:]+?)(?=\s+[A-Z][A-Z /-]*:|$)", } WEIGHTS = { "ITEM": 0.05, "TYPE": 0.05, "SIZE": 0.15, "RUN SIZE": 0.08, "BRANCH SIZE": 0.08, "RATING": 0.12, "PRESSURE CLASS": 0.06, "SCHEDULE": 0.05, "MATERIAL": 0.08, "GRADE": 0.06, "SPECIFICATION": 0.05, "STANDARD": 0.04, "CONNECTION": 0.04, "END CONNECTION": 0.03, "RADIUS": 0.03, "DEGREE": 0.02, "FACE": 0.02, "ENDS": 0.02, "BORE": 0.02, "THICKNESS": 0.02, "LENGTH": 0.02, "DIAMETER": 0.02, "MANUFACTURER": 0.02, "THREAD": 0.02, "SEAL": 0.01, "SEAT": 0.01, "DISC": 0.01, "STEM": 0.01, "BODY": 0.01, "TRIM": 0.01, } NORMALIZATION = { # ---------- MATERIAL ---------- "SS": "STAINLESS STEEL", "S.S": "STAINLESS STEEL", "STAINLESS STEEL": "STAINLESS STEEL", "CS": "CARBON STEEL", "C.S": "CARBON STEEL", "CARBON STEEL": "CARBON STEEL", "MS": "MILD STEEL", "M.S": "MILD STEEL", "GI": "GALVANIZED IRON", # ---------- CONNECTION ---------- "BW": "BUTT WELD", "BUTT WELD": "BUTT WELD", "SW": "SOCKET WELD", "SOCKET WELD": "SOCKET WELD", "THD": "THREADED", "NPT": "THREADED", "BSP": "THREADED", "RF": "RAISED FACE", "FF": "FLAT FACE", "RTJ": "RING TYPE JOINT", # ---------- RADIUS ---------- "SR": "SHORT", "SHORT RADIUS": "SHORT", "SHORT": "SHORT", "LR": "LONG", "LONG RADIUS": "LONG", "LONG": "LONG", # ---------- PRESSURE ---------- "150#": "150 LB", "300#": "300 LB", "600#": "600 LB", "900#": "900 LB", "1500#": "1500 LB", "2500#": "2500 LB", # ---------- GRADE ---------- "304 SS": "F304", "SS304": "F304", "304": "F304", "316 SS": "F316", "SS316": "F316", "316": "F316", # ---------- SCHEDULE ---------- "SCH40": "SCH 40", "SCH80": "SCH 80", "SCH160": "SCH 160", # ---------- DEGREE ---------- "90 DEG": "90", "45 DEG": "45", } def extract_attributes(desc): desc = str(desc).upper() attrs = {} for key, pattern in PATTERNS.items(): m = re.search(pattern, desc) attrs[key] = m.group(1).strip() if m else "" return attrs def normalize_value(value): value = str(value).upper().strip() value = re.sub(r"\s+", " ", value) value = value.replace('"', " IN") value = value.replace("''", " IN") value = value.replace("INCH", "IN") value = value.replace("IN.", "IN") value = value.strip() if value in NORMALIZATION: value = NORMALIZATION[value] return value def normalize_attributes(attrs): return {key: normalize_value(value) for key, value in attrs.items()} def attribute_score(a, b): a = normalize_attributes(a) b = normalize_attributes(b) score = 0 total = 0 for key, weight in WEIGHTS.items(): total += weight if a.get(key, "") == b.get(key, "") and a.get(key, "") != "": score += weight return score / total