/// <summary> /// Get list of those time ranges within the energiemenge where there are gaps. /// </summary> /// <param name="em">Energiemenge</param> /// <returns></returns> public static IList <TimeRange> GetMissingTimeRanges(this BO.Energiemenge em) { return(em.GetMissingTimeRanges(em.GetTimeRange())); }
/// <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 time frame</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 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(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."); } var periodicity = GetTimeSpans(em, wev, obis, me).Min(); 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. var duration = filteredVerbrauch.Values.Min(v => v.Enddatum) - filteredVerbrauch.Values.Min(v => v.Startdatum); var result = new List <TimeRange>(); using (MiniProfiler.Current.Step("Populating list with time slices in UTC")) { for (var 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(BO4E.BO.Energiemenge)" /> 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 BO.Energiemenge em, Wertermittlungsverfahren wev, string obiskennzahl, Mengeneinheit me) { return(em.GetAverage(em.GetTimeRange(), wev, obiskennzahl, me)); }
/// <summary> /// Same as /// <see /// cref="GetTotalConsumption(BO4E.BO.Energiemenge,BO4E.ENUM.Wertermittlungsverfahren,string,BO4E.ENUM.Mengeneinheit)" /> /// but without auto-detected 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 BO.Energiemenge em) { return(GetConsumption(em, new TimeRange(em.GetMinDate().UtcDateTime, em.GetMaxDate().UtcDateTime))); }
/// <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 BO.Energiemenge emReference, BO.Energiemenge emOther, ITimeRange timeframe = null, bool ignoreLocation = false) { using (MiniProfiler.Current.Step(nameof(GetPlausibilityReport))) { var trReference = emReference.GetTimeRange(); var trOther = emOther.GetTimeRange(); if (timeframe == null) { var 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; } var absoluteDeviation = consumptionOther.Item1 - consumptionReference.Item1; decimal?relativeDeviation; try { relativeDeviation = absoluteDeviation / consumptionReference.Item1; } catch (DivideByZeroException) { relativeDeviation = null; } var vReference = emReference.Energieverbrauch.FirstOrDefault(); // copies obiskennzahl, wertermittlungsverfahren... vReference.Wert = consumptionReference.Item1; vReference.Einheit = consumptionReference.Item2; vReference.Startdatum = timeframe.Start; vReference.Enddatum = timeframe.End; var 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 Zeitraum { Startdatum = new DateTimeOffset(timeframe.Start), Enddatum = new DateTimeOffset(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="PlausibilityReport.PlausibilityReportConfiguration.Timeframe" /> /// </param> /// <returns></returns> public static IDictionary <ITimeRange, PlausibilityReport> GetMonthlyPlausibilityReports(this 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.UtcDateTime, End = config.Timeframe.Enddatum.Value.UtcDateTime }); 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="config">container containing the relevant data</param> /// <param name="ranges">list of ranges for which the completeness reports are generated</param> /// <returns></returns> public static IDictionary <ITimeRange, PlausibilityReport> GetSlicedPlausibilityReports(this BO.Energiemenge em, PlausibilityReportConfiguration config, IEnumerable <ITimeRange> ranges) { if (ranges == null) { throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); } var 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); }