Adds the actual paragraphs of the law right next to the evaluation panel on the norm-detail page. A reviewer no longer has to leave the app to confirm what a score is about; the canonical source URL stays one click away via 'Im Browser öffnen' (url_launcher) and one click away as a clipboard copy. Schema models.dart gains a NormParagraph (number + text). Norm carries a List<NormParagraph> paragraphs, defaulted to const [] so norms harvested by text.akoma_normalize@^0 in live mode slot in without breaking the Mock path. Demo data fixtures.dart embeds the public source paragraphs verbatim under §5 UrhG (amtliche Werke) for the five pilot norms. Plumbing NormTextCard (lib/widgets/norm_text_card.dart) is the renderer: per-paragraph hanging label, selectable body text, source URL in mono at the bottom, two action buttons. NormDetailPage drops the card between the metric row and the duties card so the eye lands on it during a 'why this score?' read-through. macOS sandbox The Release entitlement file gains com.apple.security.network.client so url_launcher's NSWorkspace call and the HubRepository's gRPC channel can both reach the network. The DebugProfile already had server; client matches the production posture. GeneratedPluginRegistrant.swift was re-generated by flutter pub get after url_launcher was added — no hand edits. flutter analyze: clean. flutter test: 2/2 passing. The macOS build via build-macos.sh launches and the new card renders the GewO § 14, KassenSichV, DSGVO Art. 30, BauO Bln § 60 ff and MiLoG § 17 fixtures with their actual normative text. Signed-off-by: flemming-it <sf@flemming.it>
161 lines
4.2 KiB
Dart
161 lines
4.2 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 {
|
||
String get short => switch (this) {
|
||
EvidenceTier.t1 => 'T1',
|
||
EvidenceTier.t2 => 'T2',
|
||
EvidenceTier.t3 => 'T3',
|
||
EvidenceTier.t4 => 'T4',
|
||
};
|
||
|
||
String get description => switch (this) {
|
||
EvidenceTier.t1 => 'amtlich / peer-reviewed',
|
||
EvidenceTier.t2 => 'Verband',
|
||
EvidenceTier.t3 => 'eigene Erhebung',
|
||
EvidenceTier.t4 => 'qualitatives Signal',
|
||
};
|
||
}
|
||
|
||
/// 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.standDate,
|
||
required this.sourceUrl,
|
||
required this.sourceSha256,
|
||
this.paragraphs = const [],
|
||
});
|
||
|
||
final String eli;
|
||
final String title;
|
||
final String jurabk;
|
||
final String paragraph;
|
||
final DateTime standDate;
|
||
final String sourceUrl;
|
||
final String sourceSha256;
|
||
|
||
/// 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,
|
||
});
|
||
|
||
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;
|
||
}
|