NasaApp / app.py
sd4m's picture
Create app.py
da4b3db verified
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()