/// <summary> /// <see cref="IsEvenlySpaced(BO.Energiemenge, TimeRange, Wertermittlungsverfahren, string, Mengeneinheit, bool)"/> /// </summary> /// <param name="em">Energiemenge</param> /// <param name="allowGaps"></param> /// <returns></returns> public static bool IsEvenlySpaced(this BO4E.BO.Energiemenge em, bool allowGaps = false) { if (!em.IsPure()) { // Find all combinations of Wertermittlungsverfahren, obis and Mengeneinheit. // The Energiemenge is evenly spaced if each of the combinations is evenly spaced itself. using (MiniProfiler.Current.Step("Check all Werte/Einheit/OBIS combinations")) { ISet <Tuple <Wertermittlungsverfahren, string, Mengeneinheit> > combinations = GetWevObisMeCombinations(em); foreach (Tuple <Wertermittlungsverfahren, string, Mengeneinheit> combo in combinations) { if (!em.IsEvenlySpaced(em.GetTimeRange(), combo.Item1, combo.Item2, combo.Item3, allowGaps)) { return(false); } } } return(true); } else { Verbrauch v = em.Energieverbrauch.FirstOrDefault(); return(em.IsEvenlySpaced(em.GetTimeRange(), v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit, allowGaps)); } }
/// <summary> /// Get average of Mengeneinheit for given time interval /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time frame</param> /// <param name="wev">Wertermittlungsverfahren</param> /// <param name="obiskennzahl">OBIS</param> /// <param name="me">an extensive or intensive unit</param> /// <returns>the average value or null if no Verbrauch overlapped with the specified time interval</returns> public static decimal?GetAverage(this BO4E.BO.Energiemenge em, TimeRange reference, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit me) { decimal?result = null; decimal overallDenominator = 0.0M; foreach (Verbrauch v in em.Energieverbrauch.Where(v => v.Einheit == me)) { decimal overlapFactor = GetOverlapFactor(new TimeRange(v.Startdatum, v.Enddatum), reference, true); if (result.HasValue) { result += overlapFactor * v.Wert; } else { result = v.Wert; } overallDenominator += overlapFactor; } if (result.HasValue) { return(result / overallDenominator); } else { return(result); } }
/// <summary> /// normalise energiemenge->energieverbrauch consumption values to a given <paramref name="target"/> value /// </summary> /// <param name="em">Energiemenge</param> /// <param name="target">normalising constant (1.0 by default)</param> /// <returns>new Energiemenge object with normalised consumption values</returns> public static BO4E.BO.Energiemenge Normalise(this BO4E.BO.Energiemenge em, decimal target = 1.0M) { using (MiniProfiler.Current.Step(nameof(Normalise))) { BO4E.BO.Energiemenge result; decimal scalingFactor; Tuple <decimal, Mengeneinheit> totalConsumption; using (MiniProfiler.Current.Step("Calculating total consumption and normalisation factor.")) { totalConsumption = em.GetTotalConsumption(); result = BusinessObjectExtensions.DeepClone <BO4E.BO.Energiemenge>(em); if (totalConsumption.Item1 != 0.0M) { scalingFactor = target / totalConsumption.Item1; } else { scalingFactor = 0.0M; } } using (MiniProfiler.Current.Step("Parallelised normalising of all values.")) { Parallel.ForEach <Verbrauch>(result.Energieverbrauch.Where(v => v.Einheit == totalConsumption.Item2), v => { v.Wert = scalingFactor * v.Wert; }); } return(result); } }
/// <summary> /// Our SAP CDS has a bug: When there's a change from non-DST to DST the <see cref="Verbrauch.Enddatum"/> is set /// to the first second of the DST period. To /// </summary> /// <param name="em"></param> public static void FixSapCDSBug(this BO4E.BO.Energiemenge em) { using (MiniProfiler.Current.Step("Fix SAP CDS Bug (Energiemenge)")) { if (em.Energieverbrauch != null && !em.HasBeenSanitized()) { using (MiniProfiler.Current.Step($"for each Verbrauch entry: {nameof(FixSapCDSBug)}")) { foreach (var v in em.Energieverbrauch) { v.FixSapCdsBug(); } //em.energieverbrauch = em.energieverbrauch.Select(v => Verbrauch.FixSapCdsBug(v)).ToList(); } using (MiniProfiler.Current.Step("for list as a whole")) { foreach (var relevantEnddatum in em.Energieverbrauch.Where(v => { var localEnd = DateTime.SpecifyKind(v.Enddatum, DateTimeKind.Unspecified); // ToDo: Check .UtcDateTime var localStart = DateTime.SpecifyKind(v.Startdatum, DateTimeKind.Unspecified); return(!CentralEuropeStandardTime.CENTRAL_EUROPE_STANDARD_TIME.IsDaylightSavingTime(localStart) && CentralEuropeStandardTime.CENTRAL_EUROPE_STANDARD_TIME.IsDaylightSavingTime(localEnd)); //return !localStart.IsDaylightSavingTime() && localEnd.IsDaylightSavingTime(); }).Select(v => v.Enddatum)) { var intervalSize = em.Energieverbrauch.Where(v => v.Enddatum == relevantEnddatum).Select(v => (v.Enddatum - v.Startdatum).TotalSeconds).Min(); foreach (var v in em.Energieverbrauch.Where(v => v.Enddatum == relevantEnddatum)) { v.Enddatum = v.Startdatum.AddSeconds(intervalSize); } } if (em.Energieverbrauch.Count(v => (v.Enddatum - v.Startdatum).TotalMinutes == -45) > 1) { /*foreach (var dstAffected in em.energieverbrauch.Where(v => (v.enddatum - v.startdatum).TotalMinutes != -45)) * { * Verbrauch anythingButEnddatum = dstAffected.DeepClone<Verbrauch>(); * anythingButEnddatum.enddatum = DateTime.MinValue; * anythingButEnddatum.wert = 0; * foreach(var v in em.energieverbrauch.Where(v=> * { * var comp = v.DeepClone<Verbrauch>(); * comp.enddatum = DateTime.MinValue; * comp.wert = 0; * return comp.Equals(anythingButEnddatum); * })) * { * int a = 0; * } * }*/ } } if (em.UserProperties == null) { em.UserProperties = new Dictionary <string, JToken>(); } em.UserProperties[SAP_SANITIZED_USERPROPERTY_KEY] = true; } } }
/// <summary> /// test if Energiemenge has only one <see cref="Wertermittlungsverfahren"/> /// </summary> /// <param name="em">Energiemenge</param> /// <returns>true iff the Energiemenge->energieverbrauch list has at most one distinct Wertermittlungsverfahren</returns> public static bool IsPureWertermittlungsverfahren(this BO4E.BO.Energiemenge em) { using (MiniProfiler.Current.Step(nameof(IsPureWertermittlungsverfahren))) { ISet <Wertermittlungsverfahren> wefs = new HashSet <Wertermittlungsverfahren>(); em.Energieverbrauch.All <Verbrauch>(v => wefs.Add(v.Wertermittlungsverfahren)); return(wefs.Count <= 1); } }
/// <summary> /// test if Energiemenge has only one Obiskennzahl /// </summary> /// <param name="em">Energiemenge</param> /// <returns>true iff the Energiemenge->energieverbrauch list has at most one distinct Obiskennzahl</returns> public static bool IsPureObisKennzahl(this BO4E.BO.Energiemenge em) { using (MiniProfiler.Current.Step(nameof(IsPureObisKennzahl))) { ISet <string> obisKzs = new HashSet <string>(); em.Energieverbrauch.All <Verbrauch>(v => obisKzs.Add(v.Obiskennzahl)); return(obisKzs.Count <= 1); } }
/// <summary> /// Get a list of those time ranges within a reference, where no energieverbrauch entries are defined. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference timeframe</param> /// <param name="wev">Wertermittlungsverfahren</param> /// <param name="obis">OBIS-Kennzahl</param> /// <param name="me">Mengeneinheit</param> /// <returns></returns> public static List <TimeRange> GetMissingTimeRanges(this BO4E.BO.Energiemenge em, ITimeRange reference, Wertermittlungsverfahren wev, string obis, Mengeneinheit me) { using (MiniProfiler.Current.Step(nameof(GetMissingTimeRanges))) { IDictionary <Tuple <DateTime, DateTime>, Verbrauch> filteredVerbrauch; using (MiniProfiler.Current.Step($"Filtering energieverbrauch on OBIS={obis}, WEV={wev}, Mengeneinheit={me}")) { filteredVerbrauch = em.Energieverbrauch .Where <Verbrauch>(v => v.Wertermittlungsverfahren == wev && v.Obiskennzahl == obis && v.Einheit == me) .ToDictionary(v => new Tuple <DateTime, DateTime>(v.Startdatum, v.Enddatum), v => v); } if (filteredVerbrauch.Count < 2) { throw new ArgumentException("Not enough entries in energieverbrauch to determine periodicity."); } if (!IsEvenlySpaced(em, reference, wev, obis, me, true)) { throw new ArgumentException("The provided Energiemenge is not evenly spaced although gaps are allowed."); } TimeSpan periodicity = GetTimeSpans(em, wev, obis, me).Min <TimeSpan>(); if (Math.Abs((reference.Start - em.GetMinDate()).TotalMilliseconds % periodicity.TotalMilliseconds) != 0) { throw new ArgumentException($"The absolute difference between reference.start ({reference.Start}) and the minimal date time in the Energiemenge ({em.GetMinDate()}) has to be an integer multiple of the periodicity {periodicity.TotalMilliseconds} but was {(reference.Start - em.GetMinDate()).TotalMilliseconds}."); } // since it's assured, that the energieverbrauch entries are evenly spaced it doesn't matter which entry we use to determine the duration. TimeSpan duration = filteredVerbrauch.Values.Min(v => v.Enddatum) - filteredVerbrauch.Values.Min(v => v.Startdatum); List <TimeRange> result = new List <TimeRange>(); using (MiniProfiler.Current.Step("Populating list with time slices in UTC")) { for (DateTime dt = reference.Start; dt < reference.End; dt += periodicity) { // use a strict '==' instead of overlap. This is justified because all the other cases are considered beforehand switch (dt.Kind) { case DateTimeKind.Local: throw new ArgumentException("Local DateTime not supported!"); case DateTimeKind.Unspecified: dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc); break; case DateTimeKind.Utc: break; } //using (MiniProfiler.Current.Step("linq where on filtered verbrauch")) //{ if (!filteredVerbrauch.ContainsKey(new Tuple <DateTime, DateTime>(dt, dt + duration)))// Where<Verbrauch>(v => v.startdatum == dt && v.enddatum == dt + duration).Any()) { result.Add(new TimeRange(dt, dt + duration)); } //} } } return(result); } }
/// <summary> /// <see cref="GetMissingTimeRanges(BO.Energiemenge, TimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/> /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time frame</param> /// <returns></returns> public static List <TimeRange> GetMissingTimeRanges(this BO4E.BO.Energiemenge em, TimeRange reference) { if (!em.IsPure()) { throw new ArgumentException("The Energiemenge you provided is not pure. Consider using the overloaded method."); } Verbrauch v = em.Energieverbrauch.FirstOrDefault(); return(GetMissingTimeRanges(em, reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit)); }
/// <summary> /// Get percentage of time range covered by all Wertermittlungsverfahren/OBIS/Mengeneinheit /// combinations, that are present in the Energiemenge->energieverbrauch array. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference</param> /// <returns></returns> public static decimal GetJointCoverage(this BO4E.BO.Energiemenge em, TimeRange reference) { ISet <Tuple <Wertermittlungsverfahren, string, Mengeneinheit> > combinations = GetWevObisMeCombinations(em); decimal jointCoverage = em.Energieverbrauch //.AsParallel<Verbrauch>() .Where <Verbrauch>(v => combinations.Contains(Tuple.Create <Wertermittlungsverfahren, string, Mengeneinheit>(v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit))) .Sum(v => GetOverlapFactor(new TimeRange(v.Startdatum, v.Enddatum), reference, true)); return(jointCoverage - (combinations.Count - 1)); }
/// <summary> /// Get Zeitraum covered by Energiemenge. /// </summary> /// <param name="menge">Energiemenge</param> /// <returns>Zeitraum ranging from the earliest <see cref="Verbrauch.Startdatum"/> to the latest <see cref="Verbrauch.Enddatum"/></returns> public static Zeitraum GetZeitraum(this BO4E.BO.Energiemenge menge) { using (MiniProfiler.Current.Step(nameof(GetZeitraum))) { Zeitraum zeitraum = new Zeitraum { Startdatum = GetMinDate(menge), Enddatum = GetMaxDate(menge) }; return(zeitraum); } }
/// <summary> /// Returns the consumption of a given kind of Mengeneinheit within the specified reference time range. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time frame</param> /// <param name="wev">Wertermittlungsverfahren</param> /// <param name="obiskennzahl">OBIS number</param> /// <param name="me">an extensive unit (e.g. "kWh")</param> /// <returns>the consumption within the give time slice <paramref name="reference"/> in the unit passed as <paramref name="me"/></returns> public static decimal GetConsumption(this BO4E.BO.Energiemenge em, ITimeRange reference, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit me) { if (!me.IsExtensive()) { throw new ArgumentException($"The Mengeneinheit {me} isn't extensive. Calculating a consumption doesn't make sense."); } return(em.Energieverbrauch .Where(v => v.Wertermittlungsverfahren == wev && v.Obiskennzahl == obiskennzahl && v.Einheit == me) //.AsParallel<Verbrauch>() .Sum(v => GetOverlapFactor(new TimeRange(v.Startdatum, v.Enddatum), reference, false) * v.Wert)); }
private static HashSet <TimeSpan> GetTimeSpans(this BO4E.BO.Energiemenge em) { HashSet <TimeSpan> result = new HashSet <TimeSpan>(); List <Verbrauch> vlist = new List <Verbrauch>(em.Energieverbrauch); vlist.Sort(new VerbrauchDateTimeComparer()); for (int i = 1; i < vlist.Count; i++) { result.Add(vlist[i].Startdatum - vlist[i - 1].Startdatum); result.Add(vlist[i].Enddatum - vlist[i - 1].Enddatum); } return(result); }
private static HashSet <TimeSpan> GetTimeSpans(this BO4E.BO.Energiemenge em, Wertermittlungsverfahren wev, string obis, Mengeneinheit me) { HashSet <TimeSpan> result = new HashSet <TimeSpan>(); List <Verbrauch> vlist = new List <Verbrauch>(em.Energieverbrauch); vlist.Sort(new VerbrauchDateTimeComparer()); vlist = vlist.Where <Verbrauch>(v => v.Wertermittlungsverfahren == wev && v.Obiskennzahl == obis && v.Einheit == me).ToList <Verbrauch>(); for (int i = 1; i < vlist.Count; i++) { result.Add(vlist[i].Startdatum - vlist[i - 1].Startdatum); result.Add(vlist[i].Enddatum - vlist[i - 1].Enddatum); } return(result); }
/// <summary> /// Get ratio of overlap between given Energiemenge and a reference. /// Method is basically just another name for <see cref="GetOverlapFactor(TimeRange, TimeRange, bool)"/> /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time range</param> /// <param name="obisKz">OBIS</param> /// <param name="mengeneinheit">unit of measurement</param> /// <param name="wev">type of measurement</param> /// <param name="decimalRounding">post decimals</param> /// <returns>value between 0 (no overlap) and 1.0 (100% overlap)</returns> public static decimal GetCoverage(this BO4E.BO.Energiemenge em, ITimeRange reference, Wertermittlungsverfahren wev, string obisKz, Mengeneinheit mengeneinheit, int decimalRounding = 10) { decimal exactResult; using (MiniProfiler.Current.Step($"calculating coverage for list with {em.Energieverbrauch.Count} entries.")) { exactResult = em.Energieverbrauch //.AsParallel<Verbrauch>() .Where <Verbrauch>(v => v.Einheit == mengeneinheit && v.Obiskennzahl == obisKz && v.Wertermittlungsverfahren == wev) .Sum(v => GetOverlapFactor(new TimeRange(v.Startdatum, v.Enddatum), reference, true)); } return(Math.Round(exactResult, decimalRounding)); }
/// <summary> /// tests if the method <see cref="Verbrauch.FixSapCdsBug"/> has been executed yet. /// </summary> /// <returns>true if Energiemenge has been sanitized</returns> private static bool HasBeenSanitized(this BO4E.BO.Energiemenge em) { bool sanitized; if (em.UserProperties == null || !em.UserProperties.TryGetValue(SAP_SANITIZED_USERPROPERTY_KEY, out JToken sapSanitizedToken)) { sanitized = false; } else { sanitized = sapSanitizedToken.Value <bool>(); } return(sanitized); }
/// <summary> /// Get Average (<see cref="GetAverage(BO.Energiemenge, TimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/>) /// for a pure Energiemenge with automatically found parameters. /// </summary> /// <seealso cref="IsPure(BO4E.BO.Energiemenge)"/> /// <param name="em">Energiemenge</param> /// <returns>Tuple of average value and unit of measurement</returns> public static Tuple <decimal?, Mengeneinheit> GetAverage(this BO4E.BO.Energiemenge em) { if (!IsPure(em)) { throw new ArgumentException("Energiemenge is not pure."); } else if (em.Energieverbrauch.Count == 0) { return(Tuple.Create <decimal?, Mengeneinheit>(null, Mengeneinheit.KW)); } else { Verbrauch v = em.Energieverbrauch.First <Verbrauch>(); return(Tuple.Create <decimal?, Mengeneinheit>(em.GetAverage(v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit), v.Einheit)); } }
/// <summary> /// Get percentage of time range covered by pure Energiemenge. /// </summary> /// <param name="em">pure Energiemenge</param> /// <param name="reference">time frame reference</param> /// <returns>value between 0 (only coverage for 1 point in time) and 1.0 (100% coverage)</returns> public static decimal GetCoverage(this BO4E.BO.Energiemenge em, ITimeRange reference) { using (MiniProfiler.Current.Step(nameof(GetCoverage))) { if (!IsPure(em)) { throw new ArgumentException("The Energiemenge is not pure. Cannot determine parameters."); } if (em.Energieverbrauch.Count == 0) { return(0.0M); } Verbrauch v = em.Energieverbrauch.First <Verbrauch>(); return(em.GetCoverage(reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit)); } }
/// <summary> /// Returns the load in an intensive unit for a given point in time. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="me">an intensive unit (e.g. "kW")</param> /// <param name="dt">point in time</param> /// <returns>load if Energiemenge BO contains value for specified date time<paramref name="dt"/>, null otherwise</returns> public static decimal?GetLoad(this BO4E.BO.Energiemenge em, Mengeneinheit me, DateTime dt) { if (!me.IsIntensive()) { throw new ArgumentException($"The Mengeneinheit {me} isn't intensive. Calculating the value for a specific point in time doesn't make sense."); } decimal?result = null; foreach (Verbrauch v in em.Energieverbrauch.Where(v => v.Startdatum <= dt && dt < v.Enddatum)) { if (result.HasValue) { result += v.Wert; } else { result = v.Wert; } } return(result); }
/// <summary> /// Test, if the single entries/intervals of the energieverbrauch array share the same duration and spacing in time. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time frame</param> /// <param name="wev">Wertermittlungsverfahren</param> /// <param name="obis">OBIS-Kennzahl</param> /// <param name="me">Mengeneinheit</param> /// <param name="allowGaps">set true to allow gaps</param> /// <returns>True, if all energieverbrauch entries have the same length and their start and enddatum are evenly spaced. /// Also true, if there less than 2 entries in the energieverbrauch array.</returns> public static bool IsEvenlySpaced(this BO4E.BO.Energiemenge em, ITimeRange reference, Wertermittlungsverfahren wev, string obis, Mengeneinheit me, bool allowGaps = false) { HashSet <TimeSpan> startEndDatumPeriods; using (MiniProfiler.Current.Step("finding time spans")) { startEndDatumPeriods = GetTimeSpans(em, wev, obis, me); } if (startEndDatumPeriods.Count < 2) { return(true); } if (allowGaps) { // each time difference must be a multiple of the smallest difference. using (MiniProfiler.Current.Step("Iterating over all time spans")) { double minDiff = startEndDatumPeriods.Min <TimeSpan>().TotalSeconds; foreach (TimeSpan ts in startEndDatumPeriods) { if (Math.Abs(ts.TotalSeconds % minDiff) != 0) { // use profiler as logger: using (MiniProfiler.Current.Step($"Found TimeSpan {ts} with a duration of {ts.TotalSeconds}. This is no multiple of {minDiff} => not evenly spaced.")) { return(false); } } } } return(true); } else { // there must be only 1 time difference between all the elements return(startEndDatumPeriods.Count <= 1); } }
/// <summary> /// <see cref="GetCompletenessReport(BO.Energiemenge, ITimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/> /// for pure Energiemengen within their own time range. /// </summary> /// <param name="em">Energiemenge</param> /// <returns><see cref="GetCompletenessReport(BO.Energiemenge, ITimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/></returns> public static CompletenessReport GetCompletenessReport(this BO4E.BO.Energiemenge em) { if (!em.IsPure()) { throw new ArgumentException("The provided Energiemenge is not pure. Please use overloaded method GetCompletenessReport(... , wertermittlungsverfahren, obiskennzahl, mengeneinheit)."); } Verbrauch v; try { v = em.Energieverbrauch.First <Verbrauch>(); } catch (InvalidOperationException) { return(new CompletenessReport() { Coverage = null, LokationsId = em.LokationsId, ErrorMessage = "energieverbrauch is empty" }); } return(em.GetCompletenessReport(em.GetTimeRange(), v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit)); }
/// <summary> /// Get consumption in given time reference frame. Trying to automatically determine parameters and forward to <see cref="BO4E.BO.Energiemenge.GetConsumption(BO.Energiemenge, TimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/>. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">time reference frame</param> /// <returns>Tuple of consumption value and automatically determined unit of measurement</returns> public static Tuple <decimal, Mengeneinheit> GetConsumption(this BO4E.BO.Energiemenge em, ITimeRange reference) { using (MiniProfiler.Current.Step(nameof(GetConsumption))) { if (!IsPure(em)) { throw new ArgumentException("The Energiemenge is not pure."); } if (em.Energieverbrauch.Count == 0) { return(Tuple.Create <decimal, Mengeneinheit>(0.0M, Mengeneinheit.ANZAHL)); } ISet <Mengeneinheit> einheiten = new HashSet <Mengeneinheit>(em.Energieverbrauch.Select(x => x.Einheit)); if (einheiten.Count > 1) { // z.B. kWh und Wh oder Monat und Jahr... Die liefern IsPure==true. throw new NotImplementedException("Converting different units of same type is not supported yet."); } Verbrauch v = em.Energieverbrauch.First <Verbrauch>(); decimal consumption = em.GetConsumption(reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit); return(Tuple.Create <decimal, Mengeneinheit>(consumption, v.Einheit)); } }
/// <summary> /// Returns a <see cref="PlausibilityReport"/> that compares <paramref name="emReference"/> with <paramref name="emOther"/>. /// within the interval defined in <paramref name="timeframe"/>. /// </summary> /// <param name="emReference">reference Energiemenge (reference = used for normalisation)</param> /// <param name="emOther">other Energiemenge</param> /// <param name="timeframe">time frame to be analysed. If null, the overlap of <paramref name="emReference"/> and <paramref name="emOther"/> is used.</param> /// <param name="ignoreLocation">By default (false) an ArgumentException is thrown if the <see cref="BO4E.BO.Energiemenge.LokationsId"/> do not match. Setting this flag suppresses the error.</param> /// <returns>a <see cref="PlausibilityReport"/></returns> public static PlausibilityReport GetPlausibilityReport(this BO4E.BO.Energiemenge emReference, BO4E.BO.Energiemenge emOther, ITimeRange timeframe = null, bool ignoreLocation = false) { using (MiniProfiler.Current.Step(nameof(GetPlausibilityReport))) { TimeRange trReference = emReference.GetTimeRange(); TimeRange trOther = emOther.GetTimeRange(); if (timeframe == null) { ITimeRange overlap = trReference.GetIntersection(trOther); if (!ignoreLocation) { if (!(emReference.LokationsId == emOther.LokationsId && emReference.LokationsTyp == emOther.LokationsTyp)) { throw new ArgumentException($"locations do not match! '{emReference.LokationsId}' ({emReference.LokationsTyp}) != '{emOther.LokationsId}' ({emOther.LokationsTyp})"); } } timeframe = overlap; } Tuple <decimal, Mengeneinheit> consumptionReference; Tuple <decimal, Mengeneinheit> consumptionOtherRaw; using (MiniProfiler.Current.Step("Get Consumptions for overlap:")) { consumptionReference = emReference.GetConsumption(timeframe); consumptionOtherRaw = emOther.GetConsumption(timeframe); } Tuple <decimal, Mengeneinheit> consumptionOther; if (consumptionReference.Item2 != consumptionOtherRaw.Item2) { // unit mismatch if (consumptionReference.Item2.IsConvertibleTo(consumptionOtherRaw.Item2)) { consumptionOther = new Tuple <decimal, Mengeneinheit>(consumptionOtherRaw.Item1 * consumptionOtherRaw.Item2.GetConversionFactor(consumptionReference.Item2), consumptionReference.Item2); } else { throw new ArgumentException($"The unit {consumptionOtherRaw.Item2} is not comparable to {consumptionReference.Item2}!"); } } else { consumptionOther = consumptionOtherRaw; } decimal absoluteDeviation = consumptionOther.Item1 - consumptionReference.Item1; decimal?relativeDeviation; try { relativeDeviation = absoluteDeviation / consumptionReference.Item1; } catch (DivideByZeroException) { relativeDeviation = null; } Verbrauch vReference = emReference.Energieverbrauch.FirstOrDefault(); // copies obiskennzahl, wertermittlungsverfahren... vReference.Wert = consumptionReference.Item1; vReference.Einheit = consumptionReference.Item2; vReference.Startdatum = timeframe.Start; vReference.Enddatum = timeframe.End; Verbrauch vOther = emOther.Energieverbrauch.FirstOrDefault(); // copies obiskennzahl, wertermittlungsverfahren... vOther.Wert = consumptionOther.Item1; vOther.Einheit = consumptionOther.Item2; vOther.Startdatum = timeframe.Start; vOther.Enddatum = timeframe.End; var pr = new PlausibilityReport() { LokationsId = emReference.LokationsId, ReferenceTimeFrame = new BO4E.COM.Zeitraum() { Startdatum = timeframe.Start, Enddatum = timeframe.End }, VerbrauchReference = vReference, VerbrauchOther = vOther, AbsoluteDeviation = Math.Abs(absoluteDeviation), AbsoluteDeviationEinheit = consumptionReference.Item2 }; if (relativeDeviation.HasValue) { pr.RelativeDeviation = Math.Round(relativeDeviation.Value, 4); } else { pr.RelativeDeviation = null; } return(pr); } }
/// <summary> /// Get Monthly Completeness Reports for overall time range defined in <paramref name="config"/>. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="config">configuration that contains the overall time range in <see cref="PlausibilityReportConfiguration.Timeframe"/></param> /// <returns></returns> public static IDictionary <ITimeRange, PlausibilityReport> GetMonthlyPlausibilityReports(this BO4E.BO.Energiemenge em, PlausibilityReportConfiguration config) { if (config == null) { throw new ArgumentNullException(nameof(config)); } if (config.Timeframe == null) { throw new ArgumentNullException(nameof(config.Timeframe)); } var slices = GetLocalMonthlySlices(new TimeRange() { Start = config.Timeframe.Startdatum.Value, End = config.Timeframe.Enddatum.Value }); return(em.GetSlicedPlausibilityReports(config, slices)); }
/// <summary> /// creates a dictionary of completeness reports for a given list of reference time ranges. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="ranges">list of ranges for which the completeness reports are generated</param> /// <returns></returns> public static IDictionary <ITimeRange, PlausibilityReport> GetSlicedPlausibilityReports(this BO4E.BO.Energiemenge em, PlausibilityReportConfiguration config, IEnumerable <ITimeRange> ranges) { if (ranges == null) { throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); } Dictionary <ITimeRange, PlausibilityReport> result = new Dictionary <ITimeRange, PlausibilityReport>(); foreach (var range in ranges) { var localConfig = JsonConvert.DeserializeObject <PlausibilityReportConfiguration>(JsonConvert.SerializeObject(config)); localConfig.Timeframe = new Zeitraum() { Startdatum = range.Start, Enddatum = range.End }; var subResult = GetPlausibilityReport(em, localConfig); result.Add(range, subResult); } return(result); }
/// <summary> /// same as <see cref="GetPlausibilityReport(BO.Energiemenge, BO.Energiemenge, ITimeRange, bool)"/> but with a strongly typed container as input. /// </summary> /// <param name="config">container containing the relevant data</param> /// <returns></returns> public static PlausibilityReport GetPlausibilityReport(this BO4E.BO.Energiemenge energiemenge, PlausibilityReport.PlausibilityReportConfiguration config) { return(energiemenge.GetPlausibilityReport(config.Other, new TimeRange(config.Timeframe.Startdatum.Value, config.Timeframe.Enddatum.Value), config.IgnoreLocation)); }
/// <summary> /// Generate a <see cref="CompletenessReport"/> for the given parameters. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="reference">reference time frame</param> /// <param name="wev">Wertermittlungsverfahren</param> /// <param name="obiskennzahl">OBIS Kennzahl</param> /// <param name="einheit">Mengeneinheit</param> /// <returns>the completeness report</returns> public static CompletenessReport GetCompletenessReport(this BO4E.BO.Energiemenge em, ITimeRange reference, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit einheit) { CompletenessReport result; using (MiniProfiler.Current.Step("create completeness report skeleton + find the coverage")) { result = new CompletenessReport { LokationsId = em.LokationsId, Einheit = einheit, Coverage = GetCoverage(em, reference, wev, obiskennzahl, einheit), wertermittlungsverfahren = wev, Obiskennzahl = obiskennzahl, ReferenceTimeFrame = new Zeitraum { Startdatum = DateTime.SpecifyKind(reference.Start, DateTimeKind.Utc), Enddatum = DateTime.SpecifyKind(reference.End, DateTimeKind.Utc) }, }; } if (em.Energieverbrauch != null && em.Energieverbrauch.Count > 0) { /*using (MiniProfiler.Current.Step("populating time slices of/with missing/null values")) * { * result.values = em.GetMissingTimeRanges(reference, wev, obis, einheit) * .Select(mtr => new CompletenessReport.BasicVerbrauch * { * startdatum = DateTime.SpecifyKind(mtr.Start, DateTimeKind.Utc), * enddatum = DateTime.SpecifyKind(mtr.End, DateTimeKind.Utc), * wert = null * }).ToList<CompletenessReport.BasicVerbrauch>(); * } * using (MiniProfiler.Current.Step("populating time slices existing values")) * { * result.values.AddRange( * em.energieverbrauch * //.AsParallel<Verbrauch>() * .Where(v => v.obiskennzahl == obis && v.einheit == einheit && v.wertermittlungsverfahren == wev) * .Select(v => new CompletenessReport.BasicVerbrauch * { * startdatum = DateTime.SpecifyKind(v.startdatum, DateTimeKind.Utc), * enddatum = DateTime.SpecifyKind(v.enddatum, DateTimeKind.Utc), * wert = v.wert * }) * .ToList<CompletenessReport.BasicVerbrauch>()); * }*/ using (MiniProfiler.Current.Step("Setting aggregated gaps")) { var nonNullValues = new TimePeriodCollection(em.Energieverbrauch.Select(v => new TimeRange(v.Startdatum, v.Enddatum))); ITimeRange limits; if (result.ReferenceTimeFrame != null && result.ReferenceTimeFrame.Startdatum.HasValue && result.ReferenceTimeFrame.Enddatum.HasValue) { limits = new TimeRange(result.ReferenceTimeFrame.Startdatum.Value, result.ReferenceTimeFrame.Enddatum.Value); } else { limits = null; } var gaps = (new TimeGapCalculator <TimeRange>()).GetGaps(nonNullValues, limits: limits); result.Gaps = gaps.Select(gap => new CompletenessReport.BasicVerbrauch() { Startdatum = gap.Start, Enddatum = gap.End, Wert = null }).ToList(); } /*using (MiniProfiler.Current.Step("sorting result")) * { * result.values.Sort(new BasicVerbrauchDateTimeComparer()); * }*/ if (em.IsPure(checkUserProperties: true)) { try { foreach (var kvp in em.Energieverbrauch.Where(v => v.UserProperties != null).SelectMany(v => v.UserProperties)) { if (result.UserProperties == null) { result.UserProperties = new Dictionary <string, JToken>(); } if (!result.UserProperties.ContainsKey(kvp.Key)) { result.UserProperties.Add(kvp.Key, kvp.Value); } } } catch (InvalidOperationException) { // ok, there's no Verbrauch with user properties. } } } /*else * { * result.coverage = null; * result._errorMessage = "energieverbrauch is empty"; * }*/ return(result); }
/// <summary> /// Get Monthly Completeness Reports for <paramref name="overallTimeRange"/>. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="overallTimeRange"></param> /// <param name="useParallelExecution">set true to internally use parallel linq</param> /// <returns></returns> public static IDictionary <ITimeRange, CompletenessReport> GetMonthlyCompletenessReports(this BO4E.BO.Energiemenge em, ITimeRange overallTimeRange, bool useParallelExecution = false) { var slices = GetLocalMonthlySlices(overallTimeRange); return(em.GetSlicedCompletenessReports(slices, useParallelExecution)); }
/// <summary> /// Generate a <see cref="CompletenessReport"/> for the given configuration. Same as <see cref="GetCompletenessReport(BO.Energiemenge, TimeRange, Wertermittlungsverfahren, string, Mengeneinheit)"/> but with all parameters in a configuration container instead of loose arguments. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="config">configuration container</param> /// <returns></returns> public static CompletenessReport GetCompletenessReport(this BO4E.BO.Energiemenge em, CompletenessReport.CompletenessReportConfiguration config) { return(em.GetCompletenessReport(new TimeRange(config.ReferenceTimeFrame.Startdatum.Value, config.ReferenceTimeFrame.Enddatum.Value), config.Wertermittlungsverfahren, config.Obis, config.Einheit)); }
/// <summary> /// Same as <see cref="GetTotalConsumption(BO.Energiemenge, Wertermittlungsverfahren, string, Mengeneinheit)"/> but without autodetected parameters. /// By default a the full length of the Energiemenge is taken into account. /// </summary> /// <param name="em">Energiemenge</param> /// <returns>Tuple of consumption value and unit of measurement</returns> public static Tuple <decimal, Mengeneinheit> GetTotalConsumption(this BO4E.BO.Energiemenge em) { return(GetConsumption(em, new TimeRange(em.GetMinDate().UtcDateTime, em.GetMaxDate().UtcDateTime))); }
/// <summary> /// Test if the energiemenge contains only extensive consumption units /// </summary> /// <param name="em">Energiemenge</param> /// <returns>true iff all <paramref name="em"/>->energieverbrauch entries are extensive</returns> public static bool IsExtensive(this BO4E.BO.Energiemenge em) { return(em.IsPureMengeneinheit() && em.Energieverbrauch.First <Verbrauch>().Einheit.IsExtensive()); }