Compare commits

..

No commits in common. "main" and "v0.2.0" have entirely different histories.
main ... v0.2.0

8 changed files with 113 additions and 473 deletions

1
.gitignore vendored
View File

@ -4,7 +4,6 @@
## Get latest from `dotnet new gitignore`
**/python/
CREDENTIALS*
test_data_path/
# dotenv files
.env

View File

@ -1,13 +0,0 @@
param(
[switch]$Get = $false
)
if ($Get) {
pycage get -rt 20250409 3.11.12
pycage venv upgrade-pip
}
pycage venv add -i http://localhost:8001/simple/ delta-barth
pycage compile -f -d
pycage compile -o 1 -f
pycage clean dist-info

View File

@ -5,32 +5,28 @@ using System.Text.Json;
namespace dopt.DeltaBarth.Tests
{
using System.Collections.Immutable;
using System.Text.Encodings.Web;
using System.Text.Unicode;
internal class TestPlugin : DeltaBarth.Plugin
{
internal dynamic pyModJsonData;
internal dynamic pyModJsonStructs;
private const string absPath = @"A:\Arbeitsaufgaben\Delta-Barth\cs-wrapper\dopt.DeltaBarth";
internal TestPlugin() : base(absPath) {
using (Py.GIL())
{
pyModJsonData = Py.Import("delta_barth._csharp.json_types");
pyModJsonStructs = Py.Import("delta_barth._csharp.json_types");
}
}
}
[TestClass]
public class DataObjectsTest
public class JsonStructsTest
{
static void PrettyPrint(object toSerialise)
{
string prettyJson = JsonSerializer.Serialize(toSerialise, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
});
Console.WriteLine($"Parsed data is: {prettyJson}");
Console.WriteLine($"Parsed struct is: {prettyJson}");
}
[TestMethod]
public void Parse_Error_Status_Test()
@ -39,14 +35,13 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.status_err();
pyJson = (string)plugin.pyModJsonStructs.status_err();
}
var parsed = JsonSerializer.Deserialize<DataObjects.Status>(pyJson);
if (parsed == null) { throw new PythonParsingException("Could not correctly parse object from Python"); }
var parsed = JsonSerializer.Deserialize<JsonStructs.Status>(pyJson);
Assert.AreEqual(102, parsed.code);
Assert.IsTrue(parsed.description.Contains("internal error occurred"));
Assert.AreEqual("internal error occurred", parsed.description);
Assert.AreEqual("caused by test", parsed.message);
Assert.IsNull(parsed.api_server_error);
Assert.IsNull(parsed.apiServerError);
PrettyPrint(parsed);
plugin.Dispose();
}
@ -57,10 +52,9 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.delta_barth_api_error();
pyJson = (string)plugin.pyModJsonStructs.delta_barth_api_error();
}
var parsed = JsonSerializer.Deserialize<DataObjects.ApiServerError>(pyJson);
if (parsed == null) { throw new PythonParsingException("Could not correctly parse object from Python"); }
var parsed = JsonSerializer.Deserialize<JsonStructs.ApiServerError>(pyJson);
Assert.AreEqual(401, parsed.status_code);
Assert.AreEqual("test message", parsed.message);
Assert.AreEqual("test code", parsed.code);
@ -78,10 +72,9 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.api_credentials();
pyJson = (string)plugin.pyModJsonStructs.api_credentials();
}
var parsed = JsonSerializer.Deserialize<DataObjects.Credentials>(pyJson);
if (parsed == null) { throw new PythonParsingException("Could not correctly parse object from Python"); }
var parsed = JsonSerializer.Deserialize<JsonStructs.Credentials>(pyJson);
Assert.AreEqual("user", parsed.username);
Assert.AreEqual("pass", parsed.password);
Assert.AreEqual("test1", parsed.database);
@ -96,10 +89,9 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.sales_prognosis_result();
pyJson = (string)plugin.pyModJsonStructs.sales_prognosis_result();
}
var parsed = JsonSerializer.Deserialize<DataObjects.UmsatzPrognoseEinzelergebnis>(pyJson);
if (parsed == null) { throw new PythonParsingException("Could not correctly parse object from Python"); }
var parsed = JsonSerializer.Deserialize<JsonStructs.UmsatzPrognoseEinzelergebnis>(pyJson);
Assert.AreEqual(2023, parsed.jahr);
Assert.AreEqual(12, parsed.monat);
Assert.AreEqual(3000.3456m, parsed.vorhersage);
@ -113,15 +105,14 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.sales_prognosis_results();
pyJson = (string)plugin.pyModJsonStructs.sales_prognosis_results();
}
var parsed = JsonSerializer.Deserialize<DataObjects.UmsatzPrognoseErgebnisse>(pyJson);
Assert.IsNotNull(parsed);
var parsed = JsonSerializer.Deserialize<JsonStructs.UmsatzPrognoseErgebnisse>(pyJson);
Assert.AreEqual(3, parsed.daten.Length);
var e1 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2023, monat = 12, vorhersage = 3000.3456m };
var e2 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 1, vorhersage = 3300.685m };
var e3 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 2, vorhersage = 3700.548m };
ImmutableArray<DataObjects.UmsatzPrognoseEinzelergebnis> arr = ImmutableArray.Create(e1, e2, e3);
var e1 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2023, monat = 12, vorhersage = 3000.3456m };
var e2 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 1, vorhersage = 3300.685m };
var e3 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 2, vorhersage = 3700.548m };
ImmutableArray<JsonStructs.UmsatzPrognoseEinzelergebnis> arr = ImmutableArray.Create(e1, e2, e3);
for (int i = 0; i < parsed.daten.Length; i++)
{
@ -137,21 +128,20 @@ namespace dopt.DeltaBarth.Tests
string pyJson;
using (Py.GIL())
{
pyJson = (string)plugin.pyModJsonData.sales_prognosis_results_export();
pyJson = (string)plugin.pyModJsonStructs.sales_prognosis_results_export();
}
var parsed = JsonSerializer.Deserialize<DataObjects.UmsatzPrognoseAusgabe>(pyJson);
if (parsed == null) { throw new PythonParsingException("Could not correctly parse object from Python"); }
var parsed = JsonSerializer.Deserialize<JsonStructs.UmsatzPrognoseAusgabe>(pyJson);
// result
var e1 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2023, monat = 12, vorhersage = 3000.3456m };
var e2 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 1, vorhersage = 3300.685m };
var e3 = new DataObjects.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 2, vorhersage = 3700.548m };
ImmutableArray<DataObjects.UmsatzPrognoseEinzelergebnis> arr = ImmutableArray.Create(e1, e2, e3);
var data = new DataObjects.UmsatzPrognoseErgebnisse { daten = arr };
var e1 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2023, monat = 12, vorhersage = 3000.3456m };
var e2 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 1, vorhersage = 3300.685m };
var e3 = new JsonStructs.UmsatzPrognoseEinzelergebnis { jahr = 2024, monat = 2, vorhersage = 3700.548m };
ImmutableArray<JsonStructs.UmsatzPrognoseEinzelergebnis> arr = ImmutableArray.Create(e1, e2, e3);
var data = new JsonStructs.UmsatzPrognoseErgebnisse { daten = arr };
// check status
Assert.AreEqual(0, parsed.status.code);
Assert.AreEqual("Erfolg", parsed.status.description);
Assert.AreEqual("", parsed.status.message);
Assert.IsNull(parsed.status.api_server_error);
Assert.IsNull(parsed.status.apiServerError);
// check result
for (int i = 0; i < parsed.response.daten.Length; i++)
{

View File

@ -4,7 +4,6 @@ using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext
namespace dopt.DeltaBarth.Tests
{
using System.Text.Encodings.Web;
using Microsoft.Extensions.Configuration;
internal class Config
{
@ -40,7 +39,7 @@ namespace dopt.DeltaBarth.Tests
{
private const string absPath = @"A:\Arbeitsaufgaben\Delta-Barth\cs-wrapper\dopt.DeltaBarth";
public TPlugin() : base(absPath) { }
public new DataObjects.Credentials GetCredentials()
public new JsonStructs.Credentials GetCredentials()
{
return base.GetCredentials();
}
@ -48,25 +47,15 @@ namespace dopt.DeltaBarth.Tests
{
return base.GetBaseApiUrl();
}
public new string GetDataPath()
{
return base.GetDataPath();
}
public new void Setup(string datenPfad, string basisApiUrl)
{
base.Setup(datenPfad, basisApiUrl);
}
}
[TestClass]
public sealed class PluginTest
{
private const string baseDataPath = @"A:\Arbeitsaufgaben\Delta-Barth\cs-wrapper\dopt.DeltaBarth\test_data_path";
internal Config config = new Config();
static void PrettyPrint(object toSerialise)
{
string prettyJson = JsonSerializer.Serialize(toSerialise, new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
});
Console.WriteLine($"Parsed struct is: {prettyJson}");
@ -92,19 +81,13 @@ namespace dopt.DeltaBarth.Tests
test.Dispose();
}
[TestMethod]
public void SetupSession_Test()
public void Set_and_Obtain_BaseApiUrl_Test()
{
var test = new TPlugin();
string apiUrlSet = "http://10.2.22.21:8080/api/";
test.Setup(baseDataPath, apiUrlSet);
// data path
var dataPathGet = test.GetDataPath();
Console.WriteLine($"Result for data path from Python session was: {dataPathGet}");
bool pathElementsContained = dataPathGet.Contains("test_data_path");
Assert.IsTrue(pathElementsContained);
// API URL
test.SetzeBasisApiUrl(apiUrlSet);
var apiUrlGet = test.GetBaseApiUrl();
Console.WriteLine($"Result for API URL from Python session was: {apiUrlGet}");
Console.WriteLine($"Result from Python session was: {apiUrlGet}");
Assert.AreEqual(apiUrlSet, apiUrlGet);
test.Dispose();
}
@ -113,7 +96,7 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = "http://10.2.22.21:8080/api/", user = "user", password = "password", database = "DB1", mandant = "mandant1";
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var apiUrlGet = test.GetBaseApiUrl();
var creds = test.GetCredentials();
Console.WriteLine($"Result from Python session was: API-URL={apiUrlGet}, creds={creds}");
@ -130,10 +113,9 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = "http://10.2.22.21:8080/api/", user = "user", password = "password", database = "DB1", mandant = "mandant1";
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var res = test.UmsatzprognoseDummy(null, null);
PrettyPrint(res);
Assert.AreEqual((int)StatusCodes.Erfolg, res.status.code);
test.Dispose();
}
[TestMethod]
@ -141,11 +123,10 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = "http://10.2.22.21:8080/api/", user = "user", password = "password", database = "DB1", mandant = "mandant1";
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var comp_id = 1000;
var res = test.UmsatzprognoseDummy(comp_id, null);
PrettyPrint(res);
Assert.AreEqual((int)StatusCodes.Erfolg, res.status.code);
test.Dispose();
}
[TestMethod]
@ -153,64 +134,23 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = "http://10.2.22.21:8080/api/", user = "user", password = "password", database = "DB1", mandant = "mandant1";
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var date = new DateTime(2023, 1, 1, 12, 45, 30);
var res = test.UmsatzprognoseDummy(null, date);
PrettyPrint(res);
Assert.AreEqual((int)StatusCodes.Erfolg, res.status.code);
test.Dispose();
}
[TestMethod]
public void Umsatzprognose_NoConnectionNeeded_Test()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
var date = new DateTime(2030, 1, 1, 12, 45, 30);
var res = test.Umsatzprognose(null, date);
PrettyPrint(res);
test.Dispose();
}
[TestMethod]
[TestCategory("ActiveAPI")]
public void Umsatzprognose_WithCompanyIdWithDate_Test()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
var date = new DateTime(2023, 8, 15, 12, 45, 30);
List<int> comp_id = new List<int> { 1027, 5661, 1024 };
var res = test.Umsatzprognose(comp_id, date);
PrettyPrint(res);
Assert.AreEqual(4, res.status.code);
Assert.AreEqual((int)StatusCodes.DatensatzZuWenigeMonatsdatenpunkte, res.status.code);
test.Dispose();
}
[TestMethod]
[TestCategory("ActiveAPI")]
public void Umsatzprognose_WithCompanyIdWithoutDate_Test()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
List<int> comp_id = new List<int> { 5661 };
test.Startup(apiUrlSet, user, password, database, mandant);
var comp_id = 1024;
var res = test.Umsatzprognose(comp_id, null);
PrettyPrint(res);
Assert.AreEqual(4, res.status.code);
Assert.AreEqual((int)StatusCodes.DatensatzZuWenigeMonatsdatenpunkte, res.status.code);
test.Dispose();
}
[TestMethod]
public void Umsatzprognose_WithCompanyIdWithoutDate_Test_FailConnection()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
List<int> comp_id = new List<int> { 5661 };
var res = test.Umsatzprognose(comp_id, null);
PrettyPrint(res);
Assert.AreEqual(1, res.status.code);
Assert.AreEqual((int)StatusCodes.VerbindungTimeout, res.status.code);
test.Dispose();
}
[TestMethod]
@ -219,25 +159,10 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var date = new DateTime(2030, 1, 1, 12, 45, 30);
var res = test.Umsatzprognose(null, date);
PrettyPrint(res);
Assert.AreEqual(3, res.status.code);
Assert.AreEqual((int)StatusCodes.DatensatzZuWenigeDatenpunkte, res.status.code);
test.Dispose();
}
[TestMethod]
public void Umsatzprognose_WithoutCompanyIdWithInvalidDate_Test_FailConnection()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
var date = new DateTime(2030, 1, 1, 12, 45, 30);
var res = test.Umsatzprognose(null, date);
PrettyPrint(res);
Assert.AreEqual(1, res.status.code);
Assert.AreEqual((int)StatusCodes.VerbindungTimeout, res.status.code);
test.Dispose();
}
[TestMethod]
@ -246,25 +171,10 @@ namespace dopt.DeltaBarth.Tests
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
var date = new DateTime(2021, 1, 1, 12, 45, 30);
var res = test.Umsatzprognose(null, date);
PrettyPrint(res);
Assert.AreEqual(0, res.status.code);
Assert.AreEqual((int)StatusCodes.Erfolg, res.status.code);
test.Dispose();
}
[TestMethod]
public void Umsatzprognose_WithoutCompanyIdWithValidDate_Test_FailConnection()
{
var test = new TPlugin();
string apiUrlSet = config.apiUrl, user = config.username, password = config.password, database = config.database, mandant = config.mandant;
test.Startup(baseDataPath, apiUrlSet, user, password, database, mandant);
test.Startup(apiUrlSet, user, password, database, mandant);
var date = new DateTime(2015, 1, 1, 12, 45, 30);
var res = test.Umsatzprognose(null, date);
PrettyPrint(res);
Assert.AreEqual(1, res.status.code);
Assert.AreEqual((int)StatusCodes.VerbindungTimeout, res.status.code);
test.Dispose();
}
}

