Пример #1
0
    def wrapper(*args, **kwargs):
        form = get_form_from_request(request)
        if form is None:
            current_app.logger.warning(
                "Unsupported request method for unpacking 'values' from request."
            )
            return invalid_method(request.method)

        if "value" in form:
            value_groups = [parse_as_list(form["value"], of_type=float)]
        elif "values" in form:
            value_groups = [parse_as_list(form["values"], of_type=float)]
        elif "groups" in form:
            value_groups = []
            for group in form["groups"]:
                if "value" in group:
                    value_groups.append(parse_as_list(group["value"], of_type=float))
                elif "values" in group:
                    value_groups.append(parse_as_list(group["values"], of_type=float))
                else:
                    current_app.logger.warning("Group %s missing value(s)" % group)
                    return ptus_incomplete()
        else:
            current_app.logger.warning("Request missing value(s) or group.")
            return ptus_incomplete()

        if not contains_empty_items(value_groups):
            kwargs["value_groups"] = value_groups
            return fn(*args, **kwargs)
        else:
            extra_info = "Request includes empty or ill-formatted value(s)."
            current_app.logger.warning(extra_info)
            return ptus_incomplete(extra_info)
Пример #2
0
    def wrapper(*args, **kwargs):
        form = get_form_from_request(request)
        if form is None:
            current_app.logger.warning(
                "Unsupported request method for unpacking 'start' and 'duration' from request."
            )
            return invalid_method(request.method)

        if "start" in form:
            start = parse_isodate_str(form["start"])
            if not start:
                current_app.logger.warning("Cannot parse 'start' value")
                return invalid_period()
            if start.tzinfo is None:
                current_app.logger.warning("Cannot parse timezone of 'start' value")
                return invalid_timezone(
                    "Start time should explicitly state a timezone."
                )
        else:
            current_app.logger.warning("Request missing 'start'.")
            return invalid_period()
        kwargs["start"] = start
        if "duration" in form:
            duration = parse_duration(form["duration"], start)
            if not duration:
                current_app.logger.warning("Cannot parse 'duration' value")
                return invalid_period()
        else:
            current_app.logger.warning("Request missing 'duration'.")
            return invalid_period()
        kwargs["duration"] = duration
        return fn(*args, **kwargs)
Пример #3
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'prior' from request."
                )
                return invalid_method(request.method)

            if "prior" in form:
                prior = parse_isodate_str(form["prior"])
                if ex_post is True:
                    start = parse_isodate_str(form["start"])
                    duration = parse_duration(form["duration"], start)
                    # todo: validate start and duration (refactor already duplicate code from period_required and optional_horizon_accepted)
                    knowledge_time = (
                        start + duration
                    )  # todo: take into account knowledge horizon function
                    if prior < knowledge_time:
                        extra_info = "Meter data can only be observed after the fact."
                        return invalid_horizon(extra_info)
            else:
                prior = None

            kwargs["prior"] = prior
            return fn(*args, **kwargs)
Пример #4
0
        def decorated_service(*args, **kwargs):
            kwargs[
                "resolution"
            ] = None  # using this decorator means you can expect this attribute, None means default
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'resolution' from request."
                )
                return invalid_method(request.method)

            if "resolution" in form and form["resolution"]:
                ds_resolution = parse_duration(form["resolution"])
                if ds_resolution is None:
                    return invalid_resolution_str(form["resolution"])
                # Check if the resolution can be applied to all assets (if it is a multiple
                # of the event_resolution(s) and thus downsampling is possible)
                for asset_group in kwargs["generic_asset_name_groups"]:
                    for asset_descriptor in asset_group:
                        generic_asset = get_generic_asset(asset_descriptor, entity_type)
                        if generic_asset is None:
                            return unrecognized_asset()
                        asset_resolution = generic_asset.event_resolution
                        if ds_resolution % asset_resolution != timedelta(minutes=0):
                            return unapplicable_resolution(
                                f"{isodate.duration_isoformat(asset_resolution)} or a multiple hereof."
                            )
                kwargs["resolution"] = to_offset(
                    isodate.parse_duration(form["resolution"])
                ).freqstr  # Convert ISO period string to pandas frequency string

            return fn(*args, **kwargs)
