/// <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> /// 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)); }
/// <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)); }
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> /// 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> /// 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> /// 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> /// Same as <see cref="GetAverage(Mengeneinheit, DateTime, DateTime)"/> but without specifying a time slice. /// </summary> /// <param name="em">Energiemenge</param> /// <param name="wev">type of measurement</param> /// <param name="obiskennzahl">OBIS</param> /// <param name="me">an intensive or extensive unit</param> /// <returns>The average for the given Mengeneinheit for the Energiemenge object or null if there was no Verbrauch for the given Mengeneinheit.</returns> public static decimal?GetAverage(this BO4E.BO.Energiemenge em, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit me) { return(em.GetAverage(em.GetTimeRange(), wev, obiskennzahl, me)); }
/// <summary> /// Get total consumption for given parameters /// </summary> /// <param name="em">Energiemenge</param> /// <param name="wev">type of measurement</param> /// <param name="obiskennzahl">OBIS</param> /// <param name="me">unit of measurement</param> /// <returns>consumption value</returns> public static decimal GetTotalConsumption(this BO.Energiemenge em, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit me) { return(em.GetConsumption(em.GetTimeRange(), wev, obiskennzahl, me)); }