ffc / attributes.py
waseem11's picture
Upload attributes.py
3a16e9d verified
Raw
History Blame Contribute Delete
5.74 kB
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