Пример #5
0
 def decorated_service(*args, **kwargs):
     try:
         current_app.logger.info(get_form_from_request(request))
     except OSError as e:  # don't crash if request can't be logged (e.g. [Errno 90] Message too long)
         current_app.logger.info(e)
     response = fn(*args, **kwargs)  # expects flask response object
     if not (
         hasattr(response, "json")
         and hasattr(response, "headers")
         and hasattr(response, "status_code")
     ):
         current_app.logger.warning(
             "Response is not a Flask response object. I did not assign a response type."
         )
         return response
     data = response.json
     headers = dict(
         zip(Headers.keys(response.headers), Headers.values(response.headers))
     )
     status_code = response.status_code
     if "type" in data:
         current_app.logger.warning(
             "Response already contains 'type' key. I did not assign a new response type."
         )
     else:
         data["type"] = response_type
         headers.pop("content-length", None)
         headers.pop("Content-Length", None)
     return data, status_code, headers
Пример #6
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'prior' from request."
                )
                return invalid_method(request.method)

            if "prior" in form:
                prior = parse_isodate_str(form["prior"])
                if ex_post is True:
                    start = parse_isodate_str(form["start"])
                    duration = parse_duration(form["duration"], start)
                    # todo: validate start and duration (refactor already duplicate code from period_required and optional_horizon_accepted)
                    knowledge_time = (
                        start + duration
                    )  # todo: take into account knowledge horizon function
                    if prior < knowledge_time:
                        extra_info = "Meter data can only be observed after the fact."
                        return invalid_horizon(extra_info)
            elif infer_missing is True or (
                    infer_missing_play is True and current_app.config.get(
                        "FLEXMEASURES_MODE", "") == "play"):
                # A missing prior is inferred by the server
                prior = server_now()
            else:
                # Otherwise, a missing prior is fine (a horizon may still be inferred by the server)
                prior = None

            kwargs["prior"] = prior
            return fn(*args, **kwargs)
Пример #7
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'horizon' from request."
                )
                return invalid_method(request.method)

            rolling = True
            if "horizon" in form:
                horizon, rolling = parse_horizon(form["horizon"])
                if horizon is None:
                    current_app.logger.warning("Cannot parse 'horizon' value")
                    return invalid_horizon()
                elif ex_post is True:
                    if horizon > timedelta(hours=0):
                        extra_info = "Meter data must have a zero or negative horizon to indicate observations after the fact."
                        return invalid_horizon(extra_info)
            elif infer_missing is True:
                # A missing horizon is only accepted if the server can infer it
                if "start" in form and "duration" in form:
                    start = parse_isodate_str(form["start"])
                    duration = parse_duration(form["duration"], start)
                    if not start:
                        extra_info = "Cannot parse 'start' value."
                        current_app.logger.warning(extra_info)
                        return invalid_period(extra_info)
                    if start.tzinfo is None:
                        current_app.logger.warning(
                            "Cannot parse timezone of 'start' value"
                        )
                        return invalid_timezone(
                            "Start time should explicitly state a timezone."
                        )
                    if not duration:
                        extra_info = "Cannot parse 'duration' value."
                        current_app.logger.warning(extra_info)
                        return invalid_period(extra_info)
                    if current_app.config.get("FLEXMEASURES_MODE", "") == "play":
                        horizon = timedelta(hours=0)
                    else:
                        horizon = start + duration - server_now()
                    rolling = False
                else:
                    current_app.logger.warning(
                        "Request missing both 'horizon', 'start' and 'duration'."
                    )
                    extra_info = "Specify a 'horizon' value, or 'start' and 'duration' values so that the horizon can be inferred."
                    return invalid_horizon(extra_info)
            else:
                # Otherwise, a missing horizon is fine
                horizon = None

            kwargs["horizon"] = horizon
            if infer_missing is True:
                kwargs["rolling"] = rolling
            return fn(*args, **kwargs)
Пример #8
0
 def decorated_service(*args, **kwargs):
     form = get_form_from_request(request)
     if form is None:
         current_app.logger.warning(
             "Unsupported request method for unpacking 'type' from request."
         )
         return invalid_method(request.method)
     elif "type" not in form:
         current_app.logger.warning("Request is missing message type.")
         return no_message_type()
     elif form["type"] != message_type:
         current_app.logger.warning("Type is not accepted for this endpoint.")
         return invalid_message_type(message_type)
     else:
         return fn(*args, **kwargs)
Пример #9
0
    def wrapper(*args, **kwargs):
        form = get_form_from_request(request)
        if form is None:
            current_app.logger.warning(
                "Unsupported request method for unpacking 'unit' from request."
            )
            return invalid_method(request.method)

        if "unit" in form:
            unit = form["unit"]
        else:
            current_app.logger.warning("Request missing 'unit'.")
            return invalid_unit(quantity=None, units=None)

        kwargs["unit"] = unit
        return fn(*args, **kwargs)
Пример #10
0
 def decorated_service(*args, **kwargs):
     form = get_form_from_request(request)
     if form is None:
         current_app.logger.warning(
             "Unsupported request method for unpacking 'unit' from request."
         )
         return invalid_method(request.method)
     elif "unit" not in form:
         current_app.logger.warning("Request is missing unit.")
         return invalid_unit(quantity, units)
     elif form["unit"] not in units:
         current_app.logger.warning(
             "Unit %s is not accepted as one of %s." % (form["unit"], units)
         )
         return invalid_unit(quantity, units)
     else:
         kwargs["unit"] = form["unit"]
         return fn(*args, **kwargs)
Пример #11
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking '%s' from request."
                    % plural_name
                )
                return invalid_method(request.method)

            if generic_asset_type_name in form:
                generic_asset_name_groups = [
                    parse_as_list(form[generic_asset_type_name])
                ]
            elif plural_name in form:
                generic_asset_name_groups = [parse_as_list(form[plural_name])]
            elif groups_name in form:
                generic_asset_name_groups = []
                for group in form["groups"]:
                    if generic_asset_type_name in group:
                        generic_asset_name_groups.append(
                            parse_as_list(group[generic_asset_type_name])
                        )
                    elif plural_name in group:
                        generic_asset_name_groups.append(
                            parse_as_list(group[plural_name])
                        )
                    else:
                        current_app.logger.warning(
                            "Group %s missing %s" % (group, plural_name)
                        )
                        return unrecognized_connection_group()
            else:
                current_app.logger.warning("Request missing %s or group." % plural_name)
                return unrecognized_connection_group()

            if not contains_empty_items(generic_asset_name_groups):
                kwargs["generic_asset_name_groups"] = generic_asset_name_groups
                return fn(*args, **kwargs)
            else:
                current_app.logger.warning("Request includes empty %s." % plural_name)
                return unrecognized_connection_group()
Пример #12
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'source' from request."
                )
                return invalid_method(request.method)

            if "source" in form:
                validated_user_source_ids = validate_user_sources(
                    form["source"])
                if None in validated_user_source_ids:
                    return invalid_source(form["source"])
                kwargs["user_source_ids"] = include_current_user_source_id(
                    validated_user_source_ids)
            elif default_source is not None:
                kwargs["user_source_ids"] = include_current_user_source_id(
                    validate_user_sources(default_source))
            else:
                kwargs["user_source_ids"] = None

            return fn(*args, **kwargs)
Пример #13
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for unpacking 'horizon' from request."
                )
                return invalid_method(request.method)

            rolling = True
            if "horizon" in form:
                horizon, rolling = parse_horizon(form["horizon"])
                if horizon is None:
                    current_app.logger.warning("Cannot parse 'horizon' value")
                    return invalid_horizon()
                elif ex_post is True:
                    if horizon > timedelta(hours=0):
                        extra_info = "Meter data must have a zero or negative horizon to indicate observations after the fact."
                        return invalid_horizon(extra_info)
                elif rolling is True and accept_repeating_interval is False:
                    extra_info = (
                        "API versions 2.0 and higher use regular ISO 8601 durations instead of repeating time intervals. "
                        "For example: R/P1D should be replaced by P1D.")
                    return invalid_horizon(extra_info)
            elif infer_missing is True or (
                    infer_missing_play is True and current_app.config.get(
                        "FLEXMEASURES_MODE", "") == "play"):
                # A missing horizon is set to zero
                horizon = timedelta(hours=0)
            else:
                # Otherwise, a missing horizon is fine (a prior may still be inferred by the server)
                horizon = None

            kwargs["horizon"] = horizon
            if infer_missing is True and accept_repeating_interval is True:
                kwargs["rolling"] = rolling
            return fn(*args, **kwargs)
Пример #14
0
def post_udi_event_response(unit: str, prior: datetime):

    if not has_assets():
        current_app.logger.info("User doesn't seem to have any assets.")

    form = get_form_from_request(request)

    if "datetime" not in form:
        return invalid_datetime("Missing datetime parameter.")
    else:
        datetime = parse_isodate_str(form.get("datetime"))
        if datetime is None:
            return invalid_datetime(
                "Cannot parse datetime string %s as iso date" %
                form.get("datetime"))
        if datetime.tzinfo is None:
            current_app.logger.warning(
                "Cannot parse timezone of 'datetime' value %s" %
                form.get("datetime"))
            return invalid_timezone(
                "Datetime should explicitly state a timezone.")

    # parse event/address info
    if "event" not in form:
        return invalid_domain("No event identifier sent.")
    try:
        ea = parse_entity_address(form.get("event"),
                                  entity_type="event",
                                  fm_scheme="fm0")
    except EntityAddressException as eae:
        return invalid_domain(str(eae))

    sensor_id = ea["asset_id"]
    event_id = ea["event_id"]
    event_type = ea["event_type"]

    if event_type not in ("soc", "soc-with-targets"):
        return unrecognized_event_type(event_type)

    # Look for the Sensor object
    sensor = Sensor.query.filter_by(id=sensor_id).one_or_none()
    if sensor is None or not can_access_asset(sensor):
        current_app.logger.warning("Cannot identify sensor via %s." % ea)
        return unrecognized_connection_group()
    if sensor.generic_asset.generic_asset_type.name not in (
            "battery",
            "one-way_evse",
            "two-way_evse",
    ):
        return invalid_domain(
            f"API version 1.3 only supports UDI events for batteries and Electric Vehicle Supply Equipment (EVSE). "
            f"Sensor ID:{sensor_id} does not belong to a battery or EVSE, but {p.a(sensor.generic_asset.generic_asset_type.description)}."
        )

    # unless on play, keep events ordered by entry date and ID
    if current_app.config.get("FLEXMEASURES_MODE") != "play":
        # do not allow new date to precede previous date
        if isinstance(sensor.generic_asset.get_attribute("soc_datetime"),
                      str) and datetime < datetime.fromisoformat(
                          sensor.generic_asset.get_attribute("soc_datetime")):
            msg = "The date of the requested UDI event (%s) is earlier than the latest known date (%s)." % (
                datetime,
                datetime.fromisoformat(
                    sensor.generic_asset.get_attribute("soc_datetime")),
            )
            current_app.logger.warning(msg)
            return invalid_datetime(msg)

        # check if udi event id is higher than existing
        if sensor.generic_asset.get_attribute("soc_udi_event_id") is not None:
            if sensor.generic_asset.get_attribute(
                    "soc_udi_event_id") >= event_id:
                return outdated_event_id(
                    event_id,
                    sensor.generic_asset.get_attribute("soc_udi_event_id"))

    # get value
    if "value" not in form:
        return ptus_incomplete()
    try:
        value = float(form.get("value"))
    except ValueError:
        extra_info = "Request includes empty or ill-formatted value(s)."
        current_app.logger.warning(extra_info)
        return ptus_incomplete(extra_info)
    if unit == "kWh":
        value = value / 1000.0

    # get optional efficiency
    roundtrip_efficiency = form.get("roundtrip_efficiency", None)

    # get optional min and max SOC
    soc_min = form.get("soc_min", None)
    soc_max = form.get("soc_max", None)
    if soc_min is not None and unit == "kWh":
        soc_min = soc_min / 1000.0
    if soc_max is not None and unit == "kWh":
        soc_max = soc_max / 1000.0

    # set soc targets
    start_of_schedule = datetime
    end_of_schedule = datetime + current_app.config.get(
        "FLEXMEASURES_PLANNING_HORIZON")
    resolution = sensor.event_resolution
    soc_targets = pd.Series(
        np.nan,
        index=pd.date_range(
            start_of_schedule,
            end_of_schedule,
            freq=resolution,
            closed="right"
        ),  # note that target values are indexed by their due date (i.e. closed="right")
    )

    if event_type == "soc-with-targets":
        if "targets" not in form:
            return incomplete_event(
                event_id,
                event_type,
                "Cannot process event %s with missing targets." %
                form.get("event"),
            )
        for target in form.get("targets"):

            # get target value
            if "value" not in target:
                return ptus_incomplete("Target missing value parameter.")
            try:
                target_value = float(target["value"])
            except ValueError:
                extra_info = "Request includes empty or ill-formatted target value(s)."
                current_app.logger.warning(extra_info)
                return ptus_incomplete(extra_info)
            if unit == "kWh":
                target_value = target_value / 1000.0

            # get target datetime
            if "datetime" not in target:
                return invalid_datetime("Target missing datetime parameter.")
            else:
                target_datetime = parse_isodate_str(target["datetime"])
                if target_datetime is None:
                    return invalid_datetime(
                        "Cannot parse target datetime string %s as iso date" %
                        target["datetime"])
                if target_datetime.tzinfo is None:
                    current_app.logger.warning(
                        "Cannot parse timezone of target 'datetime' value %s" %
                        target["datetime"])
                    return invalid_timezone(
                        "Target datetime should explicitly state a timezone.")
                if target_datetime > end_of_schedule:
                    return invalid_datetime(
                        f'Target datetime exceeds {end_of_schedule}. Maximum scheduling horizon is {current_app.config.get("FLEXMEASURES_PLANNING_HORIZON")}.'
                    )
                target_datetime = target_datetime.astimezone(
                    soc_targets.index.tzinfo
                )  # otherwise DST would be problematic

            # set target
            soc_targets.loc[target_datetime] = target_value

    create_scheduling_job(
        sensor_id,
        start_of_schedule,
        end_of_schedule,
        resolution=resolution,
        belief_time=prior,  # server time if no prior time was sent
        soc_at_start=value,
        soc_targets=soc_targets,
        soc_min=soc_min,
        soc_max=soc_max,
        roundtrip_efficiency=roundtrip_efficiency,
        job_id=form.get("event"),
        enqueue=True,
    )

    # Store new soc info as GenericAsset attributes
    sensor.generic_asset.set_attribute("soc_datetime", datetime.isoformat())
    sensor.generic_asset.set_attribute("soc_udi_event_id", event_id)
    sensor.generic_asset.set_attribute("soc_in_mwh", value)

    db.session.commit()
    return request_processed()
Пример #15
0
def post_udi_event_response(unit):  # noqa: C901

    if not has_assets():
        current_app.logger.info("User doesn't seem to have any assets.")

    form = get_form_from_request(request)

    # check datetime, or use server_now
    if "datetime" not in form:
        return invalid_datetime("Missing datetime parameter.")
    else:
        datetime = parse_isodate_str(form.get("datetime"))
        if datetime is None:
            return invalid_datetime(
                "Cannot parse datetime string %s as iso date" %
                form.get("datetime"))
        if datetime.tzinfo is None:
            current_app.logger.warning(
                "Cannot parse timezone of 'datetime' value %s" %
                form.get("datetime"))
            return invalid_timezone(
                "Datetime should explicitly state a timezone.")

    # parse event/address info
    if "event" not in form:
        return invalid_domain("No event identifier sent.")
    try:
        ea = parse_entity_address(form.get("event"),
                                  entity_type="event",
                                  fm_scheme="fm0")
    except EntityAddressException as eae:
        return invalid_domain(str(eae))

    sensor_id = ea["asset_id"]
    event_id = ea["event_id"]
    event_type = ea["event_type"]

    if event_type != "soc":
        return unrecognized_event(event_id, event_type)

    # Look for the Sensor object
    sensor = Sensor.query.filter(Sensor.id == sensor_id).one_or_none()
    if sensor is None or not can_access_asset(sensor):
        current_app.logger.warning("Cannot identify sensor via %s." % ea)
        return unrecognized_connection_group()
    if sensor.generic_asset.generic_asset_type.name != "battery":
        return invalid_domain(
            "API version 1.2 only supports UDI events for batteries. "
            "Sensor ID:%s does not belong to a battery." % sensor_id)

    # unless on play, keep events ordered by entry date and ID
    if current_app.config.get("FLEXMEASURES_MODE") != "play":
        # do not allow new date to be after last date
        if (isinstance(sensor.generic_asset.get_attribute("soc_datetime"), str)
                and datetime.fromisoformat(
                    sensor.generic_asset.get_attribute("soc_datetime")) >=
                datetime):
            msg = "The date of the requested UDI event (%s) is earlier than the latest known date (%s)." % (
                datetime,
                datetime.fromisoformat(
                    sensor.generic_asset.get_attribute("soc_datetime")),
            )
            current_app.logger.warning(msg)
            return invalid_datetime(msg)

        # check if udi event id is higher than existing
        soc_udi_event_id = sensor.generic_asset.get_attribute(
            "soc_udi_event_id")
        if soc_udi_event_id is not None and soc_udi_event_id >= event_id:
            return outdated_event_id(event_id, soc_udi_event_id)

    # get value
    if "value" not in form:
        return ptus_incomplete()
    value = form.get("value")
    if unit == "kWh":
        value = value / 1000.0

    # Store new soc info as GenericAsset attributes
    sensor.generic_asset.set_attribute("soc_datetime", datetime.isoformat())
    sensor.generic_asset.set_attribute("soc_udi_event_id", event_id)
    sensor.generic_asset.set_attribute("soc_in_mwh", value)

    db.session.commit()
    return request_processed("Request has been processed.")
