diff --git a/app/assets/norms/bauo-bln-60.xml b/app/assets/norms/bauo-bln-60.xml deleted file mode 100644 index 1be1f3c..0000000 --- a/app/assets/norms/bauo-bln-60.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - BauO Bln - § 60 ff - Bauantrag - - -(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. - - - diff --git a/app/assets/norms/dsgvo-art30.xml b/app/assets/norms/dsgvo-art30.xml deleted file mode 100644 index 940eca7..0000000 --- a/app/assets/norms/dsgvo-art30.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - DSGVO - Art. 30 - Verzeichnis von Verarbeitungstätigkeiten - - -(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. - - - diff --git a/app/assets/norms/gewo-14.xml b/app/assets/norms/gewo-14.xml deleted file mode 100644 index c8985d4..0000000 --- a/app/assets/norms/gewo-14.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - GewO - § 14 - Anzeigepflicht - - -(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. - - - diff --git a/app/assets/norms/kassensichv.xml b/app/assets/norms/kassensichv.xml deleted file mode 100644 index 9e436b7..0000000 --- a/app/assets/norms/kassensichv.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - KassenSichV - § 1 ff - Kassensicherungsverordnung - - -(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. - - - diff --git a/app/assets/norms/milog-17.xml b/app/assets/norms/milog-17.xml deleted file mode 100644 index 474d64f..0000000 --- a/app/assets/norms/milog-17.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - MiLoG - § 17 - Dokumentationspflichten - - -(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. - - - diff --git a/app/lib/data/hub_repository.dart b/app/lib/data/hub_repository.dart index e1320da..768bd71 100644 --- a/app/lib/data/hub_repository.dart +++ b/app/lib/data/hub_repository.dart @@ -1,26 +1,24 @@ import 'dart:convert'; import 'package:chain_client_sdk/chain_client_sdk.dart' as chain; -import 'package:flutter/services.dart' show rootBundle; import 'hub_endpoint.dart'; -import 'live_inputs.dart'; import 'models.dart'; import 'repository.dart'; /// Live `EvaluationRepository` backed by a Ch∆In hub over gRPC. /// -/// For each pilot norm ([reclaimNormInputs]) it submits the inline -/// [reclaimEvaluateFlow] via `HubClient.submit` and maps the returned -/// `outputs` to an [Evaluation]. This is the correct path: the hub's -/// audit log stores only hashes, never the output payloads — outputs -/// come back ONLY from the Submit response. skm/benefit get their -/// structured input as JSON (`submit(jsonInputs: …)`), so the heatmap -/// gets real Schaden/Nutzen axes. +/// Reads flow.completed events from the hub's audit log, parses +/// each event's `detail` JSON as a Recl∆Im-shaped output bag +/// (see flows/durchstich-gewo-14.yaml), and converts the bag to +/// an Evaluation the UI can render. Events from non-Recl∆Im +/// flows are silently skipped (detected by absence of the +/// `norm` + `skm` keys). /// -/// Sort order matches MockRepository: highest €/Jahr first. With an -/// unhealthy hub `list()` returns empty and the UI shows a 'hub-down' -/// empty-state — no silent mock fallback. +/// Sort order matches MockRepository: highest €/Jahr first. +/// If the hub has zero matching flow.completed events the list +/// returns empty — the UI shows a "noch keine Auswertungen, ' +/// `chain run flows/durchstich-gewo-14.yaml` aufrufen"-Hinweis. class HubRepository implements EvaluationRepository { HubRepository._(this._client, this.settings, this._healthy); @@ -63,14 +61,14 @@ class HubRepository implements EvaluationRepository { @override Future> list() async { if (!_healthy) return const []; + final events = await _client.eventLog( + limit: 200, + types: const ['flow.completed'], + ); final evals = []; - for (final input in reclaimNormInputs) { - try { - final eval = await _evaluate(input); - if (eval != null) evals.add(eval); - } catch (_) { - // One norm failing must not blank the whole heatmap. - } + for (final e in events) { + final eval = _parse(e); + if (eval != null) evals.add(eval); } evals.sort((a, b) => b.skmEurPerYear.compareTo(a.skmEurPerYear)); return evals; @@ -92,141 +90,94 @@ class HubRepository implements EvaluationRepository { String get hubLabel => '${settings.url}' '${_healthy ? '' : ' — Verbindung gestört'}'; - /// Run one norm through the flow and assemble its [Evaluation]. - Future _evaluate(NormLiveInput input) async { - final asset = await rootBundle.load(input.assetPath); - final content = asset.buffer.asUint8List( - asset.offsetInBytes, - asset.lengthInBytes, - ); + /// Decode one flow.completed event into an Evaluation, or + /// return null when the event doesn't look like a Recl∆Im + /// flow. We're permissive about field absence so a partially + /// failed flow run still surfaces what it produced. + Evaluation? _parse(chain.LoggedEvent event) { + Map? bag; + try { + final decoded = jsonDecode(event.detail); + if (decoded is Map) bag = decoded; + } catch (_) { + return null; + } + if (bag == null) return null; - final resp = 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 out = _decodeOutputs(resp); - final normJson = out['norm']; + final normJson = _asMap(bag['norm']); if (normJson == null) return null; + final skmJson = _asMap(bag['skm']); - final skm = out['skm']; - final benefit = out['benefit']; - final frust = out['frust']; - final cohort = out['cohort']; - + 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?) ?? '', - // Geltungsbereich/Freshness can't be derived from the bund - // adapter for EU/Berlin norms — take the curated override. - jurisdiction: input.jurisdiction, - freshness: input.freshness, - supersededNote: input.supersededNote, - standDate: DateTime.now(), + 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']), ); - final skmEur = _asDouble(skm?['total_eur_per_year']); - final tierLowest = _parseTier(skm?['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']); + + final frustJson = _asMap(bag['frust']); + final frust = _asDouble(frustJson?['composite_frust']); return Evaluation( norm: norm, skmEurPerYear: skmEur, - benefitScore: _asDouble(benefit?['total']), - affectedCount: _asInt(cohort?['count']), - frustScore: _asDouble(frust?['composite_frust']), + benefitScore: benefit, + affectedCount: affected, + frustScore: frust, tierLowest: tierLowest, - duties: _parseDuties(out['duties']), - sources: _liveSources(cohort), - auditEventId: _invocationId(resp), - reviewedBy: input.reviewedBy, - reviewedAt: input.reviewedAt, - notes: 'Live aus Hub-Flow reclaim-evaluate.' - '${input.supersededNote != null ? '\n${input.supersededNote}' : ''}', - componentSummary: _componentSummary(skm, cohort), + duties: _parseDuties(bag['duties']), + sources: _parseSources(cohortJson), + 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?) ?? ''), ); } - /// Decode `SubmitResponse.outputs` into plain Dart maps. Each output - /// Payload serializes (proto3-json) as `{json: {value: ""}}` - /// (module `with_json`) or `{text: "…"}`; we json-decode the inner - /// string. - Map> _decodeOutputs(chain.SubmitResponse r) { - final out = >{}; - final j = r.toProto3Json(); - if (j is! Map) return out; - 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) out[entry.key] = decoded; - } catch (_) {} + 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; } - return out; } - static String _invocationId(chain.SubmitResponse r) { - try { - 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 _liveSources(Map? cohort) { - final out = []; - 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 _componentSummary( - Map? skm, - Map? cohort, - ) { - final lines = []; - 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 Map? _asMap(dynamic v) => + v is Map ? v : null; static double _asDouble(dynamic v) { 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 _parseParagraphs(dynamic v) { if (v is! List) return const []; return v.whereType>().map((p) { @@ -260,8 +214,9 @@ class HubRepository implements EvaluationRepository { }).toList(); } - static List _parseDuties(Map? v) { - final inner = v?['duties']; + static List _parseDuties(dynamic v) { + final outer = _asMap(v); + final inner = outer?['duties']; if (inner is! List) return const []; return inner.whereType>().map((d) { return Duty( @@ -275,4 +230,24 @@ class HubRepository implements EvaluationRepository { ); }).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 _parseSources(Map? cohort) { + final out = []; + 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; + } } diff --git a/app/lib/data/live_inputs.dart b/app/lib/data/live_inputs.dart deleted file mode 100644 index 84b3444..0000000 --- a/app/lib/data/live_inputs.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'models.dart'; - -/// The Recl∆Im 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 duties; - - /// `{ schutz, markt, rechtssicherheit, eu_binnenmarkt, sicherheit, - /// umwelt, einnahmen }` (each 0..5) for law.benefit_score. - final Map ratings; - - final Jurisdiction jurisdiction; - final Freshness freshness; - final String? supersededNote; - final String? reviewedBy; - final DateTime? reviewedAt; -} - -Map _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 _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 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 § 1–4', 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 § 60–69', 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.', - ), -]; diff --git a/app/pubspec.yaml b/app/pubspec.yaml index eaf0285..c43960b 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -32,7 +32,6 @@ flutter: assets: - assets/fixtures/ - - assets/norms/ # Bundled fonts — offline-first, KRITIS-compatible, no Google # Fonts runtime fetch. setup.sh downloads SIL-OFL-licensed TTFs diff --git a/app/tool/live_smoke.dart b/app/tool/live_smoke.dart deleted file mode 100644 index 29fed74..0000000 --- a/app/tool/live_smoke.dart +++ /dev/null @@ -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> decode(chain.SubmitResponse r) { - final out = >{}; - 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) out[e.key] = d; - } catch (_) {} - } - return out; -} - -Future 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(); -} diff --git a/store/SHA256.txt b/store/SHA256.txt index a93088a..d7324ae 100644 --- a/store/SHA256.txt +++ b/store/SHA256.txt @@ -5,5 +5,5 @@ d03d3dc2bfbaa413ea4e192c6147595e21d8dafd24b5d32b853a03a38e48100d text-deontic-e 5f1a0098bc4c3be8d20b34d848af884b539b0eda3c59ab56dfe99bf8f811090d text-readability-score-0.1.0.fai 31d35f6a7b61b9249520e3ae02fa398292375555430714b04ff0771fd2f3ad99 stats-cohort-size-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 diff --git a/store/store.yaml b/store/store.yaml index 38965fb..42195b5 100644 --- a/store/store.yaml +++ b/store/store.yaml @@ -134,7 +134,7 @@ modules: - version: 0.1.0 status: published 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: en: "Standard Cost Model (SKM) bureaucracy cost" de: "Standardkostenmodell (SKM) — Bürokratiekosten"