Compare commits

..

No commits in common. "bc3a0013747743f39f16774748310de6ac977aba" and "46b4003d3681c930831614a002536f233b03de5a" have entirely different histories.

11 changed files with 110 additions and 453 deletions

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dokumente>
<norm doknr="jlr-BauOBE2005V25P60">
<metadaten>
<jurabk>BauO Bln</jurabk>
<enbez>§ 60 ff</enbez>
<titel>Bauantrag</titel>
</metadaten>
<textdaten><text>
(1) Für die Errichtung, Änderung und Nutzungsänderung baulicher Anlagen ist die Baugenehmigung erforderlich, soweit in den §§ 61 bis 65 sowie 76 und 77 nichts anderes bestimmt ist.
(2) Der Bauantrag ist schriftlich bei der Bauaufsichtsbehörde unter Beifügung der erforderlichen Bauvorlagen einzureichen.
</text></textdaten>
</norm>
</dokumente>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dokumente>
<norm doknr="CELEX32016R0679A30">
<metadaten>
<jurabk>DSGVO</jurabk>
<enbez>Art. 30</enbez>
<titel>Verzeichnis von Verarbeitungstätigkeiten</titel>
</metadaten>
<textdaten><text>
(1) Jeder Verantwortliche und gegebenenfalls sein Vertreter führen ein Verzeichnis aller Verarbeitungstätigkeiten, die ihrer Zuständigkeit unterliegen.
(2) Jeder Auftragsverarbeiter führt ein Verzeichnis zu allen Kategorien von im Auftrag durchgeführten Verarbeitungstätigkeiten.
</text></textdaten>
</norm>
</dokumente>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dokumente>
<norm doknr="BJNR000020869BJNE002500314">
<metadaten>
<jurabk>GewO</jurabk>
<enbez>§ 14</enbez>
<titel>Anzeigepflicht</titel>
</metadaten>
<textdaten><text>
(1) Wer den selbstständigen Betrieb eines stehenden Gewerbes anfängt, muss dies der zuständigen Behörde gleichzeitig anzeigen.
(2) Die Anzeige hat unverzüglich zu erfolgen. Sie ist nach amtlich vorgeschriebenem Vordruck zu erstatten.
</text></textdaten>
</norm>
</dokumente>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dokumente>
<norm doknr="BJNR396000016">
<metadaten>
<jurabk>KassenSichV</jurabk>
<enbez>§ 1 ff</enbez>
<titel>Kassensicherungsverordnung</titel>
</metadaten>
<textdaten><text>
(1) Das elektronische Aufzeichnungssystem muss jeden einzelnen aufzeichnungspflichtigen Vorgang vollständig aufzeichnen. Die Aufzeichnungen sind durch eine zertifizierte technische Sicherheitseinrichtung unmittelbar zu protokollieren.
(2) Den Steuerpflichtigen treffen Mitteilungspflichten gegenüber dem zuständigen Finanzamt; der Ausfall einer zertifizierten technischen Sicherheitseinrichtung ist unverzüglich anzuzeigen.
</text></textdaten>
</norm>
</dokumente>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<dokumente>
<norm doknr="BJNR181010014BJNE001700000">
<metadaten>
<jurabk>MiLoG</jurabk>
<enbez>§ 17</enbez>
<titel>Dokumentationspflichten</titel>
</metadaten>
<textdaten><text>
(1) Ein Arbeitgeber, der Arbeitnehmer geringfügig oder in den Wirtschaftsbereichen des Schwarzarbeitsbekämpfungsgesetzes beschäftigt, ist verpflichtet, Beginn, Ende und Dauer der täglichen Arbeitszeit aufzuzeichnen und diese Aufzeichnungen mindestens zwei Jahre aufzubewahren.
(2) Die Unterlagen sind im Inland in deutscher Sprache bereitzuhalten.
</text></textdaten>
</norm>
</dokumente>

View file

