Files
Predictalytics/docs/archiv/ANALYSE-Linux-Portierung.md
RichardandClaude Opus 5 6975720dc6 Eine Roadmap statt neun Plandokumente; alte Plaene ins Archiv
Die offenen Punkte lagen ueber neun Dokumente verstreut, teils widersprechend,
teils mit Punkten, die laengst umgesetzt waren. ROADMAP.md fuehrt sie zusammen:
sechs Stufen, jeder Punkt gegen den Code geprueft.

Markierung ueber eine Legende, damit Konzepte nicht mit Aufgaben verwechselt
werden: dringend, eingeplant, Backlog, Konzept (durchdacht, aber bewusst nicht
eingeplant), liegt beim Nutzer, verworfen.

Stufen: 0 Sofort (OpenRouter-Key) - 1 Aufraeumen abschliessen (Merge nach main,
Nullable-Warnungen, Startup-Backfill) - 2 Portierung abschliessen (Linux-Erstlauf
und Verifikation, dann Deployment/CI) - 3 Analytik schaerfen (Strategie-
Klassifikation, Backtest-Harness, Track-Record im Score, Ranking) - 4 Daten-
haushalt (SQL des Nutzers) - 5 Ingest skalieren - 6 Monetarisierung vorbereiten.

Ein Anhang haelt fest, was geprueft und bewusst NICHT auf die Roadmap kam, damit
es nicht versehentlich wieder als Aufgabe auftaucht (Azuro/Limitless, die
Marketing-Seiten, Kaltarchiv, EF Core 10).

Archiv: FIXPLAN-DONE, FIXPLAN-G-Speicher, FIXPLAN-TODO, FIXPLAN-UI-Ranglisten,
UMSETZUNGSPLAN sowie ANALYSE-Linux-Portierung, PLAN-Linux-Portierung,
PLAN-Architektur-WebUI-Backend und PLAN-DatenIngest-Skalierung liegen jetzt
unter docs/archiv/ mit einer README, die jedes Dokument einordnet. Sie bleiben
als Begruendungs- und Detailquelle - die Roadmap nennt jeden Punkt knapp, die
Herleitung steht dort.

Dabei aufgefallen: die beiden nie gepflegten Plaene (Architektur, DatenIngest)
zeigten 40 offene Punkte, von denen die Haelfte umgesetzt war - Read/Control-
Split, /api/capabilities, CORS-Whitelist, Egress-Kanaele mit Cooldown und
Per-Kanal-Limiter, Ingest-Tiering. Nachgeprueft, abgehakt und mit Statusblock
eingeordnet, sonst waere das Archiv selbst eine Fehlerquelle.

Was dabei nur teilweise umgesetzt war, ist als Roadmap 5.3 aufgenommen
(Health-Statistik je Kanal, multi-homed pruefen, DB-Guard, Plausibilitaets-
pruefung), ebenso der nie durchgefuehrte Audit auf versteckte Writes in
Read-Endpunkten (6.1). Header-Rotation ist als verworfen markiert statt offen
zu bleiben: Verschleierung gegenueber Polymarket riskiert genau den Zugang, auf
dem das Projekt aufsetzt.

STATUS.md beschreibt jetzt nur noch den Ist-Stand und verweist fuer die offenen
Punkte auf die Roadmap, damit nichts doppelt gepflegt wird. CLAUDE.md nennt
beide Einstiege.

Build gruen, 126 Tests gruen, keine toten Links.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:46:00 +02:00

21 KiB
Raw Permalink Blame History

Analyse: Linux-Fähigkeit von Predictalytics

Stand: 2026-08-06 · Branch main @ aa19a89 · reine Ist-Analyse, keine Codeänderungen


0. Kernbefund vorab

Avalonia UI und LiveCharts2 werden nicht benötigt.

