feat(hub): default to live hub on startup + accept partial flow bags

Two small changes that together get Recl∆Im out of demo mode
on the very first launch after a flow.completed event lands:

  1. HubSettings.useHub defaults to true. New installs and
     post-reset launches probe the hub straight away; if the
     probe fails (no hub running, wrong port, auth denied),
     the existing graceful fallback in main.dart drops back to
     MockRepository. A user can still flip useHub off via the
     Hub-Reiter.

  2. HubRepository._parse now only requires the 'norm' key to
     consider an event 'Recl∆Im-shaped'. SKM, benefit and
     frust default to zero if absent; tier_lowest falls back to
     the cohort source's tier. This makes the partial bag
     produced by flows/durchstich-from-file.yaml — which can
     run without econ.skm_score's nested-input gymnastics —
     enough to surface a real heatmap point.

Wired together: launch the app while chain serve runs and at
least one flow.completed event is in the audit log → the
Heatmap-Tab shows that norm live, with its tier and norm-title
parsed from the actual audit-log JSON instead of Fixtures.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-18 15:43:15 +02:00
parent 34c325e419
commit e6e9c0753d
2 changed files with 13 additions and 7 deletions

View file

@ -49,7 +49,7 @@ class HubSettings {
port: prefs.getInt(_kPort) ?? defaultPort,
secure: prefs.getBool(_kSecure) ?? false,
authToken: prefs.getString(_kToken),
useHub: prefs.getBool(_kUseHub) ?? false,
useHub: prefs.getBool(_kUseHub) ?? true,
);
}

View file

@ -94,8 +94,8 @@ class HubRepository implements EvaluationRepository {
if (bag == null) return null;
final normJson = _asMap(bag['norm']);
if (normJson == null) return null;
final skmJson = _asMap(bag['skm']);
if (normJson == null || skmJson == null) return null;
final norm = Norm(
eli: (normJson['eli'] as String?) ?? 'eli/unknown',
@ -108,8 +108,17 @@ class HubRepository implements EvaluationRepository {
paragraphs: _parseParagraphs(normJson['paragraphs']),
);
final skmEur = _asDouble(skmJson['total_eur_per_year']);
final tierLowest = _parseTier(skmJson['tier_lowest']);
final cohortJson = _asMap(bag['cohort']);
final affected = _asInt(cohortJson?['count']);
// SKM may be missing if the flow only ran the normalize-/
// cohort-arm (e.g. flows/durchstich-from-file.yaml). Default
// to zero and let the tier fall back to the cohort source's
// tier that's still the most-honest reading.
final skmEur = _asDouble(skmJson?['total_eur_per_year']);
final tierLowest = skmJson != null
? _parseTier(skmJson['tier_lowest'])
: _parseTier(cohortJson?['tier']);
final benefitJson = _asMap(bag['benefit']);
final benefit = _asDouble(benefitJson?['total']);
@ -117,9 +126,6 @@ class HubRepository implements EvaluationRepository {
final frustJson = _asMap(bag['frust']);
final frust = _asDouble(frustJson?['composite_frust']);
final cohortJson = _asMap(bag['cohort']);
final affected = _asInt(cohortJson?['count']);
return Evaluation(
norm: norm,
skmEurPerYear: skmEur,