Compare commits
No commits in common. "46b4003d3681c930831614a002536f233b03de5a" and "51beb332c4c538b35e15c4969580abcadfaa761b" have entirely different histories.
46b4003d36
...
51beb332c4
20 changed files with 40 additions and 932 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -12,8 +12,3 @@ Thumbs.db
|
||||||
# Local-only scratch
|
# Local-only scratch
|
||||||
/scratch/
|
/scratch/
|
||||||
/.local/
|
/.local/
|
||||||
.playwright-mcp/
|
|
||||||
*.log
|
|
||||||
reclaim-landing.png
|
|
||||||
app/build/
|
|
||||||
store/bundles/
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ class Fixtures {
|
||||||
title: 'Anzeigepflicht',
|
title: 'Anzeigepflicht',
|
||||||
jurabk: 'GewO',
|
jurabk: 'GewO',
|
||||||
paragraph: '§ 14',
|
paragraph: '§ 14',
|
||||||
jurisdiction: Jurisdiction.deutschland,
|
|
||||||
standDate: DateTime.utc(2025, 12, 1),
|
standDate: DateTime.utc(2025, 12, 1),
|
||||||
sourceUrl: 'https://www.gesetze-im-internet.de/gewo/__14.html',
|
sourceUrl: 'https://www.gesetze-im-internet.de/gewo/__14.html',
|
||||||
sourceSha256: 'demo-${'a' * 60}',
|
sourceSha256: 'demo-${'a' * 60}',
|
||||||
|
|
@ -105,7 +104,6 @@ class Fixtures {
|
||||||
title: 'Kassensicherungsverordnung',
|
title: 'Kassensicherungsverordnung',
|
||||||
jurabk: 'KassenSichV',
|
jurabk: 'KassenSichV',
|
||||||
paragraph: 'gesamt',
|
paragraph: 'gesamt',
|
||||||
jurisdiction: Jurisdiction.deutschland,
|
|
||||||
standDate: DateTime.utc(2025, 11, 15),
|
standDate: DateTime.utc(2025, 11, 15),
|
||||||
sourceUrl:
|
sourceUrl:
|
||||||
'https://www.gesetze-im-internet.de/kassensichv/index.html',
|
'https://www.gesetze-im-internet.de/kassensichv/index.html',
|
||||||
|
|
@ -179,8 +177,6 @@ class Fixtures {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
auditEventId: 'demo-evt-kassensichv',
|
auditEventId: 'demo-evt-kassensichv',
|
||||||
reviewedBy: 'RA Dr. M. Kessler',
|
|
||||||
reviewedAt: DateTime.utc(2026, 1, 20),
|
|
||||||
notes:
|
notes:
|
||||||
'Komponenten-Tier: Initial-Erfüllungsaufwand wurde 2016 im NKR-Stellungnahme-'
|
'Komponenten-Tier: Initial-Erfüllungsaufwand wurde 2016 im NKR-Stellungnahme-'
|
||||||
'Prozess zum Kassengesetz dokumentiert (T1 für Größenordnung). '
|
'Prozess zum Kassengesetz dokumentiert (T1 für Größenordnung). '
|
||||||
|
|
@ -201,7 +197,6 @@ class Fixtures {
|
||||||
title: 'Verzeichnis von Verarbeitungstätigkeiten',
|
title: 'Verzeichnis von Verarbeitungstätigkeiten',
|
||||||
jurabk: 'DSGVO',
|
jurabk: 'DSGVO',
|
||||||
paragraph: 'Art. 30',
|
paragraph: 'Art. 30',
|
||||||
jurisdiction: Jurisdiction.eu,
|
|
||||||
standDate: DateTime.utc(2025, 10, 1),
|
standDate: DateTime.utc(2025, 10, 1),
|
||||||
sourceUrl: 'https://eur-lex.europa.eu/eli/reg/2016/679',
|
sourceUrl: 'https://eur-lex.europa.eu/eli/reg/2016/679',
|
||||||
sourceSha256: 'demo-${'c' * 60}',
|
sourceSha256: 'demo-${'c' * 60}',
|
||||||
|
|
@ -282,8 +277,6 @@ class Fixtures {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
auditEventId: 'demo-evt-dsgvo-30',
|
auditEventId: 'demo-evt-dsgvo-30',
|
||||||
reviewedBy: 'Prof. Dr. A. Lindner',
|
|
||||||
reviewedAt: DateTime.utc(2026, 2, 3),
|
|
||||||
notes:
|
notes:
|
||||||
'EU-determiniert: Reform-Vorschlag muss Brüssel adressieren, nicht '
|
'EU-determiniert: Reform-Vorschlag muss Brüssel adressieren, nicht '
|
||||||
'DE-Streichung. Komponenten-Tier: P (Fallzahl Verantwortliche) '
|
'DE-Streichung. Komponenten-Tier: P (Fallzahl Verantwortliche) '
|
||||||
|
|
@ -306,7 +299,6 @@ class Fixtures {
|
||||||
title: 'Bauantrag',
|
title: 'Bauantrag',
|
||||||
jurabk: 'BauO Bln',
|
jurabk: 'BauO Bln',
|
||||||
paragraph: '§ 60 ff',
|
paragraph: '§ 60 ff',
|
||||||
jurisdiction: Jurisdiction.berlin,
|
|
||||||
standDate: DateTime.utc(2025, 9, 1),
|
standDate: DateTime.utc(2025, 9, 1),
|
||||||
sourceUrl:
|
sourceUrl:
|
||||||
'https://gesetze.berlin.de/bsbe/document/jlr-BauOBE2005V25P60',
|
'https://gesetze.berlin.de/bsbe/document/jlr-BauOBE2005V25P60',
|
||||||
|
|
@ -371,8 +363,6 @@ class Fixtures {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
auditEventId: 'demo-evt-berlinbauo-60',
|
auditEventId: 'demo-evt-berlinbauo-60',
|
||||||
reviewedBy: 'RAin S. Brandt',
|
|
||||||
reviewedAt: DateTime.utc(2026, 1, 28),
|
|
||||||
notes:
|
notes:
|
||||||
'Berlin-spezifisch, daher kein NKR-Erfüllungsaufwand auf Bundesebene. '
|
'Berlin-spezifisch, daher kein NKR-Erfüllungsaufwand auf Bundesebene. '
|
||||||
'P aus Berliner Bauaufsichts-Statistik (T2 Handwerkskammer Berlin); '
|
'P aus Berliner Bauaufsichts-Statistik (T2 Handwerkskammer Berlin); '
|
||||||
|
|
@ -395,14 +385,9 @@ class Fixtures {
|
||||||
title: 'Dokumentationspflichten',
|
title: 'Dokumentationspflichten',
|
||||||
jurabk: 'MiLoG',
|
jurabk: 'MiLoG',
|
||||||
paragraph: '§ 17',
|
paragraph: '§ 17',
|
||||||
jurisdiction: Jurisdiction.deutschland,
|
|
||||||
standDate: DateTime.utc(2025, 11, 1),
|
standDate: DateTime.utc(2025, 11, 1),
|
||||||
sourceUrl: 'https://www.gesetze-im-internet.de/milog/__17.html',
|
sourceUrl: 'https://www.gesetze-im-internet.de/milog/__17.html',
|
||||||
sourceSha256: 'demo-${'e' * 60}',
|
sourceSha256: 'demo-${'e' * 60}',
|
||||||
freshness: Freshness.superseded,
|
|
||||||
supersededNote: 'BEG IV hat die Dokumentationspflichten zum '
|
|
||||||
'1.1.2026 entschärft — diese Auswertung bildet die Fassung '
|
|
||||||
'davor ab und ist neu zu bewerten.',
|
|
||||||
paragraphs: const [
|
paragraphs: const [
|
||||||
NormParagraph(
|
NormParagraph(
|
||||||
number: '(1)',
|
number: '(1)',
|
||||||
|
|
|
||||||
|
|
@ -36,21 +36,10 @@ class HubRepository implements EvaluationRepository {
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
authToken: settings.authToken,
|
authToken: settings.authToken,
|
||||||
);
|
);
|
||||||
// The probe must NEVER throw out of connect(): main() awaits
|
final ok = await client.healthy().timeout(
|
||||||
// this before runApp(), so any uncaught error here leaves the
|
|
||||||
// app on a blank page. A timeout maps to false; so does any
|
|
||||||
// transport error (e.g. a gRPC-web call against a plain-gRPC
|
|
||||||
// hub, or a refused connection) — the app then comes up in
|
|
||||||
// 'hub-down' state and shows the empty-state hint.
|
|
||||||
bool ok;
|
|
||||||
try {
|
|
||||||
ok = await client.healthy().timeout(
|
|
||||||
const Duration(seconds: 4),
|
const Duration(seconds: 4),
|
||||||
onTimeout: () => false,
|
onTimeout: () => false,
|
||||||
);
|
);
|
||||||
} catch (_) {
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
return HubRepository._(client, settings, ok);
|
return HubRepository._(client, settings, ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,21 +97,14 @@ class HubRepository implements EvaluationRepository {
|
||||||
if (normJson == null) return null;
|
if (normJson == null) return null;
|
||||||
final skmJson = _asMap(bag['skm']);
|
final skmJson = _asMap(bag['skm']);
|
||||||
|
|
||||||
final eli = (normJson['eli'] as String?) ?? 'eli/unknown';
|
|
||||||
final norm = Norm(
|
final norm = Norm(
|
||||||
eli: eli,
|
eli: (normJson['eli'] as String?) ?? 'eli/unknown',
|
||||||
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?) ?? '',
|
||||||
jurisdiction: JurisdictionLabel.parse(
|
|
||||||
normJson['scope'] as String?,
|
|
||||||
eli: eli,
|
|
||||||
),
|
|
||||||
standDate: _parseTimestamp(event.timestamp),
|
standDate: _parseTimestamp(event.timestamp),
|
||||||
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']),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -156,26 +138,9 @@ class HubRepository implements EvaluationRepository {
|
||||||
auditEventId: event.eventId,
|
auditEventId: event.eventId,
|
||||||
notes: 'Live aus Hub-Flow ${event.flowName} '
|
notes: 'Live aus Hub-Flow ${event.flowName} '
|
||||||
'(execution ${event.flowExecution.substring(0, 8.clamp(0, event.flowExecution.length))}).',
|
'(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?) ?? ''),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic>? _asMap(dynamic v) =>
|
static Map<String, dynamic>? _asMap(dynamic v) =>
|
||||||
v is Map<String, dynamic> ? v : null;
|
v is Map<String, dynamic> ? v : null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,138 +51,6 @@ extension EvidenceTierLabel on EvidenceTier {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// A citable source — never an LLM, always a public document or
|
||||||
/// dataset reference. UI surfaces these next to every figure.
|
/// dataset reference. UI surfaces these next to every figure.
|
||||||
class Source {
|
class Source {
|
||||||
|
|
@ -239,12 +107,9 @@ class Norm {
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.jurabk,
|
required this.jurabk,
|
||||||
required this.paragraph,
|
required this.paragraph,
|
||||||
required this.jurisdiction,
|
|
||||||
required this.standDate,
|
required this.standDate,
|
||||||
required this.sourceUrl,
|
required this.sourceUrl,
|
||||||
required this.sourceSha256,
|
required this.sourceSha256,
|
||||||
this.freshness = Freshness.current,
|
|
||||||
this.supersededNote,
|
|
||||||
this.paragraphs = const [],
|
this.paragraphs = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -252,23 +117,10 @@ class Norm {
|
||||||
final String title;
|
final String title;
|
||||||
final String jurabk;
|
final String jurabk;
|
||||||
final String paragraph;
|
final String paragraph;
|
||||||
|
|
||||||
/// Geltungsbereich — Berlin / Deutschland / EU / International.
|
|
||||||
/// Surfaced as a scope badge wherever the norm appears.
|
|
||||||
final Jurisdiction jurisdiction;
|
|
||||||
|
|
||||||
final DateTime standDate;
|
final DateTime standDate;
|
||||||
final String sourceUrl;
|
final String sourceUrl;
|
||||||
final String sourceSha256;
|
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
|
/// The actual paragraphs of the norm text, normalised by
|
||||||
/// `text.akoma_normalize` in live mode. In demo mode the
|
/// `text.akoma_normalize` in live mode. In demo mode the
|
||||||
/// fixture quotes the public source verbatim under §5 UrhG
|
/// fixture quotes the public source verbatim under §5 UrhG
|
||||||
|
|
@ -291,8 +143,6 @@ class Evaluation {
|
||||||
required this.auditEventId,
|
required this.auditEventId,
|
||||||
this.notes,
|
this.notes,
|
||||||
this.componentSummary = const [],
|
this.componentSummary = const [],
|
||||||
this.reviewedBy,
|
|
||||||
this.reviewedAt,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final Norm norm;
|
final Norm norm;
|
||||||
|
|
@ -329,15 +179,4 @@ class Evaluation {
|
||||||
/// extras — each carries its tier-tag inline. Empty list ⇒
|
/// extras — each carries its tier-tag inline. Empty list ⇒
|
||||||
/// fall back to the notes field for the list view.
|
/// fall back to the notes field for the list view.
|
||||||
final List<String> componentSummary;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,8 @@ import 'theme/reclaim_theme.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
// Acquiring settings + probing the hub must never prevent the
|
final settings = await HubSettings.load();
|
||||||
// first frame. Any failure here falls back to demo data so the
|
final repository = await _pickRepository(settings);
|
||||||
// app always paints instead of hanging on a blank page.
|
|
||||||
HubSettings settings;
|
|
||||||
EvaluationRepository repository;
|
|
||||||
try {
|
|
||||||
settings = await HubSettings.load();
|
|
||||||
repository = await _pickRepository(settings);
|
|
||||||
} catch (_) {
|
|
||||||
settings = const HubSettings(
|
|
||||||
host: HubSettings.defaultHost,
|
|
||||||
port: HubSettings.defaultPort,
|
|
||||||
secure: false,
|
|
||||||
);
|
|
||||||
repository = const MockRepository();
|
|
||||||
}
|
|
||||||
runApp(ReclaimApp(repository: repository, settings: settings));
|
runApp(ReclaimApp(repository: repository, settings: settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import '../data/repository.dart';
|
||||||
import '../theme/reclaim_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/mode_banner.dart';
|
import '../widgets/mode_banner.dart';
|
||||||
import '../widgets/heatmap_grid.dart';
|
import '../widgets/heatmap_grid.dart';
|
||||||
import '../widgets/jurisdiction_badge.dart';
|
|
||||||
import '../widgets/reclaim_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
import 'norm_detail_page.dart';
|
import 'norm_detail_page.dart';
|
||||||
|
|
@ -60,9 +59,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: ReclaimSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: items.isEmpty
|
child: ReclaimCard(
|
||||||
? _EmptyHubState(repository: widget.repository)
|
|
||||||
: ReclaimCard(
|
|
||||||
accent: true,
|
accent: true,
|
||||||
expand: true,
|
expand: true,
|
||||||
child: HeatmapGrid(
|
child: HeatmapGrid(
|
||||||
|
|
@ -80,7 +77,6 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (items.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: ReclaimSpace.md),
|
const SizedBox(height: ReclaimSpace.md),
|
||||||
const _LegendCard(),
|
const _LegendCard(),
|
||||||
const SizedBox(height: ReclaimSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
|
|
@ -108,11 +104,6 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
JurisdictionBadge(
|
|
||||||
jurisdiction: e.norm.jurisdiction,
|
|
||||||
compact: true),
|
|
||||||
const SizedBox(
|
|
||||||
width: ReclaimSpace.xs),
|
|
||||||
TierBadge(
|
TierBadge(
|
||||||
tier: e.tierLowest, compact: true),
|
tier: e.tierLowest, compact: true),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
@ -141,7 +132,6 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -206,8 +196,6 @@ class _LegendCardState extends State<_LegendCard> {
|
||||||
symbol: '●', text: 'Betroffenheit'),
|
symbol: '●', text: 'Betroffenheit'),
|
||||||
_MiniLabel(
|
_MiniLabel(
|
||||||
symbol: '◐', text: 'Evidenz-Stufe'),
|
symbol: '◐', text: 'Evidenz-Stufe'),
|
||||||
_MiniLabel(
|
|
||||||
symbol: '⌖', text: 'Geltungsbereich'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -284,21 +272,6 @@ class _LegendCardState extends State<_LegendCard> {
|
||||||
'SKM-Zahl T4. Petrol → warm = stark → '
|
'SKM-Zahl T4. Petrol → warm = stark → '
|
||||||
'schwach.',
|
'schwach.',
|
||||||
),
|
),
|
||||||
SizedBox(height: ReclaimSpace.sm),
|
|
||||||
_LegendRow(
|
|
||||||
symbol: '⌖',
|
|
||||||
axis: 'Chip-Symbol',
|
|
||||||
label: 'Geltungsbereich',
|
|
||||||
description:
|
|
||||||
'Jede Norm trägt ein Scope-Chip — '
|
|
||||||
'BE Berlin (Landesrecht, Senat/Bezirk), '
|
|
||||||
'DE Deutschland (Bundesrecht, NKR/'
|
|
||||||
'Bundestag), EU (Reform-Hebel in Brüssel), '
|
|
||||||
'INT International (WTO/OECD/UN). Es zeigt '
|
|
||||||
'an, auf welcher Ebene der Reform-Hebel '
|
|
||||||
'ansetzt: eine EU-Norm lässt sich nicht '
|
|
||||||
'durch nationale Streichung entlasten.',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -312,69 +285,6 @@ class _LegendCardState extends State<_LegendCard> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shown in place of the heatmap when the repository returns zero
|
|
||||||
/// evaluations — almost always live-hub mode against a hub that
|
|
||||||
/// has no Recl∆Im `flow.completed` events yet (or is unreachable).
|
|
||||||
/// Tells the reader exactly how to get data on screen instead of
|
|
||||||
/// leaving a blank plot.
|
|
||||||
class _EmptyHubState extends StatelessWidget {
|
|
||||||
const _EmptyHubState({required this.repository});
|
|
||||||
|
|
||||||
final EvaluationRepository repository;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Theme.of(context).textTheme;
|
|
||||||
final down = repository.mode == 'hub-down';
|
|
||||||
return ReclaimCard(
|
|
||||||
accent: true,
|
|
||||||
expand: true,
|
|
||||||
child: Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 460),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
down ? Icons.cloud_off_outlined : Icons.inbox_outlined,
|
|
||||||
size: 40,
|
|
||||||
color: ReclaimColors.mute,
|
|
||||||
),
|
|
||||||
const SizedBox(height: ReclaimSpace.md),
|
|
||||||
Text(
|
|
||||||
down
|
|
||||||
? 'Hub nicht erreichbar'
|
|
||||||
: 'Noch keine Auswertungen im Hub',
|
|
||||||
style: t.headlineSmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: ReclaimSpace.sm),
|
|
||||||
Text(
|
|
||||||
down
|
|
||||||
? 'Verbunden mit ${repository.hubLabel}. Läuft '
|
|
||||||
'`chain serve`? Sonst im Hub-Reiter den '
|
|
||||||
'Demo-Modus aktivieren — dann zeigt die '
|
|
||||||
'Heatmap das Fixture-Korpus.'
|
|
||||||
: 'Der Hub liefert keine flow.completed-Events. '
|
|
||||||
'Einen Recl∆Im-Flow laufen lassen, z. B.\n'
|
|
||||||
' chain run flows/durchstich-from-file.yaml '
|
|
||||||
'--input content=@norm.xml --input '
|
|
||||||
'cohort_id=berlin-kmu\n'
|
|
||||||
'und im Hub/Studio freigeben — oder im '
|
|
||||||
'Hub-Reiter den Demo-Modus aktivieren.',
|
|
||||||
style: t.bodyLarge?.copyWith(
|
|
||||||
color: ReclaimColors.mute,
|
|
||||||
height: 1.4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MiniLabel extends StatelessWidget {
|
class _MiniLabel extends StatelessWidget {
|
||||||
const _MiniLabel({required this.symbol, required this.text});
|
const _MiniLabel({required this.symbol, required this.text});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ import '../data/repository.dart';
|
||||||
import '../theme/reclaim_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/mode_banner.dart';
|
import '../widgets/mode_banner.dart';
|
||||||
import '../widgets/evidence_sidebar.dart';
|
import '../widgets/evidence_sidebar.dart';
|
||||||
import '../widgets/freshness_badge.dart';
|
|
||||||
import '../widgets/jurisdiction_badge.dart';
|
|
||||||
import '../widgets/norm_text_card.dart';
|
import '../widgets/norm_text_card.dart';
|
||||||
import '../widgets/reclaim_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/review_badge.dart';
|
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
|
|
||||||
class NormDetailPage extends StatelessWidget {
|
class NormDetailPage extends StatelessWidget {
|
||||||
|
|
@ -66,27 +63,7 @@ class NormDetailPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
JurisdictionBadge(
|
|
||||||
jurisdiction: e.norm.jurisdiction),
|
|
||||||
const SizedBox(height: ReclaimSpace.sm),
|
|
||||||
TierBadge(tier: e.tierLowest),
|
TierBadge(tier: e.tierLowest),
|
||||||
if (e.norm.freshness.needsBadge) ...[
|
|
||||||
const SizedBox(
|
|
||||||
height: ReclaimSpace.sm),
|
|
||||||
FreshnessBadge(
|
|
||||||
freshness: e.norm.freshness,
|
|
||||||
note: e.norm.supersededNote,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: ReclaimSpace.sm),
|
|
||||||
ReviewBadge(
|
|
||||||
evaluation: e, showUnreviewed: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: ReclaimSpace.lg),
|
const SizedBox(height: ReclaimSpace.lg),
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@ import '../data/models.dart';
|
||||||
import '../data/repository.dart';
|
import '../data/repository.dart';
|
||||||
import '../theme/reclaim_tokens.dart';
|
import '../theme/reclaim_tokens.dart';
|
||||||
import '../widgets/mode_banner.dart';
|
import '../widgets/mode_banner.dart';
|
||||||
import '../widgets/freshness_badge.dart';
|
|
||||||
import '../widgets/jurisdiction_badge.dart';
|
|
||||||
import '../widgets/reclaim_card.dart';
|
import '../widgets/reclaim_card.dart';
|
||||||
import '../widgets/review_badge.dart';
|
|
||||||
import '../widgets/tier_badge.dart';
|
import '../widgets/tier_badge.dart';
|
||||||
import 'norm_detail_page.dart';
|
import 'norm_detail_page.dart';
|
||||||
|
|
||||||
|
|
@ -111,20 +108,7 @@ class _NormRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: ReclaimSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
Wrap(
|
|
||||||
spacing: ReclaimSpace.sm,
|
|
||||||
runSpacing: ReclaimSpace.xs,
|
|
||||||
children: [
|
|
||||||
JurisdictionBadge(
|
|
||||||
jurisdiction: evaluation.norm.jurisdiction),
|
|
||||||
TierBadge(tier: evaluation.tierLowest),
|
TierBadge(tier: evaluation.tierLowest),
|
||||||
FreshnessBadge(
|
|
||||||
freshness: evaluation.norm.freshness,
|
|
||||||
note: evaluation.norm.supersededNote,
|
|
||||||
),
|
|
||||||
ReviewBadge(evaluation: evaluation),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,7 @@ class _ShellPageState extends State<ShellPage> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Static in the persistent nav rail: a 60fps pulse
|
const DeltaMark(size: 36),
|
||||||
// here repaints every scroll frame and stutters web
|
|
||||||
// scrolling. The breathing mark stays on the
|
|
||||||
// landing page (which isn't scrolled).
|
|
||||||
const DeltaMark(size: 36, animated: false),
|
|
||||||
const SizedBox(height: ReclaimSpace.sm),
|
const SizedBox(height: ReclaimSpace.sm),
|
||||||
const BrandWordmark(size: 18),
|
const BrandWordmark(size: 18),
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -36,23 +36,6 @@ class ReclaimColors {
|
||||||
static const tierT2 = Color(0xFF6CA29A); // verband
|
static const tierT2 = Color(0xFF6CA29A); // verband
|
||||||
static const tierT3 = Color(0xFFB0AC8E); // own survey
|
static const tierT3 = Color(0xFFB0AC8E); // own survey
|
||||||
static const tierT4 = Color(0xFFA86F5C); // qualitative signal only
|
static const tierT4 = Color(0xFFA86F5C); // qualitative signal only
|
||||||
|
|
||||||
// Scope badges — Geltungsbereich of a norm. Hues widen from a
|
|
||||||
// warm-local Berlin to a cool-global teal, distinct from the
|
|
||||||
// tier palette so the two chips never read as the same thing.
|
|
||||||
// Tuned for dark-first legibility on the ink canvas (bright
|
|
||||||
// enough to read as chip text/border over inkRaised).
|
|
||||||
static const scopeBerlin = Color(0xFFD0685C); // Land Berlin (brick)
|
|
||||||
static const scopeDeutschland = Color(0xFFC79A3C); // Bund (gold)
|
|
||||||
static const scopeEu = Color(0xFF5B8DD6); // EU (union blue)
|
|
||||||
static const scopeInternational = Color(0xFF4FB89E); // global (teal)
|
|
||||||
|
|
||||||
// Freshness — is our norm version current vs. the published source.
|
|
||||||
static const freshSuperseded = Color(0xFFE0973A); // amended upstream
|
|
||||||
static const freshUnknown = Color(0xFF9AA0A6); // not reconciled
|
|
||||||
|
|
||||||
// Jurist review — an evaluation confirmed (signed off) by a jurist.
|
|
||||||
static const reviewConfirmed = Color(0xFF6FBE7A); // confirm green
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spacing scale — 4/8/12/16/24/32/48 multiples, matching
|
/// Spacing scale — 4/8/12/16/24/32/48 multiples, matching
|
||||||
|
|
|
||||||
|
|
@ -46,30 +46,17 @@ class _DeltaMarkState extends State<DeltaMark>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Static mark: no controller listening, paint once.
|
return AnimatedBuilder(
|
||||||
if (!widget.animated) {
|
|
||||||
return CustomPaint(
|
|
||||||
size: Size.square(widget.size),
|
|
||||||
painter: _DeltaPainter(pulse: 1.0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Animated: isolate the per-frame repaint in its own layer so
|
|
||||||
// the breathing pulse never invalidates surrounding content
|
|
||||||
// (web scroll stays smooth).
|
|
||||||
return RepaintBoundary(
|
|
||||||
child: AnimatedBuilder(
|
|
||||||
animation: _controller,
|
animation: _controller,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final t = _controller.value;
|
final t = _controller.value;
|
||||||
// Breathe: scale 1 → 1.28 → 1 over the cycle.
|
// Breathe: scale 1 → 1.28 → 1 over the cycle.
|
||||||
final pulse =
|
final pulse = 1.0 + 0.28 * (1 - (math.cos(t * 2 * math.pi).abs()));
|
||||||
1.0 + 0.28 * (1 - (math.cos(t * 2 * math.pi).abs()));
|
|
||||||
return CustomPaint(
|
return CustomPaint(
|
||||||
size: Size.square(widget.size),
|
size: Size.square(widget.size),
|
||||||
painter: _DeltaPainter(pulse: pulse),
|
painter: _DeltaPainter(pulse: widget.animated ? pulse : 1.0),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../data/models.dart';
|
|
||||||
import '../theme/reclaim_tokens.dart';
|
|
||||||
|
|
||||||
/// Freshness badge — flags when a norm version is stale relative to
|
|
||||||
/// the published source (amended upstream) or hasn't been reconciled.
|
|
||||||
/// By design it renders NOTHING for [Freshness.current] (the common
|
|
||||||
/// case shouldn't add noise) unless [alwaysShow] is set. Same chip
|
|
||||||
/// shape as the scope/tier badges.
|
|
||||||
class FreshnessBadge extends StatelessWidget {
|
|
||||||
const FreshnessBadge({
|
|
||||||
super.key,
|
|
||||||
required this.freshness,
|
|
||||||
this.note,
|
|
||||||
this.compact = false,
|
|
||||||
this.alwaysShow = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Freshness freshness;
|
|
||||||
|
|
||||||
/// Optional plain-text hint of what changed (from Norm.supersededNote).
|
|
||||||
final String? note;
|
|
||||||
final bool compact;
|
|
||||||
final bool alwaysShow;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (!freshness.needsBadge && !alwaysShow) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
final t = Theme.of(context).textTheme;
|
|
||||||
final color = switch (freshness) {
|
|
||||||
Freshness.current => ReclaimColors.signal,
|
|
||||||
Freshness.superseded => ReclaimColors.freshSuperseded,
|
|
||||||
Freshness.unknown => ReclaimColors.freshUnknown,
|
|
||||||
};
|
|
||||||
final icon = switch (freshness) {
|
|
||||||
Freshness.current => Icons.check_circle_outline,
|
|
||||||
Freshness.superseded => Icons.history_toggle_off,
|
|
||||||
Freshness.unknown => Icons.help_outline,
|
|
||||||
};
|
|
||||||
final label = compact ? freshness.label : 'Stand: ${freshness.label}';
|
|
||||||
return Tooltip(
|
|
||||||
message: '${freshness.description}'
|
|
||||||
'${note != null && note!.isNotEmpty ? '\n$note' : ''}',
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: compact ? 6 : ReclaimSpace.sm,
|
|
||||||
vertical: compact ? 2 : 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withValues(alpha: 0.18),
|
|
||||||
border: Border.all(color: color.withValues(alpha: 0.55)),
|
|
||||||
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: compact ? 12 : 14, color: color),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: (compact ? t.labelSmall : t.labelLarge)?.copyWith(
|
|
||||||
color: color,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -267,7 +267,6 @@ class _HeatmapPainter extends CustomPainter {
|
||||||
);
|
);
|
||||||
_drawText(
|
_drawText(
|
||||||
canvas,
|
canvas,
|
||||||
'Bereich: ${e.norm.jurisdiction.short} '
|
|
||||||
'Tier: ${e.tierLowest.short} Klick → Detail',
|
'Tier: ${e.tierLowest.short} Klick → Detail',
|
||||||
box.topLeft + const Offset(10, 44),
|
box.topLeft + const Offset(10, 44),
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../data/models.dart';
|
|
||||||
import '../theme/reclaim_tokens.dart';
|
|
||||||
|
|
||||||
/// Scope badge — surfaces a norm's Geltungsbereich (Berlin /
|
|
||||||
/// Deutschland / EU / International) wherever the norm appears, so
|
|
||||||
/// the reader sees at a glance which reform lever a figure speaks
|
|
||||||
/// to. Visually mirrors [TierBadge]: a scope glyph + code in a
|
|
||||||
/// tinted chip, here with a Material icon instead of a dot.
|
|
||||||
class JurisdictionBadge extends StatelessWidget {
|
|
||||||
const JurisdictionBadge({
|
|
||||||
super.key,
|
|
||||||
required this.jurisdiction,
|
|
||||||
this.compact = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Jurisdiction jurisdiction;
|
|
||||||
final bool compact;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = Theme.of(context).textTheme;
|
|
||||||
final color = switch (jurisdiction) {
|
|
||||||
Jurisdiction.berlin => ReclaimColors.scopeBerlin,
|
|
||||||
Jurisdiction.deutschland => ReclaimColors.scopeDeutschland,
|
|
||||||
Jurisdiction.eu => ReclaimColors.scopeEu,
|
|
||||||
Jurisdiction.international => ReclaimColors.scopeInternational,
|
|
||||||
};
|
|
||||||
final icon = switch (jurisdiction) {
|
|
||||||
Jurisdiction.berlin => Icons.location_city_outlined,
|
|
||||||
Jurisdiction.deutschland => Icons.flag_outlined,
|
|
||||||
Jurisdiction.eu => Icons.star_outline_rounded,
|
|
||||||
Jurisdiction.international => Icons.public,
|
|
||||||
};
|
|
||||||
final label = compact
|
|
||||||
? jurisdiction.short
|
|
||||||
: '${jurisdiction.short} · ${jurisdiction.label}';
|
|
||||||
return Tooltip(
|
|
||||||
message: 'Geltungsbereich: ${jurisdiction.label} '
|
|
||||||
'(${jurisdiction.level}).\n${jurisdiction.reformLever}.',
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: compact ? 6 : ReclaimSpace.sm,
|
|
||||||
vertical: compact ? 2 : 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withValues(alpha: 0.18),
|
|
||||||
border: Border.all(color: color.withValues(alpha: 0.55)),
|
|
||||||
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: compact ? 12 : 14, color: color),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: (compact ? t.labelSmall : t.labelLarge)?.copyWith(
|
|
||||||
color: color,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../data/models.dart';
|
|
||||||
import '../theme/reclaim_tokens.dart';
|
|
||||||
|
|
||||||
/// Review badge — marks an evaluation a trusted jurist has confirmed
|
|
||||||
/// (signed off via the hub approval). Jurist review is OPTIONAL: an
|
|
||||||
/// evaluation stands on its own; this only adds the "bestätigt" mark.
|
|
||||||
/// Renders a confirm-green check when reviewed; renders the muted
|
|
||||||
/// "ungeprüft" state only when [showUnreviewed] is set (otherwise
|
|
||||||
/// nothing, to keep unreviewed rows clean).
|
|
||||||
class ReviewBadge extends StatelessWidget {
|
|
||||||
const ReviewBadge({
|
|
||||||
super.key,
|
|
||||||
required this.evaluation,
|
|
||||||
this.compact = false,
|
|
||||||
this.showUnreviewed = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Evaluation evaluation;
|
|
||||||
final bool compact;
|
|
||||||
final bool showUnreviewed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final reviewed = evaluation.isReviewed;
|
|
||||||
if (!reviewed && !showUnreviewed) return const SizedBox.shrink();
|
|
||||||
|
|
||||||
final t = Theme.of(context).textTheme;
|
|
||||||
final color =
|
|
||||||
reviewed ? ReclaimColors.reviewConfirmed : ReclaimColors.mute;
|
|
||||||
final icon = reviewed
|
|
||||||
? Icons.verified_outlined
|
|
||||||
: Icons.radio_button_unchecked;
|
|
||||||
final label = reviewed
|
|
||||||
? (compact ? 'geprüft' : 'Jurist-geprüft')
|
|
||||||
: (compact ? 'offen' : 'ungeprüft');
|
|
||||||
final tip = reviewed
|
|
||||||
? 'Bestätigt von ${evaluation.reviewedBy}'
|
|
||||||
'${evaluation.reviewedAt != null ? ' am ${_d(evaluation.reviewedAt!)}' : ''}.\n'
|
|
||||||
'Jurist-Review ist optional — es bestätigt die Auswertung, '
|
|
||||||
'ist aber keine Voraussetzung.'
|
|
||||||
: 'Noch von keinem Juristen bestätigt. Die Auswertung gilt '
|
|
||||||
'trotzdem (Review ist optional).';
|
|
||||||
return Tooltip(
|
|
||||||
message: tip,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: compact ? 6 : ReclaimSpace.sm,
|
|
||||||
vertical: compact ? 2 : 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withValues(alpha: 0.18),
|
|
||||||
border: Border.all(color: color.withValues(alpha: 0.55)),
|
|
||||||
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: compact ? 12 : 14, color: color),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: (compact ? t.labelSmall : t.labelLarge)?.copyWith(
|
|
||||||
color: color,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _d(DateTime d) =>
|
|
||||||
'${d.day.toString().padLeft(2, '0')}.${d.month.toString().padLeft(2, '0')}.${d.year}';
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
# Vertikaler Durchstich, Vollversion (so weit heute lauffähig).
|
|
||||||
#
|
|
||||||
# Erweitert durchstich-from-file um die Analyse-Module, deren
|
|
||||||
# Eingaben sich aus vorhergehenden Step-Outputs speisen lassen
|
|
||||||
# (alle als $ref) — Pflichten, Zitationsgraph, Lesbarkeit/Frust.
|
|
||||||
# Das flow.completed-Event trägt damit norm + duties + citations
|
|
||||||
# + frust + cohort, also die Form, die HubRepository.list() in der
|
|
||||||
# Recl∆Im-App in Evaluation-Objekte übersetzt.
|
|
||||||
#
|
|
||||||
# NICHT enthalten: econ.skm_score und law.benefit_score. Beide
|
|
||||||
# Module lesen ihren Tupel-/Ratings-Input über `require_json`,
|
|
||||||
# d.h. sie erwarten ein Payload::Json. In einem Flow lässt sich
|
|
||||||
# ein Json-Wert aber nur aus einem vorhergehenden Step-Output
|
|
||||||
# beziehen — `chain run --input name=value` erzeugt Text,
|
|
||||||
# `--input name=@file` erzeugt Bytes, nie Json, und ein Inline-
|
|
||||||
# Map-Literal in `with:` lehnt der Flow-Parser ab ("expected a
|
|
||||||
# string"). Es gibt heute also keinen Weg, skm/benefit aus CLI-
|
|
||||||
# Inputs zu treiben. Siehe Handoff an chain (json-CLI-Input).
|
|
||||||
# Sobald der Plattform-Fix steht, die skm-/benefit-Steps unten
|
|
||||||
# einkommentieren (Form: `with: { duties: $inputs.duties }` mit
|
|
||||||
# `inputs: { duties: json }`).
|
|
||||||
#
|
|
||||||
# Aufruf:
|
|
||||||
# chain run flows/durchstich-full.yaml \
|
|
||||||
# --input content=@/tmp/gewo-14-mini.xml \
|
|
||||||
# --input cohort_id=berlin-kmu
|
|
||||||
# (pausiert am system.approval-Gate — danach via Hub/Studio
|
|
||||||
# freigeben, dann wird das flow.completed-Event geschrieben.)
|
|
||||||
|
|
||||||
name: durchstich-full
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
content: bytes
|
|
||||||
cohort_id: text
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Pending platform json-CLI-input (Handoff → chain). Form, die
|
|
||||||
# nach dem Fix gilt — `inputs:` oben um `duties: json` /
|
|
||||||
# `ratings: json` ergänzen und mit --input duties=… speisen:
|
|
||||||
# - id: skm
|
|
||||||
# use: econ.skm_score@^0
|
|
||||||
# with:
|
|
||||||
# duties: $inputs.duties
|
|
||||||
# - id: benefit
|
|
||||||
# use: law.benefit_score@^0
|
|
||||||
# with:
|
|
||||||
# ratings: $inputs.ratings
|
|
||||||
|
|
||||||
- id: review
|
|
||||||
use: system.approval@^0
|
|
||||||
with:
|
|
||||||
title: "Recl∆Im — Review GewO §14 (Live)"
|
|
||||||
prompt: >-
|
|
||||||
Juristische Freigabe: Stimmen die extrahierten Pflichten, die
|
|
||||||
Zitationen und die Norm-Wiedergabe mit dem geltenden Gesetzestext
|
|
||||||
überein? Quellen siehe Payload. Bei Freigabe gilt die Auswertung
|
|
||||||
als juristisch bestätigt.
|
|
||||||
payload: $normalize.norm
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
norm: $normalize.norm
|
|
||||||
frust: $frust.score
|
|
||||||
cohort: $cohort.cohort
|
|
||||||
duties: $deontic.duties
|
|
||||||
citations: $citations.citations
|
|
||||||
1
store/.gitignore
vendored
1
store/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
bundles/
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# Local module.wasm sha256 (cross-check; canonical sha must come from the hosted .fai bundle)
|
|
||||||
0fd01a7aa7ca2a47aa27bea7b30f8b8fe2ffc551728694c5dfbadb75bc37e91d text-akoma-normalize
|
|
||||||
722da24ebc239015af344b66a438097ef607b965badb43f91e43c4b1a9fdcc19 text-deontic-extract
|
|
||||||
3f4976db72bd7410e83b0694de3e91c09b8829d3d73fa48ddf806c170cc5c602 graph-citation-extract
|
|
||||||
ba95c610cec71a694f0c33002de836e61c1bd9b9767d014e842e083cd3eead2c text-readability-score
|
|
||||||
bb722a4416e26cc58cf3d01e5f9aaf27c61f9fa2cb86983662ee16f0c68945d9 stats-cohort-size
|
|
||||||
aefa3588c33e6977ce26d4f885d4fd75ea46ee6f479ecf1e57c1ebd33b6872a1 graph-shapley-attribution
|
|
||||||
3b63c8ae21d82da0fb47e160889fa93b34f132947c461e5c199936207a2cb6a9 econ-skm-score
|
|
||||||
1275295ec9d61cccaaeb418a580637dc6ca2d8e6b5aa25937d9471b1f534cad9 law-benefit-score
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# .fai bundle sha256 (unsigned, chain pack 0.17.2) — 2026-06-19T00:12Z
|
|
||||||
15fa6d43bf6d18193282cb1c94c273b958e2639d91d52120eaedd83a99571765 text-akoma-normalize-0.1.0.fai
|
|
||||||
d03d3dc2bfbaa413ea4e192c6147595e21d8dafd24b5d32b853a03a38e48100d text-deontic-extract-0.1.0.fai
|
|
||||||
9bebd3dc877da9e3c89b521572000f14f007c6a9edfcc350edd44377d68acba3 graph-citation-extract-0.1.0.fai
|
|
||||||
5f1a0098bc4c3be8d20b34d848af884b539b0eda3c59ab56dfe99bf8f811090d text-readability-score-0.1.0.fai
|
|
||||||
31d35f6a7b61b9249520e3ae02fa398292375555430714b04ff0771fd2f3ad99 stats-cohort-size-0.1.0.fai
|
|
||||||
199927a6b8594da579a22d42b1ba438f4b2b6c90132a5c097c7c1d5eb8b9c319 graph-shapley-attribution-0.1.0.fai
|
|
||||||
2b887970d448c07cc53633124518d6f96672b0710d9e89fe4f9d6a236035f25e econ-skm-score-0.1.0.fai
|
|
||||||
4a82a07ba72cae4b49f1ff883b1ca92638e5f4ea19bc2b070288142056764025 law-benefit-score-0.1.0.fai
|
|
||||||
167
store/store.yaml
167
store/store.yaml
|
|
@ -1,167 +0,0 @@
|
||||||
# Recl∆Im module store index — source of truth before publish.
|
|
||||||
#
|
|
||||||
# Target host (chain Studio's one-click "Recl∆Im hinzufügen" button
|
|
||||||
# points here): https://releases.chain.flemming.ai/reclaim/store.yaml
|
|
||||||
#
|
|
||||||
# STATUS (2026-06-19). Per chain: publish UNSIGNED for now —
|
|
||||||
# `require_signatures` is off by default; FAI_SIGNING_KEY untouched;
|
|
||||||
# a Recl∆Im-own signing key + per-store pinning come later.
|
|
||||||
# * sha256 — REAL, from local `chain pack` of each module.
|
|
||||||
# Bundles staged in store/bundles/, hashes mirrored
|
|
||||||
# in store/SHA256.txt. Bundles are UNSIGNED.
|
|
||||||
# * signature — intentionally absent for now (see above).
|
|
||||||
# * wasm_url — points at the intended mirror path. The ONLY
|
|
||||||
# remaining step is HOSTING the 8 .fai + this
|
|
||||||
# store.yaml at releases.chain.flemming.ai/reclaim/
|
|
||||||
# — the mirror push is chain infra (scp to the
|
|
||||||
# Hetzner host); reclaim has no SSH access. Confirm
|
|
||||||
# the canonical wasm_url host (mirror vs Forgejo
|
|
||||||
# release) with chain.
|
|
||||||
#
|
|
||||||
# NAMING NOTE for chain: every module's `provides.capability` uses an
|
|
||||||
# UNDERSCORE (`econ.skm_score`), but this index + the Studio button use
|
|
||||||
# the DASH form (`econ.skm-score`) per the handoff. install resolution
|
|
||||||
# must map dash↔underscore, or the modules must be renamed. Flagging —
|
|
||||||
# this is a chain naming-convention decision.
|
|
||||||
|
|
||||||
schema_version: 1
|
|
||||||
|
|
||||||
modules:
|
|
||||||
- name: text.akoma-normalize
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/text-akoma-normalize/v0.1.0/text-akoma-normalize-0.1.0.fai
|
|
||||||
sha256: 15fa6d43bf6d18193282cb1c94c273b958e2639d91d52120eaedd83a99571765
|
|
||||||
tagline:
|
|
||||||
en: "Normalize legal documents to Akoma Ntoso"
|
|
||||||
de: "Rechtsdokumente nach Akoma Ntoso normalisieren"
|
|
||||||
description:
|
|
||||||
en: "Normalises heterogeneous legal sources (gesetze-im-internet XML, EUR-Lex FORMEX, landesrecht-berlin HTML) into a structured Akoma Ntoso 1.0 (OASIS LegalDocML) representation."
|
|
||||||
de: "Normalisiert heterogene Rechtsquellen (gesetze-im-internet-XML, EUR-Lex-FORMEX, landesrecht-berlin-HTML) in eine strukturierte Akoma-Ntoso-1.0-Darstellung (OASIS LegalDocML)."
|
|
||||||
category: text-processing
|
|
||||||
tags: [legal, german, akoma-ntoso, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/text-akoma-normalize
|
|
||||||
|
|
||||||
- name: text.deontic-extract
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/text-deontic-extract/v0.1.0/text-deontic-extract-0.1.0.fai
|
|
||||||
sha256: d03d3dc2bfbaa413ea4e192c6147595e21d8dafd24b5d32b853a03a38e48100d
|
|
||||||
tagline:
|
|
||||||
en: "Extract duties, prohibitions and permissions from norms"
|
|
||||||
de: "Pflichten, Verbote und Erlaubnisse aus Normen extrahieren"
|
|
||||||
description:
|
|
||||||
en: "Extracts atomic deontic statements (obligation / prohibition / permission) from a normalised norm. Useful far beyond law — contracts, ISO standards, internal policies."
|
|
||||||
de: "Extrahiert atomare deontische Aussagen (Pflicht / Verbot / Erlaubnis) aus einer normalisierten Norm. Auch jenseits des Rechts nützlich — Verträge, ISO-Normen, interne Richtlinien."
|
|
||||||
category: text-processing
|
|
||||||
tags: [legal, german, nlp, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/text-deontic-extract
|
|
||||||
|
|
||||||
- name: graph.citation-extract
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/graph-citation-extract/v0.1.0/graph-citation-extract-0.1.0.fai
|
|
||||||
sha256: 9bebd3dc877da9e3c89b521572000f14f007c6a9edfcc350edd44377d68acba3
|
|
||||||
tagline:
|
|
||||||
en: "Build typed citation graphs from legal text"
|
|
||||||
de: "Typisierte Zitationsgraphen aus Rechtstext bauen"
|
|
||||||
description:
|
|
||||||
en: "Extracts typed citations (cites, verweist_iVm, …) from German legal text per the Coupette 2021 schema. Citation graphs also fit scientific papers, patents and case law."
|
|
||||||
de: "Extrahiert typisierte Zitationen (cites, verweist_iVm, …) aus deutschem Rechtstext nach dem Coupette-2021-Schema. Zitationsgraphen passen auch zu Papers, Patenten und Rechtsprechung."
|
|
||||||
category: data
|
|
||||||
tags: [legal, german, graph, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/graph-citation-extract
|
|
||||||
|
|
||||||
- name: text.readability-score
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/text-readability-score/v0.1.0/text-readability-score-0.1.0.fai
|
|
||||||
sha256: 5f1a0098bc4c3be8d20b34d848af884b539b0eda3c59ab56dfe99bf8f811090d
|
|
||||||
tagline:
|
|
||||||
en: "Composite readability and complexity score"
|
|
||||||
de: "Komposit-Score für Lesbarkeit und Komplexität"
|
|
||||||
description:
|
|
||||||
en: "Composite readability score for any text — DE-adapted Flesch + reference depth + verb distance. Flags hard-to-read government letters, terms & conditions, abstracts."
|
|
||||||
de: "Komposit-Lesbarkeitsscore für beliebige Texte — DE-adaptierter Flesch + Verweistiefe + Verbdistanz. Markiert schwer lesbare Behördenbriefe, AGB und Abstracts."
|
|
||||||
category: text-processing
|
|
||||||
tags: [german, readability, analytics, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/text-readability-score
|
|
||||||
|
|
||||||
- name: stats.cohort-size
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/stats-cohort-size/v0.1.0/stats-cohort-size-0.1.0.fai
|
|
||||||
sha256: 31d35f6a7b61b9249520e3ae02fa398292375555430714b04ff0771fd2f3ad99
|
|
||||||
tagline:
|
|
||||||
en: "Resolve addressee cohort sizes"
|
|
||||||
de: "Größe von Adressaten-Kohorten ermitteln"
|
|
||||||
description:
|
|
||||||
en: "Resolves the size of an addressee cohort (e.g. 'Berlin SMEs by WZ code'). v0.1 ships a stub table for the pilot cohorts; v0.2 adds a DESTATIS GENESIS-API adapter."
|
|
||||||
de: "Ermittelt die Größe einer Adressaten-Kohorte (z. B. 'Berliner KMU nach WZ-Code'). v0.1 mit Stub-Tabelle für die Pilot-Kohorten; v0.2 mit DESTATIS-GENESIS-API-Adapter."
|
|
||||||
category: data
|
|
||||||
tags: [statistics, german, government, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/stats-cohort-size
|
|
||||||
|
|
||||||
- name: graph.shapley-attribution
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/graph-shapley-attribution/v0.1.0/graph-shapley-attribution-0.1.0.fai
|
|
||||||
sha256: 199927a6b8594da579a22d42b1ba438f4b2b6c90132a5c097c7c1d5eb8b9c319
|
|
||||||
tagline:
|
|
||||||
en: "Shapley-style attribution on weighted graphs"
|
|
||||||
de: "Shapley-Attribution auf gewichteten Graphen"
|
|
||||||
description:
|
|
||||||
en: "Splits a target node's blame or credit among its ancestors on a weighted norm/citation graph. v0.1 proportional fallback; v0.2 full Shapley value for ≤32-node neighbourhoods."
|
|
||||||
de: "Verteilt 'Schuld' oder 'Verdienst' eines Zielknotens auf seine Vorgänger in einem gewichteten Norm-/Zitationsgraphen. v0.1 proportional; v0.2 voller Shapley-Wert für ≤32-Knoten-Nachbarschaften."
|
|
||||||
category: data
|
|
||||||
tags: [graph, analytics, attribution, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/graph-shapley-attribution
|
|
||||||
|
|
||||||
- name: econ.skm-score
|
|
||||||
versions:
|
|
||||||
- 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: 2b887970d448c07cc53633124518d6f96672b0710d9e89fe4f9d6a236035f25e
|
|
||||||
tagline:
|
|
||||||
en: "Standard Cost Model (SKM) bureaucracy cost"
|
|
||||||
de: "Standardkostenmodell (SKM) — Bürokratiekosten"
|
|
||||||
description:
|
|
||||||
en: "Standard Cost Model — P×F×T×h per duty, summed across a duty list (German NKR method). The EU- and OECD-recognised methodology for bureaucracy-cost measurement, used by NL ATR, UK RPC, EU REFIT."
|
|
||||||
de: "Standardkostenmodell — P×F×T×h je Pflicht, summiert über eine Pflichtenliste (NKR-Methode). EU- und OECD-anerkannte Methodik zur Bürokratiekostenmessung, genutzt von NL ATR, UK RPC, EU REFIT."
|
|
||||||
category: data
|
|
||||||
tags: [economics, legal, government, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/econ-skm-score
|
|
||||||
|
|
||||||
- name: law.benefit-score
|
|
||||||
versions:
|
|
||||||
- version: 0.1.0
|
|
||||||
status: published
|
|
||||||
wasm_url: https://releases.chain.flemming.ai/reclaim/law-benefit-score/v0.1.0/law-benefit-score-0.1.0.fai
|
|
||||||
sha256: 4a82a07ba72cae4b49f1ff883b1ca92638e5f4ea19bc2b070288142056764025
|
|
||||||
tagline:
|
|
||||||
en: "7-dimensional regulatory benefit score"
|
|
||||||
de: "7-dimensionaler Nutzen-Score für Regulierung"
|
|
||||||
description:
|
|
||||||
en: "Seven-dimensional benefit score (protection / market / legal-certainty / EU-single-market / safety / environment / revenue), each 0–5. Domain-bound: the dimensions are legally-politically conventioned."
|
|
||||||
de: "Siebendimensionaler Nutzen-Score (Schutz / Markt / Rechtssicherheit / EU-Binnenmarkt / Sicherheit / Umwelt / Einnahmen), je 0–5. Domänengebunden: die Dimensionen sind juristisch-politisch konventioniert."
|
|
||||||
category: data
|
|
||||||
tags: [legal, german, policy, offline-capable]
|
|
||||||
license: Apache-2.0
|
|
||||||
repository: https://git.flemming.ai/chain-modules/law-benefit-score
|
|
||||||
|
|
||||||
services: []
|
|
||||||
flows: []
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue