#!/usr/bin/env python3
import cgi, sys
sys.path.insert(0, '/opt/ngon/apps')
from managers.auth_manager import AuthManager, generate_login_page_html

_form = cgi.FieldStorage()
_auth = AuthManager('how_to')
_auth_required, _should_exit, _headers = _auth.require_auth(_form)

if _should_exit:
    print("Content-Type: application/json")
    if _headers:
        print(_headers)
    print("")
    if _auth_required:
        print('{"success": false, "error": "Authentication required"}')
    else:
        print('{"success": true}')
    sys.exit(0)

if _auth_required:
    print("Content-Type: text/html")
    print("")
    print(generate_login_page_html("How-To Guide"))
    sys.exit(0)


sys.path.insert(0, '/var/www/html/ngon')
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js
_user_access = AuthManager.get_user_access()
_nav_html = generate_dropdown_html(_user_access)
_nav_css = generate_dropdown_css()
_nav_js = generate_dropdown_js()

print("Content-Type: text/html\n")

HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NGON How-To: Power Management</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="/status/styles.css?v=30" />
<style>__NAV_CSS__</style>
<style>
html, body { background: #1a1a1a !important; }
body { padding-bottom: 60px; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-thumb { background: #666; border-radius: 3px; }

/* Header - matching status page style */
.header {
    background-color: rgba(255,255,255,0.05) !important;
    border-radius: 8px !important;
    padding: 30px !important;
    margin: 20px !important;
    border: 1px solid rgba(255,255,255,0.1) !important;
    border-bottom: none !important;
    display: flex !important;
    justify-content: space-between !important;
    align-items: flex-start !important;
}
.header h1.dropdown-title {
    font-size: 2.2em !important;
    font-weight: 600 !important;
    margin: 3px 0 0 0 !important;
    line-height: 1.2 !important;
}

.howto-body { max-width: 900px; margin: 0 auto; padding: 20px; }

h2 {
    font-size: 22px;
    color: #00ff00;
    margin: 40px 0 14px 0;
    padding-bottom: 8px;
    border-bottom: 1px solid #333;
}
h3 {
    font-size: 17px;
    color: #4a9eff;
    margin: 24px 0 10px 0;
}
h4 {
    font-size: 15px;
    color: #ffaa00;
    margin: 18px 0 8px 0;
}
p { margin: 10px 0; color: #ccc; }
ul, ol { margin: 10px 0 10px 24px; color: #ccc; }
li { margin: 6px 0; }
code {
    background: #2a2a2a;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: 'Menlo', 'Consolas', monospace;
    font-size: 13px;
    color: #ffaa00;
}

.toc {
    background: #222;
    border: 1px solid #333;
    border-radius: 6px;
    padding: 16px 20px;
    margin: 20px 0;
}
.toc h3 { color: #e0e0e0; margin-top: 0; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; }
.toc ul { list-style: none; margin: 10px 0 0 0; }
.toc li { margin: 4px 0; }
.toc a { color: #4a9eff; text-decoration: none; }
.toc a:hover { color: #00ff00; }

.cheat {
    background: #1e2a1e;
    border: 1px solid #2a4a2a;
    border-radius: 6px;
    padding: 18px 22px;
    margin: 20px 0;
}
.cheat h3 { color: #00ff00; margin-top: 0; }
.cheat table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 14px; }
.cheat th, .cheat td { padding: 8px 10px; text-align: left; border-bottom: 1px solid #2a4a2a; }
.cheat th { color: #00ff00; font-weight: 600; }
.cheat td { color: #ccc; }

.callout {
    border-left: 3px solid #4a9eff;
    background: #1e232a;
    padding: 12px 16px;
    margin: 16px 0;
    border-radius: 0 4px 4px 0;
}
.callout.warn { border-left-color: #ff6600; background: #2a1f12; }
.callout.bad  { border-left-color: #ff4444; background: #2a1414; }
.callout.good { border-left-color: #00ff00; background: #122a12; }
.callout .label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    display: block;
    margin-bottom: 4px;
}
.callout.warn .label { color: #ff6600; }
.callout.bad  .label { color: #ff4444; }
.callout.good .label { color: #00ff00; }
.callout p { margin: 4px 0; }

.flow {
    background: #181818;
    border: 1px solid #333;
    border-radius: 6px;
    padding: 14px 18px;
    margin: 14px 0;
    font-family: 'Menlo', 'Consolas', monospace;
    font-size: 13px;
    color: #ccc;
    white-space: pre-wrap;
    line-height: 1.65;
}
.flow .step { color: #4a9eff; }
.flow .action { color: #00ff00; }
.flow .warn { color: #ff6600; }

.tag {
    display: inline-block;
    background: #2a2a2a;
    color: #4a9eff;
    padding: 2px 8px;
    border-radius: 3px;
    font-size: 12px;
    font-family: monospace;
    margin: 0 2px;
}
.tag.as { color: #ff6600; }
.tag.aw { color: #00ff00; }
.tag.sos { color: #4a9eff; }

/* inline icons matching the status page (Font Awesome), colored like the page */
.ic { margin: 0 3px; }
.ic-warn { color: #ff6600; }
.ic-hold { color: #4a9eff; }

@media (max-width: 700px) {
    .howto-body { padding: 14px; }
    h2 { font-size: 19px; }
    h3 { font-size: 15px; }
    .cheat table { font-size: 12px; }
    .cheat th, .cheat td { padding: 6px 4px; }
}
</style>
</head>
<body>

<div class="header">
    <div>
      <div class="dropdown">
        <h1 class="dropdown-title">NGON Mining - How-To: Power Management</h1>
        <div class="dropdown-content">__DROPDOWN_HTML__</div>
      </div>
    </div>
</div>

<div class="howto-body">

<p>This page explains how the NGON auto-sleep, auto-wake, gen-disable,
Emergency Sleep, and capacity-hold systems work together. It's written for
field techs, so you can understand <em>what the system is already doing for
you</em> and avoid unnecessary manual toggling.</p>

<div class="toc">
    <h3>Contents</h3>
    <ul>
        <li><a href="#cheat">Quick Cheat Sheet</a></li>
        <li><a href="#bigpicture">The Big Picture — How Power Is Managed</a></li>
        <li><a href="#as">Auto-Sleep (AS)</a></li>
        <li><a href="#aw">Auto-Wake (AW)</a></li>
        <li><a href="#disable">Disabling a Generator</a></li>
        <li><a href="#ems">Emergency Sleep (EMS)</a></li>
        <li><a href="#protections">Newer Automatic Protections</a></li>
        <li><a href="#scenarios">Walkthroughs: Common Scenarios</a></li>
        <li><a href="#mistakes">Common Mistakes</a></li>
    </ul>
</div>

<!-- ============================================================ -->
<h2 id="cheat">Quick Cheat Sheet</h2>

<div class="cheat">
<h3>What each feature does</h3>
<table>
<tr><th>Feature</th><th>What it does</th><th>Type</th></tr>
<tr><td><span class="tag as">AS</span> Auto-Sleep</td>
    <td>Puts miners to sleep when a gen goes down or when group kW goes over target</td>
    <td>Automatic</td></tr>
<tr><td><span class="tag aw">AW</span> Auto-Wake</td>
    <td>Wakes sleeping miners when the group has kW headroom under target</td>
    <td>Automatic</td></tr>
<tr><td>Disable Gen</td>
    <td>Tells the system "pretend this gen isn't there" — it's dropped from the group's capacity math</td>
    <td>Manual</td></tr>
<tr><td><span class="tag sos">EMS</span> Emergency Sleep</td>
    <td>Holds a pod's miners asleep — either while booting up, or to keep a running pod asleep indefinitely</td>
    <td>Manual</td></tr>
<tr><td>Capacity Hold</td>
    <td>Reserves a shaky gen's headroom <em>before</em> it fails so the group survives losing it (auto on coolant/shutdown warnings)</td>
    <td>Automatic</td></tr>
</table>
</div>

<div class="cheat">
<h3>What you usually need to touch</h3>
<table>
<tr><th>Situation</th><th>What to do</th><th>What NOT to do</th></tr>
<tr><td>Gen goes down on its own</td><td>Nothing — AS handles it</td><td>Don't sleep miners manually</td></tr>
<tr><td>Taking a gen out for service</td><td>Disable the gen in the Edit kW modal</td><td>Don't also turn off AW</td></tr>
<tr><td>Bringing a serviced gen back</td><td>Re-enable the gen in the Edit kW modal</td><td>Don't manually wake miners</td></tr>
<tr><td>Pod is physically down / being worked on</td><td>Turn on EMS for that pod</td><td>Don't rely on AS alone</td></tr>
<tr><td>Whole site under maintenance</td><td>Turn off AS <em>and</em> AW on those pods</td><td>&nbsp;</td></tr>
</table>
</div>

<!-- ============================================================ -->
<h2 id="bigpicture">The Big Picture — How Power Is Managed</h2>

<p>The whole power system runs on one simple idea: <strong>match miner load
to available generator capacity</strong>. Every generator group has a
<code>max_gen_kw</code> target (how many kW each running gen can safely
carry). Each miner type has a <code>spec_wattage</code> (set in Site
Manager → Miner Types) that tells the system how many kW one miner of
that type draws; each pod's kW-per-miner is derived from its assigned
miner type. The system continuously watches the gap between current
gen load and target, and either sleeps or wakes miners to close the gap.</p>

<p>There is no longer a separate "auto-wake cron." Both AS and AW run
inside <code>generator_monitor.py</code>, which re-evaluates every group
about every ~5 seconds. That means AS and AW are really two sides of the
same equation — one decision engine, not two independent systems.</p>

<p>The engine reads gen load from one of two feeds. Most gens now stream
<strong>live telemetry</strong> straight from the gen (~1 reading per
second). When <em>every</em> gen in a group is streaming fresh live data,
that group runs on the faster <strong>live path</strong>. If any gen's live
feed goes missing or stale, the whole group falls back to the
<strong>Mesa path</strong>, which uses the provider's ~5-minute averaged
data. Same decisions either way — just slightly different timing. That's
why some numbers below are given as a range: the live path reacts faster
and settles new gens in ~2 minutes, where the Mesa path waits ~15.</p>

<div class="callout good">
<span class="label">Key idea</span>
<p>AS and AW are not "settings you tune to respond to events." They are
the system's normal heartbeat. If you leave them on, the system will
correctly react to gen failures, capacity changes, and gen-disables
without you touching them.</p>
</div>

<h3>The math, in plain English</h3>
<ol>
<li>The system counts how many gens are <em>running</em> and <em>not disabled</em>.</li>
<li>It multiplies that count by <code>max_gen_kw</code>. That's the target.</li>
<li>It compares target to the gens' current actual kW load.</li>
<li>If load is <strong>over</strong> target by more than a small deadband (about one to two miners' worth of kW) → sleep miners (steady-state AS).</li>
<li>If load is <strong>under</strong> target by more than that deadband → wake sleeping miners, in <em>batches</em> (AW path).</li>
<li>If a gen suddenly drops offline → the system <strong>auto-arms EMS</strong> on the affected pods (the EMS toggle on the status page lights up by itself). The per-pod field agent then hammers sleep at every miner until the pod is fully asleep. AW gradually wakes them back as capacity is confirmed stable, at which point EMS auto-disarms.</li>
</ol>

<div class="callout">
<span class="label">Why we slam everything asleep on gen failure</span>
<p>When a gen drops, the load it was carrying gets redistributed onto the
surviving gens <em>instantly</em>, but our gen telemetry can be minutes
old. Trying to compute "how many miners to shed" using stale data has
caused cascade trips. Sleeping everything is the only choice that's
guaranteed safe regardless of what the data says. AW then ramps the pod
back up gradually so the survivors are never overloaded during recovery.</p>
</div>

<div class="callout">
<span class="label">EMS, AW, and R0 move together</span>
<p>Whenever EMS turns on for a pod — whether you toggled it manually or
the system auto-armed it on a gen event — the pod's Auto-Wake and
Reboot-Zero toggles automatically flip OFF too. When EMS clears, all
three flip back ON together. This is by design: EMS means "nothing
automated should be touching this pod." You'll see the toggles move as
a group on the status page.</p>
</div>

<!-- ============================================================ -->
<h2 id="as">Auto-Sleep (AS)</h2>

<p><strong>Config field:</strong> <code>auto_sleep_enabled</code>, per pod.</p>

<h3>What triggers AS</h3>
<ul>
<li><strong>Gen-down event (auto-EMS):</strong> A gen in the group transitions from Running to Down. AS arms EMS on the group's eligible pods, which causes the field-server agent to hammer sleep at every miner — no math, no estimates. The EMS toggle on the status page flips on by itself, and the AW + R0 toggles flip off in lockstep. AW then ramps the pod back up gradually over the next 15–25 minutes as the surviving gens prove stable, and once a pod is being woken EMS auto-disarms (toggles flip back).</li>
<li><strong>Steady-state over-capacity:</strong> Even with no gen-down event, if the group's actual load climbs over target (e.g., load creep, a stuck gen), AS shaves off just enough miners to bring load back under target. This path doesn't touch EMS — it's a quiet trim, not a panic.</li>
</ul>

<h3>What AS skips</h3>
<ul>
<li>Pods with <code>auto_sleep_enabled = false</code></li>
<li>Pods with <span class="tag sos">EMS</span> on (they're already being slept by the field agent)</li>
<li>Miners that are already sleeping</li>
</ul>

<h3>Why AS exists</h3>
<p>Without AS, a gen failure at 3am means miners keep drawing load from
the remaining gens, overloading them and tripping them offline too —
a cascade that can take an entire site down. AS reacts in seconds — the
live-telemetry fast-EMS path (see "Newer Automatic Protections" below)
catches a gen dropping off load within ~5 seconds, ahead of the slower
provider feed — and protects the remaining gens by arming EMS, which the
per-pod agent keeps re-asserting until the pod is fully down. That's more robust
than the old single-shot sleep call, which could miss miners that were
mid-wake or temporarily unreachable.</p>

<div class="callout">
<span class="label">In the log</span>
<p>AS actions show up in the Miner Monitor section with user <code>AS</code>. If you see <code>AS</code> events, the system just protected a gen for you.</p>
</div>

<!-- ============================================================ -->
<h2 id="aw">Auto-Wake (AW)</h2>

<p><strong>Config field:</strong> <code>auto_wake_enabled</code>, per pod.</p>

<h3>What triggers AW</h3>
<ul>
<li>The group has measurable kW headroom below target (at least one miner's worth of kW).</li>
<li>There are sleeping miners in a pod that has <code>auto_wake_enabled = true</code>.</li>
<li>Gens have been stable — a recently-started gen gets a short settle window before AW counts its capacity (about 2 minutes on the live path, up to ~15 minutes on the Mesa fallback path).</li>
<li>At least 5 minutes have passed since the last AS or AW event on this group (cooldown).</li>
</ul>

<h3>How AW wakes (batched, gradual)</h3>
<p>AW does <strong>not</strong> wake the whole pod in one shot. Each wake
batch is at least one gen's worth of miners (about 90–105 for a 330 kW
gen, depending on miner type); on larger groups it may wake somewhat more
per batch. After each batch AW waits ~5 minutes before the next — and if a
gen is already running near full load (≥90%), it stretches that wait to
~15 minutes — giving the surviving gens time to absorb the new load before
more miners are added.</p>

<p>For a fully slept 5-gen pod (e.g., right after panic-sleep or after
EMS is turned off), full recovery takes about <strong>15–25 minutes</strong>
of gradual ramp-up.</p>

<div class="callout">
<span class="label">If you need it back faster</span>
<p>Manual wake from the NMT or MFT pages bypasses the batch cap. The
batched ramp only applies to AW. So if you've personally verified the
gens are solid and want everything up now, manual wake is the escape
hatch.</p>
</div>

<h3>What AW skips</h3>
<ul>
<li>Pods with <code>auto_wake_enabled = false</code></li>
<li>Pods with <span class="tag sos">EMS</span> on</li>
<li>Stuck gens (engine hours frozen, or reporting Running but at near-zero load) — AW won't count their capacity</li>
<li>Disabled gens (they don't exist as far as AW's math is concerned)</li>
</ul>

<h3>Why AW exists</h3>
<p>When a gen comes back online after a failure or service, someone used
to have to manually wake miners across the affected pods. AW does this
automatically — and proportionally across pods in the same group — the
moment the gen starts contributing load.</p>

<div class="callout">
<span class="label">In the log</span>
<p>AW actions show up with user <code>AW</code> in the Miner Monitor log section.</p>
</div>

<!-- ============================================================ -->
<h2 id="disable">Disabling a Generator</h2>

<p><strong>Where:</strong> Status page → click the group's kW target to open
the kW modal → toggle individual gens off.</p>

<p><strong>Config field:</strong> <code>disabled_gens</code>, per generator
group (a list of gen IDs).</p>

<div class="callout warn">
<span class="label">Two different actions live in the same modal</span>
<p>The kW modal lets you do <em>two independent things</em>, and it's easy to
mix them up:</p>
<ul>
<li><strong>Change <code>max_gen_kw</code></strong> — this raises or lowers
the <em>per-gen</em> kW target for the whole group. Use this when the
generators' actual safe load has changed (e.g., derating for hot weather,
or bumping up after a tune). It affects every gen in the group equally.</li>
<li><strong>Disable a specific gen</strong> — this removes one gen from the
math. <code>max_gen_kw</code> stays exactly the same; the group just has
one fewer gen contributing to the total target.</li>
</ul>
<p>Most of the time you only want to disable the gen. Leave
<code>max_gen_kw</code> alone unless you actually need to change the
per-gen target.</p>
</div>

<h3>What disabling actually does</h3>
<p>When you toggle a gen off in the modal, the system:</p>
<ol>
<li>Saves the gen's ID into the group's <code>disabled_gens</code> list.</li>
<li>Removes that gen from <strong>all</strong> capacity math. AS, AW, and the steady-state power loop all behave as if the gen simply isn't there.</li>
<li>Immediately recalculates the group's total target: <code>total_target = max_gen_kw × (remaining enabled gens)</code>. Note that <code>max_gen_kw</code> itself does not change — there's just one fewer gen multiplying it.</li>
<li>If the new total target is below current load → sleeps enough miners to match. If above → wakes miners.</li>
</ol>

<p>Re-enabling the gen is the reverse: it comes back into the math,
total target goes up, AW wakes miners to fill the new capacity.</p>

<div class="callout good">
<span class="label">You do NOT need to turn off AW when you disable ONE gen</span>
<p>This is the single biggest point of confusion. The system already
excludes disabled gens from AW's math. AW will keep doing its job
correctly with the remaining gens — it will <em>not</em> try to wake
miners onto the disabled one. Leave AW alone.</p>
<p>If you <em>also</em> turn off AW, you've told the system "don't wake
any miners in this pod, ever, for any reason." That usually isn't what
you want, and it gets forgotten — so when the gen is put back in service
the pod sits half-asleep until someone notices.</p>
</div>

<div class="callout warn">
<span class="label">Disabling ALL gens is different</span>
<p>If you disable <em>every</em> gen in the group (the "shut the whole
group down" workflow), the system treats that like a gen-down event
and auto-arms EMS on every eligible pod in the group. EMS turning on
auto-flips Auto-Wake and Reboot-Zero off as part of the EMS coupling
described above. So when you later re-enable gens, you'll need to
toggle EMS off manually to bring the pods back — AW won't wake them on
its own because AW is also off. (See Scenario 3, Method B.)</p>
</div>

<h3>Why the disable-gen feature exists</h3>
<p>A gen being <em>physically</em> offline (failed, tripped) is different
from a gen being <em>intentionally</em> offline (servicing, swapping,
parts on order). In the failure case, the provider API tells us it's
down and AS fires. In the intentional case, the gen may still be
reporting Running, or the tech may pull it before the system notices —
so we need a way to say "don't count this one, even if it looks alive."
That's what disabling does.</p>

<!-- ============================================================ -->
<h2 id="ems">Emergency Sleep (EMS)</h2>

<p><strong>Where:</strong> Blue "EMS" toggle on the status page gen group
header (per-pod).</p>

<h3>What EMS does</h3>
<p>EMS is the heavy hammer. It lives on the <em>field server</em>, in
the per-pod agent. When EMS is on for a pod, the pod agent:</p>
<ol>
<li>Waits for the pod's router to be reachable on the network (pings every 1s).</li>
<li>Continuously hammers sleep commands at every miner in the pod, whether they just came online or have been running for hours.</li>
<li>Keeps doing that until EMS is cleared.</li>
</ol>

<p>Meanwhile, <code>generator_monitor</code> sees the pod has EMS on
and <em>skips it</em> for both AS and AW (and Reboot-Zero, since R0
also flips off as part of the EMS coupling). While EMS is on, that pod
is effectively invisible to the power manager and stays asleep no
matter what the power math says.</p>

<h3>Two ways EMS turns on</h3>
<p><strong>1. Manual toggle</strong> — you click the EMS toggle on the
status page. AW and R0 toggles flip off automatically.</p>
<p><strong>2. Auto-arm</strong> — the system arms EMS itself on a gen
event. Specifically: a gen-down alert (single gen tripping) or a
"disable all gens in group" action (the maintenance shutdown path).
You'll see the EMS toggle light up on its own and AW/R0 flip off — no
one did it, the system did. Auto-arm keeps the pod safe until AW is
ready to wake it.</p>

<h3>Two ways EMS turns off</h3>
<p><strong>1. Manual toggle</strong> — you click the EMS toggle off.
AW and R0 toggles flip back on automatically.</p>
<p><strong>2. Auto-clear</strong>. There are two automatic cases:</p>
<ul>
<li><strong>AW resume:</strong> When generator capacity returns and AW
decides it's time to wake a slept pod, it clears EMS on that pod first
(then sends the wake batch). AW/R0 flip back on. This is the normal
recovery path after a gen-down event.</li>
<li><strong>Pod-dark auto-disarm:</strong> If EMS is on but the pod's
router stays unreachable for ~90 seconds (i.e., the pod is fully dark
because the gens are off and the peplink is unpowered), the agent
asks the cloud to clear EMS. Reasoning: miners boot sleeping by
default now (see "init-disable" in the deeper docs), so resuming EMS
when power returns is wasted hammering — the pod will come up
clean on its own.</li>
</ul>

<h3>Why EMS exists — three use cases</h3>
<p><strong>1. Cold boot protection.</strong> When a pod boots cold with its
generator not yet stable, miners come up and start hashing within
seconds — often <em>before</em> the gen can handle the load. Turn EMS on
<em>before</em> powering the pod, and the agent will catch the miners
the moment they come online.</p>
<p><strong>2. Holding a pod asleep indefinitely.</strong> EMS also works on
a pod that's already up and running. Turn it on and the agent keeps
slamming sleep commands at the miners, so nothing — not AW, not a
manual wake, not a miner rebooting itself — can wake them back up. This
is the right tool when you want to park a pod for a while (maintenance,
low gas, a gen swap in the same group) without worrying about AW trying
to wake it every time capacity opens up.</p>
<p><strong>3. The system's own gen-down response.</strong> AS now uses EMS
internally for gen-trip events. You don't need to do anything for this
case; it's documented here so you understand why the EMS toggle moves
on its own sometimes.</p>

<h3>Typical EMS flow (manual)</h3>
<div class="flow"><span class="step">1.</span> Pod is down, gen is off, you're about to bring the site back.
<span class="step">2.</span> Turn EMS <span class="action">ON</span> for that pod (status page). AW and R0 toggles flip off automatically.
<span class="step">3.</span> Start the gen. Pod comes online. Miners try to start.
<span class="step">4.</span> Agent catches them and sleeps them immediately.
<span class="step">5.</span> You verify the gen is stable and producing load.
<span class="step">6.</span> Turn EMS <span class="action">OFF</span>. AW and R0 flip back on automatically.
<span class="step">7.</span> AW takes over and wakes miners as the gen can support them.</div>

<div class="callout warn">
<span class="label">If a pod looks "stuck sleeping," check EMS first</span>
<p>A pod with EMS left on stays asleep — AS and AW can't help it
because they skip EMS pods. The auto-clear paths cover most cases
(AW resume, pod-dark disarm), but a manually-toggled EMS only clears
when you toggle it off, and a Method-B-style "disable all gens" auto-arm
also stays on after re-enabling gens. Always glance at EMS state if a
pod isn't waking when you'd expect.</p>
</div>

<!-- ============================================================ -->
<h2 id="protections">Newer Automatic Protections</h2>

<p>Two newer safety layers run entirely on their own. You don't operate them,
but you <em>will</em> see them act — so here's what they are, so nothing looks
mysterious.</p>

<h3>Live Fast Auto-EMS (front-runs the provider feed)</h3>
<p>Every gen that streams live telemetry is watched for the instant it drops
off load — either its breaker opens or the engine stops. The moment that
happens, the system arms EMS on that gen's pods and sheds their miners,
usually within ~5 seconds of the trip — roughly 30 seconds to 2 minutes
<em>ahead</em> of the older provider (Mesa) feed, which lags. It's the same
auto-EMS you already know from AS; it just fires off the fast live signal
instead of waiting for Mesa. It works even if the rest of the group isn't
fully live, because sleeping miners is always the safe direction.</p>

<div class="callout good">
<span class="label">Why it matters to you</span>
<p>After a real trip you'll often see pods already asleep <em>before</em> the
gen even shows "Down" on the provider data. That's the fast path doing its
job — nothing is broken.</p>
</div>

<h3>Pre-emptive Capacity Hold</h3>
<p>When a gen throws a coolant-temp or shutdown-class warning (the
<i class="fas fa-triangle-exclamation ic ic-warn"></i> warning triangle on the
gen row), the system can put that gen on a <strong>capacity
hold</strong> <em>before</em> it actually fails. The gen keeps running and
producing, but the power engine stops counting its capacity toward the group
target — so the group sheds enough miners to leave one gen's worth of
headroom, and AW won't refill it. If that shaky gen then drops, the group
already has room to absorb the load and rides through instead of cascading.</p>

<p>A held gen shows a <i class="fas fa-circle-pause ic ic-hold"></i>
<strong>pause-circle</strong> icon on its row.</p>

<h4>How a hold clears</h4>
<ul>
<li><strong>You mark it repaired</strong> — use "Mark Repaired" on the gen
once it's been looked at. This is the normal way to release it.</li>
<li><strong>It recovers on its own</strong> — if the warning metric drops back
under the threshold, the hold releases automatically.</li>
<li><strong>The gen actually goes down</strong> — a real stop clears the hold,
and the normal gen-down handling takes over from there.</li>
</ul>

<div class="callout warn">
<span class="label">A held group runs "one gen light" on purpose</span>
<p>If a group looks like it's carrying fewer miners than its gen count
suggests it could, check for a <i class="fas fa-circle-pause ic ic-hold"></i>
hold or a <i class="fas fa-triangle-exclamation ic ic-warn"></i> warning on one
of its gens.
That headroom is being held back deliberately so losing that gen won't
cascade the group. Don't fight it by bumping kW or force-waking — clear the
underlying gen issue and mark it repaired.</p>
</div>

<!-- ============================================================ -->
<h2 id="scenarios">Walkthroughs: Common Scenarios</h2>

<h3>Scenario 1: Gen fails unexpectedly at 2am</h3>
<div class="flow"><span class="step">1.</span> The gen's live telemetry shows it dropping off load (breaker opens or engine stops). The fast-EMS path catches this within ~5 seconds — ahead of the slower provider (Mesa) feed.
<span class="step">2.</span> <span class="action">AS auto-arms EMS</span> on the affected pods. EMS toggles flip on by themselves; AW and R0 toggles flip off in lockstep.
<span class="step">3.</span> Field-server agents start hammering sleep at every miner in those pods. Within seconds the pods are fully asleep.
<span class="step">4.</span> Remaining gens stay safe — load on them drops to near zero.
<span class="step">5.</span> Wake-cooldown holds for 5 minutes (no wake activity).
<span class="step">6.</span> When the gen comes back up and gets through its 15-min settle, <span class="action">AW clears EMS on each pod it's about to wake</span> (EMS/AW/R0 toggles all flip back), then sends batched wakes (one gen's worth every ~5 min) until the pod is back to full capacity.</div>
<p><strong>What you do:</strong> Nothing. Check the log in the morning.
Expect AS-armed-EMS events followed by 4–6 small AW batches over the
next ~25 minutes. The toggles will end up exactly where they started.</p>

<h3>Scenario 2: Taking a gen out for service</h3>
<div class="flow"><span class="step">1.</span> Open status page, click the group's kW target.
<span class="step">2.</span> In the Edit kW modal, toggle the gen OFF. Save.
<span class="step">3.</span> System sleeps miners to match the new (lower) target.
<span class="step">4.</span> Service the gen.
<span class="step">5.</span> When done, open the modal again and toggle the gen back ON. Save.
<span class="step">6.</span> <span class="action">AW wakes miners</span> to fill the restored capacity.</div>
<p><strong>What you do NOT do:</strong> Turn off AW. Turn off AS. Manually
sleep miners. Manually wake miners. The disable-gen action does all of
that for you.</p>

<h3>Scenario 3: Bringing a whole pod down for maintenance</h3>
<p>You're about to shut all the gens off (gas line work, swap a header,
running cabling, etc.) and need every miner asleep <em>before</em> the
gens go down so the gens don't trip from a sudden no-load → load → no-load
cycle and so you're not racing physical shutdown against a panic-sleep
event. Two ways to do this — pick whichever is faster for your situation.</p>

<h4>Method A — EMS the pods (preferred)</h4>
<div class="flow"><span class="step">1.</span> On the status page gen group header, turn <span class="action">EMS ON</span> for every pod in the group.
<span class="step">2.</span> The field-server agent immediately starts hammering sleep at every miner. Within a few seconds the pod is asleep.
<span class="step">3.</span> Now turn the gens off physically. AS won't fire (EMS pods are skipped) — that's fine, the miners are already asleep.
<span class="step">4.</span> Do the maintenance.
<span class="step">5.</span> Bring the gens back. Verify they're stable.
<span class="step">6.</span> Turn <span class="action">EMS OFF</span>. AW will gradually wake the pod over ~15–25 min.</div>
<p><strong>Why this is preferred:</strong> EMS holds miners asleep regardless
of any other system state. It survives miner reboots, gen restarts, and
network blips. Lowest chance of surprises.</p>

<h4>Method B — Disable all gens in the kW modal</h4>
<div class="flow"><span class="step">1.</span> Click the group's kW target → Edit kW modal.
<span class="step">2.</span> Toggle every gen OFF. Save.
<span class="step">3.</span> System recognizes the group has zero capacity and <span class="action">auto-arms EMS</span> on every eligible pod. EMS toggles flip on; AW and R0 flip off (per the EMS coupling).
<span class="step">4.</span> Field-server agents hammer sleep at every miner. Pods fully asleep within seconds.
<span class="step">5.</span> Now turn the gens off physically.
<span class="step">6.</span> Do the maintenance.
<span class="step">7.</span> Restart the gens. Once one shows Running and stable, re-enable each gen in the kW modal as it stabilizes (or all at once when ready).
<span class="step">8.</span> <span class="warn">Toggle EMS OFF manually for each affected pod.</span> AW won't auto-wake them on its own here — AW is currently OFF (because EMS turned it off in step 3), and the system's "AW resume" auto-clear path therefore can't fire.
<span class="step">9.</span> Once you toggle EMS off, AW and R0 flip back on, and AW wakes the pod gradually over ~15–25 min.</div>

<div class="callout warn">
<span class="label">Method B has a manual step on the way back</span>
<p>The gen-down auto-arm path (Scenario 1, single gen) self-clears
because AW eventually wakes the pod, and AW clears EMS as it does.
Method B is different — disabling all gens leaves AW off, so AW never
fires the recovery clear. You have to toggle EMS off yourself once gens
are back. If you want to avoid this manual step, use Method A instead.</p>
</div>

<div class="callout warn">
<span class="label">Don't mix methods sloppily</span>
<p>Either EMS the pods <em>or</em> disable all gens — picking one is enough.
Using both at once is fine but redundant. The trap is doing <em>neither</em>
and just yanking the gens, which causes a real gen-down cascade event.</p>
</div>

<h3>Scenario 4: Whole pod is being physically worked on (miners only, gens still up)</h3>
<div class="flow"><span class="step">1.</span> Turn on EMS for that pod.
<span class="step">2.</span> Do the work. Miners stay asleep even if they reboot or reconnect.
<span class="step">3.</span> When done and the gen is confirmed stable, turn EMS off.
<span class="step">4.</span> <span class="action">AW wakes the pod gradually</span> in batches over ~15–25 min. If you need it faster, manually wake from NMT or MFT.</div>

<h3>Scenario 5: Entire site under planned outage</h3>
<div class="flow"><span class="step">1.</span> Turn off AS and AW on all affected pods (so the system stops reacting).
<span class="step">2.</span> Do whatever you need — the power system is now in manual mode.
<span class="step">3.</span> When done, turn AS and AW back on. The system resumes automatic management.</div>

<!-- ============================================================ -->
<h2 id="mistakes">Common Mistakes</h2>

<div class="callout bad">
<span class="label">Mistake: Turning off AW when disabling a gen</span>
<p><strong>Why it's wrong:</strong> The disable-gen action already
excludes the gen from AW's math. Turning off AW on top of that just
prevents the pod from recovering normally and gets forgotten later.</p>
<p><strong>What to do instead:</strong> Just disable the gen. Leave AW on.</p>
</div>

<div class="callout bad">
<span class="label">Mistake: Manually sleeping miners after a gen goes down</span>
<p><strong>Why it's wrong:</strong> AS panic-slept every awake miner in
the affected pods within 1–2 seconds of the gen-down alert. There are
no awake miners left to sleep — you'd be hitting an empty target.</p>
<p><strong>What to do instead:</strong> Check the log for an <code>AS</code>
entry. If you see it, the system handled it. Watch for AW batches over
the next 15–25 min as the pod ramps back up.</p>
</div>

<div class="callout bad">
<span class="label">Mistake: Manually sleeping a pod, then leaving AW on</span>
<p><strong>Why it's wrong:</strong> AW sees headroom and starts waking miners
right back up within a minute or two. It looks like the sleep didn't take.</p>
<p><strong>What to do instead:</strong> For maintenance, use EMS — it holds
the pod asleep regardless of what AW thinks. For taking a gen offline,
use the disable-gen toggle.</p>
</div>

<div class="callout bad">
<span class="label">Mistake: Leaving EMS on after service</span>
<p><strong>Why it's wrong:</strong> The pod stays asleep — AS and AW
cannot bring it back, because they skip EMS pods entirely.</p>
<p><strong>What to do instead:</strong> Always clear EMS once the gen is
stable and the pod is ready to hash. After a Method-B-style "disable
all gens" maintenance, you <em>must</em> toggle EMS off manually
because AW can't auto-clear it (AW is also off). After a single-gen
gen-down event (Scenario 1), EMS auto-clears as AW wakes the pod, so
it's normally hands-off.</p>
</div>

<div class="callout bad">
<span class="label">Mistake: Wondering why the EMS toggle moved on its own</span>
<p><strong>Why it's confusing:</strong> EMS isn't only manual anymore.
Gen-down events and "disable all gens" auto-arm EMS; AW resumes and
~90s of pod-dark-during-EMS auto-clear it. AW and R0 toggles flip with
EMS as a group. So a row of three toggles changing state at once on a
pod is normal — the system did it on purpose.</p>
<p><strong>What to do:</strong> If toggles flipped after a gen alert,
that's expected — the system armed EMS for the cascade. If toggles
flipped without an alert, glance at the gen group: any disabled gens?
That's likely the cause. If neither, then someone clicked the toggle.</p>
</div>

<div class="callout bad">
<span class="label">Mistake: Changing a gen's maintenance note/status and expecting power math to follow</span>
<p><strong>Why it's wrong:</strong> Gen notes and status (oos, parts, swap)
are informational only — they do not affect AS, AW, or capacity
calculations. Only the disable toggle in the Edit kW modal affects
the math.</p>
<p><strong>What to do instead:</strong> If the gen shouldn't be counted,
disable it in the Edit kW modal. If it's just a note for the team, set
the status in Generator Manager.</p>
</div>

</div>

<script>__DROPDOWN_JS__</script>
</body>
</html>
"""

HTML = HTML.replace("__NAV_CSS__", _nav_css)
HTML = HTML.replace("__DROPDOWN_CSS__", "")
HTML = HTML.replace("__DROPDOWN_HTML__", _nav_html)
HTML = HTML.replace("__DROPDOWN_JS__", _nav_js)

print(HTML)