@ -1,26 +1,24 @@
import 'dart:convert'; import 'dart:convert';
import 'package:chain_client_sdk/chain_client_sdk.dart' as chain; import 'package:chain_client_sdk/chain_client_sdk.dart' as chain;
import 'package:flutter/services.dart' show rootBundle;
import 'hub_endpoint.dart'; import 'hub_endpoint.dart';
import 'live_inputs.dart';
import 'models.dart'; import 'models.dart';
import 'repository.dart'; import 'repository.dart';
/// Live `EvaluationRepository` backed by a ChIn hub over gRPC. /// Live `EvaluationRepository` backed by a ChIn hub over gRPC.
/// ///
/// For each pilot norm ([reclaimNormInputs]) it submits the inline /// Reads flow.completed events from the hub's audit log, parses
/// [reclaimEvaluateFlow] via `HubClient.submit` and maps the returned /// each event's `detail` JSON as a Recl∆Im-shaped output bag
/// `outputs` to an [Evaluation]. This is the correct path: the hub's /// (see flows/durchstich-gewo-14.yaml), and converts the bag to
/// audit log stores only hashes, never the output payloads outputs /// an Evaluation the UI can render. Events from non-ReclIm
/// come back ONLY from the Submit response. skm/benefit get their /// flows are silently skipped (detected by absence of the
/// structured input as JSON (`submit(jsonInputs: )`), so the heatmap /// `norm` + `skm` keys).
/// gets real Schaden/Nutzen axes.
/// ///
/// Sort order matches MockRepository: highest /Jahr first. With an /// Sort order matches MockRepository: highest /Jahr first.
/// unhealthy hub `list()` returns empty and the UI shows a 'hub-down' /// If the hub has zero matching flow.completed events the list
/// empty-state no silent mock fallback. /// returns empty the UI shows a "noch keine Auswertungen, '
/// `chain run flows/durchstich-gewo-14.yaml` aufrufen"-Hinweis.
class HubRepository implements EvaluationRepository { class HubRepository implements EvaluationRepository {
HubRepository._(this._client, this.settings, this._healthy); HubRepository._(this._client, this.settings, this._healthy);
@ -63,14 +61,14 @@ class HubRepository implements EvaluationRepository {
@override @override
Future<List<Evaluation>> list() async { Future<List<Evaluation>> list() async {
if (!_healthy) return const []; if (!_healthy) return const [];
final events = await _client.eventLog(
limit: 200,
types: const ['flow.completed'],
);
final evals = <Evaluation>[]; final evals = <Evaluation>[];
for (final input in reclaimNormInputs) { for (final e in events) {
try { final eval = _parse(e);
final eval = await _evaluate(input); if (eval != null) evals.add(eval);
if (eval != null) evals.add(eval);
} catch (_) {
// One norm failing must not blank the whole heatmap.
}
} }
evals.sort((a, b) => b.skmEurPerYear.compareTo(a.skmEurPerYear)); evals.sort((a, b) => b.skmEurPerYear.compareTo(a.skmEurPerYear));
return evals; return evals;
@ -92,141 +90,94 @@ class HubRepository implements EvaluationRepository {
String get hubLabel => '${settings.url}' String get hubLabel => '${settings.url}'
'${_healthy ? '' : ' — Verbindung gestört'}'; '${_healthy ? '' : ' — Verbindung gestört'}';
/// Run one norm through the flow and assemble its [Evaluation]. /// Decode one flow.completed event into an Evaluation, or
Future<Evaluation?> _evaluate(NormLiveInput input) async { /// return null when the event doesn't look like a Recl∆Im
final asset = await rootBundle.load(input.assetPath); /// flow. We're permissive about field absence so a partially
final content = asset.buffer.asUint8List( /// failed flow run still surfaces what it produced.
asset.offsetInBytes, Evaluation? _parse(chain.LoggedEvent event) {
asset.lengthInBytes, Map<String, dynamic>? bag;
); try {
final decoded = jsonDecode(event.detail);
if (decoded is Map<String, dynamic>) bag = decoded;
} catch (_) {
return null;
}
if (bag == null) return null;
final resp = await _client.submit( final normJson = _asMap(bag['norm']);
flowYaml: reclaimEvaluateFlow,
fileInputs: {'content': content},
fileMimeTypes: {'content': 'application/xml'},
textInputs: {'cohort_id': input.cohortId},
jsonInputs: {'duties': input.duties, 'ratings': input.ratings},
);
final out = _decodeOutputs(resp);
final normJson = out['norm'];
if (normJson == null) return null; if (normJson == null) return null;
final skmJson = _asMap(bag['skm']);
final skm = out['skm']; final eli = (normJson['eli'] as String?) ?? 'eli/unknown';
final benefit = out['benefit'];
final frust = out['frust'];
final cohort = out['cohort'];
final norm = Norm( final norm = Norm(
eli: (normJson['eli'] as String?) ?? 'eli/unknown', eli: eli,
title: (normJson['title'] as String?) ?? '(ohne Titel)', title: (normJson['title'] as String?) ?? '(ohne Titel)',
jurabk: (normJson['jurabk'] as String?) ?? '', jurabk: (normJson['jurabk'] as String?) ?? '',
paragraph: (normJson['paragraph'] as String?) ?? '', paragraph: (normJson['paragraph'] as String?) ?? '',
// Geltungsbereich/Freshness can't be derived from the bund jurisdiction: JurisdictionLabel.parse(
// adapter for EU/Berlin norms take the curated override. normJson['scope'] as String?,
jurisdiction: input.jurisdiction, eli: eli,
freshness: input.freshness, ),
supersededNote: input.supersededNote, standDate: _parseTimestamp(event.timestamp),
standDate: DateTime.now(),
sourceUrl: (normJson['source_url'] as String?) ?? '', sourceUrl: (normJson['source_url'] as String?) ?? '',
sourceSha256: (normJson['source_sha256'] as String?) ?? '', sourceSha256: (normJson['source_sha256'] as String?) ?? '',
freshness: _parseFreshness(normJson['freshness']),
supersededNote: normJson['superseded_note'] as String?,
paragraphs: _parseParagraphs(normJson['paragraphs']), paragraphs: _parseParagraphs(normJson['paragraphs']),
); );
final skmEur = _asDouble(skm?['total_eur_per_year']); final cohortJson = _asMap(bag['cohort']);
final tierLowest = _parseTier(skm?['tier_lowest']); 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']);
final frustJson = _asMap(bag['frust']);
final frust = _asDouble(frustJson?['composite_frust']);
return Evaluation( return Evaluation(
norm: norm, norm: norm,
skmEurPerYear: skmEur, skmEurPerYear: skmEur,
benefitScore: _asDouble(benefit?['total']), benefitScore: benefit,
affectedCount: _asInt(cohort?['count']), affectedCount: affected,
frustScore: _asDouble(frust?['composite_frust']), frustScore: frust,
tierLowest: tierLowest, tierLowest: tierLowest,
duties: _parseDuties(out['duties']), duties: _parseDuties(bag['duties']),
sources: _liveSources(cohort), sources: _parseSources(cohortJson),
auditEventId: _invocationId(resp), auditEventId: event.eventId,
reviewedBy: input.reviewedBy, notes: 'Live aus Hub-Flow ${event.flowName} '
reviewedAt: input.reviewedAt, '(execution ${event.flowExecution.substring(0, 8.clamp(0, event.flowExecution.length))}).',
notes: 'Live aus Hub-Flow reclaim-evaluate.' // Jurist sign-off is recorded by the approval, not the flow
'${input.supersededNote != null ? '\n${input.supersededNote}' : ''}', // bag. Until the review-rewrite wires approvals in, these stay
componentSummary: _componentSummary(skm, cohort), // null (= unreviewed) unless the bag carries them explicitly.
reviewedBy: bag['reviewed_by'] as String?,
reviewedAt: DateTime.tryParse(
(bag['reviewed_at'] as String?) ?? ''),
); );
} }
/// Decode `SubmitResponse.outputs` into plain Dart maps. Each output static Freshness _parseFreshness(dynamic v) {
/// Payload serializes (proto3-json) as `{json: {value: "<jsonstr>"}}` switch ((v as String?)?.trim().toLowerCase()) {
/// (module `with_json`) or `{text: ""}`; we json-decode the inner case 'superseded' || 'amended' || 'geändert' || 'stale':
/// string. return Freshness.superseded;
Map<String, Map<String, dynamic>> _decodeOutputs(chain.SubmitResponse r) { case 'unknown' || 'ungeprüft':
final out = <String, Map<String, dynamic>>{}; return Freshness.unknown;
final j = r.toProto3Json(); default:
if (j is! Map) return out; return Freshness.current;
final outputs = j['outputs'];
if (outputs is! Map) return out;
for (final entry in outputs.entries) {
final v = entry.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 decoded = jsonDecode(raw);
if (decoded is Map<String, dynamic>) out[entry.key] = decoded;
} catch (_) {}
} }
return out;
} }
static String _invocationId(chain.SubmitResponse r) { static Map<String, dynamic>? _asMap(dynamic v) =>
try { v is Map<String, dynamic> ? v : null;
final j = r.toProto3Json();
if (j is Map) {
final inv = j['invocationId'];
if (inv is Map && inv['value'] is String) return inv['value'] as String;
if (inv is String) return inv;
}
} catch (_) {}
return 'live-${DateTime.now().millisecondsSinceEpoch}';
}
static List<Source> _liveSources(Map<String, dynamic>? cohort) {
final out = <Source>[];
final label = cohort?['source'] as String?;
if (label != null && label.isNotEmpty) {
out.add(Source(
label: label,
url: '',
tier: _parseTier(cohort?['tier']),
));
}
return out;
}
static List<String> _componentSummary(
Map<String, dynamic>? skm,
Map<String, dynamic>? cohort,
) {
final lines = <String>[];
final items = skm?['items'];
if (items is List && items.isNotEmpty) {
final first = items.first;
if (first is Map && first['formula'] is String) {
lines.add('SKM · ${first['formula']}');
}
}
final src = cohort?['source'] as String?;
if (src != null && src.isNotEmpty) {
lines.add('P · ${_parseTier(cohort?['tier']).short}$src');
}
return lines;
}
static double _asDouble(dynamic v) { static double _asDouble(dynamic v) {
if (v is num) return v.toDouble(); if (v is num) return v.toDouble();
@ -250,6 +201,9 @@ class HubRepository implements EvaluationRepository {
}; };
} }
static DateTime _parseTimestamp(String iso) =>
DateTime.tryParse(iso) ?? DateTime.now();
static List<NormParagraph> _parseParagraphs(dynamic v) { static List<NormParagraph> _parseParagraphs(dynamic v) {
if (v is! List) return const []; if (v is! List) return const [];
return v.whereType<Map<String, dynamic>>().map((p) { return v.whereType<Map<String, dynamic>>().map((p) {
@ -260,8 +214,9 @@ class HubRepository implements EvaluationRepository {
}).toList(); }).toList();
} }
static List<Duty> _parseDuties(Map<String, dynamic>? v) { static List<Duty> _parseDuties(dynamic v) {
final inner = v?['duties']; final outer = _asMap(v);
final inner = outer?['duties'];
if (inner is! List) return const []; if (inner is! List) return const [];
return inner.whereType<Map<String, dynamic>>().map((d) { return inner.whereType<Map<String, dynamic>>().map((d) {
return Duty( return Duty(
@ -275,4 +230,24 @@ class HubRepository implements EvaluationRepository {
); );
}).toList(); }).toList();
} }
/// Hub-mode sources are minimal: we surface the cohort source
/// as a T1/T2 reference (the field is set by stats.cohort_size)
/// plus an audit-log pointer. Richer source attribution comes
/// when the flow has a dedicated `sources.collect` step.
static List<Source> _parseSources(Map<String, dynamic>? cohort) {
final out = <Source>[];
final label = cohort?['source'] as String?;
final tierStr = (cohort?['tier'] as String?)?.toUpperCase() ?? 'T2';
if (label != null && label.isNotEmpty) {
final tier = switch (tierStr) {
'T1' => EvidenceTier.t1,
'T2' => EvidenceTier.t2,
'T3' => EvidenceTier.t3,
_ => EvidenceTier.t4,
};
out.add(Source(label: label, url: '', tier: tier));
}
return out;
}
} }

View file

@ -1,169 +0,0 @@
import 'models.dart';
/// The ReclIm evaluation flow, submitted inline per norm via
/// `HubClient.submit`. No `system.approval` step submit() returns
/// the outputs synchronously (jurist review is a separate, optional
/// after-the-fact action, not part of computing the figure).
const String reclaimEvaluateFlow = r'''
name: reclaim-evaluate
inputs:
content: bytes
cohort_id: text
duties: json
ratings: json
steps:
- id: normalize
use: text.akoma_normalize@^0
with:
content: $inputs.content
mime: "application/xml"
- id: deontic
use: text.deontic_extract@^0
with:
norm: $normalize.norm
- id: citations
use: graph.citation_extract@^0
with:
norm: $normalize.norm
- id: cohort
use: stats.cohort_size@^0
with:
cohort_id: $inputs.cohort_id
- id: frust
use: text.readability_score@^0
with:
norm: $normalize.norm
graph: $citations.citations
- id: skm
use: econ.skm_score@^0
with:
duties: $inputs.duties
- id: benefit
use: law.benefit_score@^0
with:
ratings: $inputs.ratings
outputs:
norm: $normalize.norm
skm: $skm.report
benefit: $benefit.score
frust: $frust.score
cohort: $cohort.cohort
duties: $deontic.duties
citations: $citations.citations
''';
/// One pilot norm's live inputs + curated metadata the akoma adapter
/// can't derive (true Geltungsbereich for EU/Berlin norms, freshness,
/// jurist sign-off). The hub computes skm/benefit/frust/cohort/norm
/// from these; the overrides are merged onto the resulting Evaluation.
class NormLiveInput {
const NormLiveInput({
required this.assetPath,
required this.cohortId,
required this.duties,
required this.ratings,
required this.jurisdiction,
this.freshness = Freshness.current,
this.supersededNote,
this.reviewedBy,
this.reviewedAt,
});
final String assetPath;
final String cohortId;
/// `{ duties: [ { population, frequency, tariff_eur_per_hour,
/// time_hours_per_case, source_norm, tier } ] }` for econ.skm_score.
final Map<String, Object?> duties;
/// `{ schutz, markt, rechtssicherheit, eu_binnenmarkt, sicherheit,
/// umwelt, einnahmen }` (each 0..5) for law.benefit_score.
final Map<String, Object?> ratings;
final Jurisdiction jurisdiction;
final Freshness freshness;
final String? supersededNote;
final String? reviewedBy;
final DateTime? reviewedAt;
}
Map<String, Object?> _duty(
String norm,
double population,
double frequency,
double tariff,
double time,
String tier,
) =>
{
'duties': [
{
'population': population,
'frequency': frequency,
'tariff_eur_per_hour': tariff,
'time_hours_per_case': time,
'source_norm': norm,
'tier': tier,
}
]
};
Map<String, Object?> _ratings(double schutz, double markt, double rs,
double eu, double sicherheit, double umwelt, double einnahmen) =>
{
'schutz': schutz,
'markt': markt,
'rechtssicherheit': rs,
'eu_binnenmarkt': eu,
'sicherheit': sicherheit,
'umwelt': umwelt,
'einnahmen': einnahmen,
};
/// The five pilot norms, live-evaluated against the hub.
final List<NormLiveInput> reclaimNormInputs = [
NormLiveInput(
assetPath: 'assets/norms/gewo-14.xml',
cohortId: 'berlin-kmu',
duties: _duty('GewO § 14', 180000, 1.0, 35.0, 0.5, 'T3'),
ratings: _ratings(2, 3, 3.5, 2, 1.5, 0, 2),
jurisdiction: Jurisdiction.deutschland,
),
NormLiveInput(
assetPath: 'assets/norms/kassensichv.xml',
cohortId: 'gastro-de',
duties: _duty('KassenSichV § 14', 500000, 1.0, 40.0, 1.25, 'T2'),
ratings: _ratings(3, 3, 3.5, 2.5, 3, 1, 5),
jurisdiction: Jurisdiction.deutschland,
reviewedBy: 'RA Dr. M. Kessler',
reviewedAt: DateTime.utc(2026, 1, 20),
),
NormLiveInput(
assetPath: 'assets/norms/dsgvo-art30.xml',
cohortId: 'de-kmu',
duties: _duty('DSGVO Art. 30', 3000000, 1.0, 50.0, 3.0, 'T1'),
ratings: _ratings(4, 4, 4.5, 4, 3, 2, 5),
jurisdiction: Jurisdiction.eu,
reviewedBy: 'Prof. Dr. A. Lindner',
reviewedAt: DateTime.utc(2026, 2, 3),
),
NormLiveInput(
assetPath: 'assets/norms/bauo-bln-60.xml',
cohortId: 'bauwirtschaft-be',
duties: _duty('BauO Bln § 6069', 15000, 1.0, 60.0, 80.0, 'T2'),
ratings: _ratings(4, 3, 4, 2, 4, 3, 4),
jurisdiction: Jurisdiction.berlin,
reviewedBy: 'RAin S. Brandt',
reviewedAt: DateTime.utc(2026, 1, 28),
),
NormLiveInput(
assetPath: 'assets/norms/milog-17.xml',
cohortId: 'de-kmu',
duties: _duty('MiLoG § 17', 3000000, 220.0, 30.0, 0.014, 'T1'),
ratings: _ratings(4, 3, 4, 2, 3, 1, 5),
jurisdiction: Jurisdiction.deutschland,
freshness: Freshness.superseded,
supersededNote: 'BEG IV hat die Dokumentationspflichten zum '
'1.1.2026 entschärft — diese Fassung ist neu zu bewerten.',
),
];

View file

@ -32,7 +32,6 @@ flutter:
assets: assets:
- assets/fixtures/ - assets/fixtures/
- assets/norms/
# Bundled fonts — offline-first, KRITIS-compatible, no Google # Bundled fonts — offline-first, KRITIS-compatible, no Google
# Fonts runtime fetch. setup.sh downloads SIL-OFL-licensed TTFs # Fonts runtime fetch. setup.sh downloads SIL-OFL-licensed TTFs

View file

@ -1,78 +0,0 @@
// 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();
}

View file

@ -5,5 +5,5 @@ d03d3dc2bfbaa413ea4e192c6147595e21d8dafd24b5d32b853a03a38e48100d text-deontic-e
5f1a0098bc4c3be8d20b34d848af884b539b0eda3c59ab56dfe99bf8f811090d text-readability-score-0.1.0.fai 5f1a0098bc4c3be8d20b34d848af884b539b0eda3c59ab56dfe99bf8f811090d text-readability-score-0.1.0.fai
31d35f6a7b61b9249520e3ae02fa398292375555430714b04ff0771fd2f3ad99 stats-cohort-size-0.1.0.fai 31d35f6a7b61b9249520e3ae02fa398292375555430714b04ff0771fd2f3ad99 stats-cohort-size-0.1.0.fai
199927a6b8594da579a22d42b1ba438f4b2b6c90132a5c097c7c1d5eb8b9c319 graph-shapley-attribution-0.1.0.fai 199927a6b8594da579a22d42b1ba438f4b2b6c90132a5c097c7c1d5eb8b9c319 graph-shapley-attribution-0.1.0.fai
d77afa476d4b34c22153af36c4ed3d2a0fab4c5a0dc53cba5eb217a622cd7181 econ-skm-score-0.1.0.fai 2b887970d448c07cc53633124518d6f96672b0710d9e89fe4f9d6a236035f25e econ-skm-score-0.1.0.fai
4a82a07ba72cae4b49f1ff883b1ca92638e5f4ea19bc2b070288142056764025 law-benefit-score-0.1.0.fai 4a82a07ba72cae4b49f1ff883b1ca92638e5f4ea19bc2b070288142056764025 law-benefit-score-0.1.0.fai

View file

@ -134,7 +134,7 @@ modules:
- version: 0.1.0 - version: 0.1.0
status: published status: published
wasm_url: https://releases.chain.flemming.ai/reclaim/econ-skm-score/v0.1.0/econ-skm-score-0.1.0.fai wasm_url: https://releases.chain.flemming.ai/reclaim/econ-skm-score/v0.1.0/econ-skm-score-0.1.0.fai
sha256: d77afa476d4b34c22153af36c4ed3d2a0fab4c5a0dc53cba5eb217a622cd7181 sha256: 2b887970d448c07cc53633124518d6f96672b0710d9e89fe4f9d6a236035f25e
tagline: tagline:
en: "Standard Cost Model (SKM) bureaucracy cost" en: "Standard Cost Model (SKM) bureaucracy cost"
de: "Standardkostenmodell (SKM) — Bürokratiekosten" de: "Standardkostenmodell (SKM) — Bürokratiekosten"