berekeningRapportTekst function

String berekeningRapportTekst(
  1. Invoer inv,
  2. Resultaten r,
  3. AppLocalizations l10n
)

Implementation

String berekeningRapportTekst(Invoer inv, Resultaten r, AppLocalizations l10n) {
  final buf = StringBuffer();
  final nu = DateTime.now();
  final datum =
      '${nu.day.toString().padLeft(2, '0')}-${nu.month.toString().padLeft(2, '0')}-${nu.year}'
      '  ${nu.hour.toString().padLeft(2, '0')}:${nu.minute.toString().padLeft(2, '0')}';

  void lijn([int n = 64]) => buf.writeln('─' * n);
  void titel(String t) { buf.writeln(); lijn(); buf.writeln(t); lijn(); }
  void rij(String label, String waarde) =>
      buf.writeln('${label.padRight(26)}$waarde');

  buf.writeln(l10n.rapportTitel);
  buf.writeln(l10n.rapportNorm);
  buf.writeln('${l10n.rapportDatum}: $datum');
  buf.writeln('=' * 64);

  // ── 1. INVOER ──────────────────────────────────────────────────
  titel(l10n.rapportInvoer);
  rij('Systeemtype', inv.systeem.label);
  rij('Spanning', '${inv.spanningV.toStringAsFixed(0)} V');
  if (inv.vermogenW != null && inv.vermogenW! > 0) {
    rij('Vermogen', '${inv.vermogenW!.toStringAsFixed(0)} W');
    rij('Eff. stroom (berekend)', '${inv.effectieveStroom.toStringAsFixed(2)} A');
  } else {
    rij('Stroom', '${inv.stroomA.toStringAsFixed(2)} A');
  }
  if (inv.systeem.isAC) rij('Cos φ', inv.cosPhi.toStringAsFixed(3));
  rij('Kabellengte', '${inv.lengteM.toStringAsFixed(1)} m');
  rij('Geleider', inv.geleider.label);
  rij('Isolatie', inv.isolatie.label);
  rij('Aantal aders', adersLabel(inv.aantalAders));
  if (inv.aantalAders == 1 && inv.systeem.isAC) {
    final gpkR = inv.geleidersPerKring;
    final is3F = inv.systeem == Systeemtype.ac3Fase;
    final gpkLabel = is3F
        ? switch (gpkR) {
            3 => '$gpkR  (L1 + L2 + L3)',
            4 => '$gpkR  (L1 + L2 + L3 + N)',
            _ => '$gpkR  (L1 + L2 + L3 + N + PE)',
          }
        : (gpkR == 2 ? '$gpkR  (L + N)' : '$gpkR  (L + N + PE)');
    rij('Geleiders per kring', gpkLabel);
  }
  rij('Leggingswijze', inv.legging.label);
  if (inv.isGrondkabel) {
    rij('Legdiepte', '${inv.diepteM.toStringAsFixed(2)} m');
    rij('Grondtemperatuur', '${inv.grondtempC.toStringAsFixed(0)} °C');
    rij('Bodemweerstand λ', '${inv.lambdaGrond.toStringAsFixed(2)} K·m/W');
    if (inv.cyclischProfiel != null) {
      rij('Cyclisch profiel', l10n.rapportCyclischIngeschakeld(
          inv.cyclischNKringen, inv.cyclischAanliggend));
    }
  } else {
    rij('Omgevingstemperatuur', '${inv.omgevingstempC.toStringAsFixed(0)} °C');
    if (inv.zonlichtToeslagK > 0) {
      rij('Zonlichttoeslag', '+${inv.zonlichtToeslagK.toStringAsFixed(0)} K  (NEN 1010)');
    }
  }
  if (inv.nParallel > 1) {
    rij('Parallel kabels', '${inv.nParallel} ${l10n.rapportParallelKabels}');
  }
  rij('Max. spanningsval', '${inv.maxSpanningsvalPct.toStringAsFixed(1)} %');
  if (inv.kortsluitstroomA > 0) {
    rij('Kortsluitstroom I_k', '${inv.kortsluitstroomA.toStringAsFixed(0)} A');
    rij('Kortsluitduur', '${inv.kortsluitduurMs.toStringAsFixed(0)} ms');
  } else {
    rij('Kortsluittoets', l10n.rapportKortsluitNiet);
  }
  if (inv.harmonischenActief) {
    final iN = 3.0 * (inv.derdeHarmonischePct / 100.0) * inv.effectieveStroom;
    rij('3e harmonische', '${inv.derdeHarmonischePct.toStringAsFixed(0)} %  van fasestroom');
    rij('Nulpuntsstroom I_N', '${iN.toStringAsFixed(1)} A'
        '  = 3 × ${(inv.derdeHarmonischePct / 100).toStringAsFixed(2)} × ${inv.effectieveStroom.toStringAsFixed(1)} A');
  }
  if (inv.forceerDoorsnedemm2 != null) {
    rij('Geforceerde doorsnede', '${inv.forceerDoorsnedemm2} mm²');
  }
  if (inv.bundel != null) {
    rij('Bundeling', '${inv.bundel!.nHorizontaal} naast × ${inv.bundel!.nVerticaal} hoog'
        '  (${inv.bundel!.totaalKabels} kabels)');
  }

  if (r.kabel == null) {
    titel('RESULTAAT');
    buf.writeln(l10n.rapportGeenKabel);
  } else {
    final k = r.kabel!;
    final ip = isolatieEigenschappen[k.isolatie]!;

    titel(l10n.rapportKabel);
    rij('Type', k.naam);
    rij('Doorsnede', '${k.doorsnedemm2} mm²');
    rij('Geleider', k.geleider.label);
    rij('Isolatie', '${k.isolatie.label}'
        '  (max. ${ip.maxTempContinu.toInt()} °C continu'
        ', ${ip.maxTempKortsluit.toInt()} °C KS)');
    rij('Aantal aders', '${k.aantalAders}×');
    if (inv.aantalAders == 1 && inv.systeem.isAC) {
      final gpkK = inv.geleidersPerKring;
      final totaalK = r.nParallel * gpkK;
      final is3FK = inv.systeem == Systeemtype.ac3Fase;
      final gpkLabelK = is3FK
          ? switch (gpkK) {
              3 => '$gpkK geleiders per kring  (L1 + L2 + L3)',
              4 => '$gpkK geleiders per kring  (L1 + L2 + L3 + N)',
              _ => '$gpkK geleiders per kring  (L1 + L2 + L3 + N + PE)',
            }
          : (gpkK == 2 ? '$gpkK geleiders per kring  (L + N)' : '$gpkK geleiders per kring  (L + N + PE)');
      rij('Singel-configuratie', gpkLabelK);
      rij('Totaal singels',
          '$totaalK stuks${r.nParallel > 1 ? "  (${r.nParallel}× parallel × $gpkK)" : ""}');
    }
    rij('Buitendiameter', '${k.buitendiameter.toStringAsFixed(1)} mm');
    rij('R_AC @ 20°C', '${k.rAcPerKm20C.toStringAsFixed(4)} Ω/km');
    rij('X @ 50 Hz', '${k.xAcPerKm.toStringAsFixed(4)} Ω/km');

    titel(l10n.rapportCF);
    final tMax = ip.maxTempContinu;
    final tRef = ip.refTempTabel;
    rij('θ_eff (omgeving)', '${r.tEffectief.toStringAsFixed(1)} °C'
        '${r.tEffectief > (inv.isGrondkabel ? inv.grondtempC : inv.omgevingstempC) ? "  (incl. zonlicht)" : ""}');
    rij('f_T  (temperatuur)', '${r.fT.toStringAsFixed(4)}'
        '  = √[(${tMax.toInt()}−${r.tEffectief.toStringAsFixed(1)})/(${tMax.toInt()}−${tRef.toInt()})]');
    if (r.bundelPositieWorst != null) {
      rij('f_bundel', '${r.fBundel.toStringAsFixed(4)}  = f_h × f_v');
      rij('  f_h (horizontaal)', '${r.fHorizontaal.toStringAsFixed(4)}  (IEC tabel B.52.20)');
      rij('  f_v (stapeling)', '${r.fVerticaal.toStringAsFixed(4)}  (IEC tabel B.52.21)');
      rij('  Maatg. positie', '(${r.bundelPositieWorst!.$1}, ${r.bundelPositieWorst!.$2})  — centrum bundel');
    } else {
      rij('f_bundel', '${r.fBundel.toStringAsFixed(4)}  [geen bundeling]');
    }
    rij('f_grond', '${r.fGrond.toStringAsFixed(4)}'
        '${inv.isGrondkabel ? "  (λ=${inv.lambdaGrond.toStringAsFixed(2)} K·m/W)" : "  [bovengronds]"}');
    if (r.fCyclisch != 1.0) {
      rij('M  (cyclisch)', '${r.fCyclisch.toStringAsFixed(4)}  (NEN IEC 60583-1:2002)');
    }
    if (r.iNeutraal > 0) {
      rij('f_harm  (harmonischen)', '${r.fHarmonisch.toStringAsFixed(4)}  (NEN 1010 Bijlage 52.E.1)');
      rij('  Grondslag', r.harmonischOpNul
          ? '${l10n.rapportNulpuntsstroom}${r.iNeutraal.toStringAsFixed(1)} A'
          : l10n.rapportFasestroom);
    }
    final fTotaalFormule = [
      'f_T × f_bundel × f_grond',
      if (r.fCyclisch != 1.0) ' × M',
      if (r.iNeutraal > 0) ' × f_harm',
    ].join();
    rij('f_TOTAAL', '${r.fTotaal.toStringAsFixed(4)}  = $fTotaalFormule');

    final int sOffset = (inv.cyclischProfiel != null ? 1 : 0) +
        (inv.harmonischenActief ? 1 : 0);

    if (inv.cyclischProfiel != null) {
      titel(l10n.rapportCyclischNr(4));
      buf.writeln('Formule: M = 1 / √( Σ Yᵢ·ΔθR(i)  +  μ·(1 − θR(6)) )');
      rij('Aantal kringen N', '${inv.cyclischNKringen}');
      rij('Ligging kringen', inv.cyclischAanliggend
          ? 'Aanliggend (touching)'
          : 'Gespreid (spaced)');
      if (inv.cyclischNKringen > 1 && inv.cyclischHartOpHartMm > 0) {
        rij('Hart-op-hart', '${inv.cyclischHartOpHartMm.toStringAsFixed(0)} mm');
      } else if (inv.cyclischNKringen > 1) {
        rij('Hart-op-hart', l10n.rapportAanliggendKabeldiameter);
      }
      rij('Legdiepte L', '${inv.diepteM.toStringAsFixed(2)} m');
      rij('Grondtemperatuur Tgr', '${inv.grondtempC.toStringAsFixed(0)} °C');
      rij('Thermische weerstand ξ', '${inv.lambdaGrond.toStringAsFixed(2)} K·m/W');
      buf.writeln();
      buf.writeln('Belastingsprofiel I/Imax per uur:');
      final p = inv.cyclischProfiel!;
      for (int rw = 0; rw < 4; rw++) {
        final start = rw * 6;
        final label = '  Uur ${start.toString().padLeft(2)}–${(start + 5).toString().padLeft(2)}';
        final vals = List.generate(6, (c) {
          final v = p[start + c];
          return v.toStringAsFixed(2).padLeft(5);
        }).join('  ');
        buf.writeln('$label:  $vals');
      }
      buf.writeln();
      final mu = p.fold(0.0, (s, v) => s + v * v) / 24;
      final maxI = p.reduce((a, b) => a > b ? a : b);
      rij('μ  (gem. Yi = (I/Imax)²)', mu.toStringAsFixed(4));
      rij('Piekwaarde I/Imax', maxI.toStringAsFixed(2));
      rij('Cyclische factor M', r.fCyclisch.toStringAsFixed(4));
      rij('Toelichting', l10n.rapportMHogerBelasting);
    }

    if (inv.harmonischenActief) {
      final sHarm = (inv.cyclischProfiel != null ? 5 : 4);
      titel(l10n.rapportHarmNr(sHarm));
      buf.writeln('Tabel E.52.1: correctiefactoren voor 4- of 5-aderige 3-fase kabels');
      buf.writeln('  h3 (%)    Grondslag        f_harm');
      buf.writeln('  0–15      fasestroom        1,00');
      buf.writeln('  15–33     fasestroom        0,86');
      buf.writeln('  33–45     nulpuntsstroom    0,86');
      buf.writeln('  > 45      nulpuntsstroom    1,00');
      buf.writeln();
      rij('3e harmonische h3', '${inv.derdeHarmonischePct.toStringAsFixed(0)} %');
      rij('f_harm', r.fHarmonisch.toStringAsFixed(4));
      rij('I_N = 3 × h3 × I_fase', '${r.iNeutraal.toStringAsFixed(1)} A');
      rij('Grondslag kabelkeuze', r.harmonischOpNul
          ? 'nulpuntsstroom  (h3 > 33%)'
          : 'fasestroom  (h3 ≤ 33%)');
    }

    titel(l10n.rapportBelNrVan(4 + sOffset, ''));
    if (r.nParallel > 1) {
      rij('Configuratie', '${r.nParallel} kabels parallel per fase');
      rij('I_totaal', '${r.iGevraagd.toStringAsFixed(2)} A');
      rij('I per kabel', '${r.iPerKabel.toStringAsFixed(2)} A'
          '  = ${r.iGevraagd.toStringAsFixed(2)} / ${r.nParallel}');
    }
    final methode = inv.legging.isVrijeLucht ? 'E (vrije lucht)' : 'C (in buis/wand)';
    rij('I_z0  (tabel, 30°C)', '${r.iz0.toStringAsFixed(1)} A  per kabel  [methode $methode]');
    rij('I_z  (gecorrigeerd)', '${r.iz.toStringAsFixed(1)} A  = ${r.iz0.toStringAsFixed(1)} × ${r.fTotaal.toStringAsFixed(4)}');
    final iGrondslag = r.harmonischOpNul ? r.iNeutraal : r.iPerKabel;
    final grondslagLabel = r.harmonischOpNul ? 'I_N  (maatgevend)' : 'I_gevraagd';
    rij(grondslagLabel, '${iGrondslag.toStringAsFixed(2)} A  per kabel');
    if (r.harmonischOpNul) {
      rij('I_fase', '${r.iPerKabel.toStringAsFixed(2)} A  per kabel');
    }
    rij('Veiligheidsmarge', '${r.margeStroomPct >= 0 ? "+" : ""}${r.margeStroomPct.toStringAsFixed(1)} %'
        '  = (${r.iz.toStringAsFixed(1)}/${iGrondslag.toStringAsFixed(2)} − 1) × 100');

    titel(l10n.rapportSVNr(5 + sOffset));
    final gelProp = geleiderEigenschappen[k.geleider]!;
    final rAt = k.rAcPerKm20C * (1 + gelProp.alpha20 * (tMax - 20)) / 1000;
    final xM = k.xAcPerKm / 1000;
    switch (inv.systeem) {
      case Systeemtype.ac1Fase:
        final sinPhi = sqrt(max(0.0, 1 - inv.cosPhi * inv.cosPhi));
        buf.writeln('Formule:  ΔU = 2 · I · L · (R·cosφ + X·sinφ)');
        rij('R_AC @ ${tMax.toInt()}°C', '${rAt.toStringAsFixed(6)} Ω/m');
        rij('X', '${xM.toStringAsFixed(6)} Ω/m');
        rij('sin φ', sinPhi.toStringAsFixed(4));
        buf.writeln('ΔU = 2 × ${r.iPerKabel.toStringAsFixed(2)} × ${inv.lengteM.toStringAsFixed(1)}'
            ' × (${rAt.toStringAsFixed(6)}×${inv.cosPhi.toStringAsFixed(3)} + ${xM.toStringAsFixed(6)}×${sinPhi.toStringAsFixed(4)})');
      case Systeemtype.ac3Fase:
        final sinPhi = sqrt(max(0.0, 1 - inv.cosPhi * inv.cosPhi));
        buf.writeln('Formule:  ΔU = √3 · I · L · (R·cosφ + X·sinφ)');
        rij('R_AC @ ${tMax.toInt()}°C', '${rAt.toStringAsFixed(6)} Ω/m');
        rij('X', '${xM.toStringAsFixed(6)} Ω/m');
        rij('sin φ', sinPhi.toStringAsFixed(4));
        buf.writeln('ΔU = √3 × ${r.iPerKabel.toStringAsFixed(2)} × ${inv.lengteM.toStringAsFixed(1)}'
            ' × (${rAt.toStringAsFixed(6)}×${inv.cosPhi.toStringAsFixed(3)} + ${xM.toStringAsFixed(6)}×${sinPhi.toStringAsFixed(4)})');
      case Systeemtype.dc2Draad:
        final rDc = gelProp.rDc(k.doorsnedemm2, 1.0, t: tMax);
        buf.writeln('Formule:  ΔU = 2 · I · R_DC · L');
        rij('R_DC @ ${tMax.toInt()}°C', '${rDc.toStringAsFixed(6)} Ω/m');
        buf.writeln('ΔU = 2 × ${r.iPerKabel.toStringAsFixed(2)} × ${rDc.toStringAsFixed(6)} × ${inv.lengteM.toStringAsFixed(1)}');
      case Systeemtype.dcAarde:
        final rDc = gelProp.rDc(k.doorsnedemm2, 1.0, t: tMax);
        buf.writeln('Formule:  ΔU = I · R_DC · L  (aardretour)');
        rij('R_DC @ ${tMax.toInt()}°C', '${rDc.toStringAsFixed(6)} Ω/m');
        buf.writeln('ΔU = ${r.iPerKabel.toStringAsFixed(2)} × ${rDc.toStringAsFixed(6)} × ${inv.lengteM.toStringAsFixed(1)}');
    }
    rij('ΔU (absoluut)', '${r.deltaUV.toStringAsFixed(4)} V');
    rij('ΔU (procent)', '${r.deltaUPct.toStringAsFixed(3)} %');
    rij('Max. toegestaan', '${inv.maxSpanningsvalPct.toStringAsFixed(1)} %');
    rij('Status', r.okSpanning ? l10n.rapportVoldoet : l10n.rapportOverschreden);

    titel(l10n.rapportTempNr(6 + sOffset));
    final rDcM = gelProp.rDc(k.doorsnedemm2, 1.0, t: tMax);
    rij('R_DC @ ${tMax.toInt()}°C', '${rDcM.toStringAsFixed(6)} Ω/m');
    rij('I²R-verlies', '${r.i2rVerliesWPerM.toStringAsFixed(4)} W/m');
    if (inv.isGrondkabel) {
      buf.writeln('Formule:  ΔT = P · (λ/2π) · ln(4u/d)  [IEC 60287-2-1]');
      rij('Diepte', '${inv.diepteM.toStringAsFixed(2)} m');
      rij('Buitendiameter', '${k.buitendiameter.toStringAsFixed(1)} mm');
    } else {
      buf.writeln('Formule:  ΔT = P / (π · D · h_conv)  [h_conv = 10 W/(m²·K)]');
      rij('Buitendiameter', '${k.buitendiameter.toStringAsFixed(1)} mm');
    }
    rij('Temperatuurstijging ΔT', '${r.tempStijgingK.toStringAsFixed(2)} K');
    rij('Omgevingstemperatuur', '${r.tEffectief.toStringAsFixed(1)} °C');
    rij('Geleidertemperatuur', '${r.geleiderTempC.toStringAsFixed(1)} °C');
    rij('Maximum toegestaan', '${r.maxTempC.toStringAsFixed(0)} °C');
    rij('Marge', '${(r.maxTempC - r.geleiderTempC) >= 0 ? "+" : ""}${(r.maxTempC - r.geleiderTempC).toStringAsFixed(1)} K');
    rij('Status', r.okTemp ? l10n.rapportVoldoet : l10n.rapportWaarschuwingTemp);

    if (r.okKortsluit != null) {
      titel(l10n.rapportKSNr(7 + sOffset));
      final kVal = kWaarden[(k.geleider, k.isolatie)] ?? 0.0;
      final tS = inv.kortsluitduurMs / 1000.0;
      final ikPK = inv.kortsluitstroomA / r.nParallel;
      buf.writeln('Formule min. doorsnede:  A_min = I_k · √t / k');
      rij('I_k (per kabel)', '${ikPK.toStringAsFixed(0)} A'
          '${r.nParallel > 1 ? "  = ${inv.kortsluitstroomA.toStringAsFixed(0)}/${r.nParallel}" : ""}');
      rij('t (kortsluitduur)', '${tS.toStringAsFixed(3)} s  (${inv.kortsluitduurMs.toStringAsFixed(0)} ms)');
      rij('k (materiaalconstante)', '$kVal  [${k.geleider.label} ${k.isolatie.label}]');
      rij('A_min', '${r.doorsnedeMinKortsluit.toStringAsFixed(2)} mm²');
      rij('Toegepaste doorsnede', '${k.doorsnedemm2} mm²');
      buf.writeln();
      buf.writeln('Formule eindtemperatuur:  ΔT = (I_k/A)² · t / c  →  T_eind = T_max + ΔT');
      rij('Temperatuurstijging ΔT', '${r.deltaTKortsluitK.toStringAsFixed(1)} K');
      rij('Starttemperatuur (T_max)', '${ip.maxTempContinu.toInt()} °C');
      rij('Eindtemperatuur', '${r.eindtempKortsluitC.toStringAsFixed(1)} °C');
      rij('Max. toegestaan', '${ip.maxTempKortsluit.toInt()} °C');
      rij('Status', r.okKortsluit! ? l10n.rapportVoldoet : l10n.rapportFaalt);
    }

    if (r.okMaxLengte != null) {
      titel(l10n.rapportMaxLengte);
      final ia = inv.beveiligingIa;
      if (inv.beveiligingType != null) {
        rij('Beveiligingstype', inv.beveiligingType!.label);
      }
      if (ia != null) rij('Activeringsstroom I_a', '${ia.toStringAsFixed(1)} A');
      rij('Maximale leidinglengte', '${r.maxLengteM!.toStringAsFixed(1)} m');
      if (r.ikEind != null) rij('I_k aan kabeluiteinde', '${r.ikEind!.toStringAsFixed(0)} A');
      rij('Opgegeven lengte', '${inv.lengteM.toStringAsFixed(1)} m');
      rij('Status', r.okMaxLengte! ? l10n.rapportVoldoet : l10n.rapportFaalt);
    }
  }

  buf.writeln();
  buf.writeln('=' * 64);
  buf.writeln(r.voldoet ? l10n.rapportEindVoldoet : l10n.rapportEindGefaald);
  buf.writeln('=' * 64);
  if (r.fouten.isNotEmpty) {
    buf.writeln('\n${l10n.rapportFouten}');
    for (final f in r.fouten) { buf.writeln('  • $f'); }
  }
  if (r.waarschuwingen.isNotEmpty) {
    buf.writeln('\n${l10n.rapportWaarschuwingen}');
    for (final w in r.waarschuwingen) { buf.writeln('  ⚠ $w'); }
  }
  buf.writeln('\n${l10n.rapportFooter}');
  return buf.toString();
}