#!/usr/bin/env python3
"""Reproducible sizing arithmetic; none of these results is a hardware measurement.

Run with Python 3; outputs are written next to this script. The pressure-network
example uses an explicitly invented linear fan, not a fitted Noctua/Delta curve.
"""
import csv
import json
import math
from pathlib import Path

OUT = Path(__file__).resolve().parent
RHO = 1.18  # kg/m³: design assumption near warm indoor air, not site measurement
CP = 1005.0  # J/(kg K): engineering approximation
CFM_M3_S = 0.00047194745
MM_H2O_PA = 9.80665


def required_flow(power_w, delta_air_k):
    return power_w / (RHO * CP * delta_air_k) / CFM_M3_S


heat_rows = [
    {"power_to_air_W": p, "bulk_air_rise_K": dt,
     "minimum_CFM": round(required_flow(p, dt), 6),
     "minimum_m3_h": round(required_flow(p, dt) * CFM_M3_S * 3600, 6)}
    for p in (8, 12, 18, 25, 30) for dt in (8, 12, 15)
]

# Toy network: pressure is relative to ambient; branch flow positive into case.
# Actual fan curves, filter curves, leakage and three-dimensional paths are unknown.
P0, Q0 = 20.0, 5.0  # Pa, CFM; invented fan endpoints for explanation only
SLOPE = P0 / Q0


def positive_root(k, b, c):
    return max(0.0, (-b + math.sqrt(max(0.0, b * b - 4 * k * c))) / (2 * k))


def inlet_flow(pressure_pa, k, active):
    if active:
        return positive_root(k, SLOPE, pressure_pa - P0)
    return -math.copysign(math.sqrt(abs(pressure_pa) / k), pressure_pa) if pressure_pa else 0.0


def network(bottom_active, left_active, bottom_k=2.0, left_k=2.0, outlet_k=2.0):
    def flow(p):
        a = inlet_flow(p, bottom_k, bottom_active)
        b = inlet_flow(p, left_k, left_active)
        c = positive_root(outlet_k, SLOPE, -(P0 + p))
        return a, b, c
    lo, hi = -P0 + 1e-9, P0 - 1e-9
    for _ in range(100):
        mid = (lo + hi) / 2
        a, b, c = flow(mid)
        if a + b > c:
            lo = mid
        else:
            hi = mid
    p = (lo + hi) / 2
    a, b, c = flow(p)
    assert abs(a + b - c) < 1e-8
    assert max(a, b, c) <= Q0
    return {"case_pressure_Pa": round(p, 6), "bottom_CFM": round(a, 6),
            "left_CFM_signed": round(b, 6), "top_exhaust_CFM": round(c, 6),
            "K_bottom_Pa_per_CFM2": bottom_k, "K_left_Pa_per_CFM2": left_k,
            "K_outlet_Pa_per_CFM2": outlet_k}


result = {
    "status": "ENGINEERING_ARITHMETIC_NOT_HARDWARE_VALIDATION",
    "generated_by": Path(__file__).name,
    "constants": {"rho_kg_m3": RHO, "cp_J_kg_K": CP, "CFM_to_m3_s": CFM_M3_S,
                  "mm_H2O_to_Pa": MM_H2O_PA},
    "minimum_bulk_airflow": heat_rows,
    "bypass_sensitivity": [
        {"power_W": p, "delta_air_K": 12, "assumed_useful_fraction": 0.6,
         "useful_CFM": round(required_flow(p, 12), 6),
         "fan_CFM_if_only_60_percent_reaches_hot_path": round(required_flow(p, 12) / .6, 6)}
        for p in (18, 25)
    ],
    "pad_example_not_total_thermal_resistance": [
        {"thickness_mm": t, "conductivity_W_mK": 3, "assumed_contact_area_mm2": 400,
         "pad_R_K_W": round(t / 1000 / (3 * 400e-6), 6),
         "pad_drop_at_10W_K": round(10 * t / 1000 / (3 * 400e-6), 6)}
        for t in (.5, 1.5)
    ],
    "junction_path_budget_example": {
        "ambient_C": 35, "local_air_rise_K_assumed": 12, "junction_project_target_C": 75,
        "SoC_power_W_assumed": 10, "max_effective_junction_to_local_air_K_W": (75 - 35 - 12) / 10,
        "warning": "Not datasheet thetaJA; package/TIM/spreading/heatsink all contribute. Verify in actual assembly."
    },
    "filter_face_velocity_at_3CFM": [
        {"two_equal_filters_each_side_mm": side,
         "gross_total_area_m2": 2 * (side / 1000) ** 2,
         "gross_face_velocity_m_s": round(3 * CFM_M3_S / (2 * (side / 1000) ** 2), 6)}
        for side in (40, 60)
    ],
    "bottom_gap_gross_entry_area": [
        {"case_side_mm": 90, "gap_mm": gap, "four_sides_area_mm2": 4 * 90 * gap,
         "ratio_to_40mm_fan_square": 4 * 90 * gap / 1600,
         "warning": "Upper geometric estimate; feet, furniture and grilles reduce open area."}
        for gap in (3, 8, 12)
    ],
    "independent_equal_fan_acoustic_addition_same_RPM": [
        {"count": n, "added_dB": round(10 * math.log10(n), 6)} for n in (1, 2, 3)
    ],
    "fan_catalog_conversions": {
        "Noctua_NF_A4x10_5V_PWM": {"free_air_m3_h": 8.9, "free_air_CFM": 5.24,
            "zero_flow_mmH2O": 1.95, "zero_flow_Pa": round(1.95 * MM_H2O_PA, 6),
            "three_max_input_W_excludes_controller": round(3 * .35, 6)},
        "Noctua_NF_A4x20_5V_PWM": {"free_air_m3_h": 9.4, "free_air_CFM": 5.53,
            "zero_flow_mmH2O": 2.26, "zero_flow_Pa": round(2.26 * MM_H2O_PA, 6)},
        "Delta_EFB0405HA": {"free_air_CFM": 6.92, "zero_flow_mmH2O": 3.28,
            "zero_flow_Pa": round(3.28 * MM_H2O_PA, 6)}
    },
    "cube_volume_L": {str(side): round(side ** 3 / 1e6, 6) for side in (57, 90, 105, 120, 324)},
    "hypothetical_three_port_network": {
        "status": "TEACHING_MODEL_ONLY_NOT_A_PRODUCT_FAN_CURVE_OR_PREDICTED_CASE_PERFORMANCE",
        "fan_curve": "p_f(Q) = 20 - 4*Q, pressure Pa, flow CFM, only 0<=Q<=5",
        "system_curve": "Each branch pressure loss K*Q*abs(Q); no leakage/turn interaction/reverse active-fan flow",
        "one_top_fan_two_passive_inlets": network(False, False),
        "bottom_and_top_fans_left_passive": network(True, False),
        "bottom_left_intake_and_top_exhaust": network(True, True),
        "two_fans_more_restrictive_top_can_reverse_left_port": network(True, False, outlet_k=4),
        "warning": "Do not select a fan or enclosure size from these invented numerical operating points."
    }
}
assert abs(required_flow(12, 12) - 1.7867834) < .001
assert result["hypothetical_three_port_network"]["two_fans_more_restrictive_top_can_reverse_left_port"]["left_CFM_signed"] < 0
(OUT / "compact-hardware-calculations.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
with (OUT / "compact-airflow-sizing.csv").open("w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=heat_rows[0].keys()); w.writeheader(); w.writerows(heat_rows)
print(json.dumps({"airflow": heat_rows, "toy_network": result["hypothetical_three_port_network"]}, ensure_ascii=False, indent=2))