Die Annahme im Auftrag war, dass eine WinForms-Oberfläche auf Avalonia + LiveCharts2 portiert werden muss. Das trifft auf dieses Projekt nicht zu:

  • Die eigentliche Analyse-Oberfläche ist bereits eine Browser-Anwendungsrc/Predictalytics.Api/wwwroot/ (3.745 Zeilen HTML/CSS/JS), Charts über Chart.js 4.4.4 (index.html:9, CDN). Sie ist damit heute schon plattformunabhängig.
  • Predictalytics.WinFormsHost ist kein UI-Projekt im fachlichen Sinne, sondern eine Bedien-Hülle für den Betrieb: Start/Stop der Worker, Start/Stop des Kestrel-Webservers, ein PropertyGrid für Einstellungen, eine RichTextBox als Log-Terminal und vier manuelle Wartungsaktionen. 1.698 LOC inkl. Designer-Datei. Kein einziges Diagramm.

Die Aufgabe lautet damit nicht „UI-Framework migrieren", sondern „Bedien-Hülle durch einen headless Host ersetzen" — eine Größenordnung weniger Aufwand.


1. Portierungsstatus je Projekt

Projekt TFM Status Bemerkung
Predictalytics.Domain net8.0 portabel 42 Dateien, reine Fachlogik
Predictalytics.Application net8.0 portabel 39 Dateien
Predictalytics.Infrastructure net8.0 portabel 94 Dateien, EF Core + Pomelo MySQL
Predictalytics.Api net8.0 portabel Minimal-APIs + wwwroot
Predictalytics.Worker net8.0 portabel 13 Worker-Services
Predictalytics.Application.Tests net10.0 ⚠️ Inkonsistenz s. Punkt 4.8
Predictalytics.WinFormsHost net8.0-windows muss ersetzt werden einziger Blocker
LicenseLabrador.Client (Schwester-Repo) netstandard2.0 ⚠️ teilportabel s. Punkt 3.2

54.056 Zeilen C# gesamt. Davon sind 1.698 (3,1 %) windows-gebunden — und davon ist etwa die Hälfte (EmbeddedWebServer.cs, Serilog-Setup) inhaltlich bereits portabel und nur im falschen Projekt beheimatet.


2. Was bereits sauber ist (und keinen Aufwand erzeugt)

Diese Punkte wurden explizit geprüft, weil sie erfahrungsgemäß die teuren Überraschungen einer Linux-Portierung sind. Der Code ist hier durchweg in gutem Zustand:

2.1 Zeitzonen — überwiegend sauber, mit einer wichtigen Ausnahme ⚠️

Korrektur (2026-08-06, nach Phase 0): Die erste Fassung dieser Analyse stufte den Punkt als „vollständig sauber, Aufwand 0" ein. Das war falsch — siehe unten. Die explizite Zeitzonen-API-Nutzung ist sauber, die implizite über DateTime.TryParse war es nicht.

166 × DateTime.UtcNow / DateTimeOffset.UtcNow
  0 × DateTime.Now · DateTime.Today · DateTimeOffset.Now
  0 × ToLocalTime() · DateTimeKind.Local
  0 × TimeZoneInfo / FindSystemTimeZoneById
 13 × DateTime.TryParse ohne Culture/Styles          ← Problem

Der klassische Linux-Killer TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time") (Windows-IDs existieren unter Linux nicht, dort gilt IANA Europe/Berlin) kommt nicht vor. Insoweit ist die Codebasis sauber.

Übersehen hatte ich die implizite Variante: DateTime.TryParse("...Z") ohne DateTimeStyles liefert Kind=Local und rechnet den Wert in Lokalzeit um. In beiden Providern wurden so ISO-Zeitstempel der Plattform-APIs eingelesen:

  • PolymarketProvider.csStartDate, EndDate, CreatedAt, ClosedAt (10 Stellen)
  • LimitlessProvider.csExecutedAt, ExpirationDate (2 Stellen)

Dass UTC gemeint war, belegen die Fallbacks: jede dieser Zuweisungen fällt bei Parse-Fehler auf DateTime.UtcNow zurück, und direkt daneben steht DbCreatedAt = DateTime.UtcNow. Auf der Berliner Entwicklungsmaschine landeten die geparsten Werte dadurch um 12 Stunden verschoben in der Datenbank, die Fallback-Werte dagegen korrekt.

Für die Portierung ist das doppelt relevant: derselbe Code hätte auf einem Linux-Server mit TZ=UTC andere Werte produziert als auf dem Windows-Rechner.

Behoben in Phase 0 über Infrastructure/Helpers/DateParsing.cs (InvariantCulture + AdjustToUniversal | AssumeUniversal).

⚠️ Offen: Altdaten. Bereits gespeicherte Zeilen tragen die Verschiebung weiter. Ob ein Backfill nötig ist, hängt daran, wie stark StartDate/EndDate/ClosedAt in die Auswertung eingehen — siehe „Nächste Schritte" im Portierungsplan.

2.2 Kulturabhängige Zahlenformatierung — bis auf eine Stelle sauber

Alle Parser für Fremd-APIs (Polymarket, Limitless) verwenden explizit InvariantCulture:

  • LimitlessProvider.cs:59,60,110,113,114,115
  • PolymarketModels.cs:17 · PolymarketProvider.cs:386
  • TradeHistoryWorker.cs:466,467

Eine Ausnahme (echter Latent-Bug, plattformunabhängig):

// Predictalytics.Infrastructure/Providers/Limitless/LimitlessProvider.cs:268
Volume = decimal.TryParse(raw.VolumeFormatted?.Replace(" USDC", ""), out var vol) ? vol : 0,

Ohne InvariantCulture. Auf einem System mit deutscher Locale (de_DE.UTF-8, unter Linux sehr üblich) wird "1234.56" als 123456 interpretiert — Faktor 100.000 zu hoch. Der Fehler existiert schon heute auf einem deutschsprachigen Windows; Linux macht ihn nur wahrscheinlicher, weil die Locale dort oft explizit gesetzt wird.

Aufwand: 5 Minuten. Sollte unabhängig von der Portierung gefixt werden.

Zusätzliche Absicherung empfohlen: InvariantGlobalization bzw. ein explizites CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture im neuen Host.

2.3 Pfade und Dateisystem

  • Keine hartkodierten Laufwerksbuchstaben im Code — eine Ausnahme, siehe 4.5.
  • Path.Combine() wird konsequent verwendet, keine manuell zusammengesetzten \-Pfade.
  • Kein einziger Datei-/Verzeichniszugriff in Infrastructure, Worker, Api, Application, Domain. Sämtliches File-IO liegt in WinFormsHost (Settings, Logs) und im Lizenz-Client.

2.4 Groß-/Kleinschreibung (case-sensitive Dateisystem)

Linux-Dateisysteme sind case-sensitiv, Windows nicht — ein häufiger Stolperstein bei statischen Web-Assets. Geprüft:

Referenz Datei Passt
href="css/style.css?v=20260715" wwwroot/css/style.css
src="js/app.js?v=20260715" wwwroot/js/app.js
href="./docs.html" wwwroot/docs.html
href="./index.html" wwwroot/index.html

Alles durchgängig kleingeschrieben und deckungsgleich. Aufwand: 0.

Hinweis: MySQL-Tabellennamen sind unter Linux abhängig von lower_case_table_names ebenfalls case-sensitiv. Da Prod-DB und Dev-DB bereits auf Linux-Servern laufen (lqf7.your-database.de) und der Code über EF Core mit konsistenter Schreibweise zugreift, besteht hier kein zusätzliches Risiko durch die Client-Portierung.

2.5 Keine Windows-spezifischen APIs außerhalb des Hosts

Geprüft auf Registry, Microsoft.Win32, ManagementObject/WMI, ProtectedData, DllImport, System.Drawing, Environment.OSVersion: keine Treffer außerhalb von Predictalytics.WinFormsHost. Auch Encoding.Default (unter Linux immer UTF-8, unter Windows Codepage) kommt nirgends vor.

2.6 NuGet-Abhängigkeiten

Alle verwendeten Pakete sind plattformneutral: EF Core 8 + Pomelo MySQL, Serilog (+ File/ Console/Compact), Swashbuckle, Microsoft.Extensions.*, xunit, Moq. Keine windows-exklusiven Pakete außerhalb des WinForms-Hosts.

2.7 Ein bereits vorhandener Vorteil: der Composition Root existiert schon

Predictalytics.Api/ApiConfiguration.cs (ConfigureApi) ist eine vollständige, eigenständige Host-Konfiguration — DI, CORS, Swagger, Static Files, Endpoint-Mapping. Ihr fehlt nur eine Main-Methode. Der neue headless Host kann darauf direkt aufsetzen, statt die Verdrahtung neu zu schreiben.

Ebenso ist Infrastructure/Logging/RichTextBoxSink.cs trotz des Namens vollständig portabel — es ist ein reiner Delegate-Sink (Action<string, LogEventLevel>) ohne WinForms-Referenz. Lediglich umbenennen (z. B. DelegateSink) wäre sinnvoll.


3. Die tatsächlichen Blocker

3.1 Predictalytics.WinFormsHost — Ersatz durch headless Host Hauptaufwand

Das Projekt ist der einzige Einstiegspunkt der Anwendung (static void Main existiert nur hier). Es leistet fünf Dinge, die im neuen Host anders gelöst werden müssen:

Funktion heute Ersatz unter Linux Aufwand
Serilog-Setup (Program.cs:35-148) — Datei-Sinks nach Level und Plattform 1:1 übernehmbar, ist bereits portabel. Nur RichTextBoxSink entfällt bzw. wird Console-Sink. trivial
EmbeddedWebServer.cs (365 Z.) — Kestrel- und Worker-Lifecycle Inhaltlich portabel. Wird zum normalen IHost mit WebApplication + AddWorkerServices(). Vereinfacht sich sogar deutlich, weil Start/Stop-Buttons entfallen. klein
AppSettings.cs + PropertyGrid — Laufzeit-Konfiguration in settings.json appsettings.json + Umgebungsvariablen + IOptions<T>. Die [Category]/[DisplayName]-Attribute entfallen ersatzlos. kleinmittel
Manuelle Aktionen — „Sync Markets", „DB Update", „Recalculate All Traders" (Bestätigungsdialoge + MessageBox) Entweder CLI-Verben (--sync-markets, --recalc-all) oder geschützte Admin-Endpunkte in der bestehenden Web-UI. Die Rückfrage-Logik („nicht während laufender Worker") muss erhalten bleiben. mittel
Log-Terminal (RichTextBox) + DB-Größen-Anzeige + Build-Version Entfällt / Console + journalctl. Die DB-Größenabfrage (MainForm.cs:317-343) kann als Health-Endpunkt weiterleben. klein

WatchdogHeartbeatService.cs (141 Z.) ist reines HttpClient-Verhalten und vollständig portabel — sollte nach Infrastructure oder in den neuen Host wandern.

Empfehlung: WinFormsHost nicht löschen, sondern beide Hosts parallel führen. Er ist die etablierte Betriebsoberfläche unter Windows; beide teilen sich denselben Composition Root. Der Wartungsaufwand für die dünne Hülle ist gering.

3.2 Lizenzierung — der unterschätzte Blocker

Der LicenseLabrador.Client ist erfreulich gut vorbereitet, aber die Aktivierung ist es nicht.

Was bereits funktioniert:

  • HardwareId.cs:52-84 behandelt Linux explizit: liest /etc/machine-id bzw. /var/lib/dbus/machine-id, mit MAC-Adressen-Fallback. Sauber gelöst.
  • Zielframework netstandard2.0, Microsoft.Win32.Registry wird per IsWindows()-Guard nur unter Windows angefasst.

