kommunalassist-core 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. kommunalassist_core-0.5.0/.github/workflows/ci.yml +53 -0
  2. kommunalassist_core-0.5.0/.github/workflows/publish-pypi.yml +168 -0
  3. kommunalassist_core-0.5.0/.gitignore +6 -0
  4. kommunalassist_core-0.5.0/CHANGELOG.md +129 -0
  5. kommunalassist_core-0.5.0/LICENSE +16 -0
  6. kommunalassist_core-0.5.0/PKG-INFO +124 -0
  7. kommunalassist_core-0.5.0/README.md +107 -0
  8. kommunalassist_core-0.5.0/assist_core/__init__.py +12 -0
  9. kommunalassist_core-0.5.0/assist_core/adapters.py +67 -0
  10. kommunalassist_core-0.5.0/assist_core/apps.py +13 -0
  11. kommunalassist_core-0.5.0/assist_core/dto.py +130 -0
  12. kommunalassist_core-0.5.0/assist_core/engine.py +556 -0
  13. kommunalassist_core-0.5.0/assist_core/management/__init__.py +0 -0
  14. kommunalassist_core-0.5.0/assist_core/management/commands/__init__.py +0 -0
  15. kommunalassist_core-0.5.0/assist_core/management/commands/assist_rls.py +288 -0
  16. kommunalassist_core-0.5.0/assist_core/management/commands/seed_feiertage.py +71 -0
  17. kommunalassist_core-0.5.0/assist_core/mandant.py +89 -0
  18. kommunalassist_core-0.5.0/assist_core/migrations/0001_initial.py +87 -0
  19. kommunalassist_core-0.5.0/assist_core/migrations/0002_tenancy_registries_feiertage.py +206 -0
  20. kommunalassist_core-0.5.0/assist_core/migrations/__init__.py +0 -0
  21. kommunalassist_core-0.5.0/assist_core/models.py +621 -0
  22. kommunalassist_core-0.5.0/assist_core/ports/__init__.py +45 -0
  23. kommunalassist_core-0.5.0/assist_core/ports/base.py +107 -0
  24. kommunalassist_core-0.5.0/assist_core/regelkatalog/__init__.py +149 -0
  25. kommunalassist_core-0.5.0/assist_core/registry.py +34 -0
  26. kommunalassist_core-0.5.0/assist_core/tenancy.py +181 -0
  27. kommunalassist_core-0.5.0/docs/adr/ADR-001-app-label-und-tabellennamen.md +75 -0
  28. kommunalassist_core-0.5.0/pyproject.toml +40 -0
  29. kommunalassist_core-0.5.0/tests/__init__.py +0 -0
  30. kommunalassist_core-0.5.0/tests/conftest.py +8 -0
  31. kommunalassist_core-0.5.0/tests/settings.py +44 -0
  32. kommunalassist_core-0.5.0/tests/test_engine_umzug.py +341 -0
  33. kommunalassist_core-0.5.0/tests/test_feiertage.py +86 -0
  34. kommunalassist_core-0.5.0/tests/test_ports.py +53 -0
  35. kommunalassist_core-0.5.0/tests/test_registry.py +115 -0
  36. kommunalassist_core-0.5.0/tests/test_rls.py +208 -0
  37. kommunalassist_core-0.5.0/tests/test_seed_tenant.py +100 -0
  38. kommunalassist_core-0.5.0/tests/test_serie.py +107 -0
  39. kommunalassist_core-0.5.0/tests/test_tenancy.py +171 -0
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ # Test-Gate fuer jeden PR + main-Push: Ruff, Tests und makemigrations --check
4
+ # gegen Postgres 16, damit ein Verpackungs-/Migrations-Fehler hier auffaellt
5
+ # statt erst beim Adopter (vgl. publish-pypi.yml, dieselbe Postgres-Konvention).
6
+
7
+ on:
8
+ pull_request:
9
+ push:
10
+ branches: [main]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ test:
17
+ name: Ruff, Tests, makemigrations --check
18
+ runs-on: ubuntu-latest
19
+ services:
20
+ postgres:
21
+ image: postgres:16-alpine
22
+ env:
23
+ POSTGRES_USER: test_user
24
+ POSTGRES_PASSWORD: test_pass
25
+ POSTGRES_DB: test_db
26
+ ports:
27
+ - 5432:5432
28
+ options: >-
29
+ --health-cmd pg_isready
30
+ --health-interval 10s
31
+ --health-timeout 5s
32
+ --health-retries 5
33
+ env:
34
+ POSTGRES_HOST: 127.0.0.1
35
+ POSTGRES_PORT: "5432"
36
+ POSTGRES_DB: test_db
37
+ POSTGRES_USER: test_user
38
+ POSTGRES_PASSWORD: test_pass
39
+ steps:
40
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
41
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
42
+ with:
43
+ python-version: '3.12'
44
+ - name: Install package + dev deps
45
+ run: pip install -e ".[dev]" 'psycopg[binary]'
46
+ - name: ruff check
47
+ run: ruff check .
48
+ - name: ruff format --check
49
+ run: ruff format --check .
50
+ - name: pytest gegen Postgres
51
+ run: pytest -q
52
+ - name: makemigrations --check (keine fehlenden Migrationen)
53
+ run: python -m django makemigrations --check --settings=tests.settings
@@ -0,0 +1,168 @@
1
+ name: Publish kommunalassist-core to PyPI
2
+
3
+ # Triggert auf Git-Tags v*.*.*. Trusted Publishing (OIDC), kein API-Token-Secret.
4
+ # Setup einmalig: pypi.org/manage/account/publishing/ → GitHub-Trust-Publisher
5
+ # Owner: iilgmbh
6
+ # Repository: iil-assist-core # Repo-Name, NICHT der Projektname
7
+ # Workflow filename: publish-pypi.yml
8
+ # Environment name: pypi
9
+
10
+ on:
11
+ push:
12
+ tags:
13
+ - 'v*.*.*'
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ build:
20
+ name: Build distribution
21
+ runs-on: ubuntu-latest
22
+ services:
23
+ postgres:
24
+ image: postgres:16
25
+ env:
26
+ POSTGRES_USER: t
27
+ POSTGRES_PASSWORD: t
28
+ POSTGRES_DB: t
29
+ ports:
30
+ - 5432:5432
31
+ options: >-
32
+ --health-cmd pg_isready
33
+ --health-interval 10s
34
+ --health-timeout 5s
35
+ --health-retries 5
36
+ steps:
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
38
+ with:
39
+ fetch-depth: 0
40
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
41
+ with:
42
+ python-version: '3.12'
43
+ - name: Verify version matches tag
44
+ run: |
45
+ PKG_VERSION=$(grep '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "([^"]+)".*/\1/')
46
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
47
+ echo "pyproject.toml: $PKG_VERSION"
48
+ echo "git tag: $TAG_VERSION"
49
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
50
+ echo "❌ Version-Mismatch"; exit 1
51
+ fi
52
+ - name: Install build tools
53
+ run: pip install --upgrade build
54
+ - name: Build wheel + sdist
55
+ run: python -m build
56
+ - name: Smoke test (gegen gebautes Wheel, nicht den Quellbaum)
57
+ run: |
58
+ # Gegen das WHEEL testen, nicht gegen src/: ein Verpackungsfehler faellt
59
+ # sonst erst beim Adopter auf. Das Muster stammt aus iil-klickdummy; dort
60
+ # prueft es zusaetzlich mitgelieferte Schema-Dateien. Dieses Paket hat
61
+ # keine package-data, deshalb bleibt der Importtest.
62
+ #
63
+ # ACHTUNG bei Uebernahme: Paketname (Bindestrich) und Modulname
64
+ # (Unterstrich) sind zwei verschiedene Zeichenketten. Genau daran ist der
65
+ # erste Lauf gescheitert -- `iil-klickdummy` war ersetzt, `iil_klickdummy`
66
+ # nicht.
67
+ pip install dist/*.whl 'psycopg[binary]>=3.2'
68
+ # ALLE Module importieren, nicht nur das Top-Level. Bis 0.1.2 zog
69
+ # ports/base.py `apps.adapters.dto` aus dem Herkunfts-Repo -- das Paket
70
+ # war ausserhalb von frist-hub gar nicht importierbar, und `import
71
+ # assist_core` allein hat das nicht gesehen.
72
+ python -c "
73
+ import importlib, django
74
+ from django.conf import settings
75
+ settings.configure(
76
+ INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth','assist_core'],
77
+ DATABASES={'default':{'ENGINE':'django.db.backends.postgresql','NAME':'t',
78
+ 'USER':'t','PASSWORD':'t','HOST':'127.0.0.1','PORT':'5432'}},
79
+ USE_TZ=True, DEFAULT_AUTO_FIELD='django.db.models.BigAutoField')
80
+ django.setup()
81
+ import assist_core
82
+ for m in ('assist_core.models','assist_core.mandant','assist_core.dto',
83
+ 'assist_core.ports','assist_core.regelkatalog'):
84
+ importlib.import_module(m)
85
+ print(' OK', m)
86
+ print('OK: alle Module importierbar, Version', assist_core.__version__)
87
+ "
88
+
89
+ # Ein Importtest sagt NICHT, ob die App benutzbar ist. Version 0.1.0 ging
90
+ # so durch, obwohl migrations/ nur __init__.py enthielt -- installierbar,
91
+ # aber ohne Tabellen. Gegen POSTGRES, nicht SQLite: Regelfreigabe fuehrt
92
+ # zwei JSONField, und SQLite ist bei jsonb und UniqueConstraint mit NULL
93
+ # nachsichtiger als die Zieldatenbank.
94
+ - name: migrate gegen Postgres (Wheel muss seine Tabellen anlegen koennen)
95
+ env:
96
+ PGHOST: 127.0.0.1
97
+ PGPORT: 5432
98
+ run: |
99
+ python -c "
100
+ import django
101
+ from django.conf import settings
102
+ settings.configure(
103
+ INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth','assist_core'],
104
+ DATABASES={'default':{'ENGINE':'django.db.backends.postgresql','NAME':'t',
105
+ 'USER':'t','PASSWORD':'t','HOST':'127.0.0.1','PORT':'5432'}},
106
+ USE_TZ=True, DEFAULT_AUTO_FIELD='django.db.models.BigAutoField')
107
+ django.setup()
108
+ from django.core.management import call_command
109
+ call_command('migrate', 'assist_core', verbosity=1)
110
+ from django.db import connection
111
+ with connection.cursor() as c:
112
+ c.execute(\"SELECT table_name FROM information_schema.tables WHERE table_schema='public'\")
113
+ tab = sorted(r[0] for r in c.fetchall() if not r[0].startswith('django_'))
114
+ assert tab, 'migrate hat keine Tabellen angelegt'
115
+ fremd = [t for t in tab if not t.startswith('assist_core_')]
116
+ assert not fremd, f'Fremde Tabellennamen im Paket: {fremd} (ADR-001)'
117
+ print('OK: Tabellen angelegt und korrekt benannt:', tab)
118
+ "
119
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
120
+ with:
121
+ name: dist
122
+ path: dist/
123
+
124
+ publish:
125
+ name: Publish to PyPI (Trusted Publishing / OIDC)
126
+ needs: build
127
+ runs-on: ubuntu-latest
128
+ environment: pypi
129
+ permissions:
130
+ id-token: write
131
+ steps:
132
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
133
+ with:
134
+ name: dist
135
+ path: dist/
136
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
137
+ with:
138
+ packages-dir: dist/
139
+
140
+ github-release:
141
+ name: GitHub Release
142
+ needs: publish
143
+ runs-on: ubuntu-latest
144
+ permissions:
145
+ contents: write
146
+ steps:
147
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
148
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
149
+ with:
150
+ name: dist
151
+ path: dist/
152
+ - uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
153
+ with:
154
+ name: "kommunalassist-core ${{ github.ref_name }}"
155
+ body: |
156
+ **kommunalassist-core** Release `${{ github.ref_name }}`.
157
+
158
+ Install:
159
+ ```
160
+ pip install "kommunalassist-core>=${{ github.ref_name }}"
161
+ ```
162
+
163
+ Grundlage: `meiki:ADR-044` (Paket-Topologie) ·
164
+ `meiki:ADR-022` v1.1 (Termin-Engine im Kern) ·
165
+ `meiki:ADR-043` (Produkt- und Verwertungsschnitt)
166
+ files: dist/*
167
+ draft: false
168
+ prerelease: false
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .venv/
@@ -0,0 +1,129 @@
1
+ # Changelog
2
+
3
+ Alle nennenswerten Änderungen an `kommunalassist-core` (bis 0.4.0 `iil-assist-core`).
4
+
5
+ Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/),
6
+ Versionierung nach [Semantic Versioning](https://semver.org/lang/de/).
7
+
8
+ ## [0.5.0] — 2026-09-10
9
+
10
+ **Nur der Verteilname aendert sich: `iil-assist-core` → `kommunalassist-core`.**
11
+ Am Code aendert sich nichts.
12
+
13
+ Der bisherige Name trug den des Anbieters, obwohl das Produkt in einer
14
+ Kommunalverwaltung laeuft, und er kollidierte mit `iil-assist` — so heisst seit
15
+ 2026-09-10 auch der Plattform-Mechanismus aus `platform:KONZ-platform-058`. Der
16
+ neue Name nennt den Markt statt des Anbieters, des einzelnen Hauses oder des
17
+ Projekts MEiKI, das endet (Owner-Entscheid 2026-09-10, Variante V1 zu
18
+ [meiki-hub#388](https://github.com/meiki-lra/meiki-hub/issues/388)).
19
+
20
+ **Was ein Adopter tun muss:** eine Zeile in `requirements.txt`. Der Importname
21
+ bleibt `assist_core`, App-Label und Tabellennamen (`assist_core_*`) bleiben
22
+ ebenfalls — dort steht kein Anbietername, und eine Umbenennung haette
23
+ Migrationen gekostet, ohne etwas zu loesen.
24
+
25
+ **Was mit `iil-assist-core` passiert:** Die Fassungen bis 0.4.0 bleiben auf PyPI
26
+ stehen. Sie zurueckzuziehen wuerde nur bestehende Installationen brechen; neue
27
+ Arbeit laeuft unter dem neuen Namen.
28
+
29
+ ## [0.4.0] — 2026-09-10
30
+
31
+ Tenancy-Basis, RLS-Erzeuger und Registries (KONZ-meiki-009 U3,
32
+ iilgmbh/iil-assist-core#6 und #5).
33
+
34
+ ### Added
35
+
36
+ - `assist_core.tenancy` — `TenantModel`/`TenantManager` fail-closed nach
37
+ platform:ADR-269: ohne aktiven Mandanten liefert der Manager `none()`.
38
+ Cross-Tenant gibt es nur über `all_tenants(grund=...)`, und der Zugriff landet
39
+ in der neuen append-only Tabelle `TenantAuditEintrag`. `tenant_context()` setzt
40
+ denselben Mandanten als Postgres-GUC `app.tenant_id` — **transaktionslokal**
41
+ (`set_config(..., true)`), nicht sessionweit.
42
+ - `manage.py assist_rls --apply|--check [--app-rolle <name>]` — Policy je Tabelle
43
+ mit Mandanten-Spalte, `USING (spalte = NULLIF(current_setting('app.tenant_id',
44
+ true), '')::<cast>)` **ohne** OR-Ausweich-Zweige, plus `FORCE ROW LEVEL
45
+ SECURITY` und `DROP POLICY IF EXISTS` (idempotent). Der Cast kommt aus dem
46
+ Django-Feldtyp (bigint/uuid). `--check` bricht mit Exit ≠ 0 ab, wenn eine
47
+ Policy fehlt oder einen aufmachenden Zweig trägt; `--app-rolle` setzt
48
+ `NOBYPASSRLS` und entzieht der App-Rolle `UPDATE`/`DELETE` auf den
49
+ Audit-Tabellen — append-only wird Rechtelage statt Python-Konvention.
50
+ - Registries als Tabellen: `Verfahren`, `Quellsystem`, `AdapterBindung`
51
+ (je Mandant, unique über `(tenant_id, kennung)` bzw. `(tenant_id, port)`) und
52
+ `Feiertagskalender` (Profil je Kennung, unique über `(kennung, jahr, datum)`).
53
+ Migration `0002_tenancy_registries_feiertage`.
54
+ - `manage.py seed_feiertage --kennung BY --jahr 2026` — Startbestand aus
55
+ `engine.feiertage_bayern_benannt()` als **Daten**, keine YAML im Paket.
56
+ - `assist_core.registry.pruefe_verfahren/pruefe_quellsystem` — Validierung an der
57
+ Grenze gegen die Registry-Tabelle des Mandanten, mit dokumentiertem Fallback
58
+ auf die `dto`-Enums, solange der Mandant keine Zeile gepflegt hat.
59
+ - `assist_core.adapters.resolve(tenant_id, port)` — löst die `AdapterBindung`
60
+ über `importlib` auf, aber nur gegen `settings.ASSIST_ADAPTER_ALLOWLIST`
61
+ (Default leer). Der Kern liefert weiterhin keinen Adapter mit.
62
+ - `engine.feiertage_fuer(tenant_id, jahr)` und `berechne_frist(...,
63
+ feiertage=...)` — der Mandanten-Kalender geht als Menge in die Rechnung.
64
+ `MandantEinstellung.feiertagskalender` (Default `"BY"`) sagt, welches Profil.
65
+
66
+ ### Changed
67
+
68
+ - `regelkatalog.seed_entwuerfe(verfahren, pfad=None, tenant_id=None)` setzt
69
+ `tenant_scope_id` **beim Anlegen** und prüft Idempotenz je Mandant (#5). Der
70
+ Host-Workaround (`update()` nach dem Seed) entfällt: er machte die Entwürfe
71
+ kurzzeitig global sichtbar und ließ die Idempotenz-Prüfung eine vorhandene
72
+ globale Zelle für die Haus-Zelle halten.
73
+ - `dto.Verfahren`/`dto.Quellsystem` bleiben als `StrEnum` erhalten, sind aber
74
+ nicht mehr der **Typ** der DTO-Felder — `InboundEvent.quellsystem`,
75
+ `InboundEvent.verfahren_hinweis` und `Vorgang.verfahren` sind jetzt `str`.
76
+ Weil `StrEnum`-Werte `str` sind, brechen bestehende Aufrufer nicht.
77
+ - `ist_werktag()`/`naechster_werktag()` nehmen optional eine Feiertagsmenge;
78
+ ohne Angabe rechnen sie wie bisher mit dem BY-Kalender.
79
+ - README: die bayerischen Feiertage sind nicht mehr als „Produktkonstante nach
80
+ meiki:ADR-043" beschrieben, sondern als Profil je Kennung (KONZ-009 § 3.2).
81
+
82
+ ### Hinweis für Konsumenten
83
+
84
+ - `tenant_id` bleibt `BigIntegerField` (platform:ADR-109 Fix B-1, meiki:ADR-025).
85
+ Der Vorschlag, in der ganzen Familie auf UUID zu wechseln (KONZ-meiki-009
86
+ § 4.1, D-2), ist eine offene Owner-Entscheidung (meiki-hub#390) und wird hier
87
+ **nicht** vorweggenommen. Der Policy-Generator liest den Cast aus dem Feldtyp,
88
+ damit ein späterer Wechsel eine Feldmigration bleibt.
89
+ - Wer die DB-Seite will, muss `tenant_context()` **innerhalb** von
90
+ `transaction.atomic()` betreten. Ohne offene Transaktion wird der GUC bewusst
91
+ nicht gesetzt (ein `set_config(..., true)` im Autocommit gälte nur für eine
92
+ Anweisung) und der Kontextmanager protokolliert eine Warnung.
93
+ - Eine Katalog-Zeile mit `aenderbar_durch: niemand` lässt sich nicht
94
+ mandantengebunden seeden — `Regelfreigabe.clean()` weist das ab (§ 8.2).
95
+ Solche Kataloge werden ohne `tenant_id` importiert.
96
+ - `MandantEinstellung` behält den Standard-Manager (sie wird gelesen, bevor ein
97
+ Mandanten-Kontext existiert), bekommt aber eine RLS-Policy.
98
+
99
+ ## [0.3.0] — 2026-09-10
100
+
101
+ ### Added
102
+
103
+ - `assist_core.engine` — die Termin-Engine (`berechne_frist`, Feiertags-/
104
+ Werktagslogik für Bayern, Regel-Resolver) zieht aus `frist-hub` in den Kern
105
+ (KONZ-meiki-009 U1, B1). Signaturen unverändert, keine Verhaltensänderung.
106
+ - `assist_core.engine.serie()` — neue Serien-Berechnung (monatlich/quartals-
107
+ weise/jährlich) für StatistikAssist-Meldetermine, inkl. Werktags-Verschiebung.
108
+ - `assist_core.ports.base` — die fünf Ingress-Ports (`IdentityPort`,
109
+ `FachverfahrenPort`, `StatusPort`, `DmsPort`, `ClassificationPort`) ziehen aus
110
+ `frist-hub` in den Kern (KONZ-meiki-009 U1, B2), unverändert übernommen.
111
+
112
+ ### Changed
113
+
114
+ - `assist_core.ports.base`-Docstring korrigiert: die Berechnung macht die
115
+ Engine im Kern (`assist_core.engine`), nicht mehr „der konsumierende Assist".
116
+
117
+ ### Hinweis für Konsumenten
118
+
119
+ `frist-hub` muss ab `0.3.0` `berechne_frist` und die übrigen Engine-Funktionen
120
+ aus `assist_core.engine` importieren statt aus `apps.fristen.services`, und die
121
+ fünf Ports aus `assist_core.ports` statt aus `apps.adapters.ports`.
122
+
123
+ ## [0.2.0]
124
+
125
+ Fachlogik raus, DTOs rein — das Paket wurde eigenständig (siehe Git-Historie).
126
+
127
+ ## [0.1.0]
128
+
129
+ Erstbefüllung aus `frist-hub` (meiki-lra/frist-hub#142, M3).
@@ -0,0 +1,16 @@
1
+ Proprietäre Lizenz — IIL GmbH
2
+
3
+ Copyright (c) 2026 IIL GmbH. Alle Rechte vorbehalten.
4
+
5
+ Dieses Paket ist eine wiederverwendbare Komponente im Sinne von meiki:ADR-043.
6
+ Die Rechte daran verbleiben bei der IIL GmbH.
7
+
8
+ Den Partnern der Kooperationsvereinbarung MEiKI (Landkreis Traunstein,
9
+ Hochschule Neu-Ulm, Technische Hochschule Rosenheim, Landkreis Günzburg) wird
10
+ ein nicht ausschließliches, nicht übertragbares und unentgeltliches
11
+ Nutzungsrecht für Zweck und Dauer des Verbundprojekts eingeräumt, nach dessen
12
+ Ende ein ebensolches für eigene wissenschaftliche Zwecke in Forschung und Lehre.
13
+ Weitergehende Nutzungsrechte werden zu marktüblichen Bedingungen eingeräumt.
14
+
15
+ Maßgeblich ist die Vereinbarung zwischen IIL GmbH und der Hochschule Neu-Ulm
16
+ nach § 7 Abs. 1 der Kooperationsvereinbarung MEiKI.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.5
2
+ Name: kommunalassist-core
3
+ Version: 0.5.0
4
+ Summary: Kern der Kommunalassist-Familie: Mandant, Termin-Engine (BRMS-Regelwerk mit Freigabe-Workflow), Adapter-Ports
5
+ Author-email: Achim Dehnert <achim.dehnert@iil.gmbh>
6
+ License-Expression: LicenseRef-Proprietary
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: django<6.0,>=5.0
10
+ Requires-Dist: pyyaml>=6.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: import-linter>=2.0; extra == 'dev'
13
+ Requires-Dist: pytest; extra == 'dev'
14
+ Requires-Dist: pytest-django; extra == 'dev'
15
+ Requires-Dist: ruff; extra == 'dev'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # kommunalassist-core
19
+
20
+ Der gemeinsame Kern der **Kommunalassist**-Familie: **Mandant**, **Termin-Engine**
21
+ (Regelwerk mit Freigabe-Workflow) und die **Ports** zu Fachverfahren und DMS.
22
+
23
+ > **Namenswechsel 2026-09-10.** Das Paket hiess bis 0.4.0 `iil-assist-core`. Der
24
+ > Verteilname trug den Namen des Anbieters, obwohl das Produkt in einer
25
+ > Kommunalverwaltung laeuft — und er kollidierte mit `iil-assist`, dem
26
+ > Plattform-Mechanismus aus `platform:KONZ-platform-058`. **Der Importname
27
+ > bleibt `assist_core`**, ebenso App-Label und Tabellennamen: dort steht kein
28
+ > Anbietername, eine Umbenennung haette nur Migrationen gekostet und nichts
29
+ > geloest. Aendern muss ein Adopter genau eine Zeile in `requirements.txt`.
30
+
31
+ Grundlage: `meiki:ADR-044` (Paket-Topologie) · `meiki:ADR-022` v1.1 (die
32
+ Termin-Engine gehört in den Kern) · `meiki:ADR-025` (Mandantenfähigkeit).
33
+
34
+ ## Warum es dieses Paket gibt
35
+
36
+ Die Termin-Engine hat **zwei** Konsumenten: FristAssist führt Wiedervorlagen,
37
+ StatistikAssist führt Meldetermine. Ein Meldetermin ist eine Frist mit Stichtag —
38
+ dieselbe Rechnung, zwei Zwecke. Läge die Engine hinter FristAssist, bräuchte
39
+ StatistikAssist FristAssist. Genau das schließt der Produktschnitt aus.
40
+
41
+ ## Was drin ist
42
+
43
+ | Modul | Inhalt |
44
+ |---|---|
45
+ | `models` | `Regelfreigabe` (Regelwerk mit Zustandsmodell `entwurf → eingereicht → freigegeben → abgelehnt`), `RegelAuditEintrag`, `MandantEinstellung`, `TenantAuditEintrag`, Registries (`Verfahren`, `Quellsystem`, `AdapterBindung`, `Feiertagskalender`) |
46
+ | `tenancy` | `TenantModel`/`TenantManager` (fail-closed), `tenant_context()`, GUC `app.tenant_id` |
47
+ | `registry`/`adapters` | Validierung an der Grenze gegen die Registry-Tabelle; Adapter-Auflösung gegen eine Allowlist |
48
+ | `mandant` | Hauspraxis als Konfiguration — ohne gepflegte Einstellung wird **nichts geraten**, die Renderer liefern `None` |
49
+ | `regelkatalog` | einmaliger Import von Startbeständen. **Keine Betriebsquelle** — im Betrieb gilt allein die Tabelle |
50
+ | `engine` | die Termin-Engine — `berechne_frist()`, Regel-Resolver, `serie()` |
51
+ | `ports` | Schnittstellen zu Fachverfahren und DMS; die konkreten Adapter liegen beim Konsumenten |
52
+
53
+ ## Engine
54
+
55
+ `assist_core.engine` ist die **eine Rechenstelle** für Termine (meiki:ADR-022
56
+ v1.1) — zwei Konsumenten (FristAssist, StatistikAssist), eine Implementierung.
57
+ `berechne_frist()` rechnet Fristen nach § 187/188/193 BGB mit Bekanntgabefiktion
58
+ und Bayern-Kalender-Korrektur; `serie()` leitet daraus wiederkehrende
59
+ Meldetermine (monatlich/quartalsweise/jährlich) für StatistikAssist ab. Die
60
+ bayerischen Feiertage (`feiertage_bayern()`, inkl. Mariä Himmelfahrt) sind seit
61
+ `0.4.0` **Startbestand statt Konstante**: Mariä Himmelfahrt gilt nur in Gemeinden
62
+ mit katholischer Mehrheit, das Augsburger Friedensfest nur in Augsburg. Der
63
+ Kalender ist deshalb ein Profil je Kennung (`Feiertagskalender`, Import über
64
+ `seed_feiertage`), das ein Haus kopieren und abweichend pflegen kann
65
+ (KONZ-meiki-009 § 3.2). Der Regel-Resolver liest Fristwerte
66
+ aus der Governance-Tabelle `Regelfreigabe`, mit Code-Katalog-Fallback, solange
67
+ keine Regel gepflegt ist.
68
+
69
+ ## Mandant
70
+
71
+ Jede mandantengebundene Tabelle erbt von `assist_core.tenancy.TenantModel`
72
+ (`tenant_id` als `BigIntegerField`, platform:ADR-109). Der Manager ist
73
+ **fail-closed** — ohne aktiven Mandanten `none()`, nicht ungefiltert:
74
+
75
+ ```python
76
+ with transaction.atomic(), tenant_context(4711):
77
+ Verfahren.objects.all() # Manager + Policy filtern beide
78
+ # Einziger Cross-Tenant-Pfad, schreibt TenantAuditEintrag:
79
+ Verfahren.objects.all_tenants(grund="Statistik")
80
+ ```
81
+
82
+ ## RLS
83
+
84
+ `manage.py assist_rls --apply [--app-rolle assist_app]` legt je Tabelle mit
85
+ Mandanten-Spalte eine Policy an — `USING (tenant_id = NULLIF(current_setting(
86
+ 'app.tenant_id', true), '')::bigint)`, **ohne** OR-Ausweich-Zweige. Kein Mandant
87
+ heißt keine Zeile; nur `Regelfreigabe` öffnet über `tenant_scope_id IS NULL` die
88
+ globalen Regeln, nie fremde Mandanten. Der GUC ist transaktionslokal, Aufrufer
89
+ brauchen also `transaction.atomic()`. `--app-rolle` setzt `NOBYPASSRLS` und
90
+ entzieht `UPDATE`/`DELETE` auf den Audit-Tabellen; `--check` endet mit Exit ≠ 0,
91
+ wenn eine Policy fehlt oder einen aufmachenden Zweig trägt.
92
+
93
+ ## Was **nicht** drin ist
94
+
95
+ Kein Hausinhalt. Keine Regelwerke, keine Vorlagen, keine Mandantenwerte, keine
96
+ Team- oder Fachbereichsbezeichnungen. Nach `meiki:ADR-044` ist alles, was sich je
97
+ Haus unterscheidet, **eine Zeile in Postgres** — nicht Code, nicht YAML.
98
+
99
+ Prüfbar: *ein zweites Haus geht durch Konfiguration und Uploads in Betrieb, nicht
100
+ durch einen Commit.*
101
+
102
+ ## App-Label und Tabellennamen
103
+
104
+ `assist_core`, also `assist_core_*`. Kein `db_table` auf die Namen des
105
+ Herkunfts-Repos — siehe `docs/adr/ADR-001`.
106
+
107
+ Entschieden am 2026-09-09 zugunsten eines **echten Renames** statt einer
108
+ Zustands-Migration, weil es noch keinen produktiven Datenbestand gibt. Für das
109
+ Repo, das die Tabellen heute hält (`meiki-lra/frist-hub`), bedeutet das beim
110
+ Umstellen eine Umbenennungs-Migration — beim jetzigen Stand risikoarm, mit
111
+ produktiven Daten wäre sie es nicht. **Das Zeitfenster ist begrenzt.**
112
+
113
+ ## CI
114
+
115
+ Jeder PR und jeder Push auf `main` läuft gegen Postgres 16: `ruff check`,
116
+ `ruff format --check`, `pytest` und `makemigrations --check` (siehe
117
+ `.github/workflows/ci.yml`).
118
+
119
+ ## Stand
120
+
121
+ `0.4.0` — Tenancy-Basis, RLS-Erzeuger, Registries (KONZ-meiki-009 U3). Noch von
122
+ keinem Repo als Abhängigkeit gebunden. `tenant_id` bleibt `BigIntegerField`;
123
+ der Vorschlag, in der ganzen Familie auf UUID zu wechseln (KONZ-meiki-009 § 4.1,
124
+ D-2), ist offen und hier nicht vorweggenommen.
@@ -0,0 +1,107 @@
1
+ # kommunalassist-core
2
+
3
+ Der gemeinsame Kern der **Kommunalassist**-Familie: **Mandant**, **Termin-Engine**
4
+ (Regelwerk mit Freigabe-Workflow) und die **Ports** zu Fachverfahren und DMS.
5
+
6
+ > **Namenswechsel 2026-09-10.** Das Paket hiess bis 0.4.0 `iil-assist-core`. Der
7
+ > Verteilname trug den Namen des Anbieters, obwohl das Produkt in einer
8
+ > Kommunalverwaltung laeuft — und er kollidierte mit `iil-assist`, dem
9
+ > Plattform-Mechanismus aus `platform:KONZ-platform-058`. **Der Importname
10
+ > bleibt `assist_core`**, ebenso App-Label und Tabellennamen: dort steht kein
11
+ > Anbietername, eine Umbenennung haette nur Migrationen gekostet und nichts
12
+ > geloest. Aendern muss ein Adopter genau eine Zeile in `requirements.txt`.
13
+
14
+ Grundlage: `meiki:ADR-044` (Paket-Topologie) · `meiki:ADR-022` v1.1 (die
15
+ Termin-Engine gehört in den Kern) · `meiki:ADR-025` (Mandantenfähigkeit).
16
+
17
+ ## Warum es dieses Paket gibt
18
+
19
+ Die Termin-Engine hat **zwei** Konsumenten: FristAssist führt Wiedervorlagen,
20
+ StatistikAssist führt Meldetermine. Ein Meldetermin ist eine Frist mit Stichtag —
21
+ dieselbe Rechnung, zwei Zwecke. Läge die Engine hinter FristAssist, bräuchte
22
+ StatistikAssist FristAssist. Genau das schließt der Produktschnitt aus.
23
+
24
+ ## Was drin ist
25
+
26
+ | Modul | Inhalt |
27
+ |---|---|
28
+ | `models` | `Regelfreigabe` (Regelwerk mit Zustandsmodell `entwurf → eingereicht → freigegeben → abgelehnt`), `RegelAuditEintrag`, `MandantEinstellung`, `TenantAuditEintrag`, Registries (`Verfahren`, `Quellsystem`, `AdapterBindung`, `Feiertagskalender`) |
29
+ | `tenancy` | `TenantModel`/`TenantManager` (fail-closed), `tenant_context()`, GUC `app.tenant_id` |
30
+ | `registry`/`adapters` | Validierung an der Grenze gegen die Registry-Tabelle; Adapter-Auflösung gegen eine Allowlist |
31
+ | `mandant` | Hauspraxis als Konfiguration — ohne gepflegte Einstellung wird **nichts geraten**, die Renderer liefern `None` |
32
+ | `regelkatalog` | einmaliger Import von Startbeständen. **Keine Betriebsquelle** — im Betrieb gilt allein die Tabelle |
33
+ | `engine` | die Termin-Engine — `berechne_frist()`, Regel-Resolver, `serie()` |
34
+ | `ports` | Schnittstellen zu Fachverfahren und DMS; die konkreten Adapter liegen beim Konsumenten |
35
+
36
+ ## Engine
37
+
38
+ `assist_core.engine` ist die **eine Rechenstelle** für Termine (meiki:ADR-022
39
+ v1.1) — zwei Konsumenten (FristAssist, StatistikAssist), eine Implementierung.
40
+ `berechne_frist()` rechnet Fristen nach § 187/188/193 BGB mit Bekanntgabefiktion
41
+ und Bayern-Kalender-Korrektur; `serie()` leitet daraus wiederkehrende
42
+ Meldetermine (monatlich/quartalsweise/jährlich) für StatistikAssist ab. Die
43
+ bayerischen Feiertage (`feiertage_bayern()`, inkl. Mariä Himmelfahrt) sind seit
44
+ `0.4.0` **Startbestand statt Konstante**: Mariä Himmelfahrt gilt nur in Gemeinden
45
+ mit katholischer Mehrheit, das Augsburger Friedensfest nur in Augsburg. Der
46
+ Kalender ist deshalb ein Profil je Kennung (`Feiertagskalender`, Import über
47
+ `seed_feiertage`), das ein Haus kopieren und abweichend pflegen kann
48
+ (KONZ-meiki-009 § 3.2). Der Regel-Resolver liest Fristwerte
49
+ aus der Governance-Tabelle `Regelfreigabe`, mit Code-Katalog-Fallback, solange
50
+ keine Regel gepflegt ist.
51
+
52
+ ## Mandant
53
+
54
+ Jede mandantengebundene Tabelle erbt von `assist_core.tenancy.TenantModel`
55
+ (`tenant_id` als `BigIntegerField`, platform:ADR-109). Der Manager ist
56
+ **fail-closed** — ohne aktiven Mandanten `none()`, nicht ungefiltert:
57
+
58
+ ```python
59
+ with transaction.atomic(), tenant_context(4711):
60
+ Verfahren.objects.all() # Manager + Policy filtern beide
61
+ # Einziger Cross-Tenant-Pfad, schreibt TenantAuditEintrag:
62
+ Verfahren.objects.all_tenants(grund="Statistik")
63
+ ```
64
+
65
+ ## RLS
66
+
67
+ `manage.py assist_rls --apply [--app-rolle assist_app]` legt je Tabelle mit
68
+ Mandanten-Spalte eine Policy an — `USING (tenant_id = NULLIF(current_setting(
69
+ 'app.tenant_id', true), '')::bigint)`, **ohne** OR-Ausweich-Zweige. Kein Mandant
70
+ heißt keine Zeile; nur `Regelfreigabe` öffnet über `tenant_scope_id IS NULL` die
71
+ globalen Regeln, nie fremde Mandanten. Der GUC ist transaktionslokal, Aufrufer
72
+ brauchen also `transaction.atomic()`. `--app-rolle` setzt `NOBYPASSRLS` und
73
+ entzieht `UPDATE`/`DELETE` auf den Audit-Tabellen; `--check` endet mit Exit ≠ 0,
74
+ wenn eine Policy fehlt oder einen aufmachenden Zweig trägt.
75
+
76
+ ## Was **nicht** drin ist
77
+
78
+ Kein Hausinhalt. Keine Regelwerke, keine Vorlagen, keine Mandantenwerte, keine
79
+ Team- oder Fachbereichsbezeichnungen. Nach `meiki:ADR-044` ist alles, was sich je
80
+ Haus unterscheidet, **eine Zeile in Postgres** — nicht Code, nicht YAML.
81
+
82
+ Prüfbar: *ein zweites Haus geht durch Konfiguration und Uploads in Betrieb, nicht
83
+ durch einen Commit.*
84
+
85
+ ## App-Label und Tabellennamen
86
+
87
+ `assist_core`, also `assist_core_*`. Kein `db_table` auf die Namen des
88
+ Herkunfts-Repos — siehe `docs/adr/ADR-001`.
89
+
90
+ Entschieden am 2026-09-09 zugunsten eines **echten Renames** statt einer
91
+ Zustands-Migration, weil es noch keinen produktiven Datenbestand gibt. Für das
92
+ Repo, das die Tabellen heute hält (`meiki-lra/frist-hub`), bedeutet das beim
93
+ Umstellen eine Umbenennungs-Migration — beim jetzigen Stand risikoarm, mit
94
+ produktiven Daten wäre sie es nicht. **Das Zeitfenster ist begrenzt.**
95
+
96
+ ## CI
97
+
98
+ Jeder PR und jeder Push auf `main` läuft gegen Postgres 16: `ruff check`,
99
+ `ruff format --check`, `pytest` und `makemigrations --check` (siehe
100
+ `.github/workflows/ci.yml`).
101
+
102
+ ## Stand
103
+
104
+ `0.4.0` — Tenancy-Basis, RLS-Erzeuger, Registries (KONZ-meiki-009 U3). Noch von
105
+ keinem Repo als Abhängigkeit gebunden. `tenant_id` bleibt `BigIntegerField`;
106
+ der Vorschlag, in der ganzen Familie auf UUID zu wechseln (KONZ-meiki-009 § 4.1,
107
+ D-2), ist offen und hier nicht vorweggenommen.
@@ -0,0 +1,12 @@
1
+ """kommunalassist-core — der gemeinsame Kern der Kommunalassist-Familie.
2
+
3
+ Was hier liegt, brauchen **mehrere** Assists: die Mandanten-Einstellung, die
4
+ Termin-Engine mit ihrem Regelwerk und Freigabe-Workflow, und die Ports zu
5
+ Fachverfahren und DMS.
6
+
7
+ Grundlage: `meiki:ADR-044` (Paket-Topologie) und `meiki:ADR-022` v1.1 — die
8
+ Termin-Engine hat zwei Konsumenten (FristAssist und StatistikAssist) und darf
9
+ deshalb nicht hinter einem von beiden liegen.
10
+ """
11
+
12
+ __version__ = "0.5.0"