""" ComplexMoGAPI Calculator ======================== Implements the Total Greenness Score (TGS) for Complex Modified GAPI. Scoring scale (per article Table 1): Green = 3 pts (best / lowest environmental impact) Yellow = 2 pts Red = 1 pt (worst / highest environmental impact) TGS = sum of all parameter scores. Max TGS = 21 params × 3 = 63 (all-green) Min TGS = 21 params × 1 = 21 (all-red) """ from typing import Dict, Any # ─── PRE-ANALYSIS PARAMETERS ────────────────────────────────────────────────── PRE_ANALYSIS_PARAMS = [ { "id": "pre_yield", "label": "Yield / Selectivity", "block": "Pre-Analysis", "options": [ {"label": ">89%", "score": 3, "color": "green"}, {"label": "70–89%", "score": 2, "color": "yellow"}, {"label": "<70%", "score": 1, "color": "red"}, {"label": "Not applicable", "score": 0, "color": "muted"}, ], }, { "id": "pre_temp_time", "label": "Temperature / Time", "block": "Pre-Analysis", "options": [ {"label": "Room temperature, <1 h", "score": 3, "color": "green"}, {"label": "Room temperature, >1 h OR Heating/Cooling, <1 h OR Cooling to 0°C", "score": 2, "color": "yellow"}, {"label": "Heating, >1 h OR Cooling <0°C", "score": 1, "color": "red"}, {"label": "Not applicable", "score": 0, "color": "muted"}, ], }, { "id": "pre_green_economy", "label": "Relation to Green Economy (rules met)", "block": "Pre-Analysis", "options": [ {"label": "5–6 rules met", "score": 3, "color": "green"}, {"label": "3–4 rules met", "score": 2, "color": "yellow"}, {"label": "1–2 rules met", "score": 1, "color": "red"}, ], }, { "id": "pre_health_hazard", "label": "Reagents Health Hazard (NFPA)", "block": "Pre-Analysis", "options": [ {"label": "NFPA health 0–1 (slightly toxic/irritant)", "score": 3, "color": "green"}, {"label": "NFPA health 2–3 (moderately toxic)", "score": 2, "color": "yellow"}, {"label": "NFPA health 4 (serious injury / carcinogen)", "score": 1, "color": "red"}, ], }, { "id": "pre_safety_hazard", "label": "Reagents Safety Hazard (NFPA flammability)", "block": "Pre-Analysis", "options": [ {"label": "NFPA flammability/instability 0–1, no special hazards", "score": 3, "color": "green"}, {"label": "NFPA flammability/instability 2–3, or special hazard", "score": 2, "color": "yellow"}, {"label": "NFPA flammability/instability 4", "score": 1, "color": "red"}, ], }, { "id": "pre_instrument", "label": "Technical Setup (Instrumentation)", "block": "Pre-Analysis", "options": [ {"label": "Common setup", "score": 3, "color": "green"}, {"label": "Additional/semi-advanced instruments", "score": 2, "color": "yellow"}, {"label": "Pressure >1 atm or glove box", "score": 1, "color": "red"}, ], }, { "id": "pre_energy", "label": "Energy Consumption", "block": "Pre-Analysis", "options": [ {"label": "≤0.1 kWh per sample", "score": 3, "color": "green"}, {"label": "≤1.5 kWh per sample", "score": 2, "color": "yellow"}, {"label": ">1.5 kWh per sample", "score": 1, "color": "red"}, ], }, { "id": "pre_occupational", "label": "Occupational Hazard", "block": "Pre-Analysis", "options": [ {"label": "Hermetization of the process", "score": 3, "color": "green"}, {"label": "—", "score": 2, "color": "yellow"}, {"label": "Emission of vapours to atmosphere", "score": 1, "color": "red"}, ], }, { "id": "pre_workup", "label": "Workup and Purification", "block": "Pre-Analysis", "options": [ {"label": "None or simple processes", "score": 3, "color": "green"}, {"label": "Standard purification techniques", "score": 2, "color": "yellow"}, {"label": "Advanced purification techniques", "score": 1, "color": "red"}, {"label": "Not applicable", "score": 0, "color": "muted"}, ], }, { "id": "pre_purity", "label": "Purity of End Product", "block": "Pre-Analysis", "options": [ {"label": ">98%", "score": 3, "color": "green"}, {"label": "97–98%", "score": 2, "color": "yellow"}, {"label": "<97%", "score": 1, "color": "red"}, {"label": "Not applicable", "score": 0, "color": "muted"}, ], }, ] # ─── ANALYTICAL PROCEDURE PARAMETERS ────────────────────────────────────────── ANALYTICAL_PARAMS = [ # Sample Preparation { "id": "an_collection", "label": "Collection (1)", "block": "Sample Preparation", "options": [ {"label": "In-line", "score": 3, "color": "green"}, {"label": "On-line or at-line", "score": 2, "color": "yellow"}, {"label": "Off-line", "score": 1, "color": "red"}, ], }, { "id": "an_preservation", "label": "Preservation (2)", "block": "Sample Preparation", "options": [ {"label": "None", "score": 3, "color": "green"}, {"label": "Chemical or physical", "score": 2, "color": "yellow"}, {"label": "Physicochemical", "score": 1, "color": "red"}, ], }, { "id": "an_transport", "label": "Transport (3)", "block": "Sample Preparation", "options": [ {"label": "None", "score": 3, "color": "green"}, {"label": "Required", "score": 2, "color": "yellow"}, {"label": "—", "score": 1, "color": "red"}, ], }, { "id": "an_storage", "label": "Storage (4)", "block": "Sample Preparation", "options": [ {"label": "None", "score": 3, "color": "green"}, {"label": "Under normal conditions", "score": 2, "color": "yellow"}, {"label": "Under special conditions", "score": 1, "color": "red"}, ], }, { "id": "an_method_type", "label": "Type of method: direct/indirect (5)", "block": "Sample Preparation", "options": [ {"label": "No sample preparation", "score": 3, "color": "green"}, {"label": "Simple procedures (filtration, decantation)", "score": 2, "color": "yellow"}, {"label": "Extraction required", "score": 1, "color": "red"}, ], }, { "id": "an_extraction_scale", "label": "Scale of extraction (6)", "block": "Sample Preparation", "options": [ {"label": "Nanoextraction", "score": 3, "color": "green"}, {"label": "Microextraction", "score": 2, "color": "yellow"}, {"label": "Macroextraction", "score": 1, "color": "red"}, {"label": "Not applicable", "score": 0, "color": "muted"}, ], }, { "id": "an_solvents_type", "label": "Solvents/Reagents used (7)", "block": "Sample Preparation", "options": [ {"label": "Solvent-free methods", "score": 3, "color": "green"}, {"label": "Green solvents/reagents", "score": 2, "color": "yellow"}, {"label": "Non-green solvents/reagents", "score": 1, "color": "red"}, ], }, { "id": "an_additional_treatments", "label": "Additional treatments (8)", "block": "Sample Preparation", "options": [ {"label": "None", "score": 3, "color": "green"}, {"label": "Simple (extract clean-up, solvent removal)", "score": 2, "color": "yellow"}, {"label": "Advanced (derivatization, mineralization)", "score": 1, "color": "red"}, ], }, # Reagents and Solvents { "id": "an_amount", "label": "Amount of reagents/solvents (9)", "block": "Reagents & Solvents", "options": [ {"label": "<10 mL or <10 g", "score": 3, "color": "green"}, {"label": "10–100 mL or 10–100 g", "score": 2, "color": "yellow"}, {"label": ">100 mL or >100 g", "score": 1, "color": "red"}, ], }, { "id": "an_health_hazard", "label": "Health hazard (10)", "block": "Reagents & Solvents", "options": [ {"label": "NFPA health 0–1 (slightly toxic/irritant)", "score": 3, "color": "green"}, {"label": "NFPA health 2–3 (moderately toxic)", "score": 2, "color": "yellow"}, {"label": "NFPA health 4 (serious injury / carcinogen)", "score": 1, "color": "red"}, ], }, { "id": "an_safety_hazard", "label": "Safety hazard (11)", "block": "Reagents & Solvents", "options": [ {"label": "NFPA flammability/instability 0–1, no special hazards", "score": 3, "color": "green"}, {"label": "NFPA flammability/instability 2–3, or special hazard", "score": 2, "color": "yellow"}, {"label": "NFPA flammability/instability 4", "score": 1, "color": "red"}, ], }, # Instrumentation { "id": "an_energy", "label": "Energy (12)", "block": "Instrumentation", "options": [ {"label": "≤0.1 kWh per sample", "score": 3, "color": "green"}, {"label": "≤1.5 kWh per sample", "score": 2, "color": "yellow"}, {"label": ">1.5 kWh per sample", "score": 1, "color": "red"}, ], }, { "id": "an_occupational", "label": "Occupational hazard (13)", "block": "Instrumentation", "options": [ {"label": "Hermetization of the analytical process", "score": 3, "color": "green"}, {"label": "—", "score": 2, "color": "yellow"}, {"label": "Emission of vapours to atmosphere", "score": 1, "color": "red"}, ], }, { "id": "an_waste", "label": "Waste (14)", "block": "Instrumentation", "options": [ {"label": "<1 mL or <1 g", "score": 3, "color": "green"}, {"label": "1–10 mL or 1–10 g", "score": 2, "color": "yellow"}, {"label": ">10 mL or >10 g", "score": 1, "color": "red"}, ], }, { "id": "an_waste_treatment", "label": "Waste treatment (15)", "block": "Instrumentation", "options": [ {"label": "Recycling", "score": 3, "color": "green"}, {"label": "Degradation or passivation", "score": 2, "color": "yellow"}, {"label": "No treatment", "score": 1, "color": "red"}, ], }, ] ALL_PARAMS = PRE_ANALYSIS_PARAMS + ANALYTICAL_PARAMS # Quantification mark: 5 pts (yes, oval shown) or 1 pt (no, qualification only) per article QUANT_PTS_YES = 5 QUANT_PTS_NO = 1 BASE_MAX_TGS = 80 # 25 params * 3 (75) + 5 (Quantification) class ComplexMoGAPICalculator: """Calculates the Total Greenness Score (TGS) for ComplexMoGAPI.""" def score_param(self, param_id: str, option_index: int) -> Dict[str, Any]: """Score a single parameter given its id and the chosen option index (0=green,1=yellow,2=red).""" param = next((p for p in ALL_PARAMS if p["id"] == param_id), None) if param is None: return {"score": 1, "color": "red", "label": "Unknown", "option": "N/A"} idx = max(0, min(option_index, len(param["options"]) - 1)) opt = param["options"][idx] return { "param_id": param_id, "param_label": param["label"], "block": param["block"], "option_index": idx, "option_label": opt["label"], "score": opt["score"], "color": opt["color"], } def calculate(self, selections: Dict[str, int], has_quantification: bool = True, e_factor: float = 0.0) -> Dict[str, Any]: """ Calculate TGS from a dict of {param_id: option_index}. option_index: 0 = Green (3 pts), 1 = Yellow (2 pts), 2 = Red (1 pt). has_quantification: True = oval shown = 5 pts, False = 1 pt (per ComplexGAPI article). e_factor: numeric only; displayed but NOT included in TGS. Returns structured results including breakdown, TGS, and greenness verdict. """ breakdown = [] tgs = 0 zeros = 0 for param in ALL_PARAMS: pid = param["id"] idx = selections.get(pid, 0) # Default to green if not provided result = self.score_param(pid, int(idx)) breakdown.append(result) if result["score"] == 0: zeros += 1 else: tgs += result["score"] # Quantification mark (per article: oval = 5 pts, no oval = 1 pt) quant_pts = QUANT_PTS_YES if has_quantification else QUANT_PTS_NO tgs += quant_pts # Calculate max_score dynamically based strictly on the original paper implementation max_tgs = BASE_MAX_TGS - (3 * zeros) # Greenness percentage is calculated directly as (tgs / max_tgs) * 100 tgs_pct = (tgs / max_tgs) * 100 if max_tgs > 0 else 0 if tgs_pct >= 75: verdict = "Excellent Greenness" verdict_color = "green" elif tgs_pct >= 50: verdict = "Good Greenness" verdict_color = "yellow_high" elif tgs_pct >= 25: verdict = "Moderate Greenness" verdict_color = "yellow" else: verdict = "Poor Greenness" verdict_color = "red" color_counts = { "green": sum(1 for b in breakdown if b["color"] == "green"), "yellow": sum(1 for b in breakdown if b["color"] == "yellow"), "red": sum(1 for b in breakdown if b["color"] == "red"), } pre_breakdown = [b for b in breakdown if b["param_id"].startswith("pre_")] an_breakdown = [b for b in breakdown if b["param_id"].startswith("an_")] return { "tgs": tgs, "tgs_max": max_tgs, "tgs_pct": round(tgs_pct, 1), "verdict": verdict, "verdict_color": verdict_color, "color_counts": color_counts, "breakdown": breakdown, "pre_analysis_breakdown": pre_breakdown, "analytical_breakdown": an_breakdown, "has_quantification": has_quantification, "quant_pts": quant_pts, "e_factor": e_factor, "param_definitions": ALL_PARAMS, } def get_param_definitions() -> list: """Returns the full parameter definitions for frontend rendering.""" return ALL_PARAMS