src/ApplicationBundle/Modules/HoneybeeWeb/Resources/views/inc/_hb360_report_body.html.twig line 1

Open in your IDE?
  1. {# ═══════════════════════════════════════════════════════════════════════════
  2.    HB360 report body — server-rendered saved estimate + (optional) Package B
  3.    report. Used by the account page (/tools/my-estimate) and the public
  4.    share view. Context: estimate (array), report (array|null), project.
  5.    Parent page must include _hb360_tags.html.twig once (pills CSS/JS).
  6.    ═══════════════════════════════════════════════════════════════════════════ #}
  7. {% import '@HoneybeeWeb/inc/_hb360_tags.html.twig' as hb360 %}
  8. <style>
  9. .hbr-headline { background: var(--n-dark); border-radius: var(--n-radius-sm); padding: 22px 20px; text-align: center; margin-bottom: 14px; }
  10. .hbr-headline .v { font-family: 'Montserrat', sans-serif; font-weight: 900; font-size: 2.2rem; color: #fff; line-height: 1.05; }
  11. .hbr-headline .l { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: rgba(255,255,255,.6); margin-top: 6px; }
  12. .hbr-kpis { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 4px 0 14px; }
  13. @media (min-width: 720px) { .hbr-kpis { grid-template-columns: repeat(4, 1fr); } }
  14. .hbr-kpi { background: var(--n-cream); border: 1px solid var(--n-border); border-radius: var(--n-radius-sm); padding: 14px 16px; }
  15. .hbr-kpi .v { font-family: 'Montserrat', sans-serif; font-weight: 900; font-size: 1.3rem; color: var(--n-dark); line-height: 1.1; }
  16. .hbr-kpi .l { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--n-muted); margin: 4px 0 6px; }
  17. .hbr-sec { margin: 26px 0 10px; }
  18. .hbr-sec h3 { font-family: 'Montserrat', sans-serif; font-weight: 800; font-size: 1.05rem; color: var(--n-dark); margin: 0 0 2px; }
  19. .hbr-sec .sub { font-size: 12px; color: var(--n-muted); }
  20. .hbr-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; }
  21. .hbr-table th { text-align: left; font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--n-muted); padding: 7px 8px; border-bottom: 1.5px solid var(--n-border-md); }
  22. .hbr-table td { padding: 8px; border-bottom: 1px solid var(--n-border); color: var(--n-dark); }
  23. .hbr-table td.num, .hbr-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
  24. .hbr-table tfoot td { font-weight: 800; border-top: 1.5px solid var(--n-border-md); border-bottom: none; }
  25. .hbr-bar { background: var(--n-amber-dim); height: 10px; border-radius: 5px; position: relative; overflow: hidden; min-width: 60px; }
  26. .hbr-bar span { position: absolute; inset: 0 auto 0 0; background: var(--n-amber); border-radius: 5px; }
  27. .hbr-verdict { border-left: 3px solid var(--n-amber); background: var(--n-cream); border-radius: var(--n-radius-sm); padding: 12px 14px; font-size: 13px; color: var(--n-dark); line-height: 1.6; margin-top: 8px; }
  28. .hbr-note { font-size: 12px; color: var(--n-muted); line-height: 1.6; margin-top: 16px; padding: 12px 14px; background: var(--n-cream); border-radius: var(--n-radius-sm); border-left: 3px solid var(--n-amber); }
  29. </style>
  30. {% set fig = estimate.figures|default({}) %}
  31. {# ── The saved estimate (Package A recap) ── #}
  32. <div class="hbr-headline">
  33.     <div class="v">€{{ estimate.annual_savings|number_format(0, ',', '.') }}</div>
  34.     <div class="l">estimated bill saving / year {{ hb360.tag('assumed', fig.annual_savings.basis|default('')) }}</div>
  35. </div>
  36. <div class="hbr-kpis">
  37.     <div class="hbr-kpi"><div class="v">{{ estimate.kwp|number_format(estimate.kwp < 100 ? 1 : 0, ',', '.') }}</div><div class="l">System size (kWp)</div>{{ hb360.tag(fig.system_kwp.tag|default('assumed'), fig.system_kwp.basis|default('')) }}</div>
  38.     <div class="hbr-kpi"><div class="v">{{ estimate.annual_gen_kwh|number_format(0, ',', '.') }}</div><div class="l">Annual yield (kWh)</div>{{ hb360.tag(fig.annual_yield.tag|default('assumed'), fig.annual_yield.basis|default('')) }}</div>
  39.     <div class="hbr-kpi"><div class="v">€{{ estimate.capex_eur|number_format(0, ',', '.') }}</div><div class="l">Indicative CAPEX</div>{{ hb360.tag(fig.capex.tag|default('assumed'), fig.capex.basis|default('')) }}</div>
  40.     <div class="hbr-kpi"><div class="v">{{ estimate.payback_years ? estimate.payback_years ~ ' yrs' : '—' }}</div><div class="l">Simple payback</div>{{ hb360.tag(fig.payback.tag|default('assumed'), fig.payback.basis|default('')) }}</div>
  41. </div>
  42. <div style="font-size:12.5px;color:var(--n-muted)">
  43.     {{ estimate.panels|number_format(0, ',', '.') }} modules · roof {{ estimate.area_m2|number_format(0, ',', '.') }} m²
  44.     {{ hb360.tag(fig.roof_area.tag|default('assumed'), fig.roof_area.basis|default('')) }} ·
  45.     sources: {{ estimate.yield_source|default('—') }}{{ estimate.roof_source is defined and estimate.roof_source ? ' · roof via ' ~ estimate.roof_source : '' }} ·
  46.     site works {{ hb360.tag('needs site survey', fig.structure.basis|default('')) }}
  47. </div>
  48. {% if report %}
  49.     {# ── Package B: full feasibility ── #}
  50.     {% set rfig = report.figures|default({}) %}
  51.     <div class="hbr-sec">
  52.         <h3>System design {{ hb360.tag(rfig.design.tag|default('assumed'), rfig.design.basis|default('')) }}</h3>
  53.         <table class="hbr-table">
  54.             <tr><td>Array</td><td class="num">{{ report.design.panels|number_format(0, ',', '.') }} × {{ report.design.module_wp }} Wp = {{ report.design.kwp|number_format(report.design.kwp < 100 ? 1 : 0, ',', '.') }} kWp</td></tr>
  55.             <tr><td>Inverter capacity (AC)</td><td class="num">{{ report.design.inverter_ac_kw|number_format(1, ',', '.') }} kW (DC/AC {{ report.design.dc_ac_ratio }})</td></tr>
  56.             <tr><td>Array area</td><td class="num">{{ report.design.array_area_m2|number_format(0, ',', '.') }} m²</td></tr>
  57.             <tr><td>Specific yield</td><td class="num">{{ report.design.specific_yield|number_format(0, ',', '.') }} kWh/kWp/yr</td></tr>
  58.         </table>
  59.     </div>
  60.     <div class="hbr-sec">
  61.         <h3>Month-by-month generation {{ hb360.tag(rfig.monthly.tag|default('assumed'), rfig.monthly.basis|default('')) }}</h3>
  62.         {% set maxGen = (report.monthly|map(m => m.gen_kwh)|sort)|last %}
  63.         <table class="hbr-table">
  64.             <thead><tr><th>Month</th><th style="width:40%"></th><th class="num">Generation</th><th class="num">Used on site</th><th class="num">Export</th></tr></thead>
  65.             <tbody>
  66.             {% for m in report.monthly %}
  67.                 <tr>
  68.                     <td>{{ m.month }}</td>
  69.                     <td><div class="hbr-bar"><span style="width:{{ maxGen > 0 ? (100 * m.gen_kwh / maxGen)|round : 0 }}%"></span></div></td>
  70.                     <td class="num">{{ m.gen_kwh|number_format(0, ',', '.') }}</td>
  71.                     <td class="num">{{ m.offset_kwh|number_format(0, ',', '.') }}</td>
  72.                     <td class="num">{{ m.export_kwh|number_format(0, ',', '.') }}</td>
  73.                 </tr>
  74.             {% endfor %}
  75.             </tbody>
  76.         </table>
  77.     </div>
  78.     <div class="hbr-sec">
  79.         <h3>Load match {{ hb360.tag(rfig.load_match.tag|default('assumed'), rfig.load_match.basis|default('')) }}</h3>
  80.         <table class="hbr-table">
  81.             <tr><td>Your annual consumption</td><td class="num">{{ report.load_match.annual_consumption_kwh|number_format(0, ',', '.') }} kWh</td></tr>
  82.             <tr><td>Solar used on site</td><td class="num">{{ report.load_match.offset_kwh|number_format(0, ',', '.') }} kWh ({{ report.load_match.self_consumption_pct }}% of generation)</td></tr>
  83.             {% if report.load_match.bill_coverage_pct is not null %}
  84.             <tr><td>Bill coverage</td><td class="num">{{ report.load_match.bill_coverage_pct }}%</td></tr>
  85.             {% endif %}
  86.             <tr><td>Surplus to grid {{ hb360.tag('needs utility approval', rfig.export_revenue.basis|default('')) }}</td><td class="num">{{ report.load_match.export_kwh|number_format(0, ',', '.') }} kWh</td></tr>
  87.             <tr><td>Still imported from grid</td><td class="num">{{ report.load_match.import_kwh|number_format(0, ',', '.') }} kWh</td></tr>
  88.         </table>
  89.     </div>
  90.     <div class="hbr-sec">
  91.         <h3>Battery (BESS) indication {{ hb360.tag(rfig.bess.tag|default('needs site survey'), rfig.bess.basis|default('')) }}</h3>
  92.         <div class="hbr-verdict">
  93.             {% if report.bess.recommended is same as(true) %}<b>🔋 Worth modelling.</b>{% elseif report.bess.recommended is same as(false) %}<b>Not indicated yet.</b>{% else %}<b>Needs your usage.</b>{% endif %}
  94.             {{ report.bess.verdict }}
  95.         </div>
  96.     </div>
  97.     <div class="hbr-sec">
  98.         <h3>25-year economics {{ hb360.tag(rfig.roi.tag|default('assumed'), rfig.roi.basis|default('')) }}</h3>
  99.         <div class="hbr-kpis" style="margin-top:8px">
  100.             <div class="hbr-kpi"><div class="v">{{ report.roi.payback_years ? report.roi.payback_years ~ ' yrs' : '> 25 yrs' }}</div><div class="l">Payback</div></div>
  101.             <div class="hbr-kpi"><div class="v">€{{ report.roi.net_benefit_25yr|number_format(0, ',', '.') }}</div><div class="l">25-yr net benefit</div></div>
  102.             <div class="hbr-kpi"><div class="v">€{{ report.roi.npv|number_format(0, ',', '.') }}</div><div class="l">NPV @ {{ report.roi.assumptions.discount_rate_pct }}%</div></div>
  103.             <div class="hbr-kpi"><div class="v">{{ report.load_match.self_consumption_pct }}%</div><div class="l">Self-consumption</div></div>
  104.         </div>
  105.         <table class="hbr-table">
  106.             <thead><tr><th>Year</th><th class="num">Saving</th><th class="num">O&amp;M</th><th class="num">Cumulative</th></tr></thead>
  107.             <tbody>
  108.             {% for y in report.roi.years %}
  109.                 {% if y.year in [1, 5, 10, 15, 20, 25] %}
  110.                 <tr><td>{{ y.year }}</td><td class="num">€{{ y.saving|number_format(0, ',', '.') }}</td><td class="num">€{{ y.om|number_format(0, ',', '.') }}</td><td class="num">€{{ y.cumulative|number_format(0, ',', '.') }}</td></tr>
  111.                 {% endif %}
  112.             {% endfor %}
  113.             </tbody>
  114.         </table>
  115.         <div class="sub" style="font-size:12px;color:var(--n-muted);margin-top:6px">
  116.             Assumes {{ report.roi.assumptions.degradation_pct_yr }}%/yr degradation, {{ report.roi.assumptions.tariff_escalation_pct_yr }}%/yr tariff escalation,
  117.             O&amp;M {{ report.roi.assumptions.om_pct_capex_yr }}% of CAPEX/yr. Savings from self-consumed energy only — export revenue excluded
  118.             {{ hb360.tag('needs utility approval', rfig.export_revenue.basis|default('')) }}.
  119.         </div>
  120.     </div>
  121.     <div class="hbr-sec">
  122.         <h3>Bill of quantities {{ hb360.tag(rfig.boq.tag|default('needs site survey'), rfig.boq.basis|default('')) }}</h3>
  123.         <table class="hbr-table">
  124.             <thead><tr><th>Item</th><th class="num">Qty</th><th>Unit</th><th>Note</th></tr></thead>
  125.             <tbody>
  126.             {% for b in report.boq %}
  127.                 <tr><td>{{ b.item }}</td><td class="num">{{ b.qty|number_format(0, ',', '.') }}</td><td>{{ b.unit }}</td><td style="color:var(--n-muted)">{{ b.note }}</td></tr>
  128.             {% endfor %}
  129.             </tbody>
  130.         </table>
  131.     </div>
  132.     <div class="hbr-sec">
  133.         <h3>Indicative cost split {{ hb360.tag(rfig.cost_split.tag|default('assumed'), rfig.cost_split.basis|default('')) }}</h3>
  134.         <table class="hbr-table">
  135.             <tbody>
  136.             {% for s in report.cost_split %}
  137.                 <tr><td>{{ s.block }}</td><td class="num">{{ s.share_pct }}%</td><td class="num">€{{ s.amount|number_format(0, ',', '.') }}</td></tr>
  138.             {% endfor %}
  139.             </tbody>
  140.             <tfoot><tr><td colspan="2">Indicative CAPEX (ex-VAT)</td><td class="num">€{{ report.capex_eur|number_format(0, ',', '.') }}</td></tr></tfoot>
  141.         </table>
  142.     </div>
  143.     {% if report.package_c is defined and report.package_c.ok|default(false) %}
  144.         {% set pc = report.package_c %}
  145.         {% set cfig = pc.figures|default({}) %}
  146.         <div class="hbr-sec" style="border-top:2px solid var(--n-border-md);padding-top:18px;margin-top:30px">
  147.             <h3>Package C — hybrid design (PV + battery + backup + EV)</h3>
  148.             <div class="sub">Battery absorbs the surplus first, EV charging uses what remains, the genset is capacity insurance, finance sees the whole package.</div>
  149.         </div>
  150.         <div class="hbr-sec">
  151.             <h3>Battery storage {{ hb360.tag(cfig.bess.tag|default('needs site survey'), cfig.bess.basis|default('')) }}</h3>
  152.             {% if pc.bess.ok|default(false) %}
  153.             <table class="hbr-table">
  154.                 <tr><td>Recommended size</td><td class="num">{{ pc.bess.nameplate_kwh|number_format(1, ',', '.') }} kWh / {{ pc.bess.power_kw|number_format(1, ',', '.') }} kW</td></tr>
  155.                 <tr><td>Energy shifted to evening use</td><td class="num">{{ pc.bess.shifted_kwh_yr|number_format(0, ',', '.') }} kWh/yr</td></tr>
  156.                 <tr><td>Self-consumption</td><td class="num">{{ pc.bess.self_consumption_before_pct }}% → {{ pc.bess.self_consumption_after_pct }}%</td></tr>
  157.                 <tr><td>Indicative cost / added saving</td><td class="num">€{{ pc.bess.capex_eur|number_format(0, ',', '.') }} / €{{ pc.bess.saving_eur_yr|number_format(0, ',', '.') }} per yr</td></tr>
  158.                 <tr><td>Battery payback</td><td class="num">{{ pc.bess.payback_years ? pc.bess.payback_years ~ ' yrs' : '—' }}</td></tr>
  159.             </table>
  160.             {% else %}
  161.             <div class="hbr-verdict">{{ pc.bess.reason|default('Storage not sized.') }}</div>
  162.             {% endif %}
  163.         </div>
  164.         {% if pc.evse.ok|default(false) %}
  165.         <div class="hbr-sec">
  166.             <h3>EV charging {{ hb360.tag(cfig.evse.tag|default('needs utility approval'), cfig.evse.basis|default('')) }}</h3>
  167.             <table class="hbr-table">
  168.                 <thead><tr><th>Charger</th><th class="num">Count</th><th class="num">Annual energy</th><th class="num">Indicative cost</th></tr></thead>
  169.                 <tbody>
  170.                 {% for c in pc.evse.chargers %}
  171.                     <tr><td>{{ c.type }}</td><td class="num">{{ c.count }}</td><td class="num">{{ c.annual_kwh|number_format(0, ',', '.') }} kWh</td><td class="num">€{{ c.capex_eur|number_format(0, ',', '.') }}</td></tr>
  172.                 {% endfor %}
  173.                 </tbody>
  174.             </table>
  175.             <table class="hbr-table">
  176.                 <tr><td>Added diversified peak</td><td class="num">{{ pc.evse.diversified_peak_kw|number_format(1, ',', '.') }} kW (diversity {{ pc.evse.diversity_factor }})</td></tr>
  177.                 <tr><td>Covered by PV surplus</td><td class="num">{{ pc.evse.pv_covered_kwh|number_format(0, ',', '.') }} kWh ({{ pc.evse.pv_coverage_pct }}%)</td></tr>
  178.                 <tr><td>Grid energy for charging</td><td class="num">{{ pc.evse.grid_kwh|number_format(0, ',', '.') }} kWh ≈ €{{ pc.evse.energy_cost_eur_yr|number_format(0, ',', '.') }}/yr</td></tr>
  179.             </table>
  180.             <div class="hbr-verdict">{{ pc.evse.grid_note }} {{ hb360.tag('needs utility approval') }}</div>
  181.         </div>
  182.         {% endif %}
  183.         {% if pc.genset.ok|default(false) %}
  184.         <div class="hbr-sec">
  185.             <h3>Backup genset {{ hb360.tag(cfig.genset.tag|default('needs site survey'), cfig.genset.basis|default('')) }}</h3>
  186.             <div class="hbr-verdict">{% if pc.genset.recommended %}<b>⚙ Recommended.</b>{% else %}<b>Optional.</b>{% endif %} {{ pc.genset.verdict }}</div>
  187.             <table class="hbr-table">
  188.                 <tr><td>Critical load / frame size</td><td class="num">{{ pc.genset.critical_kw|number_format(1, ',', '.') }} kW → {{ pc.genset.kva }} kVA</td></tr>
  189.                 <tr><td>Battery ride-through</td><td class="num">{{ pc.genset.bess_ride_through_hrs }} h</td></tr>
  190.                 <tr><td>Indicative cost</td><td class="num">€{{ pc.genset.capex_eur|number_format(0, ',', '.') }}</td></tr>
  191.                 <tr><td>Outage running cost</td><td class="num">€{{ pc.genset.running_cost_eur_yr|number_format(0, ',', '.') }}/yr ({{ pc.genset.fuel_l_yr|number_format(0, ',', '.') }} l diesel · €{{ pc.genset.diesel_cost_per_kwh }}/kWh vs grid €{{ pc.genset.grid_cost_per_kwh }})</td></tr>
  192.             </table>
  193.         </div>
  194.         {% endif %}
  195.         {% if pc.finance.ok|default(false) %}
  196.         <div class="hbr-sec">
  197.             <h3>Financing the package {{ hb360.tag(cfig.finance.tag|default('assumed'), cfig.finance.basis|default('')) }}</h3>
  198.             <div class="hbr-kpis" style="margin-top:8px">
  199.                 <div class="hbr-kpi"><div class="v">€{{ pc.combined.capex_eur|number_format(0, ',', '.') }}</div><div class="l">Package CAPEX</div></div>
  200.                 <div class="hbr-kpi"><div class="v">€{{ pc.combined.saving_eur_yr|number_format(0, ',', '.') }}</div><div class="l">Saving / yr</div></div>
  201.                 <div class="hbr-kpi"><div class="v">{{ pc.combined.self_sufficiency_pct is not null ? pc.combined.self_sufficiency_pct ~ '%' : '—' }}</div><div class="l">Self-sufficiency</div></div>
  202.                 <div class="hbr-kpi"><div class="v">{{ pc.finance.cash.irr_pct is not null ? pc.finance.cash.irr_pct ~ '%' : '—' }}</div><div class="l">IRR (cash)</div></div>
  203.             </div>
  204.             <table class="hbr-table">
  205.                 <tr><td>Cash purchase</td><td class="num">payback {{ pc.finance.cash.payback_years ? pc.finance.cash.payback_years ~ ' yrs' : '> ' ~ pc.finance.assumptions.years ~ ' yrs' }} · NPV €{{ pc.finance.cash.npv|number_format(0, ',', '.') }}</td></tr>
  206.                 <tr><td>{{ (pc.finance.assumptions.loan_share * 100)|round }}% loan @ {{ (pc.finance.assumptions.loan_rate * 100)|round(1) }}% / {{ pc.finance.assumptions.loan_years }} yrs</td>
  207.                     <td class="num">equity €{{ pc.finance.loan.equity_eur|number_format(0, ',', '.') }} · instalment €{{ pc.finance.loan.annuity_eur_yr|number_format(0, ',', '.') }}/yr · {{ pc.finance.loan.self_financing ? 'saving covers the instalment from year 1' : 'cash-positive in year ' ~ (pc.finance.loan.cash_positive_year ?: '> ' ~ pc.finance.assumptions.years) }}</td></tr>
  208.                 {% if pc.finance.lcoe.eur_per_kwh is not null %}
  209.                 <tr><td>Levelized cost of your solar energy</td><td class="num">€{{ pc.finance.lcoe.eur_per_kwh|number_format(3, ',', '.') }}/kWh {{ pc.finance.lcoe.beats_grid ? '— beats' : '— above' }} your grid tariff (€{{ pc.finance.lcoe.grid_tariff }}/kWh)</td></tr>
  210.                 {% endif %}
  211.             </table>
  212.             <div class="sub" style="font-size:12px;color:var(--n-muted);margin-top:6px">Indicative lenses — no tax, depreciation or subsidy treatment. Not an offer of finance.</div>
  213.         </div>
  214.         {% endif %}
  215.         <div class="hbr-note">{{ pc.disclaimer }}</div>
  216.     {% endif %}
  217.     {% if report.package_d is defined and report.package_d.ok|default(false) %}
  218.         {% set pd = report.package_d %}
  219.         {% set dfig = pd.figures|default({}) %}
  220.         <div class="hbr-sec" style="border-top:2px solid var(--n-border-md);padding-top:18px;margin-top:30px">
  221.             <h3>Package D — grid &amp; connection concept</h3>
  222.             <div class="sub">Regime: {{ pd.jurisdiction_hint }} · A briefing pack for the DNO enquiry and the protection study — not a design.</div>
  223.         </div>
  224.         <div class="hbr-verdict" style="border-left-color:#b4452f;background:#fdf3f0">
  225.             <b>⚠ {{ pd.engineer_handoff.headline }}</b><br>
  226.             {{ pd.engineer_handoff.next }}
  227.         </div>
  228.         <div class="hbr-sec">
  229.             <h3>Nearby grid infrastructure {{ hb360.tag(dfig.scan.tag|default('assumed'), dfig.scan.basis|default('')) }}</h3>
  230.             {% if pd.scan.ok|default(false) %}
  231.             <table class="hbr-table">
  232.                 <thead><tr><th>Asset</th><th class="num">Voltage</th><th class="num">Distance</th><th class="num">Confidence</th><th>Note</th></tr></thead>
  233.                 <tbody>
  234.                 {% for c in pd.scan.candidates %}
  235.                     <tr>
  236.                         <td>{{ c.type }}{{ c.name ? ' · ' ~ c.name : '' }}</td>
  237.                         <td class="num">{{ c.voltage_kv is not null ? c.voltage_kv ~ ' kV' : '—' }}</td>
  238.                         <td class="num">{{ c.distance_m|number_format(0, ',', '.') }} m</td>
  239.                         <td class="num">{{ (c.confidence * 100)|round }}%</td>
  240.                         <td style="color:var(--n-muted);font-size:12px">{{ c.note }}</td>
  241.                     </tr>
  242.                 {% endfor %}
  243.                 </tbody>
  244.             </table>
  245.             <div class="sub" style="font-size:12px;color:var(--n-muted);margin-top:6px">{{ pd.scan.caveat }} Source: {{ pd.scan.source }}.</div>
  246.             {% else %}
  247.             <div class="hbr-verdict">{{ pd.scan.reason }}</div>
  248.             {% endif %}
  249.             {% if pd.route %}
  250.             <table class="hbr-table">
  251.                 <tr><td>Indicative cable route to {{ pd.route.to }} {{ hb360.tag(dfig.route.tag|default('assumed'), dfig.route.basis|default('')) }}</td>
  252.                     <td class="num">{{ pd.route.route_m|number_format(0, ',', '.') }} m ≈ €{{ pd.route.cost_eur|number_format(0, ',', '.') }} ({{ pd.route.eur_per_m }} €/m)</td></tr>
  253.             </table>
  254.             {% endif %}
  255.         </div>
  256.         <div class="hbr-sec">
  257.             <h3>Connection level indication {{ hb360.tag(dfig.connection.tag|default('needs utility approval'), dfig.connection.basis|default('')) }}</h3>
  258.             <table class="hbr-table">
  259.                 <tr><td>Design capacity</td><td class="num">{{ pd.grid.design_kva|number_format(1, ',', '.') }} kVA (export {{ pd.grid.export_peak_kw }} kW / import {{ pd.grid.import_peak_kw }} kW{{ pd.grid.export_cap_pct < 100 ? ' · feed-in capped at ' ~ pd.grid.export_cap_pct ~ '%' : '' }})</td></tr>
  260.                 <tr><td>Indicated level</td><td class="num">{{ pd.grid.level }} — {{ pd.grid.level_label }} ({{ pd.grid.regime }})</td></tr>
  261.                 {% if pd.grid.transformer_kva %}
  262.                 <tr><td>Transformer concept</td><td class="num">{{ pd.grid.transformer_kva }} kVA</td></tr>
  263.                 {% endif %}
  264.                 {% if pd.grid.existing_connection_kva %}
  265.                 <tr><td>Your existing connection</td><td class="num">{{ pd.grid.existing_connection_kva }} kVA → headroom {{ pd.grid.headroom_kva }} kVA {{ pd.grid.upgrade_likely ? '· upgrade likely' : '· may suffice' }} {{ hb360.tag('needs utility approval') }}</td></tr>
  266.                 {% endif %}
  267.                 {% for it in pd.grid.budget.items %}
  268.                 <tr><td style="color:var(--n-muted)">{{ it.item }}</td><td class="num">€{{ it.eur|number_format(0, ',', '.') }}</td></tr>
  269.                 {% endfor %}
  270.                 <tr><td><b>Indicative connection budget (excl. DNO charges)</b></td><td class="num"><b>€{{ pd.grid.budget.total_eur|number_format(0, ',', '.') }}</b></td></tr>
  271.             </table>
  272.             <div class="sub" style="font-size:12px;color:var(--n-muted);margin-top:6px">{{ pd.grid.caveat }}</div>
  273.         </div>
  274.         <div class="hbr-sec">
  275.             <h3>Protection concept checklist {{ hb360.tag(dfig.protection.tag|default('needs utility approval'), dfig.protection.basis|default('')) }}</h3>
  276.             <div style="overflow-x:auto">
  277.                 <table class="hbr-table">
  278.                     <thead><tr><th>Location</th><th>Function</th><th>ANSI</th><th>Standard</th><th>Scope</th></tr></thead>
  279.                     <tbody>
  280.                     {% for row in pd.protection.rows %}
  281.                         <tr>
  282.                             <td>{{ row.location }}</td>
  283.                             <td>{{ row.function }}{{ row.note ? ' — ' : '' }}<span style="color:var(--n-muted);font-size:12px">{{ row.note }}</span></td>
  284.                             <td class="num">{{ row.ansi }}</td>
  285.                             <td style="font-size:12px">{{ row.standard }}</td>
  286.                             <td style="font-size:11px;white-space:nowrap">{{ row.scope == 'study' ? '📐 study first' : '🧑‍🔧 engineer + DNO' }}</td>
  287.                         </tr>
  288.                     {% endfor %}
  289.                     </tbody>
  290.                 </table>
  291.             </div>
  292.             <div class="hbr-verdict" style="border-left-color:#b4452f;background:#fdf3f0;margin-top:10px">
  293.                 <b>No settings, no ratings, no coordination.</b> {{ pd.protection.caveat }}
  294.             </div>
  295.         </div>
  296.         <div class="hbr-sec">
  297.             <h3>EMS / plant control functions {{ hb360.tag(dfig.ems.tag|default('assumed'), dfig.ems.basis|default('')) }}</h3>
  298.             <table class="hbr-table">
  299.                 <thead><tr><th>Function</th><th>Why</th><th>Where</th></tr></thead>
  300.                 <tbody>
  301.                 {% for fn in pd.ems.functions %}
  302.                     <tr><td>{{ fn.function }}</td><td style="color:var(--n-muted);font-size:12px">{{ fn.why }}</td><td style="font-size:12px">{{ fn.where }}</td></tr>
  303.                 {% endfor %}
  304.                 </tbody>
  305.             </table>
  306.             <table class="hbr-table">
  307.                 <thead><tr><th>Metering point</th><th>Why</th></tr></thead>
  308.                 <tbody>
  309.                 {% for m in pd.ems.metering %}
  310.                     <tr><td>{{ m.point }}</td><td style="color:var(--n-muted);font-size:12px">{{ m.why }}</td></tr>
  311.                 {% endfor %}
  312.                 </tbody>
  313.             </table>
  314.             <div class="hbr-verdict">🐝 {{ pd.ems.honeycore_note }}</div>
  315.         </div>
  316.         <div class="hbr-sec">
  317.             <h3>What to bring to the DNO enquiry</h3>
  318.             <ul style="font-size:13px;color:var(--n-dark);line-height:1.9;margin:6px 0 0;padding-left:20px">
  319.                 {% for b in pd.engineer_handoff.bring %}<li>{{ b }}</li>{% endfor %}
  320.             </ul>
  321.         </div>
  322.         <div class="hbr-note" style="border-left-color:#b4452f">{{ pd.disclaimer }}</div>
  323.     {% endif %}
  324.     <div class="hbr-note">{{ report.disclaimer }}</div>
  325.     <div style="margin-top:6px">{{ hb360.legend() }}</div>
  326. {% else %}
  327.     <div class="hbr-note">{{ estimate.disclaimer|default('Indicative estimate only — not a quote.') }}</div>
  328. {% endif %}