Skip to content

Commit

Permalink
Merge branch 'main' into StevenHosper/issue157
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenHosper authored Dec 18, 2024
2 parents 5428bcf + 27214f2 commit 2b3f1bf
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 55 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- Bugfix: Add correction reason to GMW-InsertRequests
- Add Events Endpoint
- Add Events Import
- Enhancement: Allow GLD-Addition date and resultTime to be unknown (IMBRO/A)
- Improve the docs


## 0.72 (2024-12-17)
Expand Down
103 changes: 73 additions & 30 deletions api/bro_upload/gld_bulk_upload.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import logging
import time
from typing import TypeVar
Expand All @@ -6,7 +7,6 @@

from api import models as api_models
from api.bro_upload.upload_datamodels import (
GLDAddition,
TimeValuePair,
)

Expand Down Expand Up @@ -85,16 +85,55 @@ def process(self) -> None:
"requestReference": self.bulk_upload_instance.metadata["requestReference"],
}

measurement_tvps: list[TimeValuePair] = [
TimeValuePair(**row) for row in measurements_df.iter_rows(named=True)
measurements_df = measurements_df.sort("time")
begin_position = measurements_df.item(0, 0)
end_position = measurements_df.item(-1, 0)

if len(begin_position) == 19:
begin_position = datetime.datetime.strptime("%Y-%m-%dT%H:%M:%S")
elif len(begin_position) > 19:
begin_position = datetime.datetime.strptime(
"%Y-%m-%dT%H:%M:%S%z"
).astimezone(datetime.UTC)
else:
raise ValueError(
f"Time has incorrect format, use: YYYY-mm-ddTHH:MM:SS+-Timezone. Not: {begin_position}."
)

if len(end_position) == 19:
end_position = datetime.datetime.strptime("%Y-%m-%dT%H:%M:%S")
elif len(end_position) > 19:
end_position = datetime.datetime.strptime("%Y-%m-%dT%H:%M:%S%z").astimezone(
datetime.UTC
)
else:
raise ValueError(
f"Time has incorrect format, use: YYYY-mm-ddTHH:MM:SS+-Timezone. Not: {end_position}."
)

if (
self.bulk_upload_instance.sourcedocument_data["validationStatus"]
== "volledigBeoordeeld"
):
result_time = end_position + datetime.timedelta(days=1)
else:
result_time = end_position

measurement_tvps: list[dict] = [
TimeValuePair(**row).model_dump_json()
for row in measurements_df.iter_rows(named=True)
]

uploadtask_sourcedocument_data: GLDAddition = create_gld_sourcedocs_data(
measurement_tvps, self.bulk_upload_instance.metadata
self.bulk_upload_instance.sourcedocument_data.update(
{
"beginPosition": begin_position.isoformat(sep="T", timespec="seconds"),
"endPosition": end_position.isoformat(sep="T", timespec="seconds"),
"resultTime": result_time.isoformat(sep="T", timespec="seconds"),
}
)

uploadtask_sourcedocument_data_dict = (
uploadtask_sourcedocument_data.model_dump()
uploadtask_sourcedocument_dict: dict = create_gld_sourcedocs_data(
measurement_tvps, self.bulk_upload_instance.sourcedocument_data
)

upload_task = api_models.UploadTask.objects.create(
Expand All @@ -104,7 +143,7 @@ def process(self) -> None:
registration_type="GLD_Addition",
request_type="registration",
metadata=uploadtask_metadata,
sourcedocument_data=uploadtask_sourcedocument_data_dict,
sourcedocument_data=uploadtask_sourcedocument_dict,
)

self.bulk_upload_instance.progress = 50.00
Expand Down Expand Up @@ -157,28 +196,32 @@ def _convert_resulttime_to_date(result_time: str) -> str:


def create_gld_sourcedocs_data(
measurement_tvps: list[TimeValuePair], metadata: dict[str, any]
) -> GLDAddition:
measurement_tvps: list[dict], sourcedocument_data: dict
) -> dict:
"""Creates a GLDAddition (the pydantic model), based on a row of the merged df of the GLD bulk upload input."""
sourcedocs_data_dict = {
"date": _convert_resulttime_to_date(metadata["resultTime"]),
"validationStatus": metadata["validationStatus"],
"investigatorKvk": metadata["investigatorKvk"],
"observationType": metadata["observationType"],
"evaluationProcedure": metadata["evaluationProcedure"],
"measurementInstrumentType": metadata["measurementInstrumentType"],
"processReference": metadata["processReference"],
"beginPosition": metadata["beginPosition"],
"endPosition": metadata["endPosition"],
"resultTime": metadata["resultTime"],
"timeValuePairs": measurement_tvps,
}

if metadata["airPressureCompensationType"]:
sourcedocs_data_dict.update(
{"airPressureCompensationType": metadata["airPressureCompensationType"]}
sourcedocument_data.update(
{
"date": _convert_resulttime_to_date(sourcedocument_data["resultTime"]),
# "validationStatus": sourcedocument_data.get("validationStatus", None),
# "investigatorKvk": sourcedocument_data.get("investigatorKvk", None),
# "observationType": sourcedocument_data.get("observationType", None),
# "evaluationProcedure": sourcedocument_data.get("evaluationProcedure", None),
# "measurementInstrumentType": sourcedocument_data.get("measurementInstrumentType"),
# "processReference": sourcedocument_data.get("processReference", None),
# "beginPosition": sourcedocument_data("beginPosition", None),
# "endPosition": sourcedocument_data.get("endPosition", None),
# "resultTime": sourcedocument_data.get("resultTime", None),
"timeValuePairs": measurement_tvps,
}
)

if sourcedocument_data["airPressureCompensationType"]:
sourcedocument_data.update(
{
"airPressureCompensationType": sourcedocument_data[
"airPressureCompensationType"
]
}
)

sourcedocs_data = GLDAddition(**sourcedocs_data_dict)

return sourcedocs_data
return sourcedocument_data
8 changes: 8 additions & 0 deletions api/bro_upload/templates/delete_GLD_Addition.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
</gmd:CI_ResponsibleParty>
</gmd:contact>
<gmd:dateStamp>
{% if not sourcedocs_data.date %}
<gco:Date>{{ sourcedocs_data.date }}</gco:Date>
{% elif not sourcedocs_data.date %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gmd:dateStamp>
<gmd:identificationInfo gco:nilReason="unknown"/>
<wml2:status xlink:href="urn:bro:gld:StatusCode:voorlopig"/>
Expand Down Expand Up @@ -61,7 +65,11 @@
</om:phenomenonTime>
<om:resultTime>
<gml:TimeInstant gml:id="id_0006">
{% if sourcedocs_data.resultTime %}
<gml:timePosition>{{ sourcedocs_data.resultTime }}</gml:timePosition>
{% elif not sourcedocs_data.resultTime %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gml:TimeInstant>
</om:resultTime>
<om:procedure>
Expand Down
8 changes: 8 additions & 0 deletions api/bro_upload/templates/registration_GLD_Addition.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
</gmd:CI_ResponsibleParty>
</gmd:contact>
<gmd:dateStamp>
{% if not sourcedocs_data.date %}
<gco:Date>{{ sourcedocs_data.date }}</gco:Date>
{% elif not sourcedocs_data.date %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gmd:dateStamp>
<gmd:identificationInfo gco:nilReason="unknown"/>
{% if sourcedocs_data.validationStatus == 'volledigBeoordeeld' %}
Expand Down Expand Up @@ -66,7 +70,11 @@
</om:phenomenonTime>
<om:resultTime>
<gml:TimeInstant gml:id="id_0006">
{% if sourcedocs_data.resultTime %}
<gml:timePosition>{{ sourcedocs_data.resultTime }}</gml:timePosition>
{% elif not sourcedocs_data.resultTime %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gml:TimeInstant>
</om:resultTime>
<om:procedure>
Expand Down
8 changes: 8 additions & 0 deletions api/bro_upload/templates/replace_GLD_Addition.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
</gmd:CI_ResponsibleParty>
</gmd:contact>
<gmd:dateStamp>
{% if not sourcedocs_data.date %}
<gco:Date>{{ sourcedocs_data.date }}</gco:Date>
{% elif not sourcedocs_data.date %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gmd:dateStamp>
<gmd:identificationInfo gco:nilReason="unknown"/>
{% if sourcedocs_data.validationStatus == 'volledigBeoordeeld' %}
Expand Down Expand Up @@ -67,7 +71,11 @@
</om:phenomenonTime>
<om:resultTime>
<gml:TimeInstant gml:id="id_0006">
{% if sourcedocs_data.resultTime %}
<gml:timePosition>{{ sourcedocs_data.resultTime }}</gml:timePosition>
{% elif not sourcedocs_data.resultTime %}
<brocom:voidReason>onbekend</brocom:voidReason>
{% endif %}
</gml:TimeInstant>
</om:resultTime>
<om:procedure>
Expand Down
14 changes: 8 additions & 6 deletions api/bro_upload/upload_datamodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,19 @@ class GLDBulkUploadMetadata(BaseModel):
qualityRegime: str
deliveryAccountableParty: str | None = None
broId: str
# For Addition (Procedure data)


class GLDBulkUploadSourcedocumentData(BaseModel):
validationStatus: str | None = None
investigatorKvk: str
observationType: str
evaluationProcedure: str
measurementInstrumentType: str
processReference: str
airPressureCompensationType: str | None = None
beginPosition: str
endPosition: str
resultTime: str
beginPosition: str | None = None
endPosition: str | None = None
resultTime: str | None = None


# GMN sourcedocs_data
Expand Down Expand Up @@ -362,7 +364,7 @@ def format_datetime(cls, value):


class GLDAddition(BaseModel):
date: str
date: str | None = None
observationId: str | None = None
observationProcessId: str | None = None
measurementTimeseriesId: str | None = None
Expand All @@ -375,7 +377,7 @@ class GLDAddition(BaseModel):
airPressureCompensationType: str | None = None
beginPosition: str
endPosition: str
resultTime: str
resultTime: str | None = None
timeValuePairs: list[TimeValuePair]

@validator("observationId", pre=True, always=True)
Expand Down
4 changes: 4 additions & 0 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from api.bro_upload.upload_datamodels import (
GARBulkUploadMetadata,
GLDBulkUploadMetadata,
GLDBulkUploadSourcedocumentData,
UploadTaskMetadata,
)
from api.choices import registration_type_datamodel_mapping
Expand Down Expand Up @@ -582,6 +583,9 @@ def create(self, request):
# Check data with pydantic models:
try:
GLDBulkUploadMetadata(**serializer.validated_data["metadata"])
GLDBulkUploadSourcedocumentData(
**serializer.validated_data["sourcedocument_data"]
)
except ValidationError as e:
errors = utils.simplify_validation_errors(e.errors())
return Response({"detail": errors}, status=status.HTTP_400_BAD_REQUEST)
Expand Down
20 changes: 10 additions & 10 deletions docs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ De browsable BROSTAR API is te vinden op [https://www.brostar.nl/api/](https://w

## API Keys

Voor elke request die op de API gedaan wordt, moet een API key gebruikt worden om de gebruiker te authenticeren. Een API key is gebruikersgebonden en kunnen momenteel alleen aangevraagd worden bij Nelen & Schuurmans. Het eigenhandig beheren van API keys in de Api en frontend zal spoedig worden ontwikkeld.
Voor elke request die op de API gedaan wordt, moet een API key gebruikt worden om de gebruiker te authenticeren. Een API key is gebruikersgebonden en kunnen momenteel alleen aangevraagd worden bij Nelen & Schuurmans. Het eigenhandig beheren van API keys in de API en frontend zal spoedig worden ontwikkeld.

Een request op de BROSTAR API, inclusief een API Key, ziet er als volgt uit:

Expand Down Expand Up @@ -89,14 +89,14 @@ Op het [https://www.brostar.nl/api/users/logged-in/](https://www.brostar.nl/api/

### Importtasks

Import taken zorgen ervoor dat de huidig aanwezige data in de BRO in de BROSTAR database belanden. Deze data is vervolgens in de specifieke endpoints op te vragen.
Importtaken zorgen ervoor dat de huidig aanwezige data in de BRO in de BROSTAR database belanden. Deze data is vervolgens in de specifieke endpoints op te vragen.

Import taken vinden plaats op basis van POST requests. In deze request wordt een combinatie van een BRO domein (GAR, GLD, GMW, GMN of FRD) en een kvk nummer meegegeven. Voordat de import start, wordt alle data voor dat domein in de BROSTAR verwijderd. Nadat een taak is geslaagd, is de data in de BROSTAR voor dat specifieke domein dus up-to-date met de BRO.
Importtaken vinden plaats op basis van POST requests. In deze request wordt een combinatie van een BRO domein (GAR, GLD, GMW, GMN of FRD) en een kvk nummer meegegeven. Voordat de import start, wordt alle data voor dat domein in de BROSTAR verwijderd. Nadat een taak is geslaagd, is de data in de BROSTAR voor dat specifieke domein dus up-to-date met de BRO.

De data die wordt geimporteerd is slechts de huidige versie van de metadata. Er wordt dus bijvoorbeeld geen geschiedenis van een GMW of de standen van een GLD geimporteerd. Hiervoor kan de BRO zelf bevraagd worden.

!!! note
De aanwezigheid van de data in de BROSTAR is essentieel voor de frontend om te bestaan. Het kan dus zijn dat je als scripter, die alleen bezig is met het aanleveren van data, geen gebruik maakt van dit endpoint. Toch kan het in sommige gevallen handig zijn. Voorbeelden hiervan zijn om een vertaling van een nitg code naar een BRO id te maken of te controleren of bepaalde objecten reeds aangeleverd zijn.
De aanwezigheid van de data in de BROSTAR is essentieel voor de frontend om te bestaan. Het kan dus zijn dat je als scripter, die alleen bezig is met het aanleveren van data, geen gebruik maakt van dit endpoint. Toch kan het in sommige gevallen handig zijn. Voorbeelden hiervan zijn om een vertaling van een NITG-code naar een BRO-id te maken of te controleren of bepaalde objecten reeds aangeleverd zijn.

Hieronder een voorbeeld van een POST request om een GMN import taak voor eigen organisatie te starten:

Expand All @@ -123,7 +123,7 @@ r = requests.post(url, auth=auth, payload=payload)

### Uploadtasks

Upload taken zijn dé kracht van de BROSTAR. Het idee achter dit endpoint is dat er slechts JSON opgesteld hoeft te worden om data aan te leveren. Om dit te visualiseren volgt hieronder een code snippet, waarmee een GMN aangemaakt kan worden in de BRO.
Uploadtaken zijn dé kracht van de BROSTAR. Het idee achter dit endpoint is dat er slechts JSON opgesteld hoeft te worden om data aan te leveren. Om dit te visualiseren volgt hieronder een code snippet, waarmee een GMN aangemaakt kan worden in de BRO.

```python
import requests
Expand Down Expand Up @@ -193,7 +193,7 @@ Het BRO project nummer is nodig om data bij de BRO aan te kunnen leveren. Deze k
Voor elk BRO domein zijn er verschillende type berichten mogelijk. Zo zijn er bijvoorbeeld voor de [GMN](https://www.bro-productomgeving.nl/bpo/latest/gmn-inname-voorbeeldberichten-in-xml) Startregistration, MeasuringPoint, en Closure als berichten mogelijk.

#### request_type
Elk registratie type kan op verschillende manieren aangeleverd worden. In principe is de registration de standaardoptie, maar als er data aangepast of verwijderd moet worden, dan zijn respectievelijk de replace en delete requests types beschikbaar. In de [BRO catalogus](https://www.bro-productomgeving.nl/bpo/latest/grondwatermonitoring) staan alle mogelijke combinaties. Dit zijn de beschikbare request types:
Elk registration type kan op verschillende manieren aangeleverd worden. In principe is de registration de standaardoptie, maar als er data aangepast of verwijderd moet worden, dan zijn respectievelijk de replace en delete requests types beschikbaar. In de [BRO catalogus](https://www.bro-productomgeving.nl/bpo/latest/grondwatermonitoring) staan alle mogelijke combinaties. Dit zijn de beschikbare request types:

- registration

Expand Down Expand Up @@ -719,14 +719,14 @@ Om meer inzicht te geven in de data die naar de BRO wordt gestuurd, is het mogel
!!! warning
Het bulk uploadtask endpoint is maatwerk. Contact [[email protected]](mailto:[email protected]?subject=Aanvraag maatwerk bulk upload BROSTAR ) om de mogelijkheden te verkennen om een specifieke bulk upload te realiseren.

Het bulk upload endpoint is gemaakt om eenvoudig een groot aantal leveringen te realiseren. Op het bulk endpoint is het mogelijk om csv/excel bestanden aan te levern. Achter de schermen wordt een taak gestart die deze bestanden opknippen in meerdere upload taken.
Het bulk upload endpoint is gemaakt om eenvoudig een groot aantal leveringen te realiseren. Op het bulk endpoint is het mogelijk om CSV/Excel bestanden aan te leveren. Achter de schermen wordt een taak gestart die deze bestanden opknipt in meerdere uploadtaken.

Het bulk upload endpoint is stukje maatwerk in de BROSTAR. Momenteel bestaat alleen de optie om een specifiek formaat van GAR csv bestanden aan te leveren. Deze zijn ontwikkeld voor Provincie Noord-Brabant, waarbij hun formaat van lab- en veldbestanden zijn gebruikt als input. Daardoor is het voor hen mogelijk om 2 bestanden in de frontend te slepen, wat metadata op te geven, en de bestanden naar de API te sturen. Hiermee wordt alle data dus vertaald naar upload taken, die vervolgens de data naar de BRO sturen.
Het bulk upload endpoint is een stukje maatwerk in de BROSTAR. Momenteel bestaat alleen de optie om een specifiek formaat van GAR CSV bestanden aan te leveren. Deze zijn ontwikkeld voor Provincie Noord-Brabant, waarbij hun formaat van lab- en veldbestanden zijn gebruikt als input. Daardoor is het voor hen mogelijk om 2 bestanden in de frontend te slepen, wat metadata op te geven, en de bestanden naar de API te sturen. Hiermee wordt alle data dus vertaald naar uploadtaken, die vervolgens de data naar de BRO sturen.

Hieronder staat een voorbeeld van hoe een bulk upload opgestuurd kan worden via een script.

!!! Wow
Dit stukje code, in combinatie met de xlsx bestanden, is dus alles wat nodig is om duizenden GAR berichten te registreren in de BRO!
Dit stukje code, in combinatie met de XLSX bestanden, is dus alles wat nodig is om duizenden GAR berichten te registreren in de BRO!

```python
import requests
Expand Down Expand Up @@ -795,4 +795,4 @@ De data endpoints zijn een tijdelijke opslag voor de data die volgt uit de impor

- `https://www.brostar.nl/api/frd/frds/`

Op deze endpoints zijn de lijsten van objecten te zien. Deze lijsten van de metadata van objecten bieden een mooi overzicht van alles wat er in de BRO aan data staat. Dit is een mooie vervanging voor de [uitgifteservice van de BRO](https://publiek.broservices.nl/gm/gmn/v1/swagger-ui/#/default/bro-ids), aangezien daar alleen request gedaan kunnen worden op basis van idividuele objecten. Deze endpoints kunnen dus helpen bij het scripten, maar dienen vooral voor de frontend om de data snel op te kunnen vragen om het vervolgens in de kaart en tabellen weer te geven.
Op deze endpoints zijn de lijsten van objecten te zien. Deze lijsten van de metadata van objecten bieden een mooi overzicht van alles wat er in de BRO aan data staat. Dit is een mooie vervanging voor de [uitgifteservice van de BRO](https://publiek.broservices.nl/gm/gmn/v1/swagger-ui/#/default/bro-ids), aangezien daar alleen requests gedaan kunnen worden op basis van individuele objecten. Deze endpoints kunnen dus helpen bij het scripten, maar dienen vooral voor de frontend om de data snel op te kunnen vragen om het vervolgens in de kaart en tabellen weer te geven.
Loading

0 comments on commit 2b3f1bf

Please sign in to comment.