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(SharpPython.PyOptimLevels.O, 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");
}
}
}