#!/usr/bin/env python3
"""
Parametric model: solar PV + storage + induction cooking + MOF atmospheric
water harvesting (AWH) for a refugee-camp household / cluster device.

Physics anchors (order-of-magnitude, from published AWH literature):
- Latent heat of vaporization of water: 2.44 MJ/kg. MOF binding enthalpy adds
  ~15-30% -> desorption heat ~3.0-3.2 MJ per liter harvested (no recovery).
- MOF working capacity per adsorption/desorption cycle (aluminum fumarate /
  MOF-303 class), by relative humidity of the adsorption period (night RH):
    20% RH -> ~0.22 L/kg/cycle, 30% -> ~0.32, 50% -> ~0.42
- Passive (single-cycle/day, solar-thermal desorption) vs active rapid-cycling
  (2-3 cycles/day with fans + electric heat + heat recovery).
- Refugee camp water benchmarks: UNHCR emergency minimum 15-20 L/person/day
  (all uses incl. washing); drinking+cooking alone ~5-7 L/person/day.
  Water trucking in camps commonly costs $5-30 per m^3 delivered.
"""

SOLAR_GHI = 5.5          # kWh/m2/day (E.Africa / MENA / S.Asia typical)
PV_DERATE = 0.78         # dust, temp, wiring
PV_COST_W = 0.45         # $/W small ruggedized system (module+mount+MPPT share)
BATT_COST_KWH = 160.0    # $/kWh usable LiFePO4 pack
FLYWHEEL_COST_KWH = 2000.0  # $/kWh steel flywheel, small scale
FLYWHEEL_STANDBY_LOSS_HR = 0.05  # 5%/hour is *good* for low-cost bearings

COOK_KWH_DAY = 2.0       # induction, family of 5, two hot meals (85% efficient)
INDUCTION_HOB = 65.0     # $ 1.8 kW single-coil commodity hob (ruggedized share)

DESORB_MJ_PER_L = 3.1    # thermal, no recovery
HEAT_RECOVERY_ACTIVE = 0.40   # active config recovers 40% between cycles
FAN_CTRL_KWH_PER_L = 0.10     # fans, condenser, controls (active)
THERMAL_COLLECTOR_EFF = 0.55  # glazed box collector
THERMAL_COLLECTOR_COST_M2 = 85.0

MOF_UPTAKE = {20: 0.22, 30: 0.32, 50: 0.42}   # L/kg/cycle vs night RH %

BOP_HOUSEHOLD = 260.0    # tank, mineralizer, condenser, enclosure, controls
BOP_CLUSTER = 900.0      # bigger tank, manifolds, dispensing, kiosk frame
LIFE_YEARS = 10
MAINT_FRAC_YR = 0.03     # 3% of capex per year
MOF_REPLACE_YR = 5       # sorbent bed replaced once at half-life


def kwh_per_L_active():
    th = DESORB_MJ_PER_L * (1 - HEAT_RECOVERY_ACTIVE) / 3.6  # MJ->kWh, resistive/inductive 1:1
    return th + FAN_CTRL_KWH_PER_L


def pv_watts_for(kwh_day):
    return kwh_day * 1000 / (SOLAR_GHI * PV_DERATE)