Пример #16
0
def post_udi_event_response(unit):  # noqa: C901

    if not has_assets():
        current_app.logger.info("User doesn't seem to have any assets.")

    form = get_form_from_request(request)

    # check datetime, or use server_now
    if "datetime" not in form:
        return invalid_datetime("Missing datetime parameter.")
    else:
        datetime = parse_isodate_str(form.get("datetime"))
        if datetime is None:
            return invalid_datetime(
                "Cannot parse datetime string %s as iso date" % form.get("datetime")
            )
        if datetime.tzinfo is None:
            current_app.logger.warning(
                "Cannot parse timezone of 'datetime' value %s" % form.get("datetime")
            )
            return invalid_timezone("Datetime should explicitly state a timezone.")

    # parse event/address info
    if "event" not in form:
        return invalid_domain("No event identifier sent.")
    try:
        ea = parse_entity_address(form.get("event"), entity_type="event")
    except EntityAddressException as eae:
        return invalid_domain(str(eae))

    asset_id = ea["asset_id"]
    event_id = ea["event_id"]
    event_type = ea["event_type"]

    if event_type != "soc":
        return unrecognized_event(event_id, event_type)

    # get asset
    asset: Asset = Asset.query.filter_by(id=asset_id).one_or_none()
    if asset is None or not can_access_asset(asset):
        current_app.logger.warning("Cannot identify asset via %s." % ea)
        return unrecognized_connection_group()
    if asset.asset_type_name != "battery":
        return invalid_domain(
            "API version 1.2 only supports UDI events for batteries. Asset ID:%s is not a battery."
            % asset_id
        )

    # unless on play, keep events ordered by entry date and ID
    if current_app.config.get("FLEXMEASURES_MODE") != "play":
        # do not allow new date to be after last date
        if asset.soc_datetime is not None:
            if asset.soc_datetime >= datetime:
                msg = (
                    "The date of the requested UDI event (%s) is earlier than the latest known date (%s)."
                    % (datetime, asset.soc_datetime)
                )
                current_app.logger.warning(msg)
                return invalid_datetime(msg)

        # check if udi event id is higher than existing
        if asset.soc_udi_event_id is not None:
            if asset.soc_udi_event_id >= event_id:
                return outdated_event_id(event_id, asset.soc_udi_event_id)

    # get value
    if "value" not in form:
        return ptus_incomplete()
    value = form.get("value")
    if unit == "kWh":
        value = value / 1000.0

    # store new soc in asset
    asset.soc_datetime = datetime
    asset.soc_udi_event_id = event_id
    asset.soc_in_mwh = value

    db.session.commit()
    return request_processed("Request has been processed.")
Пример #17
0
        def decorated_service(*args, **kwargs):
            form = get_form_from_request(request)
            if form is None:
                current_app.logger.warning(
                    "Unsupported request method for inferring resolution from request."
                )
                return invalid_method(request.method)

            if not all(
                key in kwargs
                for key in [
                    "value_groups",
                    "start",
                    "duration",
                ]
            ):
                current_app.logger.warning("Could not infer resolution.")
                fields = ("values", "start", "duration")
                return required_info_missing(fields, "Resolution cannot be inferred.")
            if "generic_asset_name_groups" not in kwargs:
                return required_info_missing(
                    (entity_type),
                    "Required resolution cannot be found without asset info.",
                )

            # Calculating (inferring) the resolution in the POSTed data
            inferred_resolution = (
                (kwargs["start"] + kwargs["duration"]) - kwargs["start"]
            ) / len(kwargs["value_groups"][0])

            # Finding the required resolution for assets affected in this request
            required_resolution = None
            last_asset = None
            for asset_group in kwargs["generic_asset_name_groups"]:
                for asset_descriptor in asset_group:
                    # Getting the asset
                    generic_asset = get_generic_asset(asset_descriptor, entity_type)
                    if generic_asset is None:
                        return unrecognized_asset(
                            f"Failed to look up asset by {asset_descriptor}"
                        )
                    # Complain if assets don't all require the same resolution
                    if (
                        required_resolution is not None
                        and generic_asset.event_resolution != required_resolution
                    ):
                        return conflicting_resolutions(
                            f"Cannot send data for both {generic_asset} and {last_asset}."
                        )
                    # Setting the resolution & remembering last looked-at asset
                    required_resolution = generic_asset.event_resolution
                    last_asset = generic_asset

            # if inferred resolution is a multiple from required_solution, we can upsample_values
            if inferred_resolution % required_resolution == timedelta(hours=0):
                for i in range(len(kwargs["value_groups"])):
                    kwargs["value_groups"][i] = upsample_values(
                        kwargs["value_groups"][i],
                        from_resolution=inferred_resolution,
                        to_resolution=required_resolution,
                    )
                inferred_resolution = required_resolution

            if inferred_resolution != required_resolution:
                current_app.logger.warning(
                    f"Resolution {inferred_resolution} is not accepted. We require {required_resolution}."
                )
                return unapplicable_resolution(
                    isodate.duration_isoformat(required_resolution)
                )
            else:
                kwargs["resolution"] = inferred_resolution
                return fn(*args, **kwargs)