feat(live): real live mode via HubClient.submit → outputs
HubRepository.list() now runs the inline reclaim-evaluate flow per pilot norm via submit() and maps the returned outputs to Evaluation — the correct path, since the hub audit log stores only hashes, not outputs. skm/benefit get their structured input as JSON (submit jsonInputs), so the live heatmap gets real Schaden/Nutzen axes. - live_inputs.dart: the inline eval flow + the 5 pilot norms' inputs (XML asset, cohort_id, skm duties, benefit ratings) plus curated overrides the bund adapter can't derive (true Geltungsbereich for the EU/Berlin norms, freshness, jurist sign-off). - assets/norms/*.xml: 5 gesetze-im-internet-style source documents. - tool/live_smoke.dart: dev smoke test (5/5 evaluate against a live hub: Schaden 3M–450M €, Nutzen 2.0–3.8, EU/Berlin scope correct). Needs econ.skm_score's float-population fix (chain-modules commit 80f6620). Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
46b4003d36
commit
425a0e5790
9 changed files with 451 additions and 108 deletions
78
app/tool/live_smoke.dart
Normal file
78
app/tool/live_smoke.dart
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Smoke test for the live submit() path — all 5 pilot norms.
|
||||
// dart run tool/live_smoke.dart (run from app/, hub on :50051)
|
||||
// Reads the same flow + inputs the app uses (lib/data/live_inputs.dart),
|
||||
// loads XML from assets via File (no Flutter binding here).
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:chain_client_sdk/chain_client_sdk.dart' as chain;
|
||||
|
||||
import 'package:reclaim_app/data/live_inputs.dart';
|
||||
|
||||
Map<String, Map<String, dynamic>> decode(chain.SubmitResponse r) {
|
||||
final out = <String, Map<String, dynamic>>{};
|
||||
final j = r.toProto3Json();
|
||||
if (j is! Map) return out;
|
||||
final outputs = j['outputs'];
|
||||
if (outputs is! Map) return out;
|
||||
for (final e in outputs.entries) {
|
||||
final v = e.value;
|
||||
String? raw;
|
||||
if (v is Map) {
|
||||
final js = v['json'];
|
||||
if (js is Map && js['value'] is String) {
|
||||
raw = js['value'] as String;
|
||||
} else if (v['text'] is String) {
|
||||
raw = v['text'] as String;
|
||||
}
|
||||
}
|
||||
if (raw == null) continue;
|
||||
try {
|
||||
final d = jsonDecode(raw);
|
||||
if (d is Map<String, dynamic>) out[e.key] = d;
|
||||
} catch (_) {}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
final client = chain.HubClient(
|
||||
endpoint: const chain.HubEndpoint(
|
||||
host: '127.0.0.1', port: 50051, secure: false),
|
||||
);
|
||||
stderr.writeln('hub healthy: ${await client.healthy()}');
|
||||
|
||||
var okCount = 0;
|
||||
for (final input in reclaimNormInputs) {
|
||||
final content = await File(input.assetPath).readAsBytes();
|
||||
try {
|
||||
final r = await client.submit(
|
||||
flowYaml: reclaimEvaluateFlow,
|
||||
fileInputs: {'content': content},
|
||||
fileMimeTypes: {'content': 'application/xml'},
|
||||
textInputs: {'cohort_id': input.cohortId},
|
||||
jsonInputs: {'duties': input.duties, 'ratings': input.ratings},
|
||||
);
|
||||
final o = decode(r);
|
||||
final norm = o['norm'] ?? {};
|
||||
final skm = o['skm'] ?? {};
|
||||
final benefit = o['benefit'] ?? {};
|
||||
final cohort = o['cohort'] ?? {};
|
||||
final frust = o['frust'] ?? {};
|
||||
stdout.writeln(
|
||||
'✔ ${norm['jurabk']} ${norm['paragraph']} '
|
||||
'[${input.jurisdiction.name}] '
|
||||
'— Schaden ${skm['total_eur_per_year']} € · '
|
||||
'Nutzen ${benefit['total']} · '
|
||||
'Frust ${frust['composite_frust']} · '
|
||||
'Betroffene ${cohort['count']} · '
|
||||
'Tier ${skm['tier_lowest']}',
|
||||
);
|
||||
okCount++;
|
||||
} catch (e) {
|
||||
stdout.writeln('✗ ${input.assetPath}: $e');
|
||||
}
|
||||
}
|
||||
stdout.writeln('--- $okCount/${reclaimNormInputs.length} evaluated ---');
|
||||
await client.close();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue