Files
elwig/Elwig/Helpers/Billing/BillingData.cs

382 lines
16 KiB
C#

using Newtonsoft.Json;
using NJsonSchema;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
public class BillingData {
public enum CalculationMode { Elwig, WgMaster }
public enum CurveMode { Oe, Kmw }
public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Gebunden);
public static JsonSchema? Schema { get; private set; }
public static async Task Init() {
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Resources.Schemas.PaymentVariantData.json");
Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found"));
}
public readonly JsonObject Data;
public readonly CalculationMode Mode;
public bool ConsiderDelieryModifiers {
get => GetConsider("consider_delivery_modifiers");
set => SetConsider(value, "consider_delivery_modifiers");
}
public bool ConsiderContractPenalties {
get => GetConsider("consider_contract_penalties");
set => SetConsider(value, "consider_contract_penalties");
}
public bool ConsiderTotalPenalty {
get => GetConsider("consider_total_penalty");
set => SetConsider(value, "consider_total_penalty");
}
public bool ConsiderAutoBusinessShares {
get => GetConsider("consider_auto_business_shares");
set => SetConsider(value, "consider_auto_business_shares");
}
private bool GetConsider(string name, string? wgMasterName = null) {
return ((Mode == CalculationMode.Elwig) ? Data[name] : Data[wgMasterName ?? ""])?.AsValue().GetValue<bool>() ?? false;
}
private void SetConsider(bool value, string name, string? wgMasterName = null) {
if (Mode == CalculationMode.WgMaster && wgMasterName == null) {
return;
} else if (value) {
Data[(Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""] = value;
} else {
Data.Remove((Mode == CalculationMode.Elwig) ? name : wgMasterName ?? "");
}
}
public BillingData(JsonObject data) {
Data = data;
var mode = Data["mode"]?.GetValue<string>();
Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
}
protected static JsonObject ParseJson(string json) {
if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first");
try {
var errors = Schema.Validate(json);
if (errors.Count != 0) throw new ArgumentException("Invalid JSON data");
return JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException("Invalid JSON data");
} catch (JsonReaderException) {
throw new ArgumentException("Invalid JSON data");
}
}
public static BillingData FromJson(string json) {
return new(ParseJson(json));
}
protected JsonArray GetCurvesEntry() {
return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
}
protected JsonNode GetPaymentEntry() {
return Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"] ?? throw new InvalidOperationException();
}
protected JsonObject? GetQualityEntry() {
return Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject();
}
private static Dictionary<double, decimal> GetCurveData(JsonObject data, CurveMode mode) {
var dict = new Dictionary<double, decimal>();
foreach (var (index, price) in data) {
double idx;
bool? gtlt = index.StartsWith('>') ? true : index.StartsWith('<') ? false : null;
if (index.EndsWith("kmw")) {
idx = double.Parse(index[(gtlt != null ? 1 : 0)..^3], CultureInfo.InvariantCulture);
if (mode == CurveMode.Oe) {
idx = Utils.KmwToOe(idx);
}
} else if (index.EndsWith("oe")) {
idx = double.Parse(index[(gtlt != null ? 1 : 0)..^2], CultureInfo.InvariantCulture);
if (mode == CurveMode.Kmw) {
idx = Utils.OeToKmw(idx);
}
} else {
throw new InvalidOperationException();
}
if (gtlt == true) {
idx = Math.BitIncrement(idx);
} else if (gtlt == false) {
idx = Math.BitDecrement(idx);
}
dict[idx] = price?.AsValue().GetValue<decimal>() ?? throw new InvalidOperationException();
}
return dict;
}
protected Dictionary<int, Curve> GetCurves() {
var dict = new Dictionary<int, Curve>();
var curves = GetCurvesEntry();
foreach (var c in curves) {
var obj = c?.AsObject() ?? throw new InvalidOperationException();
var id = obj["id"]?.GetValue<int>() ?? throw new InvalidOperationException();
var cMode = (obj["mode"]?.GetValue<string>() == "kmw") ? CurveMode.Kmw : CurveMode.Oe;
double quw = cMode == CurveMode.Oe ? 73 : 15;
Dictionary<double, decimal> c1;
Dictionary<double, decimal>? c2 = null;
var norm = obj["data"];
if (norm is JsonObject) {
c1 = GetCurveData(norm.AsObject(), cMode);
} else if (norm?.AsValue().TryGetValue(out decimal v) == true) {
c1 = new() { { quw, v } };
} else {
throw new InvalidOperationException();
}
var geb = obj["geb"];
if (geb is JsonObject) {
c2 = GetCurveData(geb.AsObject(), cMode);
} else if (geb?.AsValue().TryGetValue(out decimal v) == true) {
var splitVal = GetCurveValueAt(c1, quw);
c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0));
c2[quw] = splitVal + v;
c2[Math.BitDecrement(quw)] = splitVal;
}
dict.Add(id, new(cMode, c1, c2));
}
return dict;
}
protected static Dictionary<string, JsonValue> GetSelection(JsonNode value, IEnumerable<string> vaributes) {
if (value is JsonValue flatRate) {
return vaributes.ToDictionary(e => e, _ => flatRate);
} if (value is not JsonObject data) {
throw new InvalidOperationException();
}
Dictionary<string, JsonValue> dict;
if (data["default"] is JsonValue def) {
dict = vaributes.ToDictionary(e => e, _ => def);
} else {
dict = [];
}
var varieties = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2);
var attributes = data.Where(p => p.Key.StartsWith('/'));
var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2 && p.Key != "default");
foreach (var (idx, v) in varieties) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
foreach (var i in vaributes.Where(e => e.StartsWith(idx[..^1]))) {
dict[i] = curve;
}
}
foreach (var (idx, v) in attributes) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
foreach (var i in vaributes.Where(e => e[2..] == idx[1..])) {
dict[i] = curve;
}
}
foreach (var (idx, v) in others) {
var curve = v?.AsValue() ?? throw new InvalidOperationException();
dict[idx.Replace("/", "")] = curve;
}
return dict;
}
public static decimal GetCurveValueAt(Dictionary<double, decimal> curve, double key) {
if (curve.Count == 1) return curve.First().Value;
var lt = curve.Keys.Where(v => v <= key);
var gt = curve.Keys.Where(v => v >= key);
if (!lt.Any()) {
return curve[gt.Min()];
} else if (!gt.Any()) {
return curve[lt.Max()];
}
var max = lt.Max();
var min = gt.Min();
if (max == min) return curve[key];
var p1 = ((decimal)key - (decimal)min) / ((decimal)max - (decimal)min);
var p2 = 1 - p1;
return curve[min] * p2 + curve[max] * p1;
}
protected static JsonObject GraphToJson(Graph graph, string mode) {
var x = graph.DataX;
var y = graph.DataY;
var prec = graph.Precision;
try {
return new JsonObject() {
["15kmw"] = Math.Round(y.Distinct().Single(), prec)
};
} catch { }
var data = new JsonObject();
if (y[0] != y[1]) {
data[$"{x[0]}{mode}"] = Math.Round(y[0], prec);
}
for (int i = 1; i < x.Length - 1; i++) {
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], prec);
}
return data;
}
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(),
};
curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
if (entry.GebundenFlatBonus != null) {
curve["geb"] = (decimal)entry.GebundenFlatBonus;
} else if (entry.GebundenGraph != null) {
curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower());
}
return curve;
}
protected static void CollapsePaymentData(JsonObject data, IEnumerable<string> vaributes) {
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 >= vaributes.Count() / 2.0) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data, vaributes);
return;
}
}
foreach (var (v, ks) in rev2) {
if (ks.Count >= vaributes.Count() / 2.0) {
foreach (var k in ks) data.Remove(k);
data["default"] = v;
CollapsePaymentData(data, vaributes);
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 = vaributes.Count(e => e.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, IEnumerable<string>? vaributes = 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.Vaributes) {
if (entry.Abgewertet) {
qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
} else {
payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
}
}
}
CollapsePaymentData(payment, vaributes ?? payment.Select(e => e.Key).ToList());
CollapsePaymentData(qualityWei, vaributes ?? qualityWei.Select(e => e.Key).ToList());
var data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
if (origData?.ConsiderDelieryModifiers == true)
data["consider_delivery_modifiers"] = true;
if (origData?.ConsiderContractPenalties == true)
data["consider_contract_penalties"] = true;
if (origData?.ConsiderTotalPenalty == true)
data["consider_total_penalty"] = true;
if (origData?.ConsiderAutoBusinessShares == true)
data["consider_auto_business_shares"] = true;
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?.DeepClone()
};
} else if (qualityWei.Count > 1) {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei
};
}
data["curves"] = curves;
return data;
}
}
}