Billing: Add functionality to collapse curves

This commit is contained in:
2024-01-22 23:05:54 +01:00
parent 3642c5ac07
commit 05909919e2
6 changed files with 314 additions and 36 deletions

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Billing {
public class BillingData {
@ -206,30 +207,41 @@ namespace Elwig.Helpers.Billing {
return curve[min] * p2 + curve[max] * p1;
}
protected static JsonNode GraphToJson(Graph graph, string mode) {
protected static JsonObject GraphToJson(Graph graph, string mode) {
var x = graph.DataX;
var y = graph.DataY;
if (y.Distinct().Count() == 1) {
return JsonValue.Create((decimal)graph.DataY[0]);
}
var prec = graph.Precision;
try {
return new JsonObject() {
["15kmw"] = Math.Round(x.Distinct().Single(), prec)
};
} catch { }
var data = new JsonObject();
if (y[0] != y[1]) {
data[$"{x[0]}{mode}"] = Math.Round(y[0], graph.Precision);
data[$"{x[0]}{mode}"] = Math.Round(y[0], prec);
}
for (int i = 1; i < x.Length - 1; i++) {
if (Math.Round(y[i] - y[i - 1], 10) != Math.Round(y[i + 1] - y[i], 10)) {
data[$"{x[i]}{mode}"] = Math.Round(y[i], graph.Precision);
var d1 = Math.Round(y[i] - y[i - 1], prec);
var d2 = Math.Round(y[i + 1] - y[i], prec);
if (d1 != d2) {
data[$"{x[i]}{mode}"] = Math.Round(y[i], prec);
}
}
if (y[^1] != y[^2]) {
data[$"{x[^1]}{mode}"] = Math.Round(y[^1], graph.Precision);
data[$"{x[^1]}{mode}"] = Math.Round(y[^1], prec);
}
return data;
}
protected static JsonObject GraphEntryToJson(GraphEntry entry) {
protected static JsonNode GraphEntryToJson(GraphEntry entry) {
try {
if (entry.GebundenFlatBonus == null) {
return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single());
}
} catch { }
var curve = new JsonObject {
["id"] = entry.Id,
["mode"] = entry.Mode.ToString().ToLower(),
@ -238,7 +250,7 @@ namespace Elwig.Helpers.Billing {
curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
if (entry.GebundenFlatBonus != null) {
curve["geb"] = entry.GebundenFlatBonus;
curve["geb"] = (decimal)entry.GebundenFlatBonus;
} else if (entry.GebundenGraph != null) {
curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower());
}
@ -246,31 +258,122 @@ namespace Elwig.Helpers.Billing {
return curve;
}
public JsonObject FromGraphEntries(IEnumerable<GraphEntry> graphEntries) {
protected static void CollapsePaymentData(JsonObject data) {
Dictionary<string, List<string>> rev1 = [];
Dictionary<decimal, List<string>> rev2 = [];
foreach (var (k, v) in data) {
if (k == "default" || k.StartsWith('/') || !k.Contains('/') || v is not JsonValue val) {
continue;
} else if (val.TryGetValue<decimal>(out var dec)) {
rev2[dec] = rev2.GetValueOrDefault(dec) ?? [];
rev2[dec].Add(k);
} else if (val.TryGetValue<string>(out var cur)) {
rev1[cur] = rev1.GetValueOrDefault(cur) ?? [];
rev1[cur].Add(k);
}
}
if (!data.ContainsKey("default")) {
foreach (var (v, ks) in rev1) {
if (ks.Count >= data.Count / 2.0) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data);
return;
}
}
foreach (var (v, ks) in rev2) {
if (ks.Count >= data.Count / 2.0) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data);
return;
}
}
}
var attributes = data
.Select(e => e.Key)
.Where(k => k.Length > 3 && k.Contains('/'))
.Select(k => "/" + k.Split('/')[1])
.Distinct()
.ToList();
foreach (var idx in attributes) {
var len = data.Count(e => e.Key.EndsWith(idx));
foreach (var (v, ks) in rev1) {
var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
if (myKs.Count > 1 && myKs.Count >= len / 2.0) {
foreach (var k in myKs) data.Remove(k);
data[idx] = v;
}
}
foreach (var (v, ks) in rev2) {
var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
if (myKs.Count > 1 && myKs.Count >= len / 2.0) {
foreach (var k in myKs) data.Remove(k);
data[idx] = v;
}
}
}
}
public static JsonObject FromGraphEntries(IEnumerable<GraphEntry> graphEntries, BillingData? origData = null) {
var payment = new JsonObject();
var qualityWei = new JsonObject();
var curves = new JsonArray();
int curveId = 0;
foreach (var entry in graphEntries) {
var curve = GraphEntryToJson(entry);
JsonValue node;
if (curve is JsonObject obj) {
obj["id"] = ++curveId;
node = JsonValue.Create($"curve:{curveId}");
curves.Add(obj);
} else if (curve is JsonValue val && val.TryGetValue<decimal>(out var flat)) {
node = JsonValue.Create(flat);
} else {
continue;
}
foreach (var c in entry.Contracts) {
if (entry.Abgewertet) {
qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
} else {
payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
}
}
}
CollapsePaymentData(payment);
CollapsePaymentData(qualityWei);
var data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
if (ConsiderDelieryModifiers)
if (origData?.ConsiderDelieryModifiers == true)
data["consider_delivery_modifiers"] = true;
if (ConsiderContractPenalties)
if (origData?.ConsiderContractPenalties == true)
data["consider_contract_penalties"] = true;
if (ConsiderTotalPenalty)
if (origData?.ConsiderTotalPenalty == true)
data["consider_total_penalty"] = true;
if (ConsiderAutoBusinessShares)
if (origData?.ConsiderAutoBusinessShares == true)
data["consider_auto_business_shares"] = true;
var payment = new JsonObject();
var curves = new JsonArray();
foreach (var entry in graphEntries) {
curves.Add(GraphEntryToJson(entry));
foreach (var contract in entry.Contracts) {
payment[$"{contract.Variety?.SortId}/{contract.Attribute?.AttrId}"] = $"curve:{entry.Id}";
}
if (payment.Count == 0) {
data["payment"] = 0;
} else if (payment.Count == 1) {
data["payment"] = payment.Single().Value?.DeepClone();
} else {
data["payment"] = payment;
}
if (qualityWei.Count == 1) {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei.Single().Value
};
} else if (qualityWei.Count > 1) {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei
};
}
data["payment"] = payment;
data["curves"] = curves;
return data;

View File

@ -18,10 +18,11 @@ namespace Elwig.Helpers.Billing {
public double? GebundenFlatBonus {
get {
try {
return GebundenGraph?.DataX.Zip(GebundenGraph.DataY)
var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY)
.Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision))
.Distinct()
.Single();
return (val == 0) ? null : val;
} catch {
return null;
}

View File

@ -23,6 +23,13 @@ namespace Elwig.Models.Entities {
[Column("fill_lower")]
public int FillLower { get; set; }
public WineAttr() { }
public WineAttr(string attrId, string name) {
AttrId = attrId;
Name = name;
}
public override string ToString() {
return Name;
}

View File

@ -21,6 +21,13 @@ namespace Elwig.Models.Entities {
public bool IsRed => Type == "R";
public bool IsWhite => Type == "W";
public WineVar() { }
public WineVar(string sortId, string name) {
SortId = sortId;
Name = name;
}
public override string ToString() {
return Name;
}

View File

@ -638,7 +638,7 @@ namespace Elwig.Windows {
private async void SaveButton_Click(object sender, RoutedEventArgs e) {
var origData = BillingData.FromJson(PaymentVar.Data);
var data = origData.FromGraphEntries(GraphEntries);
var data = BillingData.FromGraphEntries(GraphEntries, origData);
EntityEntry<PaymentVar>? tr = null;
try {

View File

@ -1,10 +1,13 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text.Json;
namespace Tests.HelperTests {
[TestFixture]
public class BillingDataTest {
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
private static readonly string[] AttributeVariants = ["GV", "GVD", "GVK", "GVS", "GVZ", "WR", "WRS", "ZW", "ZWS", "ZWZ"];
[OneTimeSetUp]
@ -41,7 +44,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_01_Flatrate() {
public void TestRead_01_Flatrate() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -57,7 +60,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_02_Simple() {
public void TestRead_02_Simple() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -92,7 +95,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_03_GreaterThanAndLessThan() {
public void TestRead_03_GreaterThanAndLessThan() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -131,7 +134,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_04_VariantsAndAttributes() {
public void TestRead_04_VariantsAndAttributes() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -161,7 +164,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_05_QualityLevel() {
public void TestRead_05_QualityLevel() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -192,7 +195,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_06_ModeOeAndKmw() {
public void TestRead_06_ModeOeAndKmw() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -234,7 +237,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_07_MultipleCurves() {
public void TestRead_07_MultipleCurves() {
var data = PaymentBillingData.FromJson("""
{
"mode": "elwig",
@ -303,7 +306,7 @@ namespace Tests.HelperTests {
}
[Test]
public void Test_08_WgMaster() {
public void TestRead_08_WgMaster() {
var data = PaymentBillingData.FromJson("""
{
"mode": "wgmaster",
@ -345,5 +348,162 @@ namespace Tests.HelperTests {
TestCalcOe(data, "GVK", 115, 0.065m);
});
}
private static List<ContractSelection> GetSelection(IEnumerable<string> attVars) {
return attVars.Select(s => {
var sortid = s[..2];
var attrid = s.Length > 2 ? s[2..] : null;
return new ContractSelection(
new WineVar(sortid, sortid),
attrid == null ? null : new WineAttr(attrid, attrid)
);
}).ToList();
}
[Test]
public void TestWrite_01_Empty() {
List<GraphEntry> entries = [
new GraphEntry(1, 4, BillingData.CurveMode.Oe, new() {
[73] = 0.5m
}, null)
];
var updated = BillingData.FromGraphEntries(entries);
Assert.That(updated.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": 0,
"curves": []
}
"""));
}
[Test]
public void TestWrite_02_Flatrate() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m
}, null), GetSelection(["GV"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": 0.5,
"curves": []
}
"""));
}
[Test]
public void TestWrite_03_SingleCurve() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[83] = 1.0m
}, null), GetSelection(["GV"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": "curve:1",
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"83oe": 1
}
}
]
}
"""));
}
[Test]
public void TestWrite_04_Simple() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV", "ZW"])),
new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m,
}, null), GetSelection(["WR"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"WR/": 0.75,
"default": "curve:1"
},
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"84oe": 1
}
}
]
}
"""));
}
[Test]
public void TestWrite_05_Attribute() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GVB", "ZWB"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m,
}, null), GetSelection(["WR", "BL", "RR", "FV"])),
new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.65m,
[84] = 1.2m
}, null), GetSelection(["BP", "SA"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"BP/": "curve:2",
"SA/": "curve:2",
"default": 0.75,
"/B": "curve:1"
},
"curves": [
{
"id": 1,
"mode": "oe",
"data": {
"73oe": 0.5,
"84oe": 1
}
},
{
"id": 2,
"mode": "oe",
"data": {
"73oe": 0.65,
"84oe": 1.2
}
}
]
}
"""));
}
}
}