Add initial data dict
This commit is contained in:
parent
fb28c6c5c5
commit
36fcb5f260
19 changed files with 784 additions and 116 deletions
228
src/nrsk/models.py
Normal file
228
src/nrsk/models.py
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
"""
|
||||
Define the Data Dictionary.
|
||||
|
||||
Implementation of Data Dictionary
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. impl:: Maintain the Data Dictionary base data using Pydantic
|
||||
:id: I_DATA_DICT
|
||||
:links: R_DATA_DICT
|
||||
|
||||
The data dictionary is managed using Pydantic. Pydantic allows for
|
||||
concise Python code to richly define data models and their fields. From a single
|
||||
class definition, it provides data validation, automatic rich documentation (via
|
||||
automatic a Sphinx plugin), an integration with FastAPI for data exchange, and
|
||||
relatively easy integration with sqlalchemy for database persistence. Changes to
|
||||
the schema can be managed and controlled via the revision control system, and
|
||||
changes to a single source (the Python code) will automatically propagate the
|
||||
rendered documentation and, potentially the database (e.g. using *alembic*)
|
||||
|
||||
Using SQLAchemy as the database engine enables wide flexibility in underlying
|
||||
database technology, including PostgreSQL, MySQL, SQLite, Oracle, and MS SQL
|
||||
Server. Pydantic models allows us to validate data loaded from a database,
|
||||
directly from structured text file, or from JSON data delivered via the network.
|
||||
|
||||
Analysis of Alternatives
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SQLModel :cite:p:`SQLModel` was considered as the data layer base, but it was
|
||||
determined to be less mature than pydantic and sqlalchemy, with inadequate
|
||||
documentation related to field validation. It was determined to use Pydantic
|
||||
directly for schema definitions.
|
||||
|
||||
.. _data-dict:
|
||||
|
||||
Data Dictionary
|
||||
^^^^^^^^^^^^^^^
|
||||
This is the official Data Dictionary discussed in :ref:`the Information
|
||||
Management Plan <info-mgmt-data-dict>`.
|
||||
"""
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from pydantic import (
|
||||
AnyUrl,
|
||||
BaseModel,
|
||||
EmailStr,
|
||||
Field,
|
||||
FieldValidationInfo,
|
||||
PositiveInt,
|
||||
ValidationError,
|
||||
field_validator,
|
||||
)
|
||||
|
||||
ALL_CAPS = re.compile("^[A-Z]$")
|
||||
UUID_PK = Annotated[
|
||||
uuid.UUID,
|
||||
Field(
|
||||
default_factory=uuid.uuid4,
|
||||
description="The unique ID of this object. Used as a primary key in the database.",
|
||||
examples=["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
|
||||
frozen=True,
|
||||
primary_key=True,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
"""A person involved in the Project."""
|
||||
|
||||
uuid: UUID_PK
|
||||
given_name: str
|
||||
family_name: str
|
||||
preferred_name: Optional[str] = None
|
||||
email: EmailStr
|
||||
joined_on: Optional[datetime]
|
||||
deactivated_on: Optional[datetime]
|
||||
|
||||
|
||||
class OpenItem(BaseModel):
|
||||
uuid: UUID_PK
|
||||
name: str
|
||||
status: str
|
||||
created_on: datetime
|
||||
closed_on: Optional[datetime] = None
|
||||
|
||||
|
||||
class SSC(BaseModel):
|
||||
"""
|
||||
A Structure, System, or Component in the plant.
|
||||
|
||||
This is a generic hierarchical object that can represent plants, units,
|
||||
buildings and their structures, systems, subsystems, components,
|
||||
subcomponents, etc.
|
||||
|
||||
A physical tree of buildings/structures/rooms may have overlapping
|
||||
contents in terms of systems/components/equipment/parts
|
||||
"""
|
||||
|
||||
uuid: UUID_PK
|
||||
name: str
|
||||
pbs_code: Optional[str] = Field(
|
||||
description="An integer sequence that determines the 'system number' and also the ordering in printouts",
|
||||
examples=["1.2.3", "20.5.11"],
|
||||
default="",
|
||||
)
|
||||
"""PBS code is tied closely to the structure of the PBS, obviously. If 1.2
|
||||
is a category level, that's ok, but that doesn't imply that the second level
|
||||
of PBS 2 is also a category level; it may be systems.
|
||||
Since this can change in major PBS reorganizations, it should not be used
|
||||
for cross-referencing (use UUID).
|
||||
"""
|
||||
|
||||
abbrev: str = Field(
|
||||
description="A human-friendly abbreviation uniquely defining the system"
|
||||
)
|
||||
parent: Optional["SSC"] = None
|
||||
functions: Optional[list[str]] = Field(
|
||||
description="Functions of this system", default=None
|
||||
)
|
||||
|
||||
@field_validator("abbrev")
|
||||
@classmethod
|
||||
def abbrev_must_be_all_caps(cls, v: str, info: FieldValidationInfo) -> str: # noqa: D102
|
||||
assert re.match(ALL_CAPS, v), f"{info.field_name} must be all CAPS"
|
||||
|
||||
@field_validator("pbs_code")
|
||||
@classmethod
|
||||
def pbs_must_be_int_sequence(cls, v: str, info: FieldValidationInfo) -> str: # noqa: D102
|
||||
assert not v or re.match(r"^(\d+\.?)+$", v), (
|
||||
f"{info.field_name} must be an integer sequence, like 1.2.3"
|
||||
)
|
||||
|
||||
|
||||
class SystemsList(BaseModel):
|
||||
"""A flat list of Systems in the plant.
|
||||
|
||||
Can be used e.g. to render a snapshot of the Master Systems List.
|
||||
|
||||
Does not include categories like "Nuclear Island" or "Primary Systems".
|
||||
|
||||
We may want another structure that represents the whole tree in a
|
||||
well-defined manner, or we may want to add a 'path' attr
|
||||
to systems that define where they live.
|
||||
"""
|
||||
|
||||
systems: list[SSC]
|
||||
|
||||
|
||||
class ParamDef(BaseModel):
|
||||
"""A parameter class defining an aspect of plant design."""
|
||||
|
||||
uuid: UUID_PK
|
||||
name: str = Field(
|
||||
description="Name of parameter class", examples=["Nominal gross power"]
|
||||
)
|
||||
description: str = Field(
|
||||
description="Detailed description of what parameters of this type represent"
|
||||
)
|
||||
valid_units: Optional[list[str]] = Field(
|
||||
description="List of units allowed", examples=["MW", "W", "shp"], default=None
|
||||
)
|
||||
|
||||
|
||||
class ParamVal(BaseModel):
|
||||
"""A particular value of a Parameter, assigned to a particular SSC."""
|
||||
|
||||
ssc: SSC
|
||||
pdef: ParamDef
|
||||
value: str
|
||||
units: Optional[str] = None
|
||||
pedigree: str = Field(
|
||||
description="Indication of how well it is known (rough estimate, final design, as-built)."
|
||||
)
|
||||
source: str = Field(description="Where this version of the value came from")
|
||||
|
||||
|
||||
class ITSystem(BaseModel):
|
||||
"""An IT system used by the project."""
|
||||
|
||||
uuid: UUID_PK
|
||||
name: str
|
||||
vendor: str
|
||||
version: Optional[str] = None
|
||||
use_cases: list[str] = Field(
|
||||
description="One or more use cases this system is used for.",
|
||||
examples=[
|
||||
[
|
||||
"Document management",
|
||||
]
|
||||
],
|
||||
)
|
||||
physical_location: str = Field(description="Where the system is physically located")
|
||||
url: Optional[AnyUrl] = Field(description="Full URL to the system", default=None)
|
||||
custodian: Optional[User] = Field(
|
||||
description="Person currently in charge of system", default=None
|
||||
)
|
||||
launched_on: Optional[datetime] = None
|
||||
retired_on: Optional[datetime] = None
|
||||
quality_related: bool
|
||||
|
||||
|
||||
class Document(BaseModel):
|
||||
uuid: UUID_PK
|
||||
title: str = Field(
|
||||
description="Descriptive title explaining the contents",
|
||||
examples=["CNSG Development and Status 1966-1977"],
|
||||
)
|
||||
"""
|
||||
.. impl:: Document title
|
||||
|
||||
This is how doc titles are done.
|
||||
"""
|
||||
revision: str = Field(
|
||||
description="Revision number",
|
||||
examples=["0", "1", "1a", "A"],
|
||||
)
|
||||
type: str
|
||||
originators: list[str]
|
||||
status: str
|
||||
|
||||
@field_validator("type")
|
||||
@classmethod
|
||||
def type_must_be_valid(cls, v: str, info: FieldValidationInfo) -> str:
|
||||
assert v in ["CALC", "PROC"], (
|
||||
f"{info.field_name} must be within the list of doctypes"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue