Skip to content

Commit 6adfbc6

Browse files
committed
feat: added ability to transfer well screens and gwl observations
1 parent e30118a commit 6adfbc6

8 files changed

Lines changed: 3871 additions & 26 deletions

File tree

api/observation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def get_groundwater_level_observations(
110110
sql = sql.where(Observation.observation_datetime <= end_time)
111111

112112
sql = order_sort_filter(sql, Observation, sort, order, filter_)
113+
sql = sql.order_by(Observation.observation_datetime.desc())
113114
return paginate(query=sql, conn=session)
114115

115116

api/search.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,46 @@ def _get_contact_results(session: Session, q: str) -> list[dict]:
6464

6565
def _get_thing_results(session: Session, q: str) -> list[dict]:
6666
vector = Thing.search_vector
67-
query = search(select(Thing), q, vector=vector)
67+
water_well_query = search(select(Thing).where(Thing.thing_type=='water well'), q, vector=vector)
68+
spring_well_query = search(select(Thing).where(Thing.thing_type=='spring'), q, vector=vector)
6869

69-
things = session.scalars(query).all()
70-
results = [
71-
{
72-
"label": t.name,
73-
"group": "Things",
74-
"properties": {
75-
"well_type": t.well_type,
76-
"thing_type": t.thing_type,
77-
"id": t.id,
78-
},
79-
}
80-
for t in things
81-
]
70+
wells = session.scalars(water_well_query).all()
71+
springs = session.scalars(spring_well_query).all()
8272

83-
return results
73+
def _make_response(group: str, thing: Thing, properties: dict) -> dict:
74+
75+
if properties is None:
76+
properties = {}
77+
78+
properties['thing_type'] = thing.thing_type
79+
properties['id'] = thing.id
80+
return {
81+
"label": thing.name,
82+
"group": group,
83+
"properties": properties,
84+
}
85+
def make_well_response(thing: Thing) -> dict:
86+
return _make_response(
87+
'Wells',
88+
thing,
89+
{
90+
'well_type': thing.well_type,
91+
'well_depth': thing.well_depth,
92+
'hole_depth': thing.hole_depth,
93+
}
94+
)
95+
def make_spring_response(thing: Thing) -> dict:
96+
return _make_response(
97+
'Springs',
98+
thing,
99+
{
100+
'spring_type': thing.spring_type,
101+
}
102+
)
103+
104+
return [func(item) for items, func in ((wells, make_well_response),
105+
(springs, make_spring_response))
106+
for item in items]
84107

85108

86109
def _get_asset_results(session: Session, q: str) -> list[dict]:
@@ -118,7 +141,7 @@ def search_api(q: str, session: Session = Depends(get_db_session)):
118141
results.extend(_get_thing_results(session, q))
119142
results.extend(_get_asset_results(session, q))
120143

121-
return results
144+
return {'items': results, 'total': len(results)}
122145

123146

124147
# ============= EOF =============================================

api/thing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ async def get_springs(
136136
)
137137
async def get_well_screens(
138138
session: session_dependency,
139+
thing_id: int = None,
139140
) -> CustomPage[WellScreenResponse]:
140141
"""
141142
Retrieve all well screens from the database.
142143
"""
144+
if thing_id:
145+
sql = select(WellScreen).where(WellScreen.thing_id == thing_id)
146+
return paginate(query=sql, conn=session)
147+
148+
143149
return paginated_all_getter(session, WellScreen)
144150

145151

db/thing.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ class WellScreen(Base, AutoBaseMixin):
116116
)
117117
screen_type = lexicon_term() # e.g., "PVC", "Steel", etc.
118118

119+
screen_description = Column(
120+
String(1000), nullable=True, info={"unit": "description of the screen"}
121+
)
119122
# Define a relationship to well if needed
120123
# well = relationship("Well")
121124

schemas/observation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class BaseObservationResponse(BaseModel):
9696

9797
class GroundwaterLevelObservationResponse(BaseObservationResponse):
9898
depth_to_water: float
99-
level_status: str
99+
level_status: str | None
100100

101101

102102
class GeothermalObservationResponse(BaseObservationResponse):

schemas/thing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class CreateWellScreen(BaseModel):
7878
screen_depth_bottom: float
7979
screen_depth_top: float
8080
screen_type: str | None = None
81+
screen_description: str | None = None
8182

8283
@model_validator(mode="after")
8384
def validate_screen_type(self):

transfers/data/wellscreens.csv

Lines changed: 3726 additions & 0 deletions
Large diffs are not rendered by default.

transfers/transfer2.py

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@
1515
# ===============================================================================
1616

1717
import time
18+
import uuid
19+
from datetime import datetime
1820

1921
import numpy as np
2022
import pandas as pd
2123
import pyproj
24+
from pydantic import ValidationError
2225
from shapely import Point
2326
from shapely.ops import transform
27+
from sqlalchemy import select
2428

25-
from db import Location, LocationThingAssociation
29+
from db import Location, LocationThingAssociation, adder, WellScreen, Thing, Observation, Sample
2630
from db.engine import session_ctx
31+
from schemas.thing import CreateWellScreen
2732

2833
# from db.observation.groundwaterlevel import GroundwaterLevelObservation
2934

@@ -67,7 +72,42 @@ def make_location(row):
6772
)
6873

6974

