import math import pandas as pd import gradio as gr import plotly.graph_objects as go MATERIALS = { "aluminum": {"name":"Aluminio", "family":"structural"}, "pmc_cf_resin": {"name":"Compuesto (CF+Resina)", "family":"structural"}, "eva_waste": {"name":"EVA/Tejidos sintéticos", "family":"textiles"}, "foam_pack": {"name":"Espuma de empaque (Zotek/Plastazote)", "family":"foam"}, "bubble_wrap": {"name":"Burbuja / Air Cushions", "family":"packaging"}, "textiles": {"name":"Textiles / Wipes", "family":"textiles"}, "food_pouches": {"name":"Pouches comida/bebida/termales", "family":"packaging"}, "filters_mesh": {"name":"Filtros/Mallas lab", "family":"lab"}, "carbon": {"name":"Carbono excedente (experimentos O₂)", "family":"lab"}, "resealable_bags": {"name":"Bolsas resellables/antiestáticas", "family":"packaging"}, "nitrile_gloves": {"name":"Guantes nitrilo", "family":"lab"}, } PROCESS_EFF = { "shred": 0.95, "sort": 0.97, "compact": 0.98, "regolith_mix": 0.96, "form": 0.95 } WATER = {"wash": {"use_L_per_kg": 0.6, "recovery": 0.93}, "mix": {"use_L_per_kg": 0.4, "recovery": 0.95}} PRODUCT_YIELDS = { "Utensilios": {"kg_per_unit": 0.05}, "Cajas de Almacen": {"kg_per_unit": 0.8}, "Paneles Interiores": {"kg_per_unit": 2.0}, "Herramientas": {"kg_per_unit": 0.2}, "Pads Aislantes": {"kg_per_unit": 0.5}, } REGOLITH_FRACTION = 0.20 SCENARIOS = { "Residence Renovations": { "desc": "Reuso del cubo 3D y empaques del hábitat.", "inventory_kg": {"aluminum": 120, "pmc_cf_resin": 60, "foam_pack": 80, "bubble_wrap": 20, "resealable_bags": 10} }, "Cosmic Celebrations": { "desc": "Fiesta con materiales reutilizados dentro del hábitat.", "inventory_kg": {"textiles": 25, "eva_waste": 15, "bubble_wrap": 10, "food_pouches": 12, "resealable_bags": 5} }, "Daring Discoveries": { "desc": "Post-experimentos de O₂: reutilizar equipos y carbono excedente.", "inventory_kg": {"filters_mesh": 18, "carbon": 35, "eva_waste": 12, "nitrile_gloves": 6, "resealable_bags": 4} } } def simulate_flow(inventory_kg: dict, eff=PROCESS_EFF, rego_frac=REGOLITH_FRACTION): inv = pd.Series(inventory_kg, dtype=float).reindex(MATERIALS.keys()).fillna(0.0) total_in = inv.sum() wash_kg = inv[["eva_waste","textiles","foam_pack","bubble_wrap","food_pouches","resealable_bags","nitrile_gloves","pmc_cf_resin"]].sum() water_wash_use = wash_kg * WATER["wash"]["use_L_per_kg"] water_wash_recovered = water_wash_use * WATER["wash"]["recovery"] water_wash_loss = water_wash_use - water_wash_recovered after_shred = total_in * eff["shred"] after_sort = after_shred * eff["sort"] after_compact = after_sort * eff["compact"] polymer_mass = after_compact regolith_added = polymer_mass * rego_frac mix_total = polymer_mass + regolith_added after_mix = mix_total * eff["regolith_mix"] final_useful = after_mix * eff["form"] water_mix_use = polymer_mass * WATER["mix"]["use_L_per_kg"] water_mix_recovered = water_mix_use * WATER["mix"]["recovery"] water_mix_loss = water_mix_use - water_mix_recovered kpi = { "Input total (kg)": round(total_in, 2), "After Shred (kg)": round(after_shred, 2), "After Sort (kg)": round(after_sort, 2), "After Compact (kg)": round(after_compact, 2), "Regolith added (kg)": round(regolith_added, 2), "After Mix (kg)": round(after_mix, 2), "Final Useful Output (kg)": round(final_useful, 2), "Water use wash (L)": round(water_wash_use, 2), "Water recovered wash (L)": round(water_wash_recovered, 2), "Water loss wash (L)": round(water_wash_loss, 2), "Water use mix (L)": round(water_mix_use, 2), "Water recovered mix (L)": round(water_mix_recovered, 2), "Water loss mix (L)": round(water_mix_loss, 2), "Water total loss (L)": round(water_wash_loss + water_mix_loss, 2), "Crew time (min/day)": 12 } family_weights = {"structural": 0.35, "textiles": 0.15, "foam": 0.15, "packaging": 0.20, "lab": 0.15} family_mass = {f: final_useful*wt for f, wt in family_weights.items()} def units(m, kgpu): return int(max(0, m // kgpu)) product_plan = { "Utensilios": units(family_mass["packaging"]*0.5 + family_mass["textiles"]*0.2, PRODUCT_YIELDS["Utensilios"]["kg_per_unit"]), "Cajas de Almacen": units(family_mass["structural"]*0.35 + family_mass["packaging"]*0.15, PRODUCT_YIELDS["Cajas de Almacen"]["kg_per_unit"]), "Paneles Interiores":units(family_mass["structural"]*0.4 + family_mass["foam"]*0.3, PRODUCT_YIELDS["Paneles Interiores"]["kg_per_unit"]), "Herramientas": units(family_mass["lab"]*0.6 + family_mass["structural"]*0.1, PRODUCT_YIELDS["Herramientas"]["kg_per_unit"]), "Pads Aislantes": units(family_mass["foam"]*0.7 + family_mass["textiles"]*0.1, PRODUCT_YIELDS["Pads Aislantes"]["kg_per_unit"]), } return kpi, product_plan, inv def sankey_from_kpis(kpi): labels = ["Input","Shred","Sort","Compact","Regolith","Mix","Form (Final)"] idx = {lab:i for i,lab in enumerate(labels)} inp,a1,a2,a3,reg,mix,final = kpi["Input total (kg)"],kpi["After Shred (kg)"],kpi["After Sort (kg)"],kpi["After Compact (kg)"],kpi["Regolith added (kg)"],kpi["After Mix (kg)"],kpi["Final Useful Output (kg)"] loss_shred=max(0,inp-a1); loss_sort=max(0,a1-a2); loss_compact=max(0,a2-a3); loss_mix=max(0,(a3+reg)-mix); loss_form=max(0,mix-final) def l(s,t,v): return (s,t,round(v,2)) if v>0.0001 else None links=[l(idx["Input"],idx["Shred"],a1), l(idx["Input"],idx["Input"],loss_shred), l(idx["Shred"],idx["Sort"],a2), l(idx["Shred"],idx["Shred"],loss_sort), l(idx["Sort"],idx["Compact"],a3), l(idx["Sort"],idx["Sort"],loss_compact), l(idx["Compact"],idx["Regolith"],reg), l(idx["Regolith"],idx["Mix"],a3+reg), l(idx["Mix"],idx["Form (Final)"],final), l(idx["Mix"],idx["Mix"],loss_mix), l(idx["Form (Final)"],idx["Form (Final)"],loss_form)] links=[x for x in links if x] fig=go.Figure(data=[go.Sankey( node=dict(pad=20, thickness=18, line=dict(color="white", width=0.5), label=labels), link=dict(source=[s for s,_,__ in links], target=[t for _,t,__ in links], value=[v for __,__,v in links]) )]) fig.update_layout(margin=dict(l=10,r=10,t=10,b=10), height=430) return fig def run_sim(scenario, aluminum, pmc, eva, foam, bubble, textiles, pouches, filters, carbon, bags, gloves, rego_frac): base = SCENARIOS[scenario]["inventory_kg"].copy() base.update({ "aluminum": aluminum, "pmc_cf_resin": pmc, "eva_waste": eva, "foam_pack": foam, "bubble_wrap": bubble, "textiles": textiles, "food_pouches": pouches, "filters_mesh": filters, "carbon": carbon, "resealable_bags": bags, "nitrile_gloves": gloves }) kpi, products, inv = simulate_flow(base, PROCESS_EFF, rego_frac) kpi_df = pd.DataFrame(list(kpi.items()), columns=["Métrica","Valor"]) prod_df = pd.DataFrame(list(products.items()), columns=["Producto","Unidades (aprox)"]) inv_df = pd.DataFrame({"Material":[MATERIALS[k]["name"] for k in inv.index], "kg":inv.values}) fig = sankey_from_kpis(kpi) return kpi_df, prod_df, inv_df, fig with gr.Blocks(title="MARS-LOOP — Jezero Crater") as demo: gr.Markdown("# 🪐 MARS-LOOP — Reciclaje circular en Marte\n**Convierte residuos inorgánicos + regolito MGS-1 en piezas útiles del hábitat.** \n_Sin incinerar, minimizando agua, y con operación ligera (<15 min/día)._") with gr.Row(): scenario = gr.Dropdown(choices=list(SCENARIOS.keys()), value="Residence Renovations", label="Escenario NASA") rego_frac = gr.Slider(0.0, 0.5, value=REGOLITH_FRACTION, step=0.05, label="Fracción de regolito (0–50%)") gr.Markdown("### Inventario (kg) — Ajusta por material") with gr.Row(): with gr.Column(): aluminum = gr.Slider(0, 300, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("aluminum",0), step=5, label="Aluminio (kg)") pmc = gr.Slider(0, 300, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("pmc_cf_resin",0), step=5, label="Compuesto CF+Resina (kg)") eva = gr.Slider(0, 200, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("eva_waste",0), step=2, label="EVA/Textiles sintéticos (kg)") foam = gr.Slider(0, 200, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("foam_pack",0), step=2, label="Espuma empaque (kg)") bubble = gr.Slider(0, 100, value=SCENARIOS["Residence Renovations"]["inventory_kg"].get("bubble_wrap",0), step=2, label="Burbuja/Air Cushions (kg)") textiles = gr.Slider(0, 100, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("textiles",0), step=2, label="Textiles/Wipes (kg)") with gr.Column(): pouches = gr.Slider(0, 100, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("food_pouches",0), step=2, label="Pouches comida/bebida/termal (kg)") filters = gr.Slider(0, 100, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("filters_mesh",0), step=2, label="Filtros/Mallas (kg)") carbon = gr.Slider(0, 100, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("carbon",0), step=2, label="Carbono excedente (kg)") bags = gr.Slider(0, 60, value=SCENARIOS["Cosmic Celebrations"]["inventory_kg"].get("resealable_bags",0), step=1, label="Bolsas resellables (kg)") gloves = gr.Slider(0, 40, value=SCENARIOS["Daring Discoveries"]["inventory_kg"].get("nitrile_gloves",0), step=1, label="Guantes nitrilo (kg)") run = gr.Button("▶️ Ejecutar simulación") kpi_df = gr.Dataframe(label="KPIs") prod_df = gr.Dataframe(label="Productos estimados") inv_df = gr.Dataframe(label="Inventario usado (kg)") sankey = gr.Plot(label="Flujo de materiales — Sankey") run.click(run_sim, inputs=[scenario, aluminum, pmc, eva, foam, bubble, textiles, pouches, filters, carbon, bags, gloves, rego_frac], outputs=[kpi_df, prod_df, inv_df, sankey]) demo.launch()