feat(ui): scope, freshness and jurist-review badges + live empty-state
Adds three dark-first chip badges in the TierBadge pattern: - Jurisdiction (Berlin/DE/EU/International) — derived from the ELI, surfaced on the norms list, detail header and heatmap chips/hover. - Freshness (aktuell/geaendert/ungeprueft) — flags norms amended upstream (standDate + sourceSha256); renders only when stale. - Jurist review — marks evaluations a trusted jurist has confirmed (optional; the figure stands on its own). Models gain Jurisdiction/Freshness enums, Norm.freshness/supersededNote and Evaluation.reviewedBy/reviewedAt; HubRepository parses them from the flow bag (ELI fallback). HeatmapPage shows a real empty-state (hub-down vs no-evaluations) instead of a blank plot, and connect() no longer throws on a failed probe. Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
9dfa418e92
commit
aabff2b140
11 changed files with 604 additions and 26 deletions
|
|
@ -36,10 +36,21 @@ class HubRepository implements EvaluationRepository {
|
|||
endpoint: endpoint,
|
||||
authToken: settings.authToken,
|
||||
);
|
||||
final ok = await client.healthy().timeout(
|
||||
const Duration(seconds: 4),
|
||||
onTimeout: () => false,
|
||||
);
|
||||
// The probe must NEVER throw out of connect(): main() awaits
|
||||
// this before runApp(), so any uncaught error here leaves the
|
||||
// app on a blank page. A timeout maps to false; so does any
|
||||
// transport error (e.g. a gRPC-web call against a plain-gRPC
|
||||
// hub, or a refused connection) — the app then comes up in
|
||||
// 'hub-down' state and shows the empty-state hint.
|
||||
bool ok;
|
||||
try {
|
||||
ok = await client.healthy().timeout(
|
||||
const Duration(seconds: 4),
|
||||
onTimeout: () => false,
|
||||
);
|
||||
} catch (_) {
|
||||
ok = false;
|
||||
}
|
||||
return HubRepository._(client, settings, ok);
|
||||
}
|
||||
|
||||
|
|
@ -97,14 +108,21 @@ class HubRepository implements EvaluationRepository {
|
|||
if (normJson == null) return null;
|
||||
final skmJson = _asMap(bag['skm']);
|
||||
|
||||
final eli = (normJson['eli'] as String?) ?? 'eli/unknown';
|
||||
final norm = Norm(
|
||||
eli: (normJson['eli'] as String?) ?? 'eli/unknown',
|
||||
eli: eli,
|
||||
title: (normJson['title'] as String?) ?? '(ohne Titel)',
|
||||
jurabk: (normJson['jurabk'] as String?) ?? '',
|
||||
paragraph: (normJson['paragraph'] as String?) ?? '',
|
||||
jurisdiction: JurisdictionLabel.parse(
|
||||
normJson['scope'] as String?,
|
||||
eli: eli,
|
||||
),
|
||||
standDate: _parseTimestamp(event.timestamp),
|
||||
sourceUrl: (normJson['source_url'] as String?) ?? '',
|
||||
sourceSha256: (normJson['source_sha256'] as String?) ?? '',
|
||||
freshness: _parseFreshness(normJson['freshness']),
|
||||
supersededNote: normJson['superseded_note'] as String?,
|
||||
paragraphs: _parseParagraphs(normJson['paragraphs']),
|
||||
);
|
||||
|
||||
|
|
@ -138,9 +156,26 @@ class HubRepository implements EvaluationRepository {
|
|||
auditEventId: event.eventId,
|
||||
notes: 'Live aus Hub-Flow ${event.flowName} '
|
||||
'(execution ${event.flowExecution.substring(0, 8.clamp(0, event.flowExecution.length))}).',
|
||||
// Jurist sign-off is recorded by the approval, not the flow
|
||||
// bag. Until the review-rewrite wires approvals in, these stay
|
||||
// null (= unreviewed) unless the bag carries them explicitly.
|
||||
reviewedBy: bag['reviewed_by'] as String?,
|
||||
reviewedAt: DateTime.tryParse(
|
||||
(bag['reviewed_at'] as String?) ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
static Freshness _parseFreshness(dynamic v) {
|
||||
switch ((v as String?)?.trim().toLowerCase()) {
|
||||
case 'superseded' || 'amended' || 'geändert' || 'stale':
|
||||
return Freshness.superseded;
|
||||
case 'unknown' || 'ungeprüft':
|
||||
return Freshness.unknown;
|
||||
default:
|
||||
return Freshness.current;
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _asMap(dynamic v) =>
|
||||
v is Map<String, dynamic> ? v : null;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue