Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
3e7a953
[routes/chlorides] Update patch & post endpoints to accept null values
TylerAdamMartinez Sep 23, 2025
7bc56c1
[components/Modals] Refactor modals in monitoredWells pg
TylerAdamMartinez Sep 23, 2025
4d3eda5
[routes/meters] Add GCP signed photo urls
TylerAdamMartinez Sep 24, 2025
4399bd0
[routes/meters] Fix broken GCP signUrls
TylerAdamMartinez Sep 24, 2025
f5a3fa4
[/Reports] Update from & to controls to have days values & add sensib…
TylerAdamMartinez Sep 24, 2025
3e46d8c
[/routes/chlorides] Update report endpoints to accept YYYY-MM-DD fmt
TylerAdamMartinez Sep 26, 2025
c0de7e7
[/routes/parts] Update endpoint to accept ISO fmt
TylerAdamMartinez Sep 26, 2025
551877a
[/routes/maintenance] Update endpoint to accept ISO date YYYY-MM-DD
TylerAdamMartinez Sep 26, 2025
7d6f5a4
[routes/well_measurements] Update the waterlevel endpoint to accept Y…
TylerAdamMartinez Sep 26, 2025
b1902d7
[routes/well_measurement] Update endpoint to make from & to date opti…
TylerAdamMartinez Sep 26, 2025
98a6b50
Merge pull request #141 from NMWDI/TAM-update-date-pickers-in-reports
TylerAdamMartinez Sep 26, 2025
dd369e0
[routes/well_measurements] Create /waterlevels/woodpeckers endpoint
TylerAdamMartinez Oct 1, 2025
cd8eb81
[/Modals/Region] Update both modals to include NOT SAMPLED checkbox
TylerAdamMartinez Oct 7, 2025
91eb4ef
Merge pull request #142 from NMWDI/TAM-add-woodpecker-bucket
TylerAdamMartinez Oct 7, 2025
5b51fa2
[WellSelectionTable] Fix broken link
TylerAdamMartinez Oct 13, 2025
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
19 changes: 0 additions & 19 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
# ===============================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
from datetime import timedelta

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_pagination import add_pagination

from fastapi.middleware.cors import CORSMiddleware
from starlette import status

from api.schemas import security_schemas
from api.models.main_models import Users

from api.routes.activities import activity_router
from api.routes.admin import admin_router
from api.routes.chlorides import authenticated_chlorides_router, public_chlorides_router
Expand All @@ -33,14 +16,12 @@
from api.routes.settings import settings_router
from api.routes.well_measurements import authenticated_well_measurement_router, public_well_measurement_router
from api.routes.wells import authenticated_well_router, public_well_router

from api.security import (
authenticate_user,
create_access_token,
ACCESS_TOKEN_EXPIRE_HOURS,
authenticated_router,
)

from api.session import get_db
from sqlalchemy.orm import Session

Expand Down
6 changes: 3 additions & 3 deletions api/models/main_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,13 @@ class WellMeasurements(Base):
timestamp: Mapped[DateTime] = mapped_column(
DateTime, default=func.now(), nullable=False
)
value: Mapped[float] = mapped_column(Float, nullable=False)
value: Mapped[Optional[float]] = mapped_column(Float, nullable=True)