Was nicht funktioniert:

  1. LicenseDialog ist ein blockierender WinForms-Modaldialog. Program.cs:16LicenseGuard.EnsureLicensed()dialog.ShowDialog(). Auf einem headless Server gibt es niemanden, der einen Key eintippt. Es braucht einen nicht-interaktiven Aktivierungspfad: Lizenzschlüssel aus Umgebungsvariable oder Konfigurationsdatei, Aktivierung beim ersten Start, sauberer Abbruch mit Exit-Code und klarer Logmeldung, wenn keine gültige Lizenz vorliegt.

  2. StateStore.cs:43,92 — DPAPI-Verschlüsselung fällt unter Linux still auf Klartext zurück.

    try   { dataToSave = ProtectedData.Protect(plainBytes, null, DataProtectionScope.CurrentUser); }
    catch { dataToSave = plainBytes; }   // ← Linux landet immer hier
    

    Der Lizenz-Cache (inkl. Lizenzschlüssel) liegt dann unverschlüsselt auf der Platte. Funktional unkritisch, aber eine bewusste Entscheidung — mindestens sollten die Dateirechte (0600) und das Verzeichnis abgesichert werden.

  3. LicenseConfig.StorageDirectory nutzt Environment.SpecialFolder.ApplicationData → unter Linux ~/.config. Unter systemd mit DynamicUser=yes oder ohne $HOME existiert das nicht. Muss explizit gesetzt werden (z. B. /var/lib/predictalytics).

  4. LicenseGuard.StartPeriodicRevalidation verwendet System.Windows.Forms.TimerPeriodicTimer oder IHostedService. Application.Exit()IHostApplicationLifetime.StopApplication().

Das ist der Punkt mit der größten Restunsicherheit, weil er das Schwester-Repo LicenseLabrador berührt und serverseitig geprüft werden muss (Aktivierung ohne UI).

3.3 Kestrel bindet nur auf localhost ⚠️

// EmbeddedWebServer.cs:122
builder.WebHost.UseUrls($"http://localhost:{port}");

Das ist für einen Desktop-Host korrekt, für einen Linux-Server aber falsch: von außen nicht erreichbar. Muss konfigurierbar werden (0.0.0.0 bzw. ASPNETCORE_URLS), inkl. Anpassung der CORS-AllowedOrigins (heute fest http://localhost:5000).

Sicherheitshinweis: Sobald der Dienst nicht mehr nur auf localhost lauscht, ist die bisher implizite Absicherung weg. MapPredictalyticsControlEndpoints() (schreibende Operationen) und Swagger sind dann exponiert. Es gibt bereits ein CanControl/AuthRequired- Konzept (ApiConfiguration.cs:66) — das muss vor dem Linux-Deployment scharf geschaltet werden, plus Reverse Proxy (nginx/Caddy) mit TLS davor.


4. Weitere Fundstellen (klein, aber notwendig)

4.1 Process.Start("explorer.exe", …) — 3 Stellen

MainForm.cs:192,207,209 (Browser öffnen, Log-Ordner öffnen). Betrifft nur den WinForms-Host; im headless Host entfallen die Aktionen ersatzlos.

4.2 System.Windows.Forms.Timer — 2 Stellen

MainForm.cs:71 (DB-Größe), LicenseGuard.cs:70 (Revalidierung). → PeriodicTimer / IHostedService.

4.3 Schriftart Cascadia Code

MainForm.cs:25. Nur WinForms-relevant, entfällt.

4.4 Log-Verzeichnis

Program.cs:33: Path.Combine(AppContext.BaseDirectory, "logs"). Technisch portabel, aber unter Linux ist das Installationsverzeichnis üblicherweise nicht beschreibbar. → konfigurierbar machen, Default /var/log/predictalytics bzw. journalctl über den Console-Sink.

Nebenbefund: src/Predictalytics.WinFormsHost/logs/Predictalytics-20260428.json ist offenbar versehentlich eingecheckt und enthält Stacktraces mit lokalen Pfaden. Sollte aus der Versionierung entfernt und .gitignore ergänzt werden.

4.5 FindWwwrootPath() — hartkodierter Absolutpfad ⚠️

// EmbeddedWebServer.cs:314-321
var candidates = new[] {
    Path.Combine(baseDir, "wwwroot"),
    Path.GetFullPath(Path.Combine(baseDir, "..","..","..","..","Predictalytics.Api","wwwroot")),
    Path.GetFullPath(Path.Combine(baseDir, "..","..","..","..","..","src","Predictalytics.Api","wwwroot")),
    @"j:\Softwareprojekte\Predictalytics\Predictalytics\src\Predictalytics.Api\wwwroot"  // ← 
};

Die Suchheuristik über Build-Output-Verzeichnisse funktioniert im Deployment nicht zuverlässig, der j:\-Fallback gar nicht. Sauber: wwwroot als Content mit CopyToOutputDirectory in die Host-.csproj aufnehmen (in der Api-.csproj bereits vorhanden) und nur noch Path.Combine(AppContext.BaseDirectory, "wwwroot") verwenden.

4.6 MySQL-Verbindung: SslMode = None fest verdrahtet ⚠️

AppSettings.cs:100. Bei einem lokal laufenden Windows-Client zu einer entfernten DB war das schon fragwürdig; bei einem Serverbetrieb sollte SSL konfigurierbar (und standardmäßig aktiv) sein. Kein Linux-spezifisches Problem, fällt aber bei der Konfigurationsmigration ohnehin an.

4.7 Egress-Kanäle (SourceIp) — verhält sich unter Linux anders ⚠️

EgressPoolService.cs:70-88 bindet einen Socket per socket.Bind(new IPEndPoint(ip, 0)) an eine Quell-IP. Das funktioniert unter Linux grundsätzlich, aber:

  • Die IP muss auf einem Interface tatsächlich konfiguriert sein (sonst EADDRNOTAVAIL); Windows ist hier toleranter.
  • Für nicht-lokale IPs bräuchte es IP_FREEBIND/CAP_NET_ADMIN.

Falls Egress-Kanäle produktiv genutzt werden, ist das ein expliziter Testpunkt. Proxy-Kanäle (WebProxy) sind unproblematisch.

4.8 Zielframework-Inkonsistenz ⚠️

Predictalytics.Application.Tests steht auf net10.0, alle anderen auf net8.0. Das funktioniert (Test-Assembly darf höher liegen), erzwingt aber ein .NET-10-SDK auf dem Build-Server. Bei einer CI-Einrichtung für Linux vereinheitlichen — entweder alles auf net8.0 oder ein bewusster Sprung der gesamten Solution auf net10.0.

4.9 Predictalytics.slnx und Build

Die Solution referenziert den WinFormsHost. Auf Linux schlägt ein dotnet build der Gesamtsolution fehl (net8.0-windows + UseWindowsForms). Optionen:

  • Den WinFormsHost mit <TargetFrameworks> bzw. einer Condition auf $([MSBuild]::IsOSPlatform('Windows')) versehen, oder
  • eine zweite Solution-Datei für Linux-Builds pflegen.

Zusätzlich: Die .csproj des Hosts referenziert das Schwester-Repo über einen relativen Pfad mit vier Ebenen nach oben (..\..\..\..\LicenseLabrador\…). Das setzt eine feste Verzeichnisstruktur voraus und ist für CI/Container fragil — besser über ein NuGet-Paket oder ein Git-Submodul lösen.


5. Was zusätzlich neu entsteht (Deployment)

Kein Portierungsproblem im engeren Sinn, aber Teil der Aufgabe „Linux-fähig":

  • systemd-Unit (Restart=always, User=, WorkingDirectory=, EnvironmentFile=) oder alternativ Dockerfile + docker-compose (es existiert bereits ein J:\Softwareprojekte\dockerCompose-Verzeichnis — vermutlich anknüpfbar).
  • Reverse Proxy (nginx/Caddy) mit TLS vor Kestrel.
  • Secrets-Handling: DB-Passwort und Watchdog-API-Key liegen heute im Klartext in settings.json neben der Exe. Unter Linux → Environment-File mit 0600 oder Secret-Store. Randnotiz: In LicenseGuard.cs:16-17 stehen Basic-Auth-Zugangsdaten im Quelltext. Bei einem Server-Deployment (und erst recht bei der angedachten Monetarisierung) sollte das überdacht werden — unabhängig von dieser Portierung.
  • EF-Migrationen: EnsureDatabaseAsync() existiert bereits und wird beim Start aufgerufen — für den Serverbetrieb ist zu entscheiden, ob Migrationen automatisch laufen sollen oder als separater Schritt.
  • CI: Build/Test auf Linux, Publish als linux-x64 (ggf. self-contained).
  • docs/BETRIEB-Watchdog-Lizenz.md muss um den Linux-Betrieb ergänzt werden.

6. Aufwandsschätzung

Arbeitspaket Aufwand (PT) Risiko
A Neues Host-Projekt Predictalytics.Host (net8.0): Serilog, Kestrel, Worker, Lifecycle 23 niedrig — Composition Root existiert bereits
B Konfigurationsmigration AppSettings/PropertyGridappsettings.json + Env + IOptions 1 niedrig
C Manuelle Wartungsaktionen → CLI-Verben bzw. Admin-Endpunkte 11,5 niedrig
D Lizenzierung headless: nicht-interaktive Aktivierung, StorageDirectory, Timer, Cache-Absicherung 1,53 mittelhoch — berührt LicenseLabrador, serverseitig zu verifizieren
E Kestrel-Binding + CORS + Absicherung der Control-Endpunkte 0,51 mittel (Sicherheitsentscheidung nötig)
F Kleinkram: wwwroot-Deployment, Log-Pfad, Culture-Bug, TFM-Vereinheitlichung, Solution-Split, eingecheckte Logs 0,51 niedrig
G Deployment: systemd/Docker, Reverse Proxy, Secrets, CI 12 niedrigmittel
H Verifikation auf einer echten Linux-Instanz (Worker-Dauerlauf, DB, Egress, Watchdog, Lizenz) 2 mittel
Summe ~9,514,5 PT

Realistisch: 23 Wochen bei Teilzeit, ca. 2 Wochen fokussiert.

Zum Vergleich: Der ursprünglich angenommene Weg über Avalonia + LiveCharts2 hätte grob 48 Wochen gekostet — für eine Oberfläche, die es fachlich bereits als Web-UI gibt. Diese Arbeit wäre vollständig verloren gewesen.


7. Empfohlenes Vorgehen

  1. F zuerst (halber Tag): Culture-Bug, TFM, eingecheckte Logs, wwwroot-Deployment. Wirkt sofort, unabhängig von der Portierung, reduziert Rauschen.
  2. A + B parallel — das neue Host-Projekt entsteht neben dem WinFormsHost, nicht an seiner Stelle. Beide teilen sich ApiConfiguration und AddWorkerServices(). Ab hier läuft der Stack unter Linux (noch ohne Lizenz-Gate).
  3. E — Binding und Absicherung, bevor irgendetwas exponiert wird.
  4. D — Lizenzierung. Am besten früh anfangen zu klären, weil es das Schwester-Repo und ggf. Serveränderungen betrifft; die Umsetzung kann dann parallel laufen.
  5. C, G, H zum Abschluss.

Zwei Entscheidungen sind vorab zu treffen:

  • Bleibt der WinFormsHost erhalten? Empfehlung: ja, als Windows-Betriebsoberfläche. Er ist dünn, funktioniert und beide Hosts teilen sich denselben Kern. Der Web-UI-Zugang ist ohnehin identisch.
  • Wie wird headless lizenziert? Lizenzschlüssel per Umgebungsvariable/Config und Aktivierung beim ersten Start ist der pragmatische Weg — muss aber mit LicenseLabrador abgestimmt werden.

8. Zusammenfassung in drei Sätzen

Predictalytics ist in deutlich besserem Zustand für eine Linux-Portierung als erwartet: 96,9 % des Codes sind bereits plattformneutral, es gibt keine Windows-Zeitzonen-IDs und keine Windows-APIs außerhalb des Hosts, und die Analyse-Oberfläche ist längst eine Browser-Anwendung mit Chart.js — LiveCharts2 wird nicht gebraucht.

Zwei Defekte an der Grenze zu den Plattform-APIs (13 × lokalzeit-verzerrendes Datums-Parsing, 1 × kulturabhängiges Zahlen-Parsing) waren allerdings vorhanden und sind in Phase 0 behoben.

Der eigentliche Aufwand liegt im Ersetzen der 1.698 Zeilen umfassenden WinForms- Bedienhülle durch einen headless Host und im Umbau der Lizenzaktivierung, die heute zwingend einen Modaldialog erfordert.

Geschätzt 9,514,5 Personentage, mit dem einzigen nennenswerten Risiko im Lizenzierungspfad.