70-
#
75+
def transfer_water_levels(session):
76+
wd = pd.read_csv("./data/water_levels.csv")
77+
gwd = wd.groupby(["PointID"])
78+
79+
for index, group in gwd:
80+
for row in group.itertuples():
81+
if pd.isna(row.DepthToWater) or pd.isna(row.DateMeasured):
82+
print(f"Skipping row {row.Index} due to missing data.")
83+
continue
84+
85+
dt = datetime.fromisoformat(row.DateMeasured)
86+
thing = session.query(Thing).where(Thing.name==row.PointID).first()
87+
if thing is None:
88+
print(f"Thing with PointID {row.PointID} not found. Skipping water level.")
89+
continue
90+
91+
sample = Sample()
92+
sample.sampler_name = 'unknown'
93+
sample.sample_type = 'groundwater level'
94+
95+
sample.field_sample_id = str(uuid.uuid4())
96+
sample.sample_date = dt
97+
sample.thing = thing
98+
session.add(sample)
99+
100+
obs = Observation()
101+
obs.sensor_id = 1
102+
obs.sample = sample
103+
obs.observation_datetime = dt
104+
obs.depth_to_water = row.DepthToWater
105+
obs.observed_property = "groundwater level"
106+
obs.unit = "ft"
107+
108+
session.add(obs)
109+
session.commit()
110+
71111
# def migrate_water_levels(session, limit=800):
72112
# wd = pd.read_csv("./migration/data/water_levels.csv")
73113
# p = pd.read_csv("./migration/data/welldata.csv")
@@ -150,14 +190,14 @@ def make_location(row):
150190
ADDED = []
151191

152192

153-
def transfer_springs(session, limit=10000):
193+
def transfer_springs(session, limit=None):
154194
ldf = pd.read_csv("./data/location.csv")
155195
ldf = ldf[ldf["SiteType"] == "SP"]
156196
ldf = ldf[ldf["Easting"].notna() & ldf["Northing"].notna()]
157197
n = len(ldf)
158198
start_time = time.time()
159199
for i, row in enumerate(ldf.itertuples()):
160-
if i >= limit:
200+
if limit and i >= limit:
161201
print(f"Reached limit of {limit} rows. Stopping migration.")
162202
break
163203

@@ -185,7 +225,7 @@ def transfer_springs(session, limit=10000):
185225
session.add(assoc)
186226

187227

188-
def transfer_wells(session, limit=1000):
228+
def transfer_wells(session, limit=None):
189229
wdf = pd.read_csv("./data/welldata.csv")
190230
ldf = pd.read_csv("./data/location.csv")
191231

@@ -200,7 +240,7 @@ def transfer_wells(session, limit=1000):
200240
start_time = time.time()
201241

202242
for i, row in enumerate(wdf.itertuples()):
203-
if i >= limit:
243+
if limit and i >= limit:
204244
print("Reached limit of", limit, "rows. Stopping migration.")
205245
break
206246

@@ -242,7 +282,51 @@ def transfer_wells(session, limit=1000):
242282
session.add(assoc)
243283
# break
244284

285+
def transfer_wellscreens(session, limit=None):
286+
wdf = pd.read_csv("./data/wellscreens.csv")
287+
wdf = wdf.replace(pd.NA, None)
288+
wdf = wdf.replace({np.nan: None})
245289

290+
n = len(wdf)
291+
start_time = time.time()
292+
293+
for i, row in enumerate(wdf.itertuples()):
294+
if limit and i >= limit:
295+
print("Reached limit of", limit, "rows. Stopping migration.")
296+
break
297+
298+
if i and not i % 100:
299+
print(
300+
f"Processing row {i} of {n}. {row.PointID}, avg rows per second: {i / (time.time() - start_time):.2f}"
301+
)
302+
session.commit()
303+
# thing_id: int
304+
# screen_depth_bottom: float
305+
# screen_depth_top: float
306+
# screen_type: str | None = None
307+
# print(row)
308+
309+
sql = select(Thing).where(Thing.name == row.PointID)
310+
thing = session.execute(sql).scalar_one_or_none()
311+
if not thing:
312+
print(f"Thing with PointID {row.PointID} not found. Skipping well screen.")
313+
continue
314+
315+
well_screen_data = {
316+
"thing_id": thing.id,
317+
"screen_depth_top": row.ScreenTop,
318+
"screen_depth_bottom": row.ScreenBottom,
319+
# "screen_type": row.ScreenType,
320+
"screen_description": row.ScreenDescription,
321+
"release_status": 'draft'
322+
}
323+
try:
324+
model = CreateWellScreen.model_validate(well_screen_data)
325+
adder(session, WellScreen, model)
326+
except ValidationError as e:
327+
print(f"Validation error for row {i} with PointID {row.PointID}: {e}")
328+
continue
329+
# session.add(screen)
246330
# def reset_db():
247331
# configure_mappers()
248332
#
@@ -256,8 +340,9 @@ def transfer_wells(session, limit=1000):
256340
if __name__ == "__main__":
257341
# reset_db()
258342
with session_ctx() as sess:
259-
transfer_wells(sess, limit=10000)
260-
transfer_springs(sess, limit=10000)
261-
# migrate_water_levels(sess)
343+
# transfer_wells(sess, 1000)
344+
# transfer_springs(sess, limit=10000)
345+
# transfer_wellscreens(sess)
346+
transfer_water_levels(sess)
262347

263348
# ============= EOF =============================================

0 commit comments

Comments
 (0)