observed_property_id: Mapped[int] = mapped_column(
Integer, ForeignKey("ObservedPropertyTypeLU.id"), nullable=False
)
submitting_user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("Users.id"), nullable=False
submitting_user_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("Users.id"), nullable=True
)
unit_id: Mapped[int] = mapped_column(
Integer, ForeignKey("Units.id"), nullable=False
Expand Down
74 changes: 17 additions & 57 deletions api/routes/chlorides.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from typing import Optional, List
from datetime import datetime
import calendar
from datetime import datetime, date
import statistics
from fastapi.responses import StreamingResponse
from weasyprint import HTML
from io import BytesIO
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy import and_, select
from sqlalchemy.orm import Session, joinedload
Expand Down Expand Up @@ -117,16 +116,8 @@ class ChlorideReportNums(BaseModel):
tags=["Chlorides"],
)
def get_chlorides_report(
from_month: Optional[str] = Query(
None,
description="Month start, 'YYYY-MM'",
pattern=r"^$|^\d{4}-\d{2}$",
),
to_month: Optional[str] = Query(
None,
description="Month end, 'YYYY-MM'",
pattern=r"^$|^\d{4}-\d{2}$",
),
from_date: date = Query(..., description="Start date in ISO format, 'YYYY-MM-DD'"),
to_date: date = Query(..., description="End date in ISO format, 'YYYY-MM-DD'"),
db: Session = Depends(get_db),
):
"""
Expand All @@ -136,13 +127,9 @@ def get_chlorides_report(

CHLORIDE_OBSERVED_PROPERTY_ID = 5

# Parse months
start_dt = _parse_month(from_month) if from_month else None
end_dt = _parse_month(to_month) if to_month else None
if start_dt and not end_dt:
end_dt = start_dt
if end_dt:
end_dt = _month_end(end_dt)
# Convert to datetimes for inclusive range
start_dt = datetime.combine(from_date, datetime.min.time())
end_dt = datetime.combine(to_date, datetime.max.time())

stmt = (
select(
Expand Down Expand Up @@ -218,31 +205,23 @@ def get_chlorides_report(
tags=["Chlorides"],
)
def download_chlorides_report_pdf(
from_month: Optional[str] = Query(
None,
description="Month start, 'YYYY-MM'",
pattern=r"^$|^\d{4}-\d{2}$",
),
to_month: Optional[str] = Query(
None,
description="Month end, 'YYYY-MM'",
pattern=r"^$|^\d{4}-\d{2}$",
),
from_date: date = Query(..., description="Start date in ISO format, 'YYYY-MM-DD'"),
to_date: date = Query(..., description="End date in ISO format, 'YYYY-MM-DD'"),
db: Session = Depends(get_db),
):
"""
Generate a PDF chloride report (north/south/east/west stats)
for the SE quadrant of New Mexico.
"""
# Re-use your existing logic by calling the data endpoint’s function
report = get_chlorides_report(from_month=from_month, to_month=to_month, db=db)
# Re-use existing logic
report = get_chlorides_report(from_date=from_date, to_date=to_date, db=db)

# Render HTML using a template
template = templates.get_template("chlorides_report.html")
html_content = template.render(
report=report,
from_month=from_month,
to_month=to_month,
from_date=from_date,
to_date=to_date,
)

# Convert to PDF
Expand Down Expand Up @@ -293,12 +272,13 @@ def patch_chloride_measurement(
chloride_measurement_patch: well_schemas.PatchChlorideMeasurement,
db: Session = Depends(get_db),
):
# Find the measurement
well_measurement = (
db.scalars(select(WellMeasurements).where(WellMeasurements.id == chloride_measurement_patch.id)).first()
db.scalars(
select(WellMeasurements)
.where(WellMeasurements.id == chloride_measurement_patch.id)
).first()
)

# Update the fields, all are mandatory
well_measurement.submitting_user_id = chloride_measurement_patch.submitting_user_id
well_measurement.timestamp = chloride_measurement_patch.timestamp
well_measurement.value = chloride_measurement_patch.value
Expand Down Expand Up @@ -326,26 +306,6 @@ def delete_chloride_measurement(chloride_measurement_id: int, db: Session = Depe
return True


def _parse_month(m: Optional[str]) -> Optional[datetime]:
"""
Accepts 'YYYY-MM' or 'YYYY MM'. Returns the first day of month at 00:00:00.
"""
if not m:
return None
m = m.strip()
# Try 'YYYY-MM'
for fmt in ("%Y-%m", "%Y %m"):
try:
dt = datetime.strptime(m, fmt)
return dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
except ValueError:
continue
raise HTTPException(status_code=400, detail="Invalid month format. Use 'YYYY-MM' or 'YYYY MM'.")

def _month_end(dt: datetime) -> datetime:
last_day = calendar.monthrange(dt.year, dt.month)[1]
return dt.replace(day=last_day, hour=23, minute=59, second=59, microsecond=999999)

def _stats(values: List[Optional[float]]) -> MinMaxAvgMedCount:
clean = [v for v in values if v is not None]
if not clean:
Expand Down
Loading