name: opensolve-pipe-data-model description: OpenSolve Pipe component chain data model conventions. Use when creating or modifying components, piping segments, or project structures.
OpenSolve Pipe Data Model Skill
Component Chain Model
The primary data structure is a component chain, NOT a node-link graph.
Each component has:
-
id: unique identifier (format:{type}-{number}, e.g., "pump-1") -
type: component type enum -
name: user-friendly display name -
elevation: in project units -
upstreamPiping: optional PipingSegment -
downstreamConnections: array of Connection objects
Component ID Conventions
Use consistent naming patterns for automatic ID generation:
-
Pumps:
pump-1,pump-2, ... -
Valves:
valve-1,valve-2, ... (orvalve-prv-1for specific types) -
Tanks:
tank-1,reservoir-1, ... -
Junctions:
junction-1,junction-2, ... -
Heat Exchangers:
hx-1,hx-2, ... -
Strainers:
strainer-1,strainer-2, ... -
Orifices:
orifice-1,orifice-2, ... -
Sprinklers:
sprinkler-1,sprinkler-2, ...
Format: {type}-{incrementing-number}
PipingSegment Structure
interface PipingSegment {
pipe: {
material: string, // Reference to materials library (e.g., "carbon_steel")
nominalDiameter: number, // Nominal pipe size (e.g., 2.5, 3, 4, 6)
schedule: string, // Pipe schedule (e.g., "40", "80", "160")
length: number, // Pipe length in project units
roughness?: number // Override if user specifies custom roughness
},
fittings: Fitting[] // Array of fittings in this segment
}
Fitting Structure
interface Fitting {
id: string, // Reference to fittings library (e.g., "elbow_90_lr")
name: string, // Display name (e.g., "90° Long Radius Elbow")
quantity: number, // Number of this fitting in segment
kFactor?: number, // User override for K-factor
equivalentLengthD?: number // User override for equivalent length (L/D)
}
Notes:
- If both
kFactorandequivalentLengthDare provided,kFactortakes precedence - If neither is provided, use library default
Component Types
Node Types (Boundary Conditions)
Reservoir:
{
id: "reservoir-1",
type: "reservoir",
name: "Supply Tank",
elevation: 100, // ft or m
waterLevel: 150, // Total head (elevation + static height)
downstreamConnections: [...]
}
Tank:
{
id: "tank-1",
type: "tank",
name: "Storage Tank",
elevation: 50,
diameter: 10, // ft or m
initialLevel: 10, // ft or m above tank bottom
minLevel: 2,
maxLevel: 12,
upstreamPiping: {...},
downstreamConnections: [...]
}
Junction:
{
id: "junction-1",
type: "junction",
name: "Branch Point",
elevation: 50,
demand: 0, // Optional withdrawal (GPM or L/s)
upstreamPiping: {...},
downstreamConnections: [ // Multiple connections for branching
{ targetComponentId: "pipe-2", piping: {...} },
{ targetComponentId: "pipe-3", piping: {...} }
]
}
Link Types (Active Components)
Pump:
{
id: "pump-1",
type: "pump",
name: "Primary Pump",
elevation: 50,
curve: {
id: "curve-1",
name: "Grundfos CR10-5",
manufacturer: "Grundfos",
model: "CR10-5",
points: [
{ flow: 0, head: 150 },
{ flow: 100, head: 145 },
{ flow: 200, head: 130 },
// ... more points
],
efficiencyCurve?: [...] // Optional
},
speed: 1.0, // Fraction of rated speed (1.0 = 100%)
status: "on", // "on" or "off"
npshr?: [...], // Optional NPSH required curve
upstreamPiping: {...},
downstreamConnections: [...]
}
Valve:
{
id: "valve-prv-1",
type: "valve",
valveType: "prv", // "gate", "ball", "check", "prv", "psv", "fcv"
name: "Pressure Reducing Valve",
elevation: 50,
model: "simplified", // "simplified" or "detailed"
setpoint: 60, // Target pressure (psi or kPa)
upstreamPiping: {...},
downstreamConnections: [...]
}
Heat Exchanger:
{
id: "hx-1",
type: "heat_exchanger",
name: "Plate HX",
elevation: 50,
pressureDrop: 5, // Fixed pressure drop (psi or kPa)
upstreamPiping: {...},
downstreamConnections: [...]
}
Connection Structure
interface Connection {
targetComponentId: string, // ID of downstream component
piping: PipingSegment // Piping between this component and target
}
Validation Rules
-
Every component must have unique id
- No duplicate IDs in component array
- IDs must follow naming convention
-
Reservoir/Tank must be at start or end of chain
- Reservoir typically at start (infinite source)
- Tank typically at end (discharge point)
-
Pump must have at least one downstream connection
- Cannot be a dead end
- Must pump to somewhere
-
Branching requires Junction component
- Junction has multiple downstream connections
- Each branch modeled explicitly
-
Loops must have at least one pump
- Cannot have loop without active component
- Network solver handles loop balancing
-
Piping segments must reference valid materials/fittings
- Material ID must exist in
pipe_materials.json - Fitting ID must exist in
fittings.json
- Material ID must exist in
-
Physical constraints
- Elevations must be positive (or allow negative with datum reference)
- Pipe lengths must be positive
- Diameters must be positive
- Flow rates must be non-negative (negative = reverse flow)
Solver Conversion
Simple Solver (Single Path)
- Walks component chain in order
- Calculates head loss cumulatively
- Finds operating point by intersecting pump and system curves
Network Solver (Branching/Looped)
- Converts component chain to WNTR node-link graph
- Nodes: Reservoirs, Tanks, Junctions (implicit nodes created at pumps/valves)
- Links: Pipes (with equivalent K for fittings), Pumps, Valves
- WNTR/EPANET solves for pressures and flows
Adding New Component Types
When adding a new component type, follow these steps:
-
Add to ComponentType enum
type ComponentType = | "reservoir" | "tank" | "junction" | "pump" | "valve" | "heat_exchanger" | "strainer" // ← New type | ... -
Create interface extending BaseComponent
interface Strainer extends BaseComponent { type: "strainer"; meshSize: number; // Mesh opening size cleanPressureDrop: number; // Clean pressure drop dirtyPressureDrop: number; // Dirty pressure drop (for warnings) } -
Add to solver adapter
-
simple.py: Add component handling in chain walker -
network.py: Add conversion to WNTR equivalent
-
-
Add UI panel
-
apps/web/src/lib/components/panel/StrainerPanel.svelte
-
-
Add schematic symbol
-
apps/web/static/symbols/strainer.svg
-
-
Update data schema
- Add to
apps/api/src/opensolve_pipe/models/components.py - Add to
apps/web/src/lib/models/components.ts
- Add to
-
Document in this skill file
- Add to component types section
- Add validation rules if applicable
Example: Simple Single-Path System
{
id: "project-1",
metadata: {...},
settings: {...},
fluid: { id: "water", temperature: 68 },
components: [
{
id: "reservoir-1",
type: "reservoir",
name: "Supply Reservoir",
elevation: 0,
waterLevel: 100,
downstreamConnections: [
{
targetComponentId: "pump-1",
piping: {
pipe: {
material: "carbon_steel",
nominalDiameter: 4,
schedule: "40",
length: 20
},
fittings: [
{ id: "entrance_rounded", name: "Pipe Entrance", quantity: 1 }
]
}
}
]
},
{
id: "pump-1",
type: "pump",
name: "Main Pump",
elevation: 0,
curve: {...},
speed: 1.0,
status: "on",
upstreamPiping: null, // Connected via reservoir's downstream
downstreamConnections: [
{
targetComponentId: "tank-1",
piping: {
pipe: {
material: "carbon_steel",
nominalDiameter: 4,
schedule: "40",
length: 100
},
fittings: [
{ id: "elbow_90_lr", name: "90° Elbow", quantity: 3 },
{ id: "gate_valve", name: "Gate Valve", quantity: 1 }
]
}
}
]
},
{
id: "tank-1",
type: "tank",
name: "Elevated Tank",
elevation: 50,
diameter: 10,
initialLevel: 10,
minLevel: 2,
maxLevel: 12,
upstreamPiping: null, // Connected via pump's downstream
downstreamConnections: []
}
]
}
Common Patterns
Parallel Pumps
{
id: "junction-1",
type: "junction",
name: "Pump Suction Header",
downstreamConnections: [
{ targetComponentId: "pump-1", piping: {...} },
{ targetComponentId: "pump-2", piping: {...} }
]
},
// Both pumps discharge to junction-2
Series Pumps
// pump-1 → piping → pump-2 → piping → tank
Bypass Loop
{
id: "junction-1",
downstreamConnections: [
{ targetComponentId: "hx-1", piping: {...} }, // Main path
{ targetComponentId: "valve-bypass", piping: {...} } // Bypass path
]
}
// Both paths rejoin at junction-2
Best Practices
-
Keep component chains simple for MVP
- Start with single-path systems
- Add branching in Phase 2
-
Use descriptive names
- IDs are programmatic (
pump-1) - Names are user-facing (
"Primary Circulation Pump")
- IDs are programmatic (
-
Store piping with downstream connection
- Piping belongs to the connection, not the component
- Makes chain traversal cleaner
-
Validate early
- Check component chain validity before solving
- Provide clear error messages
-
Preserve user intent
- Don't auto-modify user's component chain
- Warn about issues, don't silently fix
-
Handle edge cases
- Zero-length pipes (K-factors only)
- Closed valves (infinite K or disconnect)
- Empty tanks (warning, don't crash)
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!