Skip to content

Commit 7966ea4

Browse files
IngeborgGjerdeIngeborg Gjerde
andauthored
Add example for getting piezometer data (#166)
Co-authored-by: Ingeborg Gjerde <ingeborg.gjerde@ngi.no>
1 parent 92f169e commit 7966ea4

3 files changed

Lines changed: 280 additions & 1 deletion

File tree

examples/EXAMPLES_OVERVIEW.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ Before running any examples:
5555
- Identifies discrepancies between data sources
5656
- Exports discrepancy reports to separate Excel files
5757

58+
### Piezometer Data
59+
- **`ex_get_piezometer_data.py`** - Get piezometer methods and data rows
60+
- Finds locations with piezometer (PZ) methods
61+
- Fetches PZ methods for each matching location
62+
- Retrieves and previews piezometer readings and calculated values
63+
- Supports optional `FIELD_MANAGER_PROJECT_ID` and limit environment variables
64+
5865
## 🎯 File Naming Convention
5966

6067
All examples follow the pattern: `ex_[verb]_[object]_[context].py`
@@ -81,4 +88,4 @@ Examples generate various output files in the `output/` directory:
8188

8289
---
8390

84-
**Need help?** Check the main [README.md](../README.md) or individual example files for detailed usage instructions.
91+
**Need help?** Check the main [README.md](../README.md) or individual example files for detailed usage instructions.

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ That's it! 🎉
4040
- **`ex_historical_projects.py`** - Analyze historical project data
4141
- **`ex_find_duplicate_locations_within_organization.py`** - Find duplicate locations
4242
- **`ex_cross_check_location_list.py`** - Cross-validate location data
43+
- **`ex_get_piezometer_data.py`** - Find PZ methods and preview piezometer data rows
4344

4445
### 🔐 Authentication Examples
4546
- **`example_using_integrated_auth.py`** - Different authentication methods
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example: Get Piezometer Data
4+
5+
This example shows how to:
6+
1. Connect to the Field Manager API
7+
2. Select a project, either from FIELD_MANAGER_PROJECT_ID or the first accessible project
8+
3. Find locations with piezometer (PZ) methods
9+
4. Fetch and print piezometer data rows for each method
10+
11+
Optional .env values:
12+
DEFAULT_EMAIL=your.email@example.com
13+
FIELD_MANAGER_ENVIRONMENT=prod
14+
FIELD_MANAGER_PROJECT_ID=<project id>
15+
FIELD_MANAGER_LOCATION_LIMIT=100
16+
FIELD_MANAGER_METHOD_LIMIT=100
17+
FIELD_MANAGER_DATA_PREVIEW_LIMIT=5
18+
19+
Run this example from the examples directory:
20+
python examples/ex_get_piezometer_data.py
21+
"""
22+
23+
from __future__ import annotations
24+
25+
import os
26+
from pathlib import Path
27+
from typing import Any
28+
29+
from dotenv import load_dotenv
30+
31+
from field_manager_python_client import get_prod_client, get_test_client
32+
from field_manager_python_client.api.locations import (
33+
get_locations_in_project_projects_project_id_locations_get,
34+
)
35+
from field_manager_python_client.api.methods import (
36+
get_methods_data_projects_project_id_locations_location_id_methods_method_id_data_get,
37+
get_methods_for_location_of_type_projects_project_id_locations_location_id_methods_type_method_type_name_get,
38+
)
39+
from field_manager_python_client.api.organizations import (
40+
get_organization_projects_organizations_organization_id_projects_get,
41+
get_organizations_organizations_get,
42+
)
43+
from field_manager_python_client.models.http_validation_error import HTTPValidationError
44+
from field_manager_python_client.models.location import Location
45+
from field_manager_python_client.models.method_pz import MethodPZ
46+
from field_manager_python_client.models.method_pz_data import MethodPZData
47+
from field_manager_python_client.models.method_type_enum import MethodTypeEnum
48+
from field_manager_python_client.models.method_type_enum_str import MethodTypeEnumStr
49+
from field_manager_python_client.models.project import Project
50+
from field_manager_python_client.types import Unset
51+
52+
# Method type id 5 is PZ in Field Manager.
53+
PZ_METHOD_TYPE_ID = MethodTypeEnum.VALUE_5
54+
get_piezometer_methods_for_location = get_methods_for_location_of_type_projects_project_id_locations_location_id_methods_type_method_type_name_get
55+
get_method_data = get_methods_data_projects_project_id_locations_location_id_methods_method_id_data_get
56+
57+
ENV_FILE = Path(__file__).resolve().parent.parent / ".env"
58+
load_dotenv(ENV_FILE)
59+
load_dotenv()
60+
61+
DEFAULT_EMAIL = os.getenv("DEFAULT_EMAIL", "your.email@example.com")
62+
FIELD_MANAGER_ENVIRONMENT = os.getenv("FIELD_MANAGER_ENVIRONMENT", "test")
63+
FIELD_MANAGER_PROJECT_ID = os.getenv("FIELD_MANAGER_PROJECT_ID")
64+
LOCATION_LIMIT = int(os.getenv("FIELD_MANAGER_LOCATION_LIMIT", "10"))
65+
METHOD_LIMIT = int(os.getenv("FIELD_MANAGER_METHOD_LIMIT", "10"))
66+
DATA_PREVIEW_LIMIT = int(os.getenv("FIELD_MANAGER_DATA_PREVIEW_LIMIT", "5"))
67+
68+
69+
def _get_client(email: str, environment: str):
70+
environment = environment.lower()
71+
if environment == "prod":
72+
return get_prod_client(email=email)
73+
if environment == "test":
74+
return get_test_client(email=email)
75+
raise ValueError("FIELD_MANAGER_ENVIRONMENT must be either 'prod' or 'test'")
76+
77+
78+
def _format_value(value: Any) -> str:
79+
if value is None or isinstance(value, Unset):
80+
return "-"
81+
return str(value)
82+
83+
84+
def _format_row(row: MethodPZData) -> str:
85+
return (
86+
f"{_format_value(row.date)} | "
87+
f"reading={_format_value(row.reading_type)} | "
88+
f"pore_pressure={_format_value(row.pore_pressure)} | "
89+
f"calculated_pore_pressure={_format_value(row.calculated_pore_pressure)} | "
90+
f"head={_format_value(row.calculated_piezometric_head)} | "
91+
f"potential_level={_format_value(row.calculated_piezometric_potential_level)} | "
92+
f"temperature={_format_value(row.temperature)}"
93+
)
94+
95+
96+
def _select_first_accessible_project(client) -> Project | None:
97+
organizations = get_organizations_organizations_get.sync(client=client)
98+
99+
if organizations is None:
100+
print("Organizations request returned no data.")
101+
return None
102+
103+
if isinstance(organizations, HTTPValidationError):
104+
print(f"Organizations request failed: {organizations}")
105+
return None
106+
107+
for organization in organizations:
108+
projects = (
109+
get_organization_projects_organizations_organization_id_projects_get.sync(
110+
client=client,
111+
organization_id=organization.organization_id,
112+
limit=100,
113+
)
114+
)
115+
116+
if projects is None or isinstance(projects, HTTPValidationError):
117+
continue
118+
119+
if projects:
120+
return projects[0]
121+
122+
return None
123+
124+
125+
def _get_locations_with_piezometers(client, project_id: str) -> list[Location]:
126+
locations = get_locations_in_project_projects_project_id_locations_get.sync(
127+
client=client,
128+
project_id=project_id,
129+
limit=LOCATION_LIMIT,
130+
method_types=[PZ_METHOD_TYPE_ID],
131+
)
132+
133+
if locations is None:
134+
print("Locations request returned no data.")
135+
return []
136+
137+
if isinstance(locations, HTTPValidationError):
138+
print(f"Locations request failed: {locations}")
139+
return []
140+
141+
return locations
142+
143+
144+
def _get_piezometer_methods(
145+
client,
146+
project_id: str,
147+
location: Location,
148+
) -> list[MethodPZ]:
149+
methods = get_piezometer_methods_for_location.sync(
150+
client=client,
151+
project_id=project_id,
152+
location_id=location.location_id,
153+
method_type_name=MethodTypeEnumStr.PZ,
154+
limit=METHOD_LIMIT,
155+
)
156+
157+
if methods is None:
158+
print(f" Methods request returned no data for {location.name}.")
159+
return []
160+
161+
if isinstance(methods, HTTPValidationError):
162+
print(f" Methods request failed for {location.name}: {methods}")
163+
return []
164+
165+
return [method for method in methods if isinstance(method, MethodPZ)]
166+
167+
168+
def _get_piezometer_data(
169+
client,
170+
project_id: str,
171+
location: Location,
172+
method: MethodPZ,
173+
) -> list[MethodPZData]:
174+
rows = get_method_data.sync(
175+
client=client,
176+
project_id=project_id,
177+
location_id=location.location_id,
178+
method_id=method.method_id,
179+
)
180+
181+
if rows is None:
182+
print(f" Data request returned no rows for method {method.name}.")
183+
return []
184+
185+
if isinstance(rows, HTTPValidationError):
186+
print(f" Data request failed for method {method.name}: {rows}")
187+
return []
188+
189+
return [row for row in rows if isinstance(row, MethodPZData)]
190+
191+
192+
def main() -> None:
193+
print("Getting piezometer data")
194+
print()
195+
print(f"Using email: {DEFAULT_EMAIL}")
196+
print(f"Using environment: {FIELD_MANAGER_ENVIRONMENT}")
197+
198+
client = _get_client(DEFAULT_EMAIL, FIELD_MANAGER_ENVIRONMENT)
199+
200+
with client as authenticated_client:
201+
project_id = FIELD_MANAGER_PROJECT_ID
202+
project_name = "configured project"
203+
204+
if not project_id:
205+
project = _select_first_accessible_project(authenticated_client)
206+
if project is None:
207+
print("No accessible projects found.")
208+
return
209+
project_id = str(project.project_id)
210+
project_name = project.name
211+
212+
print(f"Using project: {project_name} ({project_id})")
213+
print()
214+
215+
locations = _get_locations_with_piezometers(authenticated_client, project_id)
216+
if not locations:
217+
print("No locations with piezometer methods found.")
218+
return
219+
220+
print(f"Found {len(locations)} location(s) with piezometer methods.")
221+
222+
total_methods = 0
223+
total_rows = 0
224+
225+
for location in locations:
226+
print()
227+
print(f"Location: {location.name} ({location.location_id})")
228+
229+
methods = _get_piezometer_methods(
230+
authenticated_client,
231+
project_id,
232+
location,
233+
)
234+
total_methods += len(methods)
235+
236+
if not methods:
237+
print(" No PZ methods found.")
238+
continue
239+
240+
for method in methods:
241+
method_type = _format_value(method.piezometer_type)
242+
transformation = _format_value(method.transformation_type)
243+
print(f" Method: {method.name} ({method.method_id})")
244+
print(f" Type: {method_type}, transformation: {transformation}")
245+
246+
rows = _get_piezometer_data(
247+
authenticated_client,
248+
project_id,
249+
location,
250+
method,
251+
)
252+
total_rows += len(rows)
253+
254+
if not rows:
255+
print(" No piezometer data rows found.")
256+
continue
257+
258+
print(f" Rows: {len(rows)}")
259+
print(f" First {min(len(rows), DATA_PREVIEW_LIMIT)} row(s):")
260+
for row in rows[:DATA_PREVIEW_LIMIT]:
261+
print(f" - {_format_row(row)}")
262+
263+
if len(rows) > DATA_PREVIEW_LIMIT:
264+
print(f" ... and {len(rows) - DATA_PREVIEW_LIMIT} more row(s)")
265+
266+
print()
267+
print(f"Summary: {total_methods} PZ method(s), {total_rows} data row(s)")
268+
269+
270+
if __name__ == "__main__":
271+
main()

0 commit comments

Comments
 (0)