def config(name, liters_day, rh, mof_cost_kg, mode, households=1):
    """mode: 'passive' (solar-thermal desorb, 1 cycle) or
             'active'  (electric desorb + heat recovery, 3 cycles/day)"""
    uptake = MOF_UPTAKE[rh]
    cycles = 1 if mode == 'passive' else 3
    mof_kg = liters_day / (uptake * cycles)
    mof_cost = mof_kg * mof_cost_kg

    if mode == 'passive':
        # desorption heat from thermal collector; only fans/controls electric
        th_kwh = liters_day * DESORB_MJ_PER_L / 3.6
        collector_m2 = th_kwh / (SOLAR_GHI * THERMAL_COLLECTOR_EFF)
        collector_cost = collector_m2 * THERMAL_COLLECTOR_COST_M2
        water_elec = liters_day * 0.05  # small fan only
    else:
        collector_m2 = 0.0
        collector_cost = 0.0
        water_elec = liters_day * kwh_per_L_active()

    cook_elec = COOK_KWH_DAY * households
    total_elec = water_elec + cook_elec

    pv_w = pv_watts_for(total_elec)
    pv_cost = pv_w * PV_COST_W

    # battery: evening cooking (1.2 kWh/hh) + night fans + 0.5 day water buffer
    batt_kwh = 1.2 * households + 0.3 + (water_elec * 0.5 if mode == 'active' else 0.1)
    batt_cost = batt_kwh * BATT_COST_KWH

    bop = BOP_CLUSTER if households > 1 else BOP_HOUSEHOLD
    hob_cost = INDUCTION_HOB * min(households, 4)

    capex = pv_cost + batt_cost + mof_cost + collector_cost + bop + hob_cost
    # lifetime water cost: capex amortized + maintenance + one MOF replacement
    lifetime_L = liters_day * 365 * LIFE_YEARS
    lifetime_cost = capex + capex * MAINT_FRAC_YR * LIFE_YEARS + mof_cost
    usd_per_L = lifetime_cost / lifetime_L
    usd_per_m3 = usd_per_L * 1000

    return dict(name=name, mode=mode, rh=rh, L=liters_day, hh=households,
                mof_kg=round(mof_kg, 1), mof_cost=round(mof_cost),
                pv_w=round(pv_w), pv_cost=round(pv_cost),
                batt_kwh=round(batt_kwh, 1), batt_cost=round(batt_cost),
                coll_m2=round(collector_m2, 1), coll_cost=round(collector_cost),
                elec_kwh=round(total_elec, 1),
                capex=round(capex), usd_m3=round(usd_per_m3))


def flywheel_check():
    """Overnight retention of a flywheel sized for evening cooking+night fans."""
    stored = 1.5  # kWh at dusk
    hours = 12
    remaining = stored * (1 - FLYWHEEL_STANDBY_LOSS_HR) ** hours
    return stored, remaining, FLYWHEEL_COST_KWH / BATT_COST_KWH


if __name__ == '__main__':
    rows = []
    # Sweep: household configs at 3 humidities x 2 MOF prices, both modes
    for rh in (20, 30, 50):
        for mof_c in (30, 80):
            rows.append(config(f'HH-6L passive', 6, rh, mof_c, 'passive'))
            rows.append(config(f'HH-20L active', 20, rh, mof_c, 'active'))
            rows.append(config(f'HH-20L passive', 20, rh, mof_c, 'passive'))
            rows.append(config(f'Cluster-100L active', 100, rh, mof_c, 'active', households=5))

    hdr = f"{'config':<20}{'mode':<9}{'RH%':>4}{'MOF$/kg':>8}{'MOFkg':>7}{'PV W':>7}{'batt':>6}{'col m2':>7}{'kWh/d':>7}{'CAPEX$':>8}{'$/m3':>7}"
    print(hdr); print('-' * len(hdr))
    seen_mofc = {}
    for r in rows:
        mof_c = r['mof_cost'] / r['mof_kg'] if r['mof_kg'] else 0
        print(f"{r['name']:<20}{r['mode']:<9}{r['rh']:>4}{mof_c:>8.0f}{r['mof_kg']:>7}{r['pv_w']:>7}{r['batt_kwh']:>6}{r['coll_m2']:>7}{r['elec_kwh']:>7}{r['capex']:>8}{r['usd_m3']:>7}")

    print()
    s, rem, ratio = flywheel_check()
    print(f"Flywheel check: {s} kWh stored at dusk -> {rem:.2f} kWh left after 12 h "
          f"({rem/s*100:.0f}% retained) at 5%/hr standby loss; "
          f"cost {ratio:.0f}x LiFePO4 per kWh.")
    print(f"Active-mode electric water energy: {kwh_per_L_active():.2f} kWh/L")
    print(f"Trucked-water benchmark: $5-30/m3 delivered to camps")
