omnigreen-api / complexmogapi_calculator.py
DrMO2487's picture
Upload 18 files
5bf896a verified
"""
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