diff --git a/ingest/api/messages.py b/ingest/api/messages.py index 8dbf497d..ecd1d394 100644 --- a/ingest/api/messages.py +++ b/ingest/api/messages.py @@ -26,6 +26,17 @@ def build_messages(message: object, uuid_prefix: str): # Set message publication time in RFC3339 format # Create UUID for the message, and state message format version for json_msg in message: + + # Convert: (start_datetime, end_datetime) => (datetime, period) + if "start_datetime" in json_msg["properties"] and "end_datetime" in json_msg["properties"]: + json_msg["properties"]["datetime"] = json_msg["properties"]["end_datetime"] + start_dt = datetime.fromisoformat(json_msg["properties"]["start_datetime"]) + end_dt = datetime.fromisoformat(json_msg["properties"]["end_datetime"]) + period_int = end_dt - start_dt + json_msg["properties"]["period"] = "PT" + str(period_int) + "S" + json_msg["properties"].pop("start_datetime") + json_msg["properties"].pop("end_datetime") + period = json_msg["properties"]["period_int"] message_uuid = f"{uuid_prefix}:{str(uuid.uuid4())}" json_msg["id"] = message_uuid diff --git a/ingest/api/model.py b/ingest/api/model.py index 8a5ebbdf..8104b5cb 100644 --- a/ingest/api/model.py +++ b/ingest/api/model.py @@ -242,7 +242,7 @@ class Properties(BaseModel): description=("Instrument level above ground in meters."), ) period: str = Field( - ..., + None, description=( "Aggregation period for the measurement. Must be provided in ISO8601 duration format." "https://www.iso.org/iso-8601-date-and-time-format.html" @@ -284,7 +284,7 @@ class Properties(BaseModel): ), ) datetime: str = Field( - ..., + None, description="Identifies the date/time of the datas being published, in RFC3339 format.", ) start_datetime: Optional[str] = Field( @@ -345,17 +345,61 @@ def convert_to_cm(self): self.level = int(float(self.level) * 100) return self + @model_validator(mode="after") + def check_required_datetime_attributes(self) -> "Properties": + + has_start_datetime = self.start_datetime is not None and len(self.start_datetime) + has_end_datetime = self.end_datetime is not None and len(self.end_datetime) + has_datetime = self.datetime is not None and len(self.datetime) + has_period = self.period is not None and len(self.period) + if has_datetime: + if has_start_datetime or has_end_datetime: + raise ValueError("Double required attributes: datetime or start_datetime/end_datetime") + if not has_period: + raise ValueError("Required attribute: period") + else: + if not has_start_datetime: + raise ValueError("Required attribute: start_datetime") + if not has_end_datetime: + raise ValueError("Required attribute: end_datetime") + + return self + @model_validator(mode="after") def check_datetime_iso(self) -> "Properties": try: - dt = parser.isoparse(self.datetime) + has_start_datetime = self.start_datetime is not None and len(self.start_datetime) + has_end_datetime = self.end_datetime is not None and len(self.end_datetime) + + if has_start_datetime and has_end_datetime: + st = parser.isoparse(self.start_datetime) + et = parser.isoparse(self.end_datetime) + else: + dt = parser.isoparse(self.datetime) except ValueError: - raise ValueError(f"{self.datetime} not in ISO format(YYYY-MM-DDTHH:MM:SSZ)") + if has_start_datetime and has_end_datetime: + raise ValueError( + f"{self.start_datetime} or {self.end_datetime} not in ISO format(YYYY-MM-DDTHH:MM:SSZ)" + ) + else: + raise ValueError(f"{self.datetime} not in ISO format(YYYY-MM-DDTHH:MM:SSZ)") except Exception as e: raise e - if dt.tzname() != "UTC": - raise ValueError(f"Input datetime, {self.datetime}, is not in UTC timezone") + if has_start_datetime and has_end_datetime: + if et < st: + raise ValueError( + f"end_datetime {self.end_datetime} is earlier than start_datetime {self.start_datetime}" + ) + if st.tzname() != "UTC": + raise ValueError(f"Input start_datetime, {self.start_datetime}, is not in UTC timezone") + if et.tzname() != "UTC": + raise ValueError(f"Input end_datetime, {self.end_datetime}, is not in UTC timezone") + self.period = "PT" + str((et - st).seconds) + "S" + else: + if dt.tzname() != "UTC": + raise ValueError(f"Input datetime, {self.datetime}, is not in UTC timezone") + return self @model_validator(mode="after") diff --git a/ingest/src/ingest/schemas/e-soh-ingest-spec.json b/ingest/src/ingest/schemas/e-soh-ingest-spec.json index 876a1ae7..adad38ed 100644 --- a/ingest/src/ingest/schemas/e-soh-ingest-spec.json +++ b/ingest/src/ingest/schemas/e-soh-ingest-spec.json @@ -268,7 +268,8 @@ "allOf": [ { "required": [ - "datetime" + "datetime", + "period" ] } ] diff --git a/ingest/src/ingest/schemas/e-soh-message-spec.json b/ingest/src/ingest/schemas/e-soh-message-spec.json index cf934926..d751f5eb 100644 --- a/ingest/src/ingest/schemas/e-soh-message-spec.json +++ b/ingest/src/ingest/schemas/e-soh-message-spec.json @@ -331,7 +331,8 @@ "allOf": [ { "required": [ - "datetime" + "datetime", + "period" ] } ]