"""Reproducible FAN-R2 power estimates, not startup or protection certification.""" import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def calculate(selection): fans_w = selection['quantity'] * selection['rated_at_pwm_100']['input_w'] fans_a = selection['quantity'] * selection['rated_at_pwm_100']['current_a'] rail_output_w = 5 * 4.01 * 4 buck_efficiency = 0.92 scenarios = [] # Auxiliary currents are example operating cases, not guaranteed maxima. for name, aux_w in [('fans_and_rails_only', 0), ('aux_5v_0p5a_3v3_0p5a', (5 * .5 + 3.3 * .5) / .9), ('aux_5v_2p8a_3v3_0p5a', (5 * 2.8 + 3.3 * .5) / .9)]: power_w = rail_output_w / buck_efficiency + fans_w + aux_w for bus_v in (10.8, 12.0, 13.2): scenarios.append({ 'case': name, 'bus_v': bus_v, 'input_w_estimate': power_w, 'input_a_estimate': power_w / bus_v, 'headroom_to_10a_w': 10 * bus_v - power_w, 'rail_output_budget_at_8a_w': max(0, (8 * bus_v - fans_w - aux_w) * buck_efficiency), }) return { 'mpn': selection['mpn'], 'certified': False, 'assumptions': { 'rail_output_w': rail_output_w, 'rail_conversion_efficiency': buck_efficiency, 'aux_conversion_efficiency': .9, 'adapter_current_limit_a': 10, 'provisional_continuous_current_target_a': 8, 'fan_input_model': 'Constant 12V-rated power for sensitivity only; voltage dependence is unknown', 'exclusions': ['input-path loss', 'startup peak', 'locked-rotor retries', 'maximum fan current', 'adapter temperature derating', 'efficiency tolerance', 'fan switch/receiver losses'], }, 'fan_rated_power_w': fans_w, 'fan_rated_current_a': fans_a, 'q7_conduction_at_25c_60mohm': { 'drop_v': fans_a * .06, 'loss_w': fans_a ** 2 * .06, 'limitation': '25C on-state only; no startup SOA, hot Rds(on), or short-circuit guarantee', }, 'c738_nominal_energy_at_13p2v_j': .5 * 22e-6 * 13.2 ** 2, 'c738_energy_limitation': 'Excludes both fan input capacitors and ceramic tolerance; does not bound peak current', 'scenarios': scenarios, 'thermal': { 'total_heat_target_w': 40, 'remaining_heat_budget_at_fan_rated_full_speed_w': 40 - fans_w, 'rise_k_at_6cfm_40w': 40 / (1200 * .00047195 * 6), }, } def main(): selection = json.loads((ROOT / 'fan-selection.json').read_text(encoding='utf-8')) report = calculate(selection) destination = ROOT / 'out/fan-power-budget.json' destination.write_text(json.dumps(report, indent=2) + '\n', encoding='utf-8') for row in report['scenarios']: print(f'{row["case"]}: {row["bus_v"]:.1f}V, ' f'{row["input_w_estimate"]:.2f}W, {row["input_a_estimate"]:.3f}A, ' f'10A headroom {row["headroom_to_10a_w"]:.2f}W') print(f'Conditional estimates only: {destination}') if __name__ == '__main__': main()