Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions api/routes/well_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
from api.models.main_models import WellMeasurements, ObservedPropertyTypeLU, Units, Wells
from api.session import get_db
from api.enums import ScopedUser
from google.cloud import storage

from pathlib import Path
from jinja2 import Environment, FileSystemLoader, select_autoescape

import json
import os
import matplotlib

matplotlib.use("Agg") # Force non-GUI backend

WOODPECKER_BUCKET_NAME = os.getenv("GCP_WOODPECKER_BUCKET_NAME", "")

TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates"

templates = Environment(
Expand Down Expand Up @@ -66,6 +72,46 @@ def add_waterlevel(
return well_measurement


@public_well_measurement_router.get(
"/waterlevels/woodpeckers",
response_model=List[well_schemas.WellMeasurementDTO],
tags=["WaterLevels"],
)
def read_woodpecker_waterlevels(
well_id: int = Query(..., description="At least one well ID is required"),
):
SP_JOHNSON_WELL_ID = 2599

if well_id != SP_JOHNSON_WELL_ID:
raise HTTPException(status_code=400, detail="Invalid well ID")

client = storage.Client()
bucket = client.bucket(WOODPECKER_BUCKET_NAME)

blobs = bucket.list_blobs()

results = []
for blob in blobs:
if blob.name.endswith(".json"):
content = blob.download_as_text()
data = json.loads(content)

measurement = well_schemas.WellMeasurementDTO(
id=data["id"],
timestamp=datetime.fromisoformat(data["timestamp"]),
value=data.get("value"),
submitting_user=well_schemas.WellMeasurementDTO.UserDTO(
full_name=data["submitting_user"]["full_name"]
),
well=well_schemas.WellMeasurementDTO.WellDTO(
ra_number=data["well"]["ra_number"]
),
)
results.append(measurement)

return results


@public_well_measurement_router.get(
"/waterlevels",
response_model=List[well_schemas.WellMeasurementDTO],
Expand Down
1 change: 1 addition & 0 deletions docker-compose.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ services:
working_dir: /app
environment:
- GCP_BUCKET_NAME=pvacd
- GCP_WOODPECKER_BUCKET_NAME=pvacd-woodpecker
- GCP_BACKUP_PREFIX=pre-prod-db-backups
- GCP_PHOTO_PREFIX=pre-prod-meter-activities-photos
- BACKUP_RETENTION_DAYS=14
Expand Down
1 change: 1 addition & 0 deletions docker-compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ services:
working_dir: /app
environment:
- GCP_BUCKET_NAME=pvacd
- GCP_WOODPECKER_BUCKET_NAME=pvacd-woodpecker
- GCP_BACKUP_PREFIX=prod-db-backups
- GCP_PHOTO_PREFIX=prod-meter-activities-photos
- BACKUP_RETENTION_DAYS=90
Expand Down
41 changes: 35 additions & 6 deletions frontend/src/components/Modals/Region/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
InputLabel,
Grid,
Typography,
FormControlLabel,
Checkbox,
} from "@mui/material";
import { useState } from "react";
import { useAuthUser } from "react-auth-kit";
Expand All @@ -22,6 +24,7 @@ import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
import { DatePicker, TimePicker } from "@mui/x-date-pickers";
import { RadioButtonUnchecked, TaskAlt } from "@mui/icons-material";
import { useGetUserList } from "../../../service/ApiServiceNew";
import { useQuery } from "react-query";
import { useFetchWithAuth } from "../../../hooks/useFetchWithAuth.js";
Expand Down Expand Up @@ -70,6 +73,7 @@ export const CreateModal = ({

const userList = useGetUserList();
const [value, setValue] = useState<number | null>(null);
const [notSampled, setNotSampled] = useState<boolean>(false);
const [selectedUserID, setSelectedUserID] = useState<number | string>("");
const [selectedWellID, setSelectedWellID] = useState<number | string>("");
const [date, setDate] = useState<Dayjs | null>(dayjs.utc());
Expand Down Expand Up @@ -184,17 +188,42 @@ export const CreateModal = ({
onChange={setTime}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
value="bottom"
control={
<Checkbox
size="large"
icon={<RadioButtonUnchecked />}
checkedIcon={<TaskAlt />}
checked={notSampled}
onChange={(e) => {
const checked = e.target.checked;
setNotSampled(checked)

if (checked) {
setValue(null);
}
}}
/>
}
label="Well was visited but NOT SAMPLED"
labelPlacement="end"
/>
</Grid>
<Grid item xs={12}>
<TextField
required
required={!notSampled}
fullWidth
size={"small"}
type="number"
value={value}
label="Value"
onChange={(event) =>
setValue(event.target.value as unknown as number)
}
disabled={notSampled}
value={notSampled ? "" : value ?? ""}
label={notSampled ? "NOT SAMPLED" : "Value"}
onChange={(event) => {
const newValue = event.target.value;
setValue(newValue === "" ? null : Number(newValue));
}}
/>
</Grid>
<Grid item xs={12}>
Expand Down
55 changes: 52 additions & 3 deletions frontend/src/components/Modals/Region/Update.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

import { useState } from "react";
import {
Modal,
TextField,
Expand All @@ -8,6 +10,8 @@ import {
InputLabel,
Grid,
Typography,
FormControlLabel,
Checkbox,
} from "@mui/material";
import {
MonitoredWell,
Expand All @@ -19,6 +23,7 @@ import dayjs from "dayjs";
dayjs.extend(utc);
dayjs.extend(timezone);
import { DatePicker, TimePicker } from "@mui/x-date-pickers";
import { RadioButtonUnchecked, TaskAlt } from "@mui/icons-material";
import { useGetUserList } from "../../../service/ApiServiceNew";
import { useQuery } from "react-query";
import { useFetchWithAuth } from "../../../hooks/useFetchWithAuth.js";
Expand All @@ -44,6 +49,10 @@ export const UpdateModal = ({
}) => {
const userList = useGetUserList();
const fetchWithAuth = useFetchWithAuth();

const [notSampled, setNotSampled] = useState<boolean>(false);
const [previousValue, setPreviousValue] = useState<number | null>(null);

const { data: wells, isLoading: isLoadingWells } = useQuery<
{ items: MonitoredWell[] },
Error,
Expand All @@ -66,6 +75,21 @@ export const UpdateModal = ({
select: (res) => res.items,
});

const handleToggleNotSampled = (checked: boolean) => {
setNotSampled(checked);

if (checked) {
// Store previous numeric value and clear backend value
setPreviousValue(measurement.value ?? null);
onUpdateMeasurement({ value: null });
} else {
// Restore previous numeric value when toggled back
if (previousValue !== null) {
onUpdateMeasurement({ value: previousValue });
}
}
};

return (
<Modal open={isMeasurementModalOpen} onClose={handleCloseMeasurementModal}>
<ModalBackgroundBox>
Expand Down Expand Up @@ -127,16 +151,41 @@ export const UpdateModal = ({
}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel
value="bottom"
control={
<Checkbox
size="large"
icon={<RadioButtonUnchecked />}
checkedIcon={<TaskAlt />}
checked={notSampled}
onChange={(e) => handleToggleNotSampled(e.target.checked)}
/>
}
label="Well was visited but NOT SAMPLED"
labelPlacement="end"
/>
</Grid>
<Grid item xs={12}>
<TextField
required={!notSampled}
fullWidth
size={"small"}
type="number"
value={measurement.value}
label="Value"
disabled={notSampled}
value={
notSampled
? "" // visually empty
: measurement.value ?? ""
}
label={notSampled ? "NOT SAMPLED" : "Value"}
onChange={(event) =>
onUpdateMeasurement({
value: event.target.value as unknown as number,
value:
event.target.value === ""
? null
: Number(event.target.value),
})
}
/>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ export interface PatchWellMeasurement {
export interface NewRegionMeasurement {
region_id: number
timestamp: string
value: number
value?: number | null
submitting_user_id: number
well_id: number
}
Expand All @@ -583,7 +583,7 @@ export interface PatchRegionMeasurement {
submitting_user_id: number
well_id: number
timestamp: dayjs.Dayjs
value?: number
value?: number | null
}

export interface CreateUser {
Expand Down