Skip to content
3 changes: 3 additions & 0 deletions core/lexicon.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@
{"categories": [{"name": "sample_method", "description": null}], "term": "pump", "definition": "pump"},
{"categories": [{"name": "sample_method", "description": null}], "term": "thief sampler", "definition": "thief sampler"},

{"categories": [{"name": "analysis_method_type", "description": null}], "term": "Laboratory", "definition": "A procedure performed on a physical sample in a controlled, off-site laboratory environment. These methods typically involve complex instrumentation, standardized reagents, and formal quality control protocols."},
{"categories": [{"name": "analysis_method_type", "description": null}], "term": "Field Procedure", "definition": "A standardized procedure performed on-site at the time of sample collection. This can involve direct measurement of the environmental medium using a calibrated field instrument or a specific, documented technique for collecting a sample."},
{"categories": [{"name": "analysis_method_type", "description": null}], "term": "Calculation", "definition": "A mathematical procedure used to derive a new data point from one or more directly measured values. This type is used to document the provenance of calculated data, providing an auditable trail."},

{"categories": [{"name": "organization", "description": null}], "term": "USGS", "definition": "US Geological Survey"},
{"categories": [{"name": "organization", "description": null}], "term": "TWDB", "definition": "Texas Water Development Board"},
Expand Down
1 change: 1 addition & 0 deletions db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from db.base import *
from db.base import Base

from db.analysis_method import *
from db.asset import *
from db.collabnet import *
from db.contact import *
Expand Down
45 changes: 45 additions & 0 deletions db/analysis_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
This table is a citable library of abstract analytical methods and
standardized field procedures.
"""

from typing import List, TYPE_CHECKING

from sqlalchemy.orm import relationship, Mapped, mapped_column

from db.base import Base, AutoBaseMixin, ReleaseMixin, lexicon_term

if TYPE_CHECKING:
from db.observation import Observation


class AnalysisMethod(Base, AutoBaseMixin, ReleaseMixin):
"""
Represents a single, citable analytical method or standard procedure.
This is a lookup table.
"""

# --- Columns ---
analysis_method_code: Mapped[str] = mapped_column(
nullable=True,
unique=True,
comment="The official code or identifier for the method (e.g., 'EPA 300.0').",
)
analysis_method_name: Mapped[str] = mapped_column(
nullable=False,
comment="The common, human-readable name of the method (e.g., Ion Chromatography for Anions).",
)
analysis_method_type: Mapped[str] = lexicon_term(
nullable=True,
comment="A controlled vocabulary field to categorize the method (e.g., 'Laboratory', 'Field Procedure', 'Calculation').",
)
source_organization: Mapped[str] = lexicon_term(
nullable=True,
comment="The organization that published the method (e.g., 'US EPA', 'USGS').",
)

# --- Relationships ---
# One-To-Many: An AnalysisMethod can be used for many Observations.
observations: Mapped[List["Observation"]] = relationship(
"Observation", back_populates="analysis_method"
)
22 changes: 22 additions & 0 deletions db/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@
from sqlalchemy import (
ForeignKey,
DateTime,
Integer,
)
from sqlalchemy.orm import mapped_column, relationship, Mapped

from db.base import Base, AutoBaseMixin, ReleaseMixin, lexicon_term

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from db.sample import Sample
from db.sensor import Sensor
from db.analysis_method import AnalysisMethod


class Observation(Base, AutoBaseMixin, ReleaseMixin):
__versioned__ = {}

# NM_Aquifer fields for audits
nma_pk_waterlevels: Mapped[str] = mapped_column(nullable=True)

# --- Foreign Keys ---
sample_id: Mapped[int] = mapped_column(
ForeignKey("sample.id", ondelete="CASCADE"),
nullable=False,
Expand All @@ -37,7 +46,11 @@ class Observation(Base, AutoBaseMixin, ReleaseMixin):
ForeignKey("sensor.id", ondelete="CASCADE"),
nullable=True,
)
analysis_method_id: Mapped[int] = mapped_column(
Integer, ForeignKey("analysis_method.id"), nullable=True
)

# --- Columns ---
observation_datetime: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, doc="Timestamp of the observation"
)
Expand All @@ -63,12 +76,21 @@ class Observation(Base, AutoBaseMixin, ReleaseMixin):
doc="Depth of the geothermal observation in feet",
)

# --- Relationships ---
# Many-To-One: An Observation can be generated by one piece of Equipment.
sensor: Mapped["Sensor"] = relationship( # noqa: F821
"Sensor", back_populates="observations", passive_deletes=True
) # noqa: F821

# Many-To-One: An Observation is derived from one Sample.
sample: Mapped["Sample"] = relationship( # noqa: F821
"Sample", back_populates="observations", passive_deletes=True
) # noqa: F821

# Many-To-One: An Observation can be generated using one AnalysisMethod.
analysis_method: Mapped["AnalysisMethod"] = relationship(
"AnalysisMethod", back_populates="observations"
)


# ============= EOF =============================================
Loading