MakPr016
commited on
Commit
·
e9170b7
1
Parent(s):
8deea46
Update
Browse files
main.py
CHANGED
|
@@ -19,6 +19,169 @@ os.makedirs(DATA_DIR, exist_ok=True)
|
|
| 19 |
|
| 20 |
_MASTER_INDEX_CACHE = {}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def load_master_index():
|
| 23 |
global _MASTER_INDEX_CACHE
|
| 24 |
if _MASTER_INDEX_CACHE: return _MASTER_INDEX_CACHE
|
|
@@ -47,45 +210,23 @@ def is_garbage_row(row_text: str) -> bool:
|
|
| 47 |
def determine_item_type(description: str, form: str) -> str:
|
| 48 |
"""
|
| 49 |
Determines the category of the item based on its description and form/unit.
|
| 50 |
-
|
| 51 |
"""
|
| 52 |
text = (description + " " + form).lower()
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
equipment_keywords = [
|
| 68 |
-
'thermometer', 'sphygmomanometer', 'stethoscope', 'oximeter',
|
| 69 |
-
'glucometer', 'nebulizer', 'otoscope', 'penlight', 'monitor',
|
| 70 |
-
'scale', 'microscope', 'centrifuge', 'refrigerator', 'cool box',
|
| 71 |
-
'freezer', 'lamp', 'bed', 'chair', 'pump', 'bp machine', 'device'
|
| 72 |
-
]
|
| 73 |
-
if any(k in text for k in equipment_keywords):
|
| 74 |
-
return 'Medical Equipment'
|
| 75 |
-
|
| 76 |
-
# Priority 3: Pharmaceuticals (Medicines/Drugs)
|
| 77 |
-
pharma_keywords = [
|
| 78 |
-
'tablet', 'capsule', 'cap', 'tab', 'syrup', 'suspension', 'susp',
|
| 79 |
-
'injection', 'inj', 'ampoule', 'amp', 'vial', 'cream', 'ointment',
|
| 80 |
-
'gel', 'suppository', 'supp', 'drops', 'inhaler', 'vaccine', 'sera',
|
| 81 |
-
'insulin', 'medicine', 'drug', 'mg', 'ml', 'mcg', 'iu', 'dose',
|
| 82 |
-
'solution', 'infusion', 'spray', 'lozenge'
|
| 83 |
-
]
|
| 84 |
-
if any(k in text for k in pharma_keywords):
|
| 85 |
-
return 'Pharmaceuticals'
|
| 86 |
-
|
| 87 |
-
# Fallback
|
| 88 |
-
return 'Medical Supplies'
|
| 89 |
|
| 90 |
async def delete_file_safety_net(file_path: str, delay: int = 600):
|
| 91 |
await asyncio.sleep(delay)
|
|
@@ -215,20 +356,18 @@ async def match_all(req: MatchRequest):
|
|
| 215 |
|
| 216 |
matches = []
|
| 217 |
for v in vendors:
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
'score': 9.5
|
| 231 |
-
})
|
| 232 |
|
| 233 |
results.append({
|
| 234 |
"medicine": name,
|
|
|
|
| 19 |
|
| 20 |
_MASTER_INDEX_CACHE = {}
|
| 21 |
|
| 22 |
+
# --- CATEGORY DEFINITIONS ---
|
| 23 |
+
# Ordered by specificity. "Whole word" matching will apply to these keywords.
|
| 24 |
+
CATEGORY_DEFINITIONS = {
|
| 25 |
+
"Pharmaceuticals & Biologics": [
|
| 26 |
+
"tablet", "tab", "capsule", "cap", "syrup", "suspension", "susp", "injection", "inj", "vial", "ampoule", "amp",
|
| 27 |
+
"drops", "gtt", "inhaler", "vaccine", "insulin", "dose", "drug", "medication", "ointment", "cream", "gel",
|
| 28 |
+
"lotion", "suppository", "supp", "antibiotic", "antiviral", "analgesic", "anesthetic", "hormone", "steroid",
|
| 29 |
+
"vitamin", "mineral", "supplement", "lozenge", "patch", "solution", "powder for suspension", "elixir", "serum",
|
| 30 |
+
"antitoxin"
|
| 31 |
+
],
|
| 32 |
+
"Surgical Products": [
|
| 33 |
+
"scalpel", "forceps", "retractor", "clamp", "suture", "stapler", "surgical mesh", "hemostatic", "sealant",
|
| 34 |
+
"surgical drape", "surgical gown", "laparoscopic", "robotic surgery", "electrosurgical", "surgical laser",
|
| 35 |
+
"surgical blade", "trocar", "surgical clip", "surgical scissor", "needle holder"
|
| 36 |
+
],
|
| 37 |
+
"Orthopedic & Spine": [
|
| 38 |
+
"orthopedic", "spine", "joint replacement", "trauma fixation", "bone plate", "bone screw", "intramedullary rod",
|
| 39 |
+
"bone nail", "spinal implant", "spinal fusion", "bone graft", "orthopedic brace", "cast", "arthroscopy",
|
| 40 |
+
"fixator", "prosthesis", "bone drill", "bone saw"
|
| 41 |
+
],
|
| 42 |
+
"Cardiovascular Products": [
|
| 43 |
+
"cardiac stent", "pacemaker", "defibrillator", "icd", "heart valve", "vascular graft", "cardiac catheter",
|
| 44 |
+
"guidewire", "cardiac balloon", "ablation", "coronary", "angioplasty", "introducer sheath"
|
| 45 |
+
],
|
| 46 |
+
"Medical Imaging Equipment": [
|
| 47 |
+
"mri", "ct scanner", "x-ray", "ultrasound", "mammography", "fluoroscopy", "pet scanner", "c-arm",
|
| 48 |
+
"medical imaging", "transducer", "x-ray film", "contrast media", "lead apron"
|
| 49 |
+
],
|
| 50 |
+
"Diagnostic Products": [
|
| 51 |
+
"diagnostic", "test kit", "glucose test", "reagent", "immunoassay", "chemistry analyzer", "hematology",
|
| 52 |
+
"microbiology", "culture media", "pregnancy test", "covid", "rapid test", "urinalysis", "penlight",
|
| 53 |
+
"specula", "otoscope", "ophthalmoscope", "lancet", "glucometer strips", "test strip"
|
| 54 |
+
],
|
| 55 |
+
"Patient Monitoring Equipment": [
|
| 56 |
+
"vital signs", "ecg", "ekg", "pulse oximeter", "blood pressure monitor", "sphygmomanometer",
|
| 57 |
+
"medical thermometer", "capnography", "fetal monitor", "telemetry", "spo2 sensor", "bp cuff", "temperature probe"
|
| 58 |
+
],
|
| 59 |
+
"Respiratory & Anesthesia": [
|
| 60 |
+
"ventilator", "anesthesia machine", "oxygen concentrator", "nebulizer", "cpap", "bipap", "respiratory",
|
| 61 |
+
"endotracheal", "tracheostomy", "spirometer", "oxygen mask", "breathing circuit", "nasal cannula",
|
| 62 |
+
"resuscitator", "laryngoscope"
|
| 63 |
+
],
|
| 64 |
+
"Infusion & Vascular Access": [
|
| 65 |
+
"infusion pump", "syringe pump", "iv set", "iv catheter", "venous", "picc", "iv port", "dialysis catheter",
|
| 66 |
+
"administration set", "extension set", "stopcock", "giving set", "saline", "dextrose", "ringer",
|
| 67 |
+
"sodium chloride", "water for injection"
|
| 68 |
+
],
|
| 69 |
+
"Wound Care & Tissue Management": [
|
| 70 |
+
"wound dressing", "bandage", "gauze", "medical tape", "plaster", "adhesive", "wound foam", "alginate",
|
| 71 |
+
"hydrocolloid", "compression bandage", "ostomy", "skin substitute", "negative pressure"
|
| 72 |
+
],
|
| 73 |
+
"Dialysis & Renal Care": [
|
| 74 |
+
"hemodialysis", "peritoneal", "dialyzer", "blood line", "fistula needle", "dialysis concentrate", "bicarbonate"
|
| 75 |
+
],
|
| 76 |
+
"Ophthalmic Products": [
|
| 77 |
+
"intraocular", "intraocular lens", "phaco", "vitrectomy", "lasik", "contact lens", "viscoelastic",
|
| 78 |
+
"ophthalmic solution", "eye drops"
|
| 79 |
+
],
|
| 80 |
+
"Dental Products": [
|
| 81 |
+
"dental implant", "orthodontic", "dental bracket", "dental wire", "dental drill", "dental handpiece",
|
| 82 |
+
"dental cement", "dental composite", "amalgam", "impression material", "teeth whitening", "dental chair"
|
| 83 |
+
],
|
| 84 |
+
"Neurology & Neurosurgery": [
|
| 85 |
+
"neurostimulation", "spinal cord stimulator", "neuro coil", "flow diverter", "cranial", "shunt",
|
| 86 |
+
"neuro electrode", "eeg", "emg"
|
| 87 |
+
],
|
| 88 |
+
"Laboratory Equipment & Supplies": [
|
| 89 |
+
"microscope", "lab centrifuge", "incubator", "autoclave", "pipette", "glassware", "test tube", "petri dish",
|
| 90 |
+
"flask", "beaker", "microscope slide", "cover glass", "fume hood", "biosafety cabinet"
|
| 91 |
+
],
|
| 92 |
+
"Personal Protective Equipment (PPE)": [
|
| 93 |
+
"ppe", "n95", "face shield", "safety eyewear", "goggles", "protective apron", "shoe cover", "head cover",
|
| 94 |
+
"coverall", "isolation gown", "hazmat", "surgical mask"
|
| 95 |
+
],
|
| 96 |
+
"Sterilization & Disinfection": [
|
| 97 |
+
"sterilization", "disinfectant", "antiseptic", "povidone", "iodine", "chlorhexidine", "alcohol swab",
|
| 98 |
+
"hand sanitizer", "medical soap", "enzymatic cleaner", "detergent", "washer disinfector", "sterilizer",
|
| 99 |
+
"sterilization indicator"
|
| 100 |
+
],
|
| 101 |
+
"Hospital Furniture & Equipment": [
|
| 102 |
+
"hospital bed", "examination table", "stretcher", "medical trolley", "medical cart", "medical cabinet",
|
| 103 |
+
"bedside locker", "overbed table", "iv pole", "wheelchair"
|
| 104 |
+
],
|
| 105 |
+
"Rehabilitation & Physical Therapy": [
|
| 106 |
+
"rehabilitation", "physiotherapy", "walker", "walking cane", "crutch", "exercise band", "traction",
|
| 107 |
+
"electrotherapy", "massage table", "orthosis"
|
| 108 |
+
],
|
| 109 |
+
"Home Healthcare Products": [
|
| 110 |
+
"home care", "blood glucose meter", "hearing aid", "mobility aid", "bathroom safety", "commode"
|
| 111 |
+
],
|
| 112 |
+
"Emergency & Trauma Care": [
|
| 113 |
+
"emergency kit", "trauma kit", "first aid", "aed", "defibrillator", "manual resuscitator", "suction unit",
|
| 114 |
+
"immobilizer", "cervical collar", "splint", "tourniquet", "crash cart"
|
| 115 |
+
],
|
| 116 |
+
"Maternal & Neonatal Care": [
|
| 117 |
+
"maternal", "neonatal", "infant incubator", "infant warmer", "phototherapy", "breast pump", "obstetric",
|
| 118 |
+
"birthing bed", "fetal doppler", "umbilical"
|
| 119 |
+
],
|
| 120 |
+
"Urology Products": [
|
| 121 |
+
"urology", "foley catheter", "urine bag", "urinary drainage", "ureteral stent", "stone basket"
|
| 122 |
+
],
|
| 123 |
+
"Gastroenterology & Endoscopy": [
|
| 124 |
+
"endoscope", "gastroscope", "colonoscope", "biopsy forceps", "polypectomy snare", "gastric balloon", "ercp"
|
| 125 |
+
],
|
| 126 |
+
"Oncology Products": [
|
| 127 |
+
"oncology", "chemotherapy", "radiotherapy", "brachytherapy", "port-a-cath", "cancer diagnostic"
|
| 128 |
+
],
|
| 129 |
+
"Pain Management": [
|
| 130 |
+
"pain management", "pca pump", "epidural", "nerve block", "tens unit"
|
| 131 |
+
],
|
| 132 |
+
"Sleep Medicine": [
|
| 133 |
+
"sleep apnea", "cpap mask", "bipap mask", "sleep tubing", "polysomnography"
|
| 134 |
+
],
|
| 135 |
+
"Telemedicine & Digital Health": [
|
| 136 |
+
"telemedicine", "telehealth", "remote monitor", "medical software", "health app"
|
| 137 |
+
],
|
| 138 |
+
"Blood Management": [
|
| 139 |
+
"blood bag", "blood transfusion", "blood bank", "blood warmer", "apheresis"
|
| 140 |
+
],
|
| 141 |
+
"Mortuary & Pathology": [
|
| 142 |
+
"mortuary", "autopsy", "body bag", "morgue fridge", "dissection table", "microtome", "tissue processor"
|
| 143 |
+
],
|
| 144 |
+
"Environmental Control": [
|
| 145 |
+
"medical gas", "medical vacuum", "medical air plant", "gas manifold", "gas outlet", "gas alarm"
|
| 146 |
+
],
|
| 147 |
+
"Mobility & Accessibility": [
|
| 148 |
+
"patient lift", "patient hoist", "wheelchair ramp", "stair lift", "transfer board"
|
| 149 |
+
],
|
| 150 |
+
"Bariatric Products": [
|
| 151 |
+
"bariatric bed", "bariatric wheelchair", "heavy duty scale"
|
| 152 |
+
],
|
| 153 |
+
"Medical Textiles": [
|
| 154 |
+
"hospital linen", "bed sheet", "pillow case", "medical blanket", "towel", "privacy curtain", "medical uniform",
|
| 155 |
+
"scrub suit", "lab coat"
|
| 156 |
+
],
|
| 157 |
+
"Infection Control Products": [
|
| 158 |
+
"waste bin", "sharps container", "biohazard bag", "spill kit", "air purifier"
|
| 159 |
+
],
|
| 160 |
+
"Medical Gases & Cryogenics": [
|
| 161 |
+
"gas cylinder", "oxygen regulator", "flowmeter", "liquid oxygen", "nitrogen tank"
|
| 162 |
+
],
|
| 163 |
+
"Nutrition & Feeding": [
|
| 164 |
+
"enteral feeding", "clinical nutrition", "nasogastric tube", "feeding pump", "feeding set", "peg tube"
|
| 165 |
+
],
|
| 166 |
+
"Specimen Collection & Transport": [
|
| 167 |
+
"specimen container", "sample collection", "transport media", "transport swab", "urine container",
|
| 168 |
+
"stool container", "cool box", "transport bag"
|
| 169 |
+
],
|
| 170 |
+
"Medical Software & IT": [
|
| 171 |
+
"emr", "ehr", "pacs", "ris", "lis", "his", "hospital information system"
|
| 172 |
+
],
|
| 173 |
+
"Aesthetics & Dermatology": [
|
| 174 |
+
"dermatology", "aesthetic laser", "ipl", "dermal filler", "botulinum", "botox", "chemical peel",
|
| 175 |
+
"microdermabrasion"
|
| 176 |
+
],
|
| 177 |
+
# Catch-all for basic items not caught above
|
| 178 |
+
"Medical Supplies & Consumables": [
|
| 179 |
+
"syringe", "needle", "glove", "examination glove", "disposable", "consumable", "cotton wool", "alcohol prep",
|
| 180 |
+
"urinal", "bedpan", "underpad", "tongue depressor", "applicator", "lubricant jelly", "cannula"
|
| 181 |
+
# Note: Cannula is here as fallback if not specific nasal/iv
|
| 182 |
+
]
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
def load_master_index():
|
| 186 |
global _MASTER_INDEX_CACHE
|
| 187 |
if _MASTER_INDEX_CACHE: return _MASTER_INDEX_CACHE
|
|
|
|
| 210 |
def determine_item_type(description: str, form: str) -> str:
|
| 211 |
"""
|
| 212 |
Determines the category of the item based on its description and form/unit.
|
| 213 |
+
Uses regex for whole-word matching to prevent substring errors (e.g. 'fusion' in 'infusion').
|
| 214 |
"""
|
| 215 |
text = (description + " " + form).lower()
|
| 216 |
|
| 217 |
+
for category, keywords in CATEGORY_DEFINITIONS.items():
|
| 218 |
+
for k in keywords:
|
| 219 |
+
# \b matches word boundaries.
|
| 220 |
+
# This ensures "fusion" matches "spinal fusion" but NOT "infusion".
|
| 221 |
+
# It ensures "coat" matches "lab coat" but NOT "coated".
|
| 222 |
+
# We escape the keyword to handle any special characters safely.
|
| 223 |
+
pattern = r'\b' + re.escape(k) + r'\b'
|
| 224 |
+
|
| 225 |
+
if re.search(pattern, text):
|
| 226 |
+
return category
|
| 227 |
+
|
| 228 |
+
# Fallback default
|
| 229 |
+
return 'Medical Supplies & Consumables'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
async def delete_file_safety_net(file_path: str, delay: int = 600):
|
| 232 |
await asyncio.sleep(delay)
|
|
|
|
| 356 |
|
| 357 |
matches = []
|
| 358 |
for v in vendors:
|
| 359 |
+
# Simplified matching logic for demonstration
|
| 360 |
+
matches.append({
|
| 361 |
+
'vendor_id': v.get('vendor_id'),
|
| 362 |
+
'name': v.get('legal_name'),
|
| 363 |
+
'country': (v.get('countries_served') or ['Unknown'])[0],
|
| 364 |
+
'landedCost': v.get('landedCost', 10),
|
| 365 |
+
'deliveryDays': v.get('deliveryDays', 5),
|
| 366 |
+
'availableQty': v.get('availableQty', 1000),
|
| 367 |
+
'qualityScore': v.get('confidence_score', 80) / 10.0,
|
| 368 |
+
'reliabilityScore': 5,
|
| 369 |
+
'score': 9.5
|
| 370 |
+
})
|
|
|
|
|
|
|
| 371 |
|
| 372 |
results.append({
|
| 373 |
"medicine": name,
|