Skip to content

Commit a0b8140

Browse files
sunt05claude
andcommitted
docs: document physics option naming convention (GH#972)
Add comprehensive documentation for the physics option alias system: In model.py: - Detailed comment block explaining the 5 input format types - Examples for each format type - Note on case-insensitivity In sample_config.yml: - Header explaining the naming convention - Inline comments showing valid values for each option - Clear separation between multi-dimensional and single-dimensional options Format types documented: 1. Numeric codes (legacy): storageheatmethod: 1 2. Model names: ohm, anohm, estm, ehc, narp, most, rst 3. Author-year (Xyy): K09, CN98, W16, J11 4. Descriptive: fixed, variable, auto, model, obs 5. Binary: yes/no 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 913c28b commit a0b8140

File tree

3 files changed

+75
-6
lines changed

3 files changed

+75
-6
lines changed

.claude/rules/python/conventions.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,32 @@ SUEWS-specific Python conventions. Complements ruff for standard linting.
2323

2424
## Critical Rules
2525

26-
1. **Config separation**: No config objects in low-level functions
26+
1. **Python 3.9 compatibility**: Use `Optional[X]` not `X | None` for union types
27+
```python
28+
from typing import Optional, Union
29+
30+
# BAD: PEP 604 syntax requires Python 3.10+
31+
def foo(x: str | None = None): ...
32+
def bar(items: list[int] | None): ...
33+
34+
# GOOD: Works on Python 3.9+
35+
def foo(x: Optional[str] = None): ...
36+
def bar(items: Optional[list[int]]): ...
37+
```
38+
**Why**: PEP 604 union syntax (`X | Y`) requires Python 3.10+. Pydantic evaluates
39+
type hints at runtime, so `from __future__ import annotations` is not a safe workaround.
40+
41+
2. **Config separation**: No config objects in low-level functions
2742
```python
2843
# BAD: def save_supy(df_output, config): ...
2944
# GOOD: def save_supy(df_output, freq_s: int = 3600): ...
3045
```
3146

32-
2. **Deep copy**: Use `copy.deepcopy()` for mutable state
47+
3. **Deep copy**: Use `copy.deepcopy()` for mutable state
3348

34-
3. **Logging**: Use `logger_supy` not `print()`
49+
4. **Logging**: Use `logger_supy` not `print()`
3550

36-
4. **pathlib**: Use `Path` not `os.path`
51+
5. **pathlib**: Use `Path` not `os.path`
3752

3853
---
3954

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ markers = [
139139
line-length = 120
140140
target-version = "py39"
141141

142+
[tool.ruff.lint]
143+
# FA102: Catch PEP 604 union syntax (X | None) which requires Python 3.10+
144+
# This prevents CI failures on Python 3.9 due to runtime TypeError
145+
extend-select = ["FA102"]
146+
147+
# Note: FA102 auto-fix adds `from __future__ import annotations` which can break Pydantic
148+
# Instead, manually use Optional[X] from typing module (see .claude/rules/python/conventions.md)
149+
142150
[tool.ruff.format]
143151
quote-style = "double"
144152
indent-style = "space"

src/supy/data_model/core/model.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,54 @@ def _coerce_enum_value(
132132
return v
133133

134134

135-
# Short aliases for physics options
136-
# Use explicit model names where they exist; otherwise use Xyy (author initial + year)
135+
# =============================================================================
136+
# Physics Option Aliases
137+
# =============================================================================
138+
#
139+
# Naming Convention for Physics Options
140+
# -------------------------------------
141+
# Physics options support multiple input formats for flexibility:
142+
#
143+
# 1. NUMERIC CODES (legacy, still supported)
144+
# - Integer values matching Fortran interface
145+
# - Example: storageheatmethod: 1
146+
#
147+
# 2. EXPLICIT MODEL NAMES (preferred for named models)
148+
# - Use the established model acronym in lowercase
149+
# - Examples:
150+
# - ohm: Objective Hysteresis Model
151+
# - anohm: Analytical OHM
152+
# - estm: Element Surface Temperature Method
153+
# - ehc: Explicit Heat Conduction
154+
# - dyohm: Dynamic OHM
155+
# - narp: Net All-wave Radiation Parameterization
156+
# - most: Monin-Obukhov Similarity Theory
157+
# - rst: Roughness Sublayer Theory
158+
#
159+
# 3. AUTHOR-YEAR FORMAT (Xyy) for methods without model names
160+
# - Format: First letter of first author surname + two-digit year
161+
# - Case-insensitive (K09, k09, K09 all work)
162+
# - Examples:
163+
# - K09: Kawai et al. 2009 (thermal roughness)
164+
# - CN98: Campbell & Norman 1998 (stability functions)
165+
# - W16: Ward et al. 2016 (stomatal conductance)
166+
# - J11: Järvi et al. 2011 (stomatal conductance, QF)
167+
# - B82: Brutsaert 1982 (thermal roughness)
168+
# - M98: MacDonald et al. 1998 (momentum roughness)
169+
# - GO99: Grimmond & Oke 1999 (roughness length)
170+
#
171+
# 4. GENERIC DESCRIPTIVE TERMS (for simple choices)
172+
# - fixed, variable, auto: method selection
173+
# - model, obs: modelled vs observed
174+
# - provided, calc: use input vs calculate
175+
#
176+
# 5. BINARY OPTIONS (yes/no)
177+
# - ohmincqf: yes/no (include QF in OHM)
178+
# - snowuse: yes/no (enable snow module)
179+
#
180+
# All string inputs are case-insensitive.
181+
# =============================================================================
182+
137183
# Maps short alias → enum member name
138184
STORAGE_HEAT_ALIASES = {
139185
"obs": "OBSERVED",

0 commit comments

Comments
 (0)