swmm-params
Deterministic mapping from land use and soil texture to SWMM runoff/subarea and Green-Ampt infiltration parameters. Use when generating first-pass subcatchment parameter tables for swmm-builder.
Install
npx skills add https://github.com/Zhonghao1995/agentic-swmm-workflow/tree/main/skills/swmm-params
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install zhonghao1995-agentic-swmm-workflow@llmmart
git clone https://github.com/Zhonghao1995/agentic-swmm-workflow.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole zhonghao1995/agentic-swmm-workflow collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
SWMM Params (MVP mapping layer)
Part of Agentic SWMM — install the project first for the executable toolchain (aiswmm CLI, SWMM solver, MCP servers).
What this skill provides
- Transparent CSV-to-JSON mapping for:
- land use class -> SWMM
[SUBCATCHMENTS]+[SUBAREAS]defaults - soil texture/type -> SWMM
[INFILTRATION](Green-Ampt) defaults
- land use class -> SWMM
- Deterministic, auditable outputs with explicit fallback usage and unmatched-key reporting.
- Optional merge step that emits one builder-ready JSON artifact.
Scripts
scripts/landuse_to_swmm_params.py- maps
subcatchment_id + landuse_classto runoff/subarea parameters
- maps
scripts/soil_to_greenampt.py- maps
subcatchment_id + soil_textureto Green-Ampt infiltration parameters
- maps
scripts/merge_swmm_params.py- merges outputs from the two mapping scripts into one JSON package for future
swmm-builder
- merges outputs from the two mapping scripts into one JSON package for future
Default lookup tables
By default, scripts read bundled lookup CSVs:
skills/swmm-params/references/landuse_class_to_subcatch_params.csvskills/swmm-params/references/soil_texture_to_greenampt.csv
You can override lookup paths with CLI flags.
Minimal input format
Land use input CSV:
- required columns:
subcatchment_id,landuse_class
Soil input CSV:
- required columns:
subcatchment_id,soil_texture
Example files are provided under examples/.
Outputs
Each mapper writes explicit JSON containing:
records(row-level audit trail)sections(SWMM-oriented lists keyed by subcatchment)unmatched_*lists (rows that used fallback)countssummary
The merge script writes:
sections(subcatchments,subareas,infiltration)by_subcatchment(combined record per subcatchment ID)incomplete_ids(IDs missing one or more sections)
CLI flags
All three scripts share these optional flags:
--strict— fail instead of using theDEFAULTfallback row when an input key is missing from the lookup table. Useful for auditable production runs where silent fallback would mask a data gap.
landuse_to_swmm_params.py also accepts:
--subcatchment-column <col>— override the CSV column used as the subcatchment ID (default:subcatchment_id).--landuse-column <col>— override the CSV column used as the land use class (default:landuse_class).
soil_to_greenampt.py also accepts:
--subcatchment-column <col>— override the CSV column used as the subcatchment ID (default:subcatchment_id).--soil-column <col>— override the CSV column used as the soil texture/type (default:soil_texture).
MCP
MCP wrapper location:
mcp/swmm-params/server.js
Exposed tools:
map_landuse(inputCsvPath, optionallookupCsvPath,outputPath)map_soil(inputCsvPath, optionallookupCsvPath,outputPath)merge_params(landuseJsonPath,soilJsonPath,outputPath)
Quick start:
npm --prefix mcp/swmm-params install
npm --prefix mcp/swmm-params run start
Known limitations
- Lookup mapping is key-based only (no spatial interpolation or fuzzy matching).
- A fallback row is expected in lookup tables (
DEFAULTfor land use,-orDEFAULTfor soil). - No unit conversion or calibration logic is included here.
- This skill only maps parameters; it does not write a full SWMM
.inp.
Example commands
python3 skills/swmm-params/scripts/landuse_to_swmm_params.py \
--input skills/swmm-params/examples/landuse_input.csv \
--output runs/swmm-params/example_landuse.json
python3 skills/swmm-params/scripts/soil_to_greenampt.py \
--input skills/swmm-params/examples/soil_input.csv \
--output runs/swmm-params/example_soil.json
python3 skills/swmm-params/scripts/merge_swmm_params.py \
--landuse-json runs/swmm-params/example_landuse.json \
--soil-json runs/swmm-params/example_soil.json \
--output runs/swmm-params/example_builder_params.json
Files (agentic-swmm-workflow)
-
examples
-
landuse_input.csv 96 B · in bundle
-
soil_input.csv 74 B · in bundle
-
-
references
-
landuse_class_to_subcatch_params.csv 1.3 KB · in bundle
-
soil_texture_to_greenampt.csv 363 B · in bundle
-
-
scripts
-
landuse_to_swmm_params.py 7.4 KB
#!/usr/bin/env python3 from __future__ import annotations import argparse import csv import json from pathlib import Path from typing import Any SKILL_DIR = Path(__file__).resolve().parents[1] DEFAULT_LOOKUP = SKILL_DIR / "references/landuse_class_to_subcatch_params.csv" def normalize_key(value: str) -> str: return " ".join(str(value).strip().lower().split()) def load_csv_rows(path: Path) -> list[dict[str, str]]: with path.open(newline="", encoding="utf-8") as f: rows = list(csv.DictReader(f)) if not rows: raise ValueError(f"CSV has no data rows: {path}") return rows def parse_float(value: Any, *, field: str, csv_path: Path, row_number: int) -> float: if value is None or str(value).strip() == "": raise ValueError(f"Missing numeric value for '{field}' at {csv_path}:{row_number}") try: return float(str(value).strip()) except ValueError as exc: raise ValueError(f"Invalid float for '{field}' at {csv_path}:{row_number}: {value}") from exc def save_json(path: Path, obj: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(obj, indent=2), encoding="utf-8") def build_lookup(lookup_rows: list[dict[str, str]], lookup_path: Path) -> tuple[dict[str, dict[str, Any]], dict[str, Any] | None]: lookup: dict[str, dict[str, Any]] = {} default_record: dict[str, Any] | None = None for i, row in enumerate(lookup_rows, start=2): raw_key = (row.get("landuse_class") or "").strip() if not raw_key: raise ValueError(f"Missing 'landuse_class' in lookup at {lookup_path}:{i}") key = normalize_key(raw_key) if key in lookup: raise ValueError(f"Duplicate lookup key '{raw_key}' in {lookup_path}") rec = { "landuse_class": raw_key, "imperv_pct": parse_float(row.get("imperv_pct"), field="imperv_pct", csv_path=lookup_path, row_number=i), "n_imperv": parse_float(row.get("n_imperv"), field="n_imperv", csv_path=lookup_path, row_number=i), "n_perv": parse_float(row.get("n_perv"), field="n_perv", csv_path=lookup_path, row_number=i), "dstore_imperv_in": parse_float( row.get("dstore_imperv_in"), field="dstore_imperv_in", csv_path=lookup_path, row_number=i ), "dstore_perv_in": parse_float( row.get("dstore_perv_in"), field="dstore_perv_in", csv_path=lookup_path, row_number=i ), "zero_imperv_pct": parse_float( row.get("zero_imperv_pct"), field="zero_imperv_pct", csv_path=lookup_path, row_number=i ), "route_to": (row.get("route_to") or "").strip(), "pct_routed": parse_float(row.get("pct_routed"), field="pct_routed", csv_path=lookup_path, row_number=i), "notes": (row.get("notes") or "").strip(), } lookup[key] = rec if raw_key.upper() == "DEFAULT": default_record = rec return lookup, default_record def require_column(row: dict[str, str], col: str, *, csv_path: Path, row_number: int) -> str: if col not in row: raise ValueError(f"Missing required column '{col}' in {csv_path}") value = (row.get(col) or "").strip() if not value: raise ValueError(f"Missing value for '{col}' at {csv_path}:{row_number}") return value def main() -> None: ap = argparse.ArgumentParser( description="Map land use class to SWMM runoff/subarea parameters (deterministic CSV -> JSON)." ) ap.add_argument("--input", type=Path, required=True, help="Input CSV with subcatchment_id and landuse_class columns.") ap.add_argument("--lookup", type=Path, default=DEFAULT_LOOKUP, help="Lookup CSV for land use mapping.") ap.add_argument("--output", type=Path, required=True, help="Output JSON path.") ap.add_argument("--subcatchment-column", default="subcatchment_id") ap.add_argument("--landuse-column", default="landuse_class") ap.add_argument("--strict", action="store_true", help="Fail if any land use class is missing from lookup.") args = ap.parse_args() input_rows = load_csv_rows(args.input) lookup_rows = load_csv_rows(args.lookup) lookup_map, default_record = build_lookup(lookup_rows, args.lookup) seen_ids: set[str] = set() unmatched: set[str] = set() records: list[dict[str, Any]] = [] section_subcatchments: list[dict[str, Any]] = [] section_subareas: list[dict[str, Any]] = [] for i, row in enumerate(input_rows, start=2): subcatchment_id = require_column(row, args.subcatchment_column, csv_path=args.input, row_number=i) landuse_raw = require_column(row, args.landuse_column, csv_path=args.input, row_number=i) if subcatchment_id in seen_ids: raise ValueError(f"Duplicate subcatchment_id '{subcatchment_id}' in {args.input}") seen_ids.add(subcatchment_id) key = normalize_key(landuse_raw) lookup_rec = lookup_map.get(key) used_default = False if lookup_rec is None: if args.strict or default_record is None: raise ValueError( f"Unmapped landuse_class '{landuse_raw}' at {args.input}:{i}. " "Add a lookup row or run without --strict and ensure DEFAULT exists." ) lookup_rec = default_record used_default = True unmatched.add(landuse_raw) subcatchment_entry = { "id": subcatchment_id, "pct_imperv": lookup_rec["imperv_pct"], } subarea_entry = { "id": subcatchment_id, "n_imperv": lookup_rec["n_imperv"], "n_perv": lookup_rec["n_perv"], "dstore_imperv_in": lookup_rec["dstore_imperv_in"], "dstore_perv_in": lookup_rec["dstore_perv_in"], "zero_imperv_pct": lookup_rec["zero_imperv_pct"], "route_to": lookup_rec["route_to"], "pct_routed": lookup_rec["pct_routed"], } section_subcatchments.append(subcatchment_entry) section_subareas.append(subarea_entry) records.append( { "subcatchment_id": subcatchment_id, "input_landuse_class": landuse_raw, "lookup_landuse_class": lookup_rec["landuse_class"], "used_default": used_default, "subcatchment": subcatchment_entry, "subarea": subarea_entry, "notes": lookup_rec["notes"], } ) payload = { "ok": True, "mapping": "landuse_to_runoff_subarea", "input_csv": str(args.input), "lookup_csv": str(args.lookup), "counts": { "input_rows": len(input_rows), "mapped_rows": len(records), "used_default_rows": sum(1 for r in records if r["used_default"]), }, "unmatched_landuse_classes": sorted(unmatched), "sections": { "subcatchments": section_subcatchments, "subareas": section_subareas, }, "records": records, } save_json(args.output, payload) print( json.dumps( { "ok": True, "output_json": str(args.output), "mapped_rows": payload["counts"]["mapped_rows"], "used_default_rows": payload["counts"]["used_default_rows"], "unmatched_landuse_classes": payload["unmatched_landuse_classes"], }, indent=2, ) ) if __name__ == "__main__": main() -
merge_swmm_params.py 5.2 KB
#!/usr/bin/env python3 from __future__ import annotations import argparse import json from pathlib import Path from typing import Any def load_json(path: Path) -> Any: return json.loads(path.read_text(encoding="utf-8")) def save_json(path: Path, obj: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(obj, indent=2), encoding="utf-8") def index_by_id(entries: list[dict[str, Any]], *, section: str) -> dict[str, dict[str, Any]]: idx: dict[str, dict[str, Any]] = {} for entry in entries: raw_id = entry.get("id") if raw_id is None: raise ValueError(f"Missing 'id' in section '{section}'") subcatchment_id = str(raw_id).strip() if not subcatchment_id: raise ValueError(f"Blank 'id' in section '{section}'") if subcatchment_id in idx: raise ValueError(f"Duplicate id '{subcatchment_id}' in section '{section}'") idx[subcatchment_id] = entry return idx def main() -> None: ap = argparse.ArgumentParser( description="Merge land use and soil mapping outputs into one explicit JSON payload for future swmm-builder." ) ap.add_argument("--landuse-json", type=Path, default=None, help="Output JSON from landuse_to_swmm_params.py") ap.add_argument("--soil-json", type=Path, default=None, help="Output JSON from soil_to_greenampt.py") ap.add_argument("--output", type=Path, required=True, help="Output merged JSON path.") ap.add_argument("--strict", action="store_true", help="Fail if any subcatchment is missing one or more sections.") args = ap.parse_args() if args.landuse_json is None and args.soil_json is None: raise ValueError("At least one source is required: --landuse-json and/or --soil-json") landuse_subcatchments: dict[str, dict[str, Any]] = {} landuse_subareas: dict[str, dict[str, Any]] = {} soil_infiltration: dict[str, dict[str, Any]] = {} if args.landuse_json is not None: landuse_obj = load_json(args.landuse_json) sections = landuse_obj.get("sections", {}) landuse_subcatchments = index_by_id(sections.get("subcatchments", []), section="subcatchments") landuse_subareas = index_by_id(sections.get("subareas", []), section="subareas") if args.soil_json is not None: soil_obj = load_json(args.soil_json) sections = soil_obj.get("sections", {}) soil_infiltration = index_by_id(sections.get("infiltration", []), section="infiltration") all_ids = sorted(set(landuse_subcatchments) | set(landuse_subareas) | set(soil_infiltration)) by_subcatchment: list[dict[str, Any]] = [] incomplete_ids: list[dict[str, Any]] = [] for subcatchment_id in all_ids: rec: dict[str, Any] = {"id": subcatchment_id} missing: list[str] = [] subcatchment = landuse_subcatchments.get(subcatchment_id) if subcatchment is not None: rec["subcatchment"] = subcatchment else: missing.append("subcatchments") subarea = landuse_subareas.get(subcatchment_id) if subarea is not None: rec["subarea"] = subarea else: missing.append("subareas") infiltration = soil_infiltration.get(subcatchment_id) if infiltration is not None: rec["infiltration"] = infiltration else: missing.append("infiltration") if missing: rec["missing_sections"] = missing incomplete_ids.append({"id": subcatchment_id, "missing_sections": missing}) by_subcatchment.append(rec) if args.strict and incomplete_ids: raise ValueError( f"Found incomplete subcatchment mappings under --strict: {json.dumps(incomplete_ids, ensure_ascii=True)}" ) payload = { "ok": True, "mapping": "merged_swmm_params", "sources": { "landuse_json": str(args.landuse_json) if args.landuse_json is not None else None, "soil_json": str(args.soil_json) if args.soil_json is not None else None, }, "counts": { "subcatchment_count": len(all_ids), "subcatchments_with_subcatchment_section": len(landuse_subcatchments), "subcatchments_with_subarea_section": len(landuse_subareas), "subcatchments_with_infiltration_section": len(soil_infiltration), "incomplete_subcatchment_count": len(incomplete_ids), }, "incomplete_ids": incomplete_ids, "sections": { "subcatchments": [landuse_subcatchments[sid] for sid in sorted(landuse_subcatchments)], "subareas": [landuse_subareas[sid] for sid in sorted(landuse_subareas)], "infiltration": [soil_infiltration[sid] for sid in sorted(soil_infiltration)], }, "by_subcatchment": by_subcatchment, } save_json(args.output, payload) print( json.dumps( { "ok": True, "output_json": str(args.output), "subcatchment_count": payload["counts"]["subcatchment_count"], "incomplete_subcatchment_count": payload["counts"]["incomplete_subcatchment_count"], }, indent=2, ) ) if __name__ == "__main__": main() -
soil_to_greenampt.py 6.4 KB
#!/usr/bin/env python3 from __future__ import annotations import argparse import csv import json from pathlib import Path from typing import Any SKILL_DIR = Path(__file__).resolve().parents[1] DEFAULT_LOOKUP = SKILL_DIR / "references/soil_texture_to_greenampt.csv" def normalize_key(value: str) -> str: return " ".join(str(value).strip().lower().split()) def load_csv_rows(path: Path) -> list[dict[str, str]]: with path.open(newline="", encoding="utf-8") as f: rows = list(csv.DictReader(f)) if not rows: raise ValueError(f"CSV has no data rows: {path}") return rows def parse_float(value: Any, *, field: str, csv_path: Path, row_number: int) -> float: if value is None or str(value).strip() == "": raise ValueError(f"Missing numeric value for '{field}' at {csv_path}:{row_number}") try: return float(str(value).strip()) except ValueError as exc: raise ValueError(f"Invalid float for '{field}' at {csv_path}:{row_number}: {value}") from exc def save_json(path: Path, obj: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(obj, indent=2), encoding="utf-8") def build_lookup(lookup_rows: list[dict[str, str]], lookup_path: Path) -> tuple[dict[str, dict[str, Any]], dict[str, Any] | None]: lookup: dict[str, dict[str, Any]] = {} default_record: dict[str, Any] | None = None for i, row in enumerate(lookup_rows, start=2): raw_key = (row.get("texture") or "").strip() if not raw_key: raise ValueError(f"Missing 'texture' in lookup at {lookup_path}:{i}") key = normalize_key(raw_key) if key in lookup: raise ValueError(f"Duplicate lookup key '{raw_key}' in {lookup_path}") rec = { "texture": raw_key, "suction_mm": parse_float(row.get("suction_mm"), field="suction_mm", csv_path=lookup_path, row_number=i), "ksat_mm_per_hr": parse_float( row.get("ksat_mm_per_hr"), field="ksat_mm_per_hr", csv_path=lookup_path, row_number=i ), "imdmax": parse_float(row.get("imdmax"), field="imdmax", csv_path=lookup_path, row_number=i), "notes": (row.get("notes") or "").strip(), } lookup[key] = rec if raw_key in {"-", "DEFAULT", "default"}: default_record = rec if default_record is None: default_record = lookup.get(normalize_key("-")) or lookup.get(normalize_key("default")) return lookup, default_record def require_column(row: dict[str, str], col: str, *, csv_path: Path, row_number: int) -> str: if col not in row: raise ValueError(f"Missing required column '{col}' in {csv_path}") value = (row.get(col) or "").strip() if not value: raise ValueError(f"Missing value for '{col}' at {csv_path}:{row_number}") return value def main() -> None: ap = argparse.ArgumentParser( description="Map soil texture/type to SWMM Green-Ampt infiltration parameters (deterministic CSV -> JSON)." ) ap.add_argument("--input", type=Path, required=True, help="Input CSV with subcatchment_id and soil_texture columns.") ap.add_argument("--lookup", type=Path, default=DEFAULT_LOOKUP, help="Lookup CSV for soil texture mapping.") ap.add_argument("--output", type=Path, required=True, help="Output JSON path.") ap.add_argument("--subcatchment-column", default="subcatchment_id") ap.add_argument("--soil-column", default="soil_texture") ap.add_argument("--strict", action="store_true", help="Fail if any soil texture is missing from lookup.") args = ap.parse_args() input_rows = load_csv_rows(args.input) lookup_rows = load_csv_rows(args.lookup) lookup_map, default_record = build_lookup(lookup_rows, args.lookup) seen_ids: set[str] = set() unmatched: set[str] = set() records: list[dict[str, Any]] = [] section_infiltration: list[dict[str, Any]] = [] for i, row in enumerate(input_rows, start=2): subcatchment_id = require_column(row, args.subcatchment_column, csv_path=args.input, row_number=i) soil_raw = require_column(row, args.soil_column, csv_path=args.input, row_number=i) if subcatchment_id in seen_ids: raise ValueError(f"Duplicate subcatchment_id '{subcatchment_id}' in {args.input}") seen_ids.add(subcatchment_id) key = normalize_key(soil_raw) lookup_rec = lookup_map.get(key) used_default = False if lookup_rec is None: if args.strict or default_record is None: raise ValueError( f"Unmapped soil texture '{soil_raw}' at {args.input}:{i}. " "Add a lookup row or run without --strict and ensure '-' or DEFAULT exists." ) lookup_rec = default_record used_default = True unmatched.add(soil_raw) infiltration_entry = { "id": subcatchment_id, "suction_mm": lookup_rec["suction_mm"], "ksat_mm_per_hr": lookup_rec["ksat_mm_per_hr"], "imdmax": lookup_rec["imdmax"], } section_infiltration.append(infiltration_entry) records.append( { "subcatchment_id": subcatchment_id, "input_soil_texture": soil_raw, "lookup_texture": lookup_rec["texture"], "used_default": used_default, "infiltration": infiltration_entry, "notes": lookup_rec["notes"], } ) payload = { "ok": True, "mapping": "soil_to_green_ampt", "input_csv": str(args.input), "lookup_csv": str(args.lookup), "counts": { "input_rows": len(input_rows), "mapped_rows": len(records), "used_default_rows": sum(1 for r in records if r["used_default"]), }, "unmatched_soil_textures": sorted(unmatched), "sections": { "infiltration": section_infiltration, }, "records": records, } save_json(args.output, payload) print( json.dumps( { "ok": True, "output_json": str(args.output), "mapped_rows": payload["counts"]["mapped_rows"], "used_default_rows": payload["counts"]["used_default_rows"], "unmatched_soil_textures": payload["unmatched_soil_textures"], }, indent=2, ) ) if __name__ == "__main__": main()
-
-
SKILL.md 4.1 KB
--- name: swmm-params description: Deterministic mapping from land use and soil texture to SWMM runoff/subarea and Green-Ampt infiltration parameters. Use when generating first-pass subcatchment parameter tables for swmm-builder. --- # SWMM Params (MVP mapping layer) Part of [Agentic SWMM](https://github.com/Zhonghao1995/agentic-swmm-workflow) — install the project first for the executable toolchain (aiswmm CLI, SWMM solver, MCP servers). ## What this skill provides - Transparent CSV-to-JSON mapping for: - land use class -> SWMM `[SUBCATCHMENTS]` + `[SUBAREAS]` defaults - soil texture/type -> SWMM `[INFILTRATION]` (Green-Ampt) defaults - Deterministic, auditable outputs with explicit fallback usage and unmatched-key reporting. - Optional merge step that emits one builder-ready JSON artifact. ## Scripts - `scripts/landuse_to_swmm_params.py` - maps `subcatchment_id + landuse_class` to runoff/subarea parameters - `scripts/soil_to_greenampt.py` - maps `subcatchment_id + soil_texture` to Green-Ampt infiltration parameters - `scripts/merge_swmm_params.py` - merges outputs from the two mapping scripts into one JSON package for future `swmm-builder` ## Default lookup tables By default, scripts read bundled lookup CSVs: - `skills/swmm-params/references/landuse_class_to_subcatch_params.csv` - `skills/swmm-params/references/soil_texture_to_greenampt.csv` You can override lookup paths with CLI flags. ## Minimal input format Land use input CSV: - required columns: `subcatchment_id`, `landuse_class` Soil input CSV: - required columns: `subcatchment_id`, `soil_texture` Example files are provided under `examples/`. ## Outputs Each mapper writes explicit JSON containing: - `records` (row-level audit trail) - `sections` (SWMM-oriented lists keyed by subcatchment) - `unmatched_*` lists (rows that used fallback) - `counts` summary The merge script writes: - `sections` (`subcatchments`, `subareas`, `infiltration`) - `by_subcatchment` (combined record per subcatchment ID) - `incomplete_ids` (IDs missing one or more sections) ## CLI flags All three scripts share these optional flags: - `--strict` — fail instead of using the `DEFAULT` fallback row when an input key is missing from the lookup table. Useful for auditable production runs where silent fallback would mask a data gap. `landuse_to_swmm_params.py` also accepts: - `--subcatchment-column <col>` — override the CSV column used as the subcatchment ID (default: `subcatchment_id`). - `--landuse-column <col>` — override the CSV column used as the land use class (default: `landuse_class`). `soil_to_greenampt.py` also accepts: - `--subcatchment-column <col>` — override the CSV column used as the subcatchment ID (default: `subcatchment_id`). - `--soil-column <col>` — override the CSV column used as the soil texture/type (default: `soil_texture`). ## MCP MCP wrapper location: - `mcp/swmm-params/server.js` Exposed tools: - `map_landuse` (`inputCsvPath`, optional `lookupCsvPath`, `outputPath`) - `map_soil` (`inputCsvPath`, optional `lookupCsvPath`, `outputPath`) - `merge_params` (`landuseJsonPath`, `soilJsonPath`, `outputPath`) Quick start: ```bash npm --prefix mcp/swmm-params install npm --prefix mcp/swmm-params run start ``` ## Known limitations - Lookup mapping is key-based only (no spatial interpolation or fuzzy matching). - A fallback row is expected in lookup tables (`DEFAULT` for land use, `-` or `DEFAULT` for soil). - No unit conversion or calibration logic is included here. - This skill only maps parameters; it does not write a full SWMM `.inp`. ## Example commands ```bash python3 skills/swmm-params/scripts/landuse_to_swmm_params.py \ --input skills/swmm-params/examples/landuse_input.csv \ --output runs/swmm-params/example_landuse.json ``` ```bash python3 skills/swmm-params/scripts/soil_to_greenampt.py \ --input skills/swmm-params/examples/soil_input.csv \ --output runs/swmm-params/example_soil.json ``` ```bash python3 skills/swmm-params/scripts/merge_swmm_params.py \ --landuse-json runs/swmm-params/example_landuse.json \ --soil-json runs/swmm-params/example_soil.json \ --output runs/swmm-params/example_builder_params.json ```
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.