View File

@ -1,140 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Immutable;
namespace dopt.DeltaBarth.DataObjects
{
/// <summary>
/// Eine verpackte Fehlermeldung, die vom API-Server zur Verfügung gestellt wurde.
/// </summary>
public record class ApiServerError
{
/// <summary>
/// Statuscode der HTTP-Anfrage
/// </summary>
public required int status_code { get; init; }
/// <summary>
/// Fehlerbezeichnung
/// </summary>
public required string message { get; init; }
/// <summary>
/// optional: spezifischer Code als Text, der vom Server ausgegeben wurde
/// </summary>
public string? code { get; init; }
/// <summary>
/// optional: spezifische Hinweise als Text, der vom Server ausgegeben wurde
/// </summary>
public string? hints { get; init; }
/// <summary>
/// optional: spezifischer Typus als Text, der vom Server ausgegeben wurde
/// </summary>
public string? type { get; init; }
/// <summary>
/// optional: spezifischer Titel als Text, der vom Server ausgegeben wurde
/// </summary>
public string? title { get; init; }
/// <summary>
/// optional: zusätzliche Fehlerdetails, die vom Server ausgegeben wurden
/// </summary>
public Dictionary<string, List<string>>? errors { get; init; }
/// <summary>
/// optional: spezifischer ID (vermutlich zur Nachverfolgung) als Text, der vom Server ausgegeben wurde
/// </summary>
public string? traceId { get; init; }
}
/// <summary>
/// Status-Objekt: Gibt Aufschluss über Erfolg/Misserfolg einer Routine und beschreibt aufgetretene Fehler
/// mit zusätzlichen Informationen
/// </summary>
public record class Status
{
/// <summary>
/// bibliotheksinterner Fehlercode
/// </summary>
public required int code { get; init; }
/// <summary>
/// Fehlerbeschreibung oder Python-Exception-Name
/// </summary>
public required string description { get; init; }
/// <summary>
/// genauere Beschreibung oder Fehler-Inhalt
/// </summary>
public required string message { get; init; }
/// <summary cref="ApiServerError">
/// optional: eventuell aufgetretener API-Server-Fehler
/// </summary>
public ApiServerError? api_server_error { get; init; }
}
/// <summary>
/// Nutzerdaten zur API-Interaktion
/// </summary>
public record class Credentials
{
/// <summary>
/// Nutzername
/// </summary>
public required string username { get; init; }
/// <summary>
/// Passwort
/// </summary>
public required string password { get; init; }
/// <summary>
/// Datenbank
/// </summary>
public required string database { get; init; }
/// <summary>
/// Mandant
/// </summary>
public required string mandant { get; init; }
}
/// <summary>
/// Einzelergebnis der Umsatzprognose, 1 Eintrag von potenziell mehreren
/// </summary>
public record class UmsatzPrognoseEinzelergebnis
{
/// <summary>
/// Jahr des Eintrags
/// </summary>
public required int jahr { get; init; }
/// <summary>
/// Monat des Eintrags
/// </summary>
public required int monat { get; init; }
/// <summary>
/// Prognosewert für den Umsatz
/// </summary>
public required decimal vorhersage { get; init; }
}
/// <summary>
/// Sammlung von unterschiedlichen Einzelergebnissen
/// <see cref="UmsatzPrognoseEinzelergebnis"/>
/// </summary>
public record class UmsatzPrognoseErgebnisse
{
/// <summary>
/// unveränderliche Sammlung von Einzelergebnissen in Form eines Array
/// </summary>
public required ImmutableArray<UmsatzPrognoseEinzelergebnis> daten { get; init; }
}
/// <summary>
/// Ausgabe der Prognose-Pipeline:
/// enthält das Ergebnis als auch einen dazugehörigen Status
/// </summary>
public record class UmsatzPrognoseAusgabe
{
/// <summary>
/// Sammlung von Prognosewerten
/// <see cref="UmsatzPrognoseErgebnisse"/>
/// </summary>
public required UmsatzPrognoseErgebnisse response { get; init; }
/// <summary>
/// Status über den Erfolg/Misserfolg der Routine
/// <see cref="Status"/>
/// </summary>
public required Status status { get; init; }
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Immutable;
namespace dopt.DeltaBarth.JsonStructs
{
public readonly struct ApiServerError
{
public int status_code { get; init; }
public string message { get; init; }
public string? code { get; init; }
public string? hints { get; init; }
public string? type { get; init; }
public string? title { get; init; }
public string? traceId { get; init; }
}
public readonly struct Status
{
public int code { get; init; }
public string description { get; init; }
public string message { get; init; }
public ApiServerError? apiServerError { get; init; }
}
public readonly struct Credentials
{
public string username { get; init; }
public string password { get; init; }
public string database { get; init; }
public string mandant { get; init; }
}
public readonly struct UmsatzPrognoseEinzelergebnis
{
public int jahr { get; init; }
public int monat { get; init; }
public decimal vorhersage { get; init; }
}
public readonly struct UmsatzPrognoseErgebnisse
{
public ImmutableArray<UmsatzPrognoseEinzelergebnis> daten { get; init; }
}
public readonly struct UmsatzPrognoseAusgabe
{
public UmsatzPrognoseErgebnisse response { get; init; }
public Status status { get; init; }
}
}

View File

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

View File

@ -6,12 +6,11 @@
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms>
<Version>0.4.1</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>0.2.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dopt.SharpPython" Version="0.4.4" />
<PackageReference Include="dopt.SharpPython" Version="0.4.0" />
</ItemGroup>
</Project>