Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
190 commits
Select commit Hold shift + click to select a range
4a6e9e6
Update default.config
ShiZhongming Nov 3, 2025
1fcb1a7
base case network layout exposed
ShiZhongming Nov 3, 2025
6b99d5a
some small fix
ShiZhongming Nov 3, 2025
8e1bb9b
nodes too far from edge
ShiZhongming Nov 3, 2025
7d92a38
validating planting number
ShiZhongming Nov 3, 2025
4905783
ruff
ShiZhongming Nov 3, 2025
581ba35
ruff2
ShiZhongming Nov 3, 2025
12b4e24
Merge branch 'fix-network' into expose-network-layout-for-base-case
ShiZhongming Nov 3, 2025
04a747d
plant = type, not building
ShiZhongming Nov 3, 2025
e039863
allow missing building nodes for user friendliness
ShiZhongming Nov 3, 2025
0938d32
ruff
ShiZhongming Nov 3, 2025
d705969
Update config.pyi
ShiZhongming Nov 3, 2025
4fb46cb
Update graph_helper.py
ShiZhongming Nov 3, 2025
9dc99e0
Merge branch 'master' into expose-network-layout-for-base-case
reyery Nov 3, 2025
fb8b388
disconnected network =? number of plants
ShiZhongming Nov 5, 2025
af2e92e
Merge branch 'master' into expose-network-layout-for-base-case
ShiZhongming Nov 5, 2025
7285a91
multi plants nodes
ShiZhongming Nov 6, 2025
ab9576c
log: edge snap
ShiZhongming Nov 6, 2025
e4007d0
geometry clean before all
ShiZhongming Nov 6, 2025
856b4f0
Error messages improved
ShiZhongming Nov 6, 2025
1c89fdb
ruff
ShiZhongming Nov 6, 2025
56ff5ab
coderabbit
ShiZhongming Nov 6, 2025
fdc3d9a
updating Part 1
ShiZhongming Nov 6, 2025
0ebebdf
dc dh
ShiZhongming Nov 6, 2025
7d163a2
part 1
ShiZhongming Nov 6, 2025
5d1f50e
zero demand is now not filtered
ShiZhongming Nov 6, 2025
f45d697
Update cea/technologies/network_layout/main.py
ShiZhongming Nov 7, 2025
cbb4f93
Merge branch 'expose-network-layout-for-base-case' into user-defined-…
ShiZhongming Nov 7, 2025
bc15183
memory allocation + wording clarity
ShiZhongming Nov 7, 2025
9d85736
Handle None result as a valid "no network provided" signal.
ShiZhongming Nov 7, 2025
c1ae9cd
Merge branch 'expose-network-layout-for-base-case' into user-defined-…
ShiZhongming Nov 7, 2025
f9673c3
remove default pipe width values
ShiZhongming Nov 7, 2025
26702c5
ui update
ShiZhongming Nov 7, 2025
844bd5e
user defined network check
ShiZhongming Nov 7, 2025
9b1a258
plant-building
ShiZhongming Nov 7, 2025
0ed8650
plant building
ShiZhongming Nov 7, 2025
0342eb8
missing nodes to auto completed
ShiZhongming Nov 7, 2025
22eb5fc
fix config
ShiZhongming Nov 7, 2025
22ceae1
bug fix: missing plant
ShiZhongming Nov 7, 2025
49940e4
additional building checks
ShiZhongming Nov 7, 2025
935e219
working
ShiZhongming Nov 7, 2025
186c439
network selection for part 2
ShiZhongming Nov 8, 2025
258d9e6
switch between DH and DC
ShiZhongming Nov 8, 2025
d7383e9
part 2 have the most recent network name
ShiZhongming Nov 8, 2025
dc17cbc
hide unnecessary parameters
ShiZhongming Nov 8, 2025
f1c6364
renaming form
ShiZhongming Nov 8, 2025
c8bbfae
Update config.py
ShiZhongming Nov 9, 2025
880d18e
restructuring directory
ShiZhongming Nov 9, 2025
7c7deda
backend running
ShiZhongming Nov 9, 2025
7d6cb1c
DH DC switching work
ShiZhongming Nov 9, 2025
5711ccb
no network available
ShiZhongming Nov 9, 2025
e478161
backward compatible for existing networks in the older folder structure
ShiZhongming Nov 9, 2025
745f3ef
improved feature description
ShiZhongming Nov 9, 2025
1a5392f
number of components validation
ShiZhongming Nov 9, 2025
76917b8
sorting network name by recentness
ShiZhongming Nov 9, 2025
ad4d1a5
map layer to show the most recent
ShiZhongming Nov 9, 2025
5c7cf46
Merge branch 'master' into expose-network-layout-for-base-case
reyery Nov 11, 2025
79862e7
Remove locator argument from load_user_defined_network
reyery Nov 11, 2025
3ae0236
Inherit UserNetworkLoaderError from CEAException
reyery Nov 11, 2025
7970afe
Merge branch 'user-defined-network-for-thermal-network' into expose-n…
reyery Nov 11, 2025
45ac54f
Merge pull request #3956 from architecture-building-systems/expose-ne…
reyery Nov 11, 2025
b47757c
Refactor print statements and minor code cleanup
reyery Nov 11, 2025
0e3c24d
Add validation to network layout name and choice parameters
reyery Nov 11, 2025
d5eb48d
Add type hints to Parameter encode and decode methods
reyery Nov 11, 2025
49d6636
Add nullable support to SingleBuildingParameter
reyery Nov 11, 2025
36923ac
Require network name for layout variants
reyery Nov 12, 2025
855bb28
Remove empty network name validation in decode method
reyery Nov 12, 2025
ee4880b
Simplify error message for empty network name
reyery Nov 12, 2025
7e532d2
Add dependency and validation metadata to network parameters
reyery Nov 12, 2025
a6aa16c
Add field validation and parameter metadata endpoints
reyery Nov 12, 2025
f529935
Update config.pyi
reyery Nov 12, 2025
04c5d88
Replace print statements with logging and refactor validation
reyery Nov 12, 2025
eb646e0
Refactor NetworkLayoutChoiceParameter for clarity and efficiency
reyery Nov 12, 2025
6069042
Refactor network name validation logic
reyery Nov 12, 2025
049dcfe
Add documentation for agent parameter configuration
reyery Nov 12, 2025
ecfb56e
Improve network layout parameter validation and sorting
reyery Nov 12, 2025
ddef721
Refactor network_type config to thermal_network section
reyery Nov 12, 2025
951791c
Refactor user-defined network to base network layout
reyery Nov 12, 2025
90286c7
Update config.pyi
reyery Nov 12, 2025
7b702f0
Remove unused imports and variables in main.py
reyery Nov 12, 2025
df9a5ad
proposing ui change for network: part 1
ShiZhongming Nov 12, 2025
42fa00f
Merge branch 'master' into user-defined-network-for-thermal-network
reyery Nov 13, 2025
f3f165c
Merge branch 'master' into user-defined-network-for-thermal-network
reyery Nov 13, 2025
2e8b879
Refactor network layout input handling and cleanup
reyery Nov 13, 2025
c4e4df2
Refactor NetworkLayout class and network naming logic
reyery Nov 13, 2025
12b00d2
user provided geometry helper function
ShiZhongming Nov 13, 2025
c4e14fd
input locator
ShiZhongming Nov 13, 2025
fa1de4c
include-service parameter
ShiZhongming Nov 14, 2025
c393e58
not overwrite + user-defined
ShiZhongming Nov 14, 2025
84b6eec
user layout + overwrite
ShiZhongming Nov 14, 2025
fcb3fc3
before some small bug fixes
ShiZhongming Nov 14, 2025
79d4f30
small bug fix
ShiZhongming Nov 14, 2025
dc704b8
working with wrong input locator
ShiZhongming Nov 14, 2025
c295ae8
auto working
ShiZhongming Nov 14, 2025
41ec943
british english
ShiZhongming Nov 14, 2025
e54e05a
warning about connected buildings being ignored
ShiZhongming Nov 14, 2025
f4e7105
plant by dh or dc
ShiZhongming Nov 14, 2025
53b7c57
fixing plant conflict
ShiZhongming Nov 14, 2025
bb68efa
max plant 3
ShiZhongming Nov 14, 2025
5fd76e2
PLANT_DC, PLANT_DH
ShiZhongming Nov 14, 2025
59ba633
relaxed nodes.shp
ShiZhongming Nov 14, 2025
df37689
multi plant bug fixed, dc and dh nodes being different is fine
ShiZhongming Nov 14, 2025
09294c2
plant node for user-provided case
ShiZhongming Nov 14, 2025
0923362
not removing building node that was supposed to be there
ShiZhongming Nov 14, 2025
f3b5f3c
plant building validation
ShiZhongming Nov 14, 2025
d764529
Refactor network layout parameter validation and selection
reyery Nov 14, 2025
f667693
Refactor thermal network file path methods
reyery Nov 14, 2025
c7e6d48
Clarify agent documentation guidelines in AGENTS.md
reyery Nov 14, 2025
87094d2
Refactor and simplify thermal network layer handling
reyery Nov 15, 2025
fa20e30
Enable network layout file requirement in map layer
reyery Nov 15, 2025
1dada13
Make error message more generic
reyery Nov 15, 2025
f59f19b
Support optional input files in map layer checks
reyery Nov 15, 2025
40f78ee
Add support for network nodes, edges, and results files
reyery Nov 15, 2025
2c80df3
Merge branch 'master' into user-defined-network-for-thermal-network
reyery Nov 15, 2025
9c9bae3
Improve duplicate folder detection in schema tests
reyery Nov 15, 2025
414a6ad
Update config.pyi
reyery Nov 17, 2025
c2e863f
Refine network layout logging and warnings
reyery Nov 17, 2025
5ebfb85
Add network-name parameter to test workflows
reyery Nov 17, 2025
675b4e6
Add error handling and cleanup for network layout generation
reyery Nov 17, 2025
9b52a6c
Refactor demand checks to loop over service types
reyery Nov 17, 2025
24bc811
Update agent guidelines with new code conventions
reyery Nov 17, 2025
bc85954
Handle single-building networks in Steiner tree calculation
reyery Nov 17, 2025
cc1c418
Improve network folder cleanup on error
reyery Nov 17, 2025
6f67c67
Ensure network name parameter is always a string
reyery Nov 17, 2025
b50c0ec
Rename total_demand to total_demand_path in function
reyery Nov 17, 2025
427ab25
Add k-nearest building-street connection candidates
reyery Nov 17, 2025
dca59f0
Refine network pruning and edge contraction in Steiner tree
reyery Nov 18, 2025
7392696
Improve network connectivity validation and auto-retry
reyery Nov 18, 2025
a870628
Improve network connectivity validation and metadata update
reyery Nov 18, 2025
ba8852b
Fix network drift and add orphan node merging for connectivity
reyery Nov 18, 2025
6597324
Optimize terminal rerouting in network layout
reyery Nov 18, 2025
48255e7
Add tests for coordinate drift and orphan node merging
reyery Nov 18, 2025
f4e1138
Remove print
reyery Nov 18, 2025
55b1a34
Fix argument name for network layout function
reyery Nov 18, 2025
4f19e95
Improve orphan node merging in network graph
reyery Nov 18, 2025
b267f68
Improve plant node handling and network validation
reyery Nov 18, 2025
74d75fb
Refactor network file loading logic in layers.py
reyery Nov 18, 2025
95f61ae
Refactor network folder migration and parameter order
reyery Nov 18, 2025
e544f49
Update network folder structure and shapefile migration
reyery Nov 18, 2025
53aece5
Update network metadata file location
reyery Nov 18, 2025
5c10024
Support multiple network types in thermal network scripts
reyery Nov 18, 2025
f1ec769
Relax plant node check in thermal network
reyery Nov 18, 2025
db31940
Handle missing demand profiles for network buildings
reyery Nov 14, 2025
e1d588d
Remove unused variable in DH network logic
reyery Nov 18, 2025
3710ad0
Improve edge validation in thermal network extraction
reyery Nov 19, 2025
b34e8be
Add validation and fix for duplicate node names in network layout
reyery Nov 19, 2025
e9dd82e
Fix test configs and add file existence check
reyery Nov 19, 2025
84396d6
Refactor node naming to use unique helper function
reyery Nov 19, 2025
6e16893
Refine network layout logging and output formatting
reyery Nov 19, 2025
a18c724
Refactor network type handling in network layout
reyery Nov 19, 2025
890328e
Refactor network type handling and add helper for determination
reyery Nov 19, 2025
dc12bbc
Preserve original street junctions in network layout
reyery Nov 19, 2025
cdc4658
Add robust validation and error handling to WNTR simulation
reyery Nov 19, 2025
1174a6e
Refactor plant node operations into separate module
reyery Nov 19, 2025
adb03e4
Improve edge handling and geometry normalization in network layout
reyery Nov 19, 2025
5554f08
Improve plant node creation and pipe renaming logic
reyery Nov 20, 2025
07282d1
Ensure consistent coordinate handling in snapping and normalization
reyery Nov 20, 2025
2f956b2
Improve case-insensitive handling and validation in network loader
reyery Nov 20, 2025
c004040
Use default network-type in test workflow
reyery Nov 20, 2025
44aec5f
Refactor main to unify network model processing
reyery Nov 20, 2025
4f1a0b5
Use network_type instead of config parameter
reyery Nov 21, 2025
88ec134
Add configurable snap-tolerance for network layout
reyery Nov 21, 2025
12820c1
Improve orphan component merging and rerouting in network layout
reyery Nov 21, 2025
fe9e01e
Standardize warning and info messages in network layout
reyery Nov 21, 2025
96aebab
Refactor detailed network config handling
reyery Nov 21, 2025
83955a8
Refactor docstring and import in network layout modules
reyery Nov 21, 2025
1ec8e3a
Fix type handling in NetworkLayoutChoiceParameter validation
reyery Nov 21, 2025
1d98d8f
Refactor orphan node merging and network name validation
reyery Nov 21, 2025
991d138
Remove representative_week argument from file outputs
reyery Nov 21, 2025
7e52fe3
error: no demand layout
ShiZhongming Nov 21, 2025
1cfec68
better error message when DC/DH is missing
ShiZhongming Nov 21, 2025
601a5a4
connection-candidates default from 3 to 2
ShiZhongming Nov 21, 2025
26e31c3
detailed model input and output path changes
ShiZhongming Nov 21, 2025
6c60e42
enhanced error message for part 1
ShiZhongming Nov 21, 2025
4417bcf
filter unused layout edge for specific nodes for the detailed model
ShiZhongming Nov 21, 2025
0e2239c
bug fix for loops detailed network
ShiZhongming Nov 21, 2025
298454c
Update thermal_network.py
ShiZhongming Nov 21, 2025
9f4489a
improve final error message to show succeeded vs failed networks
ShiZhongming Nov 21, 2025
c250b81
ruff
ShiZhongming Nov 21, 2025
4bb357e
Revert "improve final error message to show succeeded vs failed netwo…
ShiZhongming Nov 21, 2025
84cc1dd
better error message at the end of part 2: what worked, and what did not
ShiZhongming Nov 21, 2025
adb3b6b
removing not usable code
ShiZhongming Nov 21, 2025
1350347
optimisation-fix
ShiZhongming Nov 21, 2025
bff46e1
improved error message
ShiZhongming Nov 21, 2025
4e5d8a3
part 2 form can load now without thermal-network folder
ShiZhongming Nov 21, 2025
20a2ff6
update inputlocator
ShiZhongming Nov 21, 2025
de5254f
filter not used edges from layout.shp
ShiZhongming Nov 21, 2025
8c7a7a7
source of truth - network name not suppy.csv
ShiZhongming Nov 21, 2025
5fdb09b
source of truth - network name
ShiZhongming Nov 21, 2025
5d58d44
DH for optimisation: fixed
ShiZhongming Nov 21, 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
20 changes: 18 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Guidelines for LLMs

**Creating new documentation**:
**Creating new documentation for agents (e.g. AGENTS.md` or `CLAUDE.md`)**:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo: extra backtick in documentation.

The line contains an extra backtick at the end of `AGENTS.md``.

Apply this diff:

-**Creating new documentation for agents (e.g. AGENTS.md` or `CLAUDE.md`)**:
+**Creating new documentation for agents (e.g. `AGENTS.md` or `CLAUDE.md`)**:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Creating new documentation for agents (e.g. AGENTS.md` or `CLAUDE.md`)**:
**Creating new documentation for agents (e.g. `AGENTS.md` or `CLAUDE.md`)**:
🤖 Prompt for AI Agents
In AGENTS.md around line 3, the documentation line "**Creating new documentation
for agents (e.g. AGENTS.md` or `CLAUDE.md`)**:" contains an extra trailing
backtick after AGENTS.md; remove the stray backtick so the text reads
"AGENTS.md" (i.e., change `AGENTS.md`` to `AGENTS.md`) ensuring consistent
backtick pairing and correct markdown formatting.

- **Always** create context-specific documentation as `AGENTS.md` (not `CLAUDE.md`)
- **Always** symlink the new `AGENTS.md` file as `CLAUDE.md` in the same directory
- **Don't create AGENTS.md in every directory** - Only create when the directory contains complex patterns that aren't obvious from code
Expand All @@ -27,6 +27,7 @@
- **Focus on patterns, not details** - What to do, not why it exists
- **Scannable structure** - Use headers, bullets, and short paragraphs
- **Reference, don't explain** - Link to related files instead of duplicating information
- **No emoticons in code** - Never add emoji or emoticons to code files, comments, or print statements

**Good example structure**:
```markdown
Expand All @@ -46,7 +47,7 @@
- `file.py` - Purpose
```

**Updating existing documentation**:
**Updating existing agent documentation**:
- **IMPORTANT**: When making code changes in a directory, ALWAYS update the corresponding `AGENTS.md` file in that directory
- If no `AGENTS.md` exists in the directory where you're making changes, create one following the structure above
- Keep documentation synchronized with code changes to help other LLMs understand the current state
Expand All @@ -55,6 +56,20 @@
- Focus on architectural patterns, state management, data flow, and key concepts that aren't obvious from code alone
- If you need to preserve detailed explanations, move them to a separate `*_GUIDE.md` file for human readers

**Code quality directives**:
- **Extract meaningful patterns, not trivial wrappers** - Only create helper functions when they add real value:
- ✅ **DO extract** when:
- Logic must be done in a specific way to avoid bugs (e.g., `get_next_node_name()` - prevents duplicates)
- Complex workflow logic that's hard to understand inline (e.g., multi-step validation)
- Algorithm that requires deep understanding to get right
- Pattern that encapsulates important business rules
- ❌ **DON'T extract** when:
- It's just a 1-2 line wrapper around existing functions
- It's standard library usage (file I/O, simple pandas operations)
- The abstraction obscures rather than clarifies intent
- It would be clearer to just write inline
- **Rule of thumb**: If the helper function is shorter/simpler than its call sites, don't extract it

**Directory-specific AGENTS.md files**:
- `cea/databases/AGENTS.md` - Database structure, COMPONENTS vs ASSEMBLIES
- `cea/analysis/costs/AGENTS.md` - Cost calculation patterns
Expand Down Expand Up @@ -135,6 +150,7 @@ cea.api.demand(scenario='/path/to/scenario')
4. **Multiprocessing**: Check `config.multiprocessing` before using `Pool`
5. **Scenario Structure**: Respect `/inputs/`, `/outputs/` conventions
6. **Config Type Hints**: After modifying `config.py`, regenerate `config.pyi` by running `pixi run python cea/utilities/config_type_generator.py`
7. **F-strings**: Only use f-strings when string contains variables (e.g., `f"Value: {x}"`). Use regular strings otherwise (e.g., `"No variables"`) to avoid linter warnings

## Module Documentation

Expand Down
122 changes: 122 additions & 0 deletions cea/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Configuration Parameters

## Main API

- `Parameter.decode(value: str) → Any` - Parse value from config file (lenient)
- `Parameter.encode(value: Any) → str` - Validate value before saving (strict)
- `ChoiceParameter._choices → list[str]` - Available options (can be dynamic via `@property`)

## Key Pattern: decode() vs encode()

### ✅ DO: Separate parsing from validation

```python
class MyParameter(Parameter):
def decode(self, value):
"""Parse - security checks only"""
if not value:
return ""
value = value.strip()

# Only validate security concerns (path traversal, injection, etc.)
if self._has_security_issue(value):
raise ValueError("Security violation")

return value # Don't check business rules

def encode(self, value):
"""Validate - all business rules"""
if not value or not value.strip():
raise ValueError("Value required")

value = value.strip()

# Security check
if self._has_security_issue(value):
raise ValueError("Security violation")

# Business rule check
if self._resource_exists(value):
raise ValueError(f"Resource '{value}' already exists")

return value
```

### ❌ DON'T: Validate business rules in decode()

```python
def decode(self, value):
# ❌ Expensive I/O on every config load
if not self._resource_exists(value):
raise ValueError("Resource not found")

# ❌ Breaks loading old configs when resources deleted
if self._check_collision(value):
raise ValueError("Already exists")

return value
```

**Why**: decode() is called when loading config files - must be lenient and fast.

## Dynamic Choices

### ✅ DO: Use @property for dynamic choices

```python
class DynamicChoiceParameter(ChoiceParameter):
def initialize(self, parser):
self.depends_on = ['other-param'] # Declare dependencies

@property
def _choices(self):
"""Scan resources on each access"""
return self._get_available_options()

def _get_available_options(self):
# Scan filesystem/database for available options
if not self._can_scan():
return []

# Return list of valid choices
return self._scan_resources()
```

## Validation Helpers

Extract shared validation into helpers:

```python
def _validate_security(self, value):
"""Security checks (used by encode AND decode)"""
invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
if any(char in value for char in invalid_chars):
raise ValueError("Invalid characters")

def _validate_business(self, value):
"""Business rules (used by encode ONLY)"""
if self._resource_exists(value):
raise ValueError("Already exists")

def decode(self, value):
return self._validate_security(value.strip())

def encode(self, value):
self._validate_security(value.strip())
self._validate_business(value)
return value
```

## Common Pitfalls

1. **Validating in decode()** → Fragile config loading
2. **No dependency declaration** → Dynamic choices don't update
3. **Caching without invalidation** → Stale options
4. **Mixing security/business validation** → Security checks in both, business in encode only

## Related Files

- `config.py` - All parameter classes (PathParameter, ChoiceParameter, etc.)
- `config.pyi` - Type stubs (regenerate: `pixi run python cea/utilities/config_type_generator.py`)
- `default.config` - Default values for all parameters
- `interfaces/dashboard/api/tools.py` - Validation API endpoints (`validate_field`, `get_parameter_metadata`)
1 change: 1 addition & 0 deletions cea/CLAUDE.md
179 changes: 176 additions & 3 deletions cea/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,11 @@ def initialize(self, parser):
the default.config
"""

def encode(self, value):
def encode(self, value) -> str:
"""Encode ``value`` to a string representation for writing to the configuration file"""
return str(value)

def decode(self, value):
def decode(self, value) -> Any:
"""Decode ``value`` to the type supported by this Parameter"""
return value

Expand Down Expand Up @@ -754,6 +754,69 @@ class StringParameter(Parameter):
"""Default Parameter type"""""


class NetworkLayoutNameParameter(StringParameter):
"""
Parameter for network layout names with collision detection.
Validates in real-time to prevent overwriting existing network layouts.
"""

def _validate_network_name(self, value) -> str:
"""
Validate network name for invalid characters and collision with existing networks.
"""
value = value.strip()

# Check for invalid filesystem characters
invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
if any(char in value for char in invalid_chars):
raise ValueError(
f"Network name contains invalid characters. "
f"Avoid: {' '.join(invalid_chars)}"
)

# Check for collision with existing networks
scenario = self.config.scenario
locator = cea.inputlocator.InputLocator(scenario)

# Check network folder exists
network_folder = locator.get_thermal_network_folder_network_name_folder(value)
if os.path.exists(network_folder):
raise ValueError(
f"Network '{value}' already exists. "
f"Choose a different name or delete the existing network."
)

return value

def encode(self, value):
"""
Validate and encode network name.
Raises ValueError if name contains invalid characters or collides with existing network.
"""
if not str(value) or str(value).strip() == '':
raise ValueError("Network name is required. Please provide a valid name.")

return self._validate_network_name(str(value))

def decode(self, value):
"""Parse and normalize network name from config file"""
if not value:
return ""

value = value.strip()

# Only validate filesystem characters (security concern)
# Don't check collision - that's encode's job when saving
invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
if any(char in value for char in invalid_chars):
raise ValueError(
f"Network name contains invalid characters. "
f"Avoid: {' '.join(invalid_chars)}"
)

return value


class OptimizationIndividualListParameter(ListParameter):
def initialize(self, parser):
# allow the parent option to be set
Expand Down Expand Up @@ -803,6 +866,106 @@ def decode(self, value):
return self._choices[0]


class NetworkLayoutChoiceParameter(ChoiceParameter):
"""
Parameter for selecting existing network layouts based on network type.
"""

_network_types = {'DH', 'DC'}

def initialize(self, parser):
# Override to dynamically populate choices based on available networks
pass

@property
def _choices(self):
networks = self._get_available_networks()
sorted_networks = self._sort_networks_by_modification_time(networks)
return sorted_networks

def _get_available_networks(self) -> List[str]:
locator = cea.inputlocator.InputLocator(self.config.scenario)
network_folder = locator.get_thermal_network_folder()
if not os.path.exists(network_folder):
return []
return [name for name in os.listdir(network_folder)
if os.path.isdir(os.path.join(network_folder, name)) and name not in self._network_types]

def _get_network_file_paths(self, network_type: str, network_name: str) -> Tuple[str, str]:
"""Get path for network node and edge files for the given network name"""
locator = cea.inputlocator.InputLocator(self.config.scenario)

network_type_folder = locator.get_output_thermal_network_type_folder(network_type, network_name)
# Remove trailing slash/separator if present
network_type_folder = network_type_folder.rstrip(os.sep)

edges_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
nodes_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
Comment on lines +902 to +903
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: edges_path assigned wrong method.

Line 900 calls get_network_layout_nodes_shapefile instead of get_network_layout_edges_shapefile. Both edges_path and nodes_path are being assigned the same file path.

Apply this diff:

-        edges_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
+        edges_path = locator.get_network_layout_edges_shapefile(network_type, network_name)
         nodes_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
🤖 Prompt for AI Agents
In cea/config.py around lines 900 to 901, edges_path is mistakenly assigned
using locator.get_network_layout_nodes_shapefile resulting in both edges_path
and nodes_path pointing to the same file; change the assignment for edges_path
to call locator.get_network_layout_edges_shapefile(network_type, network_name)
while keeping nodes_path as
locator.get_network_layout_nodes_shapefile(network_type, network_name) so each
variable references the correct shapefile.


return edges_path, nodes_path
Comment on lines +869 to +905
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix edges path construction and avoid shapefile getter side-effects

NetworkLayoutChoiceParameter._get_network_file_paths currently has two issues:

  1. edges_path mistakenly calls the nodes locator, so both paths point to the nodes shapefile:
    edges_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
    nodes_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
  2. Using get_network_layout_*_shapefile introduces check_cpg side-effects when merely checking for existence, which can raise if .cpg is missing and break network discovery.

Use the network-type folder and join the layout filenames directly instead:

     def _get_network_file_paths(self, network_type: str, network_name: str) -> Tuple[str, str]:
-        """Get path for network node and edge files for the given network name"""
-        locator = cea.inputlocator.InputLocator(self.config.scenario)
-
-        network_type_folder = locator.get_output_thermal_network_type_folder(network_type, network_name)
-        # Remove trailing slash/separator if present
-        network_type_folder = network_type_folder.rstrip(os.sep)
-
-        edges_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
-        nodes_path = locator.get_network_layout_nodes_shapefile(network_type, network_name)
-
-        return edges_path, nodes_path
+        """Get paths for network edge and node files for the given network name (no locator side-effects)."""
+        locator = cea.inputlocator.InputLocator(self.config.scenario)
+        base = locator.get_output_thermal_network_type_folder(network_type, network_name).rstrip(os.sep)
+        edges_path = os.path.join(base, "layout", "edges.shp")
+        nodes_path = os.path.join(base, "layout", "nodes.shp")
+        return edges_path, nodes_path

This fixes the edges/nodes mix-up and keeps _choices computation robust even when .cpg files are absent.


def _sort_networks_by_modification_time(self, network_names: List[str]) -> List[str]:
"""Sort network layouts by modification time (most recent first)"""
modified_times = []
for network_name in network_names:
network_time = []
for network_type in self._network_types:
edges_path, nodes_path = self._get_network_file_paths(network_type, network_name)
if os.path.exists(edges_path) and os.path.exists(nodes_path):
sort_time = os.path.getmtime(edges_path)
network_time.append((network_name, sort_time))
if network_time:
# Get the most recent modification time across network types
most_recent_time = max(time for _, time in network_time)
modified_times.append((network_name, most_recent_time))

# Sort by modification time, most recent first
modified_times.sort(key=lambda x: x[1], reverse=True)
sorted_networks = [network_name for network_name, _ in modified_times]
return sorted_networks

def encode(self, value):
"""
Validate and encode value.
Raises ValueError if the network layout doesn't exist.
"""
# Empty value not allowed
if not value or str(value).strip() == '':
raise ValueError("Network layout is required. Please select a network layout.")

available_networks = self._get_available_networks()

# Validate that the network exists
if str(value) not in available_networks:
raise ValueError(
f"Network layout '{value}' not found. "
f"Available layouts: {', '.join(available_networks)}"
)
return str(value)

def decode(self, value):
"""
Decode and validate value exists in available networks.
Returns the selected value if valid, otherwise returns the most recent network as default.

If no value provided and no networks found for current network-type, try to find
the most recent network across ALL network types and switch network-type accordingly.
"""
available_networks = self._get_available_networks()

# If value is provided and valid, return it
if value and value in available_networks:
return value

# Otherwise, return the most recent network (first choice) if available
if available_networks:
sorted_networks = self._sort_networks_by_modification_time(available_networks)
most_recent_network = sorted_networks[0]
return most_recent_network

return ''


class DatabasePathParameter(Parameter):
"""A parameter that can either be set to a region-specific CEA Database (e.g. CH or SG) or to a user-defined
folder that has the same structure."""
Expand Down Expand Up @@ -989,7 +1152,10 @@ class SingleBuildingParameter(ChoiceParameter):

def initialize(self, parser):
# skip the default ChoiceParameter initialization of _choices
pass
try:
self.nullable = parser.getboolean(self.section.name, f"{self.name}.nullable")
except configparser.NoOptionError:
self.nullable = False

@property
def _choices(self):
Expand All @@ -999,8 +1165,15 @@ def _choices(self):
if not building_names:
raise cea.ConfigError("Either no buildings in zone or no zone geometry found.")
return building_names

def decode(self, value):
if self.nullable and (value is None or value == ''):
return None
return super().decode(value)

def encode(self, value):
if self.nullable and (value is None or value == ''):
return ''
if str(value) not in self._choices:
return self._choices[0]
return str(value)
Expand Down
Loading
Loading