Skip to content

F4 — Eigenprofil-Overlay persistieren und ausliefern (Sektion B) - #35

Merged
patricznr1 merged 1 commit into
mainfrom
feat/f4-eigenprofil-overlay
Jun 23, 2026
Merged

F4 — Eigenprofil-Overlay persistieren und ausliefern (Sektion B)#35
patricznr1 merged 1 commit into
mainfrom
feat/f4-eigenprofil-overlay

Conversation

@patricznr1

@patricznr1 patricznr1 commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Was

Persistiert das zustandsspezifische Drift-Eigenprofil (Reasoner #2) und liefert es als profile_band über HTTP + WS aus; das Maschinen-Detail-Chart (Sektion B) rendert es als gestrichelten Erwartungskorridor neben dem statischen Normalband, mit Profil-Stand. Schließt den F4-Anschlusspunkt (profile_band war reserviert/null).

Die Ehrlichkeitslinie (Kern dieser Aufgabe)

Das ausgelieferte Band ist nachweislich die echte Detektor-Bewertungsbasis, keine Read-Rekonstruktion:

  • median(state_key) +/- effect_size_k * noise_sigmacurrent_median je Zustand + die eingefrorene robuste noise_sigma (MAD×1.4826) + effect_size_k = min_effect_size (3.0), am Laufende des gegateten Replays weggeschrieben.
  • Die state_key-Logik (Tagesstunde) ist geteilte Funktion (reasoners/drift/baseline.state_key_for) zwischen Detektor-Lauf und Read-Expansion — nicht dupliziert (sonst falscher Zustands-Korridor).
  • Kein etabliertes/ausreichendes Profil → profile_band null (graceful, FE lässt das Overlay weg). DB-CHECK noise_sigma > 0 hält diese Linie an der Persistenzgrenze; Zustände mit < 10 Samples fehlen ehrlich.
  • Sektion A unberührt (Designstudie: Cockpit hat keinen Profil-Overlay, nur Heatmap-Färbung).

Umfang (25 Dateien)

Backend — Migration 0011 + DriftProfile-Modell (UNIQUE/CHECK/FK CASCADE); baseline.state_key_for/state_profiles, detector.noise_sigma; service.extract_profile + Runner-Upsert; reads.load_drift_profile + trend.expand_profile_band; schemas.ProfileBandOut (beide Transport-Einstiege).
Frontendcontracts/types/trend-series-Mapping (snake→camel, Fenster-Merge), TimeSeriesChart-Korridor (data-series-2, gestrichelt, yScale-Einbezug), MachineTrendPanel-Profilstand.
Doku — GROUND_TRUTH §5/§20.5/§21.11 + WALKTHROUGH im selben Commit.

Gates

  • Backend: mypy strict 0, ruff check+format clean, pytest grün (Coverage 89% auf den berührten Modulen), Migration 0011 up/down + Constraints getestet.
  • Frontend: vitest 638 passed, tsc 0, ESLint 0, tokens:check synchron, next build ✓.
  • Adversariale Multi-Agent-Review (4 Dimensionen + Verifikation): 0 Befunde. Hidden-Term-Scan sauber.

Pflicht-Tests (alle grün)

Runner persistiert Profil · Band = Detektor-Basis · Read-Expansion wählt korrekten Zustand · graceful null · HTTP+WS befüllt · Migration up/down · FE rendert unterscheidbar + Profil-Stand.

Hinweis: tests/migrations/test_migration_0010 zeigt im lokalen Volllauf gelegentlich ein DB-Cleanup-Race (ObjectInUseError beim Drop einer ephemeren DB) — isoliert und in der Migrations-Suite grün, durch diese Änderung nicht berührt.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added drift profile bands to machine trend charts, displaying expected value corridors based on detected drift patterns.
    • Profile metadata including computation timestamp is now visible in trend panels.
    • Drift profiles are persisted in the database for continuous monitoring baseline reference.
  • Documentation

    • Updated documentation explaining the new profile band visualization and its integration with trend displays.
  • Tests

    • Added comprehensive test coverage for profile persistence, visualization, and database migration.

Persistiert das zustandsspezifische Drift-Eigenprofil (drift_profiles,
Migration 0011) und liefert es als profile_band ueber HTTP + WS aus; das
Maschinen-Detail-Chart (Sektion B) rendert es als gestrichelten
Erwartungskorridor neben dem statischen Normalband, mit Profil-Stand.

Das ausgelieferte Band ist nachweislich die echte Detektor-Bewertungsbasis:
current_median(state_key) +/- effect_size_k * noise_sigma, am Laufende des
gegateten Replays weggeschrieben. Die state_key-Logik (Tagesstunde) ist als
gemeinsame Funktion (baseline.state_key_for) zwischen Detektor-Lauf und
Read-Expansion geteilt, NICHT dupliziert. Kein etabliertes/ausreichendes
Profil -> profile_band null (graceful, FE laesst das Overlay weg); DB-CHECK
noise_sigma>0 haelt diese Ehrlichkeitslinie an der Persistenzgrenze. Sektion A
unberuehrt (Designstudie: Cockpit hat keinen Profil-Overlay).

Backend: Migration 0011 + DriftProfile-Modell; baseline.state_key_for/
state_profiles, detector.noise_sigma; service.extract_profile + Runner-Write
(upsert je Datenpunkt); reads.load_drift_profile + trend.expand_profile_band;
schemas ProfileBandOut (beide Transport-Einstiege).
FE: contracts/types/trend-series-Mapping, TimeSeriesChart-Korridor (data-series-2,
gestrichelt), MachineTrendPanel-Profilstand.

Tests: Migration 0011 up/down + CHECK/UNIQUE; Band=Detektor-Basis (unit);
Read-Expansion je Zustand; graceful null; HTTP+WS befuellt; FE-Rendering/Stand.
Coverage beruehrte Module 89%. GROUND_TRUTH (5/20.5/21.11) + WALKTHROUGH im
selben Commit. Adversariale Multi-Agent-Review: 0 Befunde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Der PR führt persistierte zustandsbasierte Drift-Eigenprofile (drift_profiles-Tabelle, Alembic Migration 0011, ORM-Modell) ein. Der Drift-Service extrahiert nach jedem Maschinenlauf ein Profil pro Datenpunkt und schreibt es per Upsert. Der Read-Layer expandiert das Profil zu einem profile_band pro Trend-Bucket; das Frontend rendert dieses als gestrichelten Erwartungskorridor mit Stand-Label.

Änderungen

F4 Drift Eigenprofil: Persistenz, Read-Layer und Frontend-Overlay

Layer / Datei(en) Zusammenfassung
drift_profiles Datenbankschema und Migration
src/foreman/db/models.py, migrations/versions/0011_drift_profiles.py
DriftProfile ORM-Modell mit JSONB state_medians, CHECK-Constraints (noise_sigma > 0, effect_size_k > 0), Unique auf data_point_id, FK-Cascades und machine_id-Index. Alembic-Migration 0011 legt Tabelle, Constraints und Index an; downgrade() entfernt alles wieder.
Detektor-Erweiterungen: noise_sigma, state_key_for, state_profiles
src/foreman/reasoners/drift/detector.py, src/foreman/reasoners/drift/baseline.py
DataPointDriftState exponiert noise_sigma als öffentliche Property. RollingResidualBaseline erhält state_profiles() für Median+Count je Zustand. state_key_for(moment) liefert moment.hour als Zustandsschlüssel.
Drift-Service: Profilextraktion und Persistenz-Upsert
src/foreman/reasoners/drift/service.py
DataPointProfile-Dataclass und extract_profile() bauen aus DataPointDriftState ein serialisierbares Profil (gefiltert nach MIN_STATE_PROFILE_SAMPLES, None bei fehlender noise_sigma). run_machine ruft _persist_profiles() auf; _upsert_profile() schreibt INSERT … ON CONFLICT DO UPDATE auf data_point_id.
Read-Layer: load_drift_profile und expand_profile_band
src/foreman/reads/queries.py, src/foreman/reads/trend.py
load_drift_profile() liest DriftProfile per data_point_id. ProfileBandPoint/ProfileBand Dataclasses. expand_profile_band() mappt Buckets über state_key_for auf state_medians und berechnet lower/mid/upper. build_trend und build_trend_by_id integrieren das Ergebnis in MachineTrend.profile_band.
API-Schema und Pydantic-Contracts: ProfileBandOut
src/foreman/schemas/dashboard.py, frontend/lib/api/contracts.ts
Pydantic-Klassen ProfileBandPointOut und ProfileBandOut in dashboard.py. MachineTrendOut.profile_band von None auf ProfileBandOut | None umgestellt. Entsprechende TypeScript-Interfaces in contracts.ts; MachineTrendOut.profile_band ebenfalls auf ProfileBandOut | null erweitert.
Frontend View-State-Typen und mergeTrendSeries
frontend/lib/machine/types.ts, frontend/lib/machine/trend-series.ts
ProfileBandPoint und ProfileBand Interfaces in types.ts; TrendSeries.profileBand von null auf ProfileBand | null. mergeProfileBands() dedupliziert Punkte beider Fenster nach Zeitstempel, priorisiert Live-Metadaten. mergeTrendSeries setzt profileBand via mergeProfileBands.
Frontend-Rendering: TimeSeriesChart und MachineTrendPanel
frontend/components/machine/time-series-chart.tsx, frontend/components/machine/machine-trend-panel.tsx
TimeSeriesChart bezieht profileBand.lower/upper in Y-Domain ein, erweitert aria-label, rendert drei gestrichelte SVG-Pfade im g[data-testid=profile-band]. MachineTrendPanel zeigt formatProfileStand()-Label (de-DE) für Eigenprofil-Stand im Header, wechselt zu space-between-Layout.
Unit-Tests: Detektor, Service, Schemas und Frontend
tests/unit/test_drift_detector.py, tests/unit/test_drift_service.py, tests/unit/test_dashboard_schemas.py, frontend/lib/machine/trend-series.test.ts, frontend/components/machine/time-series-chart.test.tsx, frontend/components/machine/machine-trend-panel.test.tsx
Neue Unit-Tests für noise_sigma, state_profiles(), state_key_for, extract_profile (Median-Übernahme, Sample-Filter, Null-bei-kein-Warmup). Dashboard-Schema-Tests für profile_band null/nicht-null. Frontend-Tests für mergeTrendSeries, TimeSeriesChart (SVG, stroke-dasharray, aria-label) und MachineTrendPanel (profile-stamp vorhanden/absent).
Integrationstests: Persistenz, Reads-Trend, Dashboard-Routes, Migration
tests/integration/test_drift_profile_persistence.py, tests/integration/test_reads_trend.py, tests/integration/test_dashboard_routes.py, tests/migrations/test_migration_0011.py
Persistenz-Tests prüfen run_machine mit healthy_baseline und kurzem Lauf (kein Profil). Read-Trend-Tests validieren profile_band-Korridorwerte und Null-Fälle. Dashboard-Route-Tests decken HTTP- und WS-Transport ab. Migration-0011-Test führt Upgrade/Downgrade-Roundtrip mit Constraint- und Drop-Nachweis durch.
Dokumentation: GROUND_TRUTH und WALKTHROUGH
GROUND_TRUTH.md, docs/WALKTHROUGH.md
GROUND_TRUTH.md dokumentiert drift_profiles-Schema, Migration 0011 und das aktive profile_band-Rendering (Korridorformel, Stand-Label, graceful null). WALKTHROUGH.md beschreibt die Eigenprofil-Overlay-Schicht und die konkretisierte TimeSeriesChart-Darstellung.

Sequenzdiagramm

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over DriftService,drift_profiles: Schreibpfad — nach Maschinenlauf
    DriftService->>DriftReasoner: detect_drift_in_stream(samples)
    DriftService->>DriftService: _persist_profiles(reasoner, topology, computed_at=end)
    DriftService->>extract_profile: state_for(dp_id) → extract_profile(state)
    extract_profile-->>DriftService: DataPointProfile | None
    DriftService->>drift_profiles: INSERT … ON CONFLICT (data_point_id) DO UPDATE
  end

  rect rgba(144, 238, 144, 0.5)
    Note over Client,TimeSeriesChart: Lesepfad — Trend-Anfrage
    Client->>build_trend: GET /trend oder WS trend_topic
    build_trend->>drift_profiles: load_drift_profile(data_point_id)
    drift_profiles-->>build_trend: DriftProfile | None
    build_trend->>expand_profile_band: expand_profile_band(profile, reading_buckets)
    expand_profile_band-->>build_trend: ProfileBand | None
    build_trend-->>Client: MachineTrendOut { profile_band }
    Client->>TimeSeriesChart: series.profileBand
    TimeSeriesChart->>TimeSeriesChart: SVG gestrichelter Korridor (upper/mid/lower)
  end
Loading

Geschätzter Code-Review-Aufwand

🎯 4 (Komplex) | ⏱️ ~60 Minuten

Möglicherweise verwandte PRs

  • patricznr1/foreman#4: Führt den F4-Drift-Reasoner (State-Gating, ADWIN, DriftFinding, Drift-Service) ein — genau die Baseline/Detektor-Pipeline, auf der dieser PR state_key_for, state_profiles() und extract_profile() aufbaut.
  • patricznr1/foreman#26: Einführung des TimeSeriesChart in Section B mit dem damals noch null-reservierten profileBand-Slot — dieser PR implementiert nun das eigentliche SVG-Overlay und die Y-Domain-Integration.

Gedicht

🐇 Ein Profil entsteht aus Rauschen und Zeit,
noise_sigma gefroren, der Korridor bereit.
Gestrichelt im Chart — drei Pfade fein,
computed_at stempelt: Eigenprofil dein!
Was einst null war, zeigt jetzt den Pfad,
den Hasi im Drift-Tunnel tapfer trat. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Der Titel bezieht sich direkt auf die Hauptänderung: Persistierung und Auslieferung von Eigenprofil-Overlays (Sektion B). Dies spiegelt den Kern der Änderungen wider.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/f4-eigenprofil-overlay

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/foreman/reasoners/drift/service.py`:
- Around line 406-408: The code passes `self.min_effect_size` as the
`effect_size_k` parameter to `extract_profile`, but the database has a
constraint requiring `effect_size_k > 0`. If `min_effect_size` is set to a value
less than or equal to 0, it will cause an IntegrityError during profile
insertion, rolling back the entire transaction. Add validation at the entry
point of the DriftService initialization or the replay_machine function to check
that `min_effect_size > 0` and raise a meaningful error immediately, preventing
invalid values from reaching the database operation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 139314df-813c-448a-9592-6d641a8147ed

📥 Commits

Reviewing files that changed from the base of the PR and between 07fc97b and a611154.

📒 Files selected for processing (25)
  • GROUND_TRUTH.md
  • docs/WALKTHROUGH.md
  • frontend/components/machine/machine-trend-panel.test.tsx
  • frontend/components/machine/machine-trend-panel.tsx
  • frontend/components/machine/time-series-chart.test.tsx
  • frontend/components/machine/time-series-chart.tsx
  • frontend/lib/api/contracts.ts
  • frontend/lib/machine/trend-series.test.ts
  • frontend/lib/machine/trend-series.ts
  • frontend/lib/machine/types.ts
  • migrations/versions/0011_drift_profiles.py
  • src/foreman/db/models.py
  • src/foreman/reads/queries.py
  • src/foreman/reads/trend.py
  • src/foreman/reasoners/drift/baseline.py
  • src/foreman/reasoners/drift/detector.py
  • src/foreman/reasoners/drift/service.py
  • src/foreman/schemas/dashboard.py
  • tests/integration/test_dashboard_routes.py
  • tests/integration/test_drift_profile_persistence.py
  • tests/integration/test_reads_trend.py
  • tests/migrations/test_migration_0011.py
  • tests/unit/test_dashboard_schemas.py
  • tests/unit/test_drift_detector.py
  • tests/unit/test_drift_service.py

Comment on lines +406 to +408
profile = extract_profile(
data_point_id, topology.machine_id, state, effect_size_k=self.min_effect_size
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Default-Wert und etwaige Validierung von min_effect_size prüfen
rg -nP -C2 'DEFAULT_MIN_EFFECT_SIZE\s*[:=]' src/foreman/reasoners/drift
rg -nP -C2 'min_effect_size' src/foreman/reasoners/drift/service.py

Repository: patricznr1/foreman

Length of output: 1916


🏁 Script executed:

# Prüfe DriftReasoner-Klasse und Initialisierung
ast-grep outline src/foreman/reasoners/drift/service.py | head -50

# Suche nach extract_profile Implementierung
rg -n 'def extract_profile' src/foreman/reasoners/drift/

# Prüfe auf Validierung von min_effect_size
rg -n -B3 -A3 'min_effect_size\s*[<>=]' src/foreman/reasoners/drift/

# Suche nach DB Constraints
rg -n 'effect_size_k' src/foreman/reasoners/drift/ --type py

Repository: patricznr1/foreman

Length of output: 3637


🏁 Script executed:

# Read extract_profile function implementation
sed -n '119,154p' src/foreman/reasoners/drift/service.py

# Check runner.py context for min_effect_size parameter
sed -n '40,70p' src/foreman/reasoners/drift/runner.py

# Search for DB constraint on effect_size_k in schema or models
rg -n 'effect_size_k' src/ --type py -B2 -A2 | head -80

# Check if there's a migration or schema file defining the constraint
fd -e sql -e py | grep -i migrat | head -10

Repository: patricznr1/foreman

Length of output: 8211


🏁 Script executed:

# Search in migration files for drift_profiles table definition and constraint
rg -n 'drift_profiles\|effect_size_k' migrations/ --type py -B3 -A3

# Check runner.py entry point for min_effect_size parameter source
sed -n '1,50p' src/foreman/reasoners/drift/runner.py

# Look for main entry point or CLI that calls replay_machine
rg -n 'replay_machine\|min_effect_size' src/foreman/ --type py | grep -E '(def|import|argparse|click|param)' | head -20

Repository: patricznr1/foreman

Length of output: 2275


🏁 Script executed:

# Find the DB constraint on effect_size_k in migrations
cat migrations/versions/*.py | grep -A10 -B10 'effect_size_k\|drift_profiles'

# Search for CLI or main entry point that calls replay_machine
rg -n 'replay_machine' src/ --type py

# Check if there's a CLI module or main entry point
find src -name 'cli.py' -o -name '__main__.py' -o -name 'main.py' | head -5

# Look for argparse or click usage in runner or related modules
rg -n 'argparse\|`@click`\|typer' src/foreman/reasoners/drift/ --type py

Repository: patricznr1/foreman

Length of output: 3589


🏁 Script executed:

# Check main.py for CLI and min_effect_size parameter handling
cat src/foreman/main.py | grep -A50 -B5 'min_effect_size\|replay_machine\|drift'

# Check _persist_profiles and transaction context
sed -n '360,430p' src/foreman/reasoners/drift/service.py

# Search for all callers of replay_machine to see if min_effect_size is overridden
rg -n 'replay_machine' src/foreman/ --type py -A3 -B3

Repository: patricznr1/foreman

Length of output: 6602


🏁 Script executed:

# Check run_machine implementation and transaction context for _persist_profiles
sed -n '235,350p' src/foreman/reasoners/drift/service.py

# Look for validation of min_effect_size at any point
rg -n 'min_effect_size\s*[<>=]|assert.*min_effect_size|if.*min_effect_size' src/foreman/reasoners/drift/ --type py

# Check if replay_machine/DriftService is called from CLI or other places
rg -n 'DriftService\|replay_machine' src/foreman/reasoners/drift/router.py --type py -B3 -A3

Repository: patricznr1/foreman

Length of output: 5441


🏁 Script executed:

# Check the drift router to see how replay_machine is called and transaction context
cat src/foreman/reasoners/drift/router.py

# Check if there's a transaction context in run_machine
rg -n 'begin\|transaction\|async with' src/foreman/reasoners/drift/service.py -B2 -A2

# Look for any async session transaction context
rg -n 'AsyncSession\|sessionmaker' src/foreman/reasoners/drift/service.py -B2 -A2

Repository: patricznr1/foreman

Length of output: 4074


🏁 Script executed:

# Look for background worker or scheduler that runs replay_machine
rg -n 'replay_machine\|DriftService' src/foreman/ --type py -B3 -A3 | head -80

# Check if there's a worker or task scheduler module
fd -name 'worker*' -o -name '*worker*' -o -name '*task*' src/foreman/ --type f

# Look in reasoners directory for any orchestration
ls -la src/foreman/reasoners/

Repository: patricznr1/foreman

Length of output: 732


🏁 Script executed:

# Search for where replay_machine is called
rg 'replay_machine' src/foreman/ --type py -B3 -A3

# Check the reasoners __init__.py
cat src/foreman/reasoners/__init__.py

# Look for worker or scheduler
find src/foreman -name '*worker*' -o -name '*task*' -o -name '*schedule*' | grep -v __pycache__

Repository: patricznr1/foreman

Length of output: 1025


🏁 Script executed:

# Look for CLI or API entry points that instantiate DriftService or call replay_machine
rg 'DriftService|replay_machine' src/foreman/ --type py | grep -v 'from foreman.reasoners.drift'

# Check if replay_machine is exposed as an API endpoint or CLI
rg '`@router`\|`@app`\|`@click`\|typer' src/foreman/reasoners/drift/ --type py

# Look for any test files that might show how min_effect_size is set
find src -name '*test*drift*' -o -name '*drift*test*' | head -5

# Check if there's validation of min_effect_size in DriftReasoner or DriftService
rg 'class DriftService|class DriftReasoner' src/foreman/reasoners/drift/ --type py -A20 | head -60

Repository: patricznr1/foreman

Length of output: 4137


🏁 Script executed:

# Search for all places where replay_machine is called or DriftService is instantiated
rg 'replay_machine\(' src/foreman/ --type py -B2 -A5

# Look for any settings or configuration that passes min_effect_size
rg 'min_effect_size' src/foreman/settings.py src/foreman/config.py 2>/dev/null

# Check if there's a main entry point that calls replay_machine
find src/foreman -name 'main.py' -o -name 'cli.py' -o -name '__main__.py' | xargs grep -l 'replay_machine' 2>/dev/null

# Check the transaction context and session usage
sed -n '215,235p' src/foreman/reasoners/drift/service.py

Repository: patricznr1/foreman

Length of output: 1571


🏁 Script executed:

# Check Settings class or config for min_effect_size
rg -n 'class Settings' src/foreman/ --type py -A50 | head -100

# Look for any place that might set min_effect_size externally
rg -n 'min_effect_size\s*=' src/foreman/ --type py | grep -v 'def\|async def\|self.min_effect_size\|:.*float'

# Verify the entire flow from runner.py to service initialization
cat src/foreman/reasoners/drift/runner.py

Repository: patricznr1/foreman

Length of output: 6144


🏁 Script executed:

# Check if replay_machine is called from anywhere in the codebase
rg 'replay_machine\|DriftService(' src/foreman/ --type py -B5 -A5 | grep -v test

# Check if there are any tests that set min_effect_size
find . -path ./migrations -prune -o -name '*test*.py' -type f -print | xargs grep -l 'min_effect_size' 2>/dev/null

# Verify transaction context - check if session is in a transaction
rg 'async with.*session\|begin\|transaction' src/foreman/reasoners/drift/service.py --type py -B2 -A2

Repository: patricznr1/foreman

Length of output: 338


🏁 Script executed:

# Check the test files for min_effect_size usage
cat ./tests/unit/test_drift_service.py | head -200

# Also check the relevance filter test
cat ./tests/unit/test_relevance_filter.py | grep -A10 -B10 'min_effect_size'

# Check if there's any validation or guards in the code
rg 'min_effect_size.*<|min_effect_size.*<=' src/foreman/reasoners/drift/ --type py

Repository: patricznr1/foreman

Length of output: 9185


🏁 Script executed:

# Final verification: find ALL instantiations of DriftService or calls to replay_machine
rg -n 'DriftService\(' src/foreman/ tests/ --type py

# Check if there are any dataclass/model validators
rg -n '`@validator`\|`@field_validator`' src/foreman/reasoners/drift/ --type py

Repository: patricznr1/foreman

Length of output: 225


effect_size_k-Validierung: Defensives Programmieren gegen potenzielle Constraint-Verletzung.

effect_size_k wird aus self.min_effect_size befüllt; der DB-CHECK effect_size_k > 0 weist <= 0 ab. Momentan ist DEFAULT_MIN_EFFECT_SIZE = 3.0 (Final), und replay_machine standardisiert ebenfalls auf diesen Wert. Jedoch: Falls min_effect_size programmatisch mit <= 0 gesetzt wird (möglich via replay_machine-Parameter, kein Config-Override-Mechanismus gefunden), würde _upsert_profile beim DB-INSERT einen IntegrityError werfen. Da _persist_profiles im selben Try-Block wie die Drift-Event-Persistierung läuft, rollte der komplette run_machine-Lauf inkl. bereits emittierter Events zurück.

Empfehlung: Validierung min_effect_size > 0 am Eingang von replay_machine oder DriftService.__init__ hinzufügen, um das Constraint defensiv zu schützen und eine aussagekräftige Fehlermeldung früh zu geben.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/foreman/reasoners/drift/service.py` around lines 406 - 408, The code
passes `self.min_effect_size` as the `effect_size_k` parameter to
`extract_profile`, but the database has a constraint requiring `effect_size_k >
0`. If `min_effect_size` is set to a value less than or equal to 0, it will
cause an IntegrityError during profile insertion, rolling back the entire
transaction. Add validation at the entry point of the DriftService
initialization or the replay_machine function to check that `min_effect_size >
0` and raise a meaningful error immediately, preventing invalid values from
reaching the database operation.

@patricznr1
patricznr1 merged commit aeab6d7 into main Jun 23, 2026
3 checks passed
@patricznr1
patricznr1 deleted the feat/f4-eigenprofil-overlay branch June 23, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant