using Python.Runtime; using System.ComponentModel; using System.Text.Json; namespace dopt.DeltaBarth { /// /// Spiegelung der internen Fehlertypen innerhalb der Python-Bibliothek /// public enum StatusCodes { /// /// erfolgreiche, fehlerfreie Durchführung der Routine /// [Description("Keine Fehler aufgetreten")] Erfolg = 0, /// /// Bei der API-Abfrage wurde der Timeout getriggert. /// [Description("Bei der Verbindung zum API-Server kam es zum Timeout")] VerbindungTimeout = 1, /// /// Bei der API-Abfrage ist ein unerwarteter Fehler aufgetreten, der unmittelbar die HTTP-Anfrage betrifft. /// [Description("Bei der Verbindung zum API-Server ist ein Fehler aufgetreten")] VerbindungFehler = 2, /// /// Der ausgewählte Datensatz enthält nicht genügend Datenpunkte. /// [Description("Der bereitgestellte Datensatz enthält in Summe zu wenige Einzeleinträge")] DatensatzZuWenigeDatenpunkte = 3, /// /// Der ausgewählte Datensatz enthält nach Aggregation pro Monat nicht genügend Datenpunkte. /// [Description("Der bereitgestellte Datensatz enthält nach Aggregation zu Monaten zu wenig Einträge")] DatensatzZuWenigeMonatsdatenpunkte = 4, /// /// Die Prognosequalität des Modells ist nicht zufriedenstellend. Eine verlässliche Prognose ist nicht möglich. /// [Description("Die Prognosequalität des Modells erfüllt nicht die Mindestanforderungen")] KeineVerlaesslichePrognose = 5, /// /// Es ist intern ein Fehler aufgetreten aufgetreten /// [Description("Interne Fehler, die während der Routine aufgetreten sind")] InternerFehler = 100, /// /// Es ist ein Fehler beim Schreiben der programminternen Datenbank aufgetreten. /// [Description("Interne Fehler, die während der Datenbankinteraktion aufgetreten sind")] InternerDbFehler = 150, /// /// Es ist ein Fehler auf dem API-Server aufgetreten. /// Das dazugehörige Status-Objekt sollte diesen Fehler zur Verfügung stellen können. /// /// /// [Description("Vom API-Server wurde eine Fehlermeldung zurückgegeben")] ApiServerFehler = 400, } /// /// Eine Exception, die genutzt wird, um anzuzeigen, dass beim Parsen der Python-Objekte /// ein Fehler aufgetreten ist. /// public class PythonParsingException : Exception { /// /// Konstruktor ohne Inhalt /// public PythonParsingException() { } /// /// Konstruktor mit Nachricht /// /// public PythonParsingException(string message) : base(message) { } } /// /// Plugin-Klasse, mit der die Interaktion der zugrundeliegenden Python-Runtime erfolgt /// public class Plugin : SharpPython.BasePlugin { /// /// Python-interne Zustandsverwaltung /// protected dynamic pyModManagement; /// /// Python-interne Routinen und Pipelines /// protected dynamic pyModPipeline; /// /// Konstruktor der Plugin-Klasse. /// Kann mit beliebigem Pfad zu einer Python-Runtime initialisiert werden. /// /// Der Pfad zur Python-Runtime. Dieser muss zu dem Ordner zeigen, /// in welchem die Runtime in Form eines Ordners mit dem Namen "python" abliegt. public Plugin(string runtimePath) : base(runtimePath: runtimePath, verbose: false) { base.Initialise(); using (Py.GIL()) { pyModManagement = Py.Import("delta_barth.management"); pyModPipeline = Py.Import("delta_barth.pipelines"); } } /// /// Initialisiert das Plugin mit allen relevanten Paramtern für die weitere Nutzung. /// Diese Methode sollte nur einmal je Instanz genutzt werden. /// /// Pfad zu einem Ordner, in dem Programmdaten ohne Bedenken dauerhaft abgelegt werden können. /// Basis-URL zum Zugriff auf die API. Dies muss eine vollständige URL sein inkl. der Route "/api". /// Nutzername für die Datenbankanmeldung. /// Passwort für die Datenbankanmeldung. /// Name der Datenbank, bei der die Anmeldung erfolgen soll. /// Mandant für die Datenbankanmeldung. public void Startup(string datenPfad, string basisApiUrl, string nutzername, string passwort, string datenbank, string mandant) { AssertNotDisposed(); Setup(datenPfad, basisApiUrl); SetzeNutzerdaten(nutzername, passwort, datenbank, mandant); } /// /// Diese Methode erlaubt es, die relevanten Nutzerdaten zur Laufzeit des Plugins zu ändern. /// Dies beinhaltet: Nutzername, Passwort, Datenbankname, Mandant /// /// Nutzername für die Datenbankanmeldung. /// Passwort für die Datenbankanmeldung. /// Name der Datenbank, bei der die Anmeldung erfolgen soll. /// Mandant für die Datenbankanmeldung. public void SetzeNutzerdaten(string nutzername, string passwort, string datenbank, string mandant) { AssertNotDisposed(); using (Py.GIL()) { pyModManagement.set_credentials(nutzername, passwort, datenbank, mandant); } } /// /// Ausführung der Umsatzprognose-Pipeline mit Dummy-Daten. /// Es werden keine API-Abrufe durchgeführt und somit auch keine Live-Daten genutzt. /// /// optional: Firmen-ID, für die die Pipeline ausgeführt werden soll. /// Wird der Parameter nicht zur Verfügung gestellt, werden alle Firmen bzw. Kunden abgerufen /// optional: Start-Datum, ab dem die Daten für die Erstellung des Prognosemodells genutzt werden. /// Daten, die weiter in der Vergangenheit liegen, werden nicht berücksichtigt. /// Wird der Parameter nicht zur Verfügung gestellt, wird die gesamte Historie genutzt. /// Umsatzprognose inkl. Status-Objekt zur Nachvollziehbarkeit etwaig aufgetretener Fehler. /// public DataObjects.UmsatzPrognoseAusgabe UmsatzprognoseDummy(int? firmaId, DateTime? analyseBeginn) { AssertNotDisposed(); string pyJson; using (Py.GIL()) { pyJson = pyModPipeline.pipeline_sales_forecast_dummy(firmaId, analyseBeginn); } var parsed = JsonSerializer.Deserialize(pyJson) ?? throw new PythonParsingException("Could not correctly parse object from Python"); return parsed; } /// /// Ausführung der Umsatzprognose-Pipeline mit Live-Daten. /// Es werden API-Abrufe durchgeführt und somit auch Live-Daten genutzt. /// Hierfür muss sichergestellt sein, dass der API-Server erreichbar und abrufbereit ist. /// /// optional: Firmen-ID, für die die Pipeline ausgeführt werden soll. /// Wird der Parameter nicht zur Verfügung gestellt, werden alle Firmen bzw. Kunden abgerufen /// optional: Start-Datum, ab dem die Daten für die Erstellung des Prognosemodells genutzt werden. /// Daten, die weiter in der Vergangenheit liegen, werden nicht berücksichtigt. /// Wird der Parameter nicht zur Verfügung gestellt, wird die gesamte Historie genutzt. /// Umsatzprognose inkl. Status-Objekt zur Nachvollziehbarkeit etwaig aufgetretener Fehler. /// public DataObjects.UmsatzPrognoseAusgabe Umsatzprognose(int? firmaId, DateTime? analyseBeginn) { AssertNotDisposed(); string pyJson; using (Py.GIL()) { pyJson = pyModPipeline.pipeline_sales_forecast(firmaId, analyseBeginn); } var parsed = JsonSerializer.Deserialize(pyJson) ?? throw new PythonParsingException("Could not correctly parse object from Python"); return parsed; } /// /// Setup der Python-internen Umgebung /// /// Pfad zu einem Ordner, in dem Programmdaten ohne Bedenken dauerhaft abgelegt werden können. /// Basis-URL zum Zugriff auf die API. Dies muss eine vollständige URL sein inkl. der Route "/api". protected void Setup(string datenPfad, string basisApiUrl) { AssertNotDisposed(); using (Py.GIL()) { pyModManagement.setup(datenPfad, basisApiUrl); } } /// /// Hole die konfigurierte API-Basis-URL aus der Python-Umgebung /// /// konfigurierte Basis-URL protected string GetBaseApiUrl() { AssertNotDisposed(); string pyJson; using (Py.GIL()) { pyJson = (string)pyModManagement.get_base_url(); } return pyJson; } /// /// Hole den konfigurierten Datenpfad zur Dateiverwaltung aus der Python-Umgebung /// /// konfigurierten Datenpfad protected string GetDataPath() { AssertNotDisposed(); string pyJson; using (Py.GIL()) { pyJson = (string)pyModManagement.get_data_path(); } return pyJson; } /// /// Hole die konfigurierten Nutzerdaten zur API-Interaktion aus der Python-Umgebung /// /// konfigurierte Nutzerdaten /// protected DataObjects.Credentials GetCredentials() { AssertNotDisposed(); string pyJson; using (Py.GIL()) { pyJson = (string)pyModManagement.get_credentials(); } DataObjects.Credentials? parsed = JsonSerializer.Deserialize(pyJson); return parsed ?? throw new PythonParsingException("Could not correctly parse object from Python"); } } }