chain's catalog charset (naming.rs check_segment, capability-namespaces.yaml) allows only [a-z0-9-] per dot-segment — underscores are rejected by `chain validate`. Repack all 8 modules with the dash capability form (econ.skm-score, law.benefit-score, …) and align the store index, flows, app strings and docs to match. Bundles now use the .chain extension (content-addressed; legacy .fai would still install). - store/store.yaml: name: dash, wasm_url .chain, refreshed sha256 - store/SHA256.txt: 8 new .chain hashes - flows + app/lib + RUN.md + MACHBARKEITSSTUDIE.md: capability refs _ -> - - left untouched: text_akoma_normalize.wasm artifact, DB field skm_score, backlog module law.skm_score Signed-off-by: flemming-it <sf@flemming.it>
343 lines
11 KiB
Dart
343 lines
11 KiB
Dart
// Domain model for the F∆I Recl∆Im client.
|
||
//
|
||
// These types are the lingua franca between the (eventually
|
||
// gRPC-fed) repository and the UI. Names mirror the Studie's
|
||
// §5.3 data model.
|
||
|
||
/// Evidence tier of the lowest-tier ingredient in a value.
|
||
/// Per Studie §6.7: "Regel der niedrigsten Stufe" — any composed
|
||
/// figure carries the tier of its weakest input.
|
||
enum EvidenceTier {
|
||
/// Official / peer-reviewed: NKR, DESTATIS, ifo, ZEW.
|
||
t1,
|
||
|
||
/// Verbands-Erhebung: DIHK, BDI, ZDH, IHK.
|
||
t2,
|
||
|
||
/// Eigene strukturierte Erhebung (Phase 2/3).
|
||
t3,
|
||
|
||
/// Qualitatives Signal — pain indicator only, never magnitude.
|
||
t4,
|
||
}
|
||
|
||
extension EvidenceTierLabel on EvidenceTier {
|
||
/// Kompakt-Code für enge Layouts.
|
||
String get short => switch (this) {
|
||
EvidenceTier.t1 => 'T1',
|
||
EvidenceTier.t2 => 'T2',
|
||
EvidenceTier.t3 => 'T3',
|
||
EvidenceTier.t4 => 'T4',
|
||
};
|
||
|
||
/// Ein-Wort-Bedeutung, immer neben dem T-Code zu zeigen.
|
||
String get headline => switch (this) {
|
||
EvidenceTier.t1 => 'amtlich',
|
||
EvidenceTier.t2 => 'Verband',
|
||
EvidenceTier.t3 => 'Erhebung',
|
||
EvidenceTier.t4 => 'Signal',
|
||
};
|
||
|
||
/// Lange Form für Listen, Tooltips, Legend-Texte.
|
||
String get description => switch (this) {
|
||
EvidenceTier.t1 =>
|
||
'amtlich oder peer-reviewed (NKR, DESTATIS, ifo, ZEW)',
|
||
EvidenceTier.t2 =>
|
||
'Verbands-Erhebung (DIHK, IHK, ZDH, BDI)',
|
||
EvidenceTier.t3 =>
|
||
'eigene strukturierte Erhebung (Phase 2/3)',
|
||
EvidenceTier.t4 =>
|
||
'qualitatives Signal — nur Hinweis, keine Magnitude',
|
||
};
|
||
}
|
||
|
||
/// Geltungsbereich — the geographic/legal scope a norm belongs to.
|
||
/// Nested by widening reach: Berlin ⊂ Deutschland ⊂ EU ⊂ Welt.
|
||
/// Every norm carries one so the UI can flag, at a glance, *which*
|
||
/// reform lever a figure speaks to (Bezirk/Senat, Bund, Brüssel,
|
||
/// or völkerrechtlich) — see Studie §5.3.
|
||
enum Jurisdiction {
|
||
/// Landesrecht Berlin (BauO Bln, ASOG, …) — Senat/Bezirk lever.
|
||
berlin,
|
||
|
||
/// Bundesrecht (GewO, MiLoG, KassenSichV) — NKR/Bundestag lever.
|
||
deutschland,
|
||
|
||
/// EU-Recht (DSGVO, REFIT) — Reform-Hebel liegt in Brüssel.
|
||
eu,
|
||
|
||
/// Völker-/Welthandelsrecht (WTO, OECD, UN) — international.
|
||
international,
|
||
}
|
||
|
||
extension JurisdictionLabel on Jurisdiction {
|
||
/// Kompakt-Code für Chips und enge Layouts.
|
||
String get short => switch (this) {
|
||
Jurisdiction.berlin => 'BE',
|
||
Jurisdiction.deutschland => 'DE',
|
||
Jurisdiction.eu => 'EU',
|
||
Jurisdiction.international => 'INT',
|
||
};
|
||
|
||
/// Voll ausgeschriebener Name.
|
||
String get label => switch (this) {
|
||
Jurisdiction.berlin => 'Berlin',
|
||
Jurisdiction.deutschland => 'Deutschland',
|
||
Jurisdiction.eu => 'EU',
|
||
Jurisdiction.international => 'International',
|
||
};
|
||
|
||
/// Rechtsebene — für Tooltip und Legend.
|
||
String get level => switch (this) {
|
||
Jurisdiction.berlin => 'Landesrecht Berlin',
|
||
Jurisdiction.deutschland => 'Bundesrecht',
|
||
Jurisdiction.eu => 'Recht der Europäischen Union',
|
||
Jurisdiction.international => 'Völker-/Welthandelsrecht',
|
||
};
|
||
|
||
/// Wo der Reform-Hebel ansetzt — die handlungsleitende Aussage.
|
||
String get reformLever => switch (this) {
|
||
Jurisdiction.berlin =>
|
||
'Reform-Hebel: Senat / Bezirk (Land Berlin)',
|
||
Jurisdiction.deutschland =>
|
||
'Reform-Hebel: Bundestag / NKR (Bund)',
|
||
Jurisdiction.eu =>
|
||
'Reform-Hebel: Brüssel — DE-Streichung allein wirkt nicht',
|
||
Jurisdiction.international =>
|
||
'Reform-Hebel: völkerrechtlich (WTO / OECD / UN)',
|
||
};
|
||
|
||
/// Ableitung aus der ELI/CELEX-Kennung, wenn kein Feld gesetzt
|
||
/// ist (Hub-Live-Modus liefert die ELI, nicht immer den Scope).
|
||
/// eli/eu/… → eu
|
||
/// eli/land/be/… → berlin
|
||
/// eli/bund/… → deutschland
|
||
/// eli/int|un|wto/… → international
|
||
static Jurisdiction fromEli(String eli) {
|
||
final e = eli.toLowerCase();
|
||
if (e.contains('/eu/') || e.startsWith('eli/eu') || e.contains('celex')) {
|
||
return Jurisdiction.eu;
|
||
}
|
||
if (e.contains('/land/be') || e.contains('/land/berlin') ||
|
||
e.contains('/be/')) {
|
||
return Jurisdiction.berlin;
|
||
}
|
||
if (e.contains('/int/') || e.contains('/un/') || e.contains('/wto/') ||
|
||
e.contains('/oecd/')) {
|
||
return Jurisdiction.international;
|
||
}
|
||
return Jurisdiction.deutschland;
|
||
}
|
||
|
||
/// Parse a serialized scope string from a hub flow bag, falling
|
||
/// back to ELI-derivation when absent or unrecognized.
|
||
static Jurisdiction parse(String? raw, {required String eli}) {
|
||
switch (raw?.trim().toLowerCase()) {
|
||
case 'berlin' || 'be' || 'land-berlin':
|
||
return Jurisdiction.berlin;
|
||
case 'deutschland' || 'de' || 'bund' || 'bundesrecht':
|
||
return Jurisdiction.deutschland;
|
||
case 'eu' || 'europa' || 'union':
|
||
return Jurisdiction.eu;
|
||
case 'international' || 'int' || 'welt' || 'global':
|
||
return Jurisdiction.international;
|
||
default:
|
||
return fromEli(eli);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Aktualität einer Norm-Fassung relativ zu unserer Datenbasis.
|
||
/// Quelle der Wahrheit: `Norm.sourceSha256` gegen die aktuell
|
||
/// veröffentlichte Quelle + `standDate`. Surfaced als Badge, damit
|
||
/// eine veraltete Auswertung sofort erkennbar ist (Studie §5.3).
|
||
enum Freshness {
|
||
/// Unsere Fassung == aktuell veröffentlichter Stand.
|
||
current,
|
||
|
||
/// Upstream geändert/aufgehoben — unsere Auswertung ist veraltet.
|
||
superseded,
|
||
|
||
/// Noch nicht gegen die Quelle abgeglichen.
|
||
unknown,
|
||
}
|
||
|
||
extension FreshnessLabel on Freshness {
|
||
String get label => switch (this) {
|
||
Freshness.current => 'aktuell',
|
||
Freshness.superseded => 'geändert',
|
||
Freshness.unknown => 'ungeprüft',
|
||
};
|
||
|
||
String get description => switch (this) {
|
||
Freshness.current =>
|
||
'Fassung entspricht dem aktuell veröffentlichten Stand.',
|
||
Freshness.superseded =>
|
||
'Quelle wurde upstream geändert — Auswertung veraltet, '
|
||
'Neubewertung nötig.',
|
||
Freshness.unknown =>
|
||
'Aktualität nicht gegen die Quelle abgeglichen.',
|
||
};
|
||
|
||
/// Stale-Zustände bekommen ein sichtbares Badge; `current` nicht.
|
||
bool get needsBadge => this != Freshness.current;
|
||
}
|
||
|
||
/// A citable source — never an LLM, always a public document or
|
||
/// dataset reference. UI surfaces these next to every figure.
|
||
class Source {
|
||
const Source({
|
||
required this.label,
|
||
required this.url,
|
||
required this.tier,
|
||
this.note,
|
||
});
|
||
|
||
final String label;
|
||
final String url;
|
||
final EvidenceTier tier;
|
||
final String? note;
|
||
}
|
||
|
||
/// One atomic obligation (Pflicht) extracted from a norm.
|
||
/// Tuple shape matches Studie §6.3.
|
||
class Duty {
|
||
const Duty({
|
||
required this.modality,
|
||
required this.addressee,
|
||
required this.action,
|
||
required this.frequency,
|
||
required this.sourceNorm,
|
||
this.authority,
|
||
this.consequence,
|
||
});
|
||
|
||
final String modality; // OBLIGATION | PROHIBITION | …
|
||
final String addressee;
|
||
final String action;
|
||
final String frequency;
|
||
final String sourceNorm;
|
||
final String? authority;
|
||
final String? consequence;
|
||
}
|
||
|
||
/// One Absatz/paragraph of the actual normative text.
|
||
class NormParagraph {
|
||
const NormParagraph({required this.number, required this.text});
|
||
|
||
/// Source numbering as it appears, e.g. "(1)", "(2a)". Empty
|
||
/// when the source has no Absatz numbering.
|
||
final String number;
|
||
final String text;
|
||
}
|
||
|
||
/// One norm version — a versioned legal text identified by its
|
||
/// (synthetic for now) ELI/CELEX.
|
||
class Norm {
|
||
const Norm({
|
||
required this.eli,
|
||
required this.title,
|
||
required this.jurabk,
|
||
required this.paragraph,
|
||
required this.jurisdiction,
|
||
required this.standDate,
|
||
required this.sourceUrl,
|
||
required this.sourceSha256,
|
||
this.freshness = Freshness.current,
|
||
this.supersededNote,
|
||
this.paragraphs = const [],
|
||
});
|
||
|
||
final String eli;
|
||
final String title;
|
||
final String jurabk;
|
||
final String paragraph;
|
||
|
||
/// Geltungsbereich — Berlin / Deutschland / EU / International.
|
||
/// Surfaced as a scope badge wherever the norm appears.
|
||
final Jurisdiction jurisdiction;
|
||
|
||
final DateTime standDate;
|
||
final String sourceUrl;
|
||
final String sourceSha256;
|
||
|
||
/// Aktualität dieser Fassung gegenüber der Quelle.
|
||
final Freshness freshness;
|
||
|
||
/// Optionaler Klartext-Hinweis, *was* sich geändert hat (z. B.
|
||
/// "BEG IV hat §17 zum 1.1.2026 entschärft"). Nur gesetzt, wenn
|
||
/// freshness != current.
|
||
final String? supersededNote;
|
||
|
||
/// The actual paragraphs of the norm text, normalised by
|
||
/// `text.akoma-normalize` in live mode. In demo mode the
|
||
/// fixture quotes the public source verbatim under §5 UrhG
|
||
/// (amtliche Werke).
|
||
final List<NormParagraph> paragraphs;
|
||
}
|
||
|
||
/// A heatmap point: one norm with its computed scores.
|
||
/// Axes mirror §6 in the study.
|
||
class Evaluation {
|
||
const Evaluation({
|
||
required this.norm,
|
||
required this.skmEurPerYear,
|
||
required this.benefitScore,
|
||
required this.affectedCount,
|
||
required this.frustScore,
|
||
required this.tierLowest,
|
||
required this.duties,
|
||
required this.sources,
|
||
required this.auditEventId,
|
||
this.notes,
|
||
this.componentSummary = const [],
|
||
this.reviewedBy,
|
||
this.reviewedAt,
|
||
});
|
||
|
||
final Norm norm;
|
||
|
||
/// Yearly bureaucracy cost across the addressee population.
|
||
/// €/year, computed via SKM (`P × F × T × h`).
|
||
final double skmEurPerYear;
|
||
|
||
/// 0–5, composite of seven dimensions
|
||
/// (safety / market / legal-certainty / EU-harmony / …).
|
||
final double benefitScore;
|
||
|
||
/// Number of addressees (KMU, Bürger, etc.). UI uses this for
|
||
/// heatmap point size.
|
||
final int affectedCount;
|
||
|
||
/// 0–10, composite of readability + Verweistiefe + Akteurs-
|
||
/// pluralität + Norm-Volatilität.
|
||
final double frustScore;
|
||
|
||
/// Lowest tier among all ingredients of any displayed figure.
|
||
final EvidenceTier tierLowest;
|
||
|
||
final List<Duty> duties;
|
||
final List<Source> sources;
|
||
|
||
/// Pointer into the Ch∆In audit log (SQLite event_log row).
|
||
final String auditEventId;
|
||
|
||
final String? notes;
|
||
|
||
/// Compact lines for at-a-glance reading on list views.
|
||
/// One line per SKM-Komponente (P / F / T / h) plus optional
|
||
/// extras — each carries its tier-tag inline. Empty list ⇒
|
||
/// fall back to the notes field for the list view.
|
||
final List<String> componentSummary;
|
||
|
||
/// Jurist who confirmed this evaluation (the `by` of the hub
|
||
/// approval). Null ⇒ not yet reviewed. Jurist review is OPTIONAL:
|
||
/// the figure stands on its own; a review only *confirms* it.
|
||
final String? reviewedBy;
|
||
|
||
/// When the jurist signed off. Null ⇒ not reviewed.
|
||
final DateTime? reviewedAt;
|
||
|
||
/// True once a trusted jurist has confirmed this evaluation.
|
||
bool get isReviewed => reviewedBy != null;
|
||
}
|