mcda-suitability-analysis
Always invoke for spatial suitability, site selection, AHP, criteria weights, or weighted-overlay work, including audits of inconsistent pairwise judgments and requests for only a final map. Covers consistency, standardization, constraints, ranked surfaces, shortlists, and sensit
Install
npx skills add https://github.com/muend/geoai-skills/tree/main/skills/mcda-suitability-analysis
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install muend-geoai-skills@llmmart
git clone https://github.com/muend/geoai-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole muend/geoai-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
MCDA & Suitability Analysis
Purpose: produce suitability maps whose weights, scales, and assumptions are explicit, consistent, and stress-tested. A suitability map without a sensitivity analysis is an opinion with a legend.
Workflow
- Structure: goal → criteria (factors) → constraints. Constraints are binary masks (legal exclusions, water bodies, slope > threshold) applied at the END by multiplication; factors are continuous and weighted. Keep them apart — encoding a constraint as a heavily-weighted factor is a classic error that lets forbidden areas score "acceptable".
- Criteria layers: each factor as a raster on a COMMON grid (same CRS, extent, cell size, snap). Resample categorical layers with nearest, continuous with bilinear; document each.
- Standardization to a common suitability scale (0-1 or 0-255):
- Linear min-max for monotonic "more is better/worse".
- Fuzzy membership (sigmoid/linear with control points) when suitability saturates — justify control points from domain knowledge.
- Categorical layers: explicit reclass table, shown to the user. Direction check: confirm for EVERY layer whether high raw value means high or low suitability (slope: low=good; distance-to-road: usually low=good). Direction bugs survive to the final map invisibly.
- Weights (AHP below, or direct/ranked methods with rationale).
- Aggregation: weighted linear combination (WLC) default; OWA when the decision-maker's risk attitude (AND-like vs OR-like) matters.
- Constraint mask multiply; classify the result (equal interval or quantiles — say which and why); sensitivity analysis; validate against known good/bad sites if any exist.
AHP with consistency enforcement
Pairwise comparisons on Saaty's 1-9 scale; weights from the principal
eigenvector; consistency ratio (CR) must be < 0.10 or the matrix goes back
for revision. Run scripts/ahp_weights.py to compute weights + CR from a
reciprocal comparison matrix (it validates reciprocity and reports λ_max).
Practices: elicit comparisons pair by pair with verbal anchors ("moderately more important" = 3); with multiple experts, aggregate judgments by geometric mean BEFORE computing weights; report the full matrix, weights, λ_max and CR in the deliverable. If CR ≥ 0.10, identify the most inconsistent triad and ask the expert to revisit it — do not silently massage numbers.
Aggregation
suit = np.zeros_like(factors[0], dtype="float32")
for w_i, f in zip(weights, factors): # factors already standardized 0-1
suit += w_i * f
suit *= constraint_mask # binary 0/1, applied last
OWA variant: sort factor values per cell and apply order weights — full AND (min) to full OR (max) continuum; use when stakeholders disagree on risk tolerance and show 2-3 scenarios.
Sensitivity analysis — mandatory
A result that flips with a small weight change is not a result:
- One-at-a-time: perturb each weight ±20% (renormalize), recompute, report % of area changing suitability class and a stability map (cells that never change class across perturbations).
- Scenario: 2-3 alternative weight sets from different stakeholder priorities; present side-by-side.
- If a Monte Carlo budget exists: sample weights from Dirichlet around the AHP vector; per-cell probability of "highly suitable" is a far stronger product than a single map.
Deliverable standard
Suitability map (classified + continuous), constraint mask map, weights
table with CR, standardization functions per criterion (with direction),
sensitivity/stability summary, and limitations paragraph (data currency,
resolution, criteria omitted). Route cartography to cartography-geoviz;
network-access criteria come from network-accessibility-analysis.
Pitfalls checklist
- Direction inversion on a criterion (the silent killer — double-check distance-based factors).
- Mixing resolutions without declaring the resampling rule.
- CR ignored or unreported.
- Constraints blended as weights → forbidden zones scored medium.
- Classifying with quantiles then reading them as absolute suitability.
- No sensitivity analysis; single map presented as truth.
Execution contract
- Workflow: define decision and stakeholders; separate constraints from factors; standardize criteria; elicit and validate weights; aggregate; test sensitivity; communicate uncertainty.
- Decision rules: use MCDA for transparent criteria-ranked surfaces, network analysis for route-constrained access, and optimization when discrete placement or capacity decisions dominate.
- Verification protocol: check criterion direction and alignment, AHP consistency, constraint enforcement, weight and threshold perturbations, and stable-versus-fragile areas.
- Failure modes: reject the model when criteria double-count the same construct, weights lack provenance, constraints leak into compensation, or rankings collapse under plausible perturbations.
- Deliverables: continuous and classified suitability maps, constraints, criteria transformations, weights and consistency ratio, sensitivity results, and limitations.
- Source freshness: consult the authoritative source registry before applying methods or implementation APIs and record the checked date.
Files (geoai-skills)
-
agents
-
openai.yaml 229 B
interface: display_name: "MCDA Suitability Analysis" short_description: "Build transparent spatial suitability models" default_prompt: "Use $mcda-suitability-analysis to create and stress-test this site suitability model."
-
-
references
-
authoritative-sources.md 867 B
# Authoritative sources - Last verified: 2026-07-19 - Review cadence: every 12 months - Refresh triggers: weighting method change, decision-policy change, or implementation dependency release ## Canonical sources - [Saaty, The Analytic Hierarchy Process](https://doi.org/10.1016/0270-0255(87)90473-8) — primary AHP scale and consistency foundation. - [JRC sensitivity analysis resources](https://joint-research-centre.ec.europa.eu/sensitivity-analysis-samo_en) — European Commission guidance and tools for sensitivity analysis. - [NumPy linear algebra documentation](https://numpy.org/doc/stable/reference/routines.linalg.html) — implementation behavior for the bundled weight calculator. Document stakeholder provenance and the exact pairwise matrix. A mathematically consistent matrix does not make criteria, preferences, or policy assumptions objective.
-
-
scripts
-
ahp_weights.py 2.8 KB
"""AHP weights + consistency ratio from a pairwise comparison matrix. Run: python ahp_weights.py matrix.csv (n x n CSV, no header) Import: from ahp_weights import ahp_weights """ from __future__ import annotations import argparse from pathlib import Path import numpy as np # Saaty's random consistency index by matrix order RI = {1: 0.0, 2: 0.0, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45, 10: 1.49} def ahp_weights(A: np.ndarray) -> tuple[np.ndarray, float]: """Principal-eigenvector AHP weights and consistency ratio. Args: A: n x n positive reciprocal pairwise comparison matrix (Saaty 1-9 scale). Returns: (weights summing to 1, CR). CR >= 0.10 means judgments are too inconsistent to use — revise the most discordant comparison. Raises: ValueError: If the matrix is not finite, positive, square, reciprocal, or supported by the bundled random consistency index table. """ A = np.asarray(A, dtype=float) if A.ndim != 2 or A.shape[0] != A.shape[1]: raise ValueError("AHP matrix must be a square 2D array.") n = A.shape[0] if n not in RI: raise ValueError(f"AHP matrix order {n} is unsupported; expected 1–10.") if not np.isfinite(A).all(): raise ValueError("AHP matrix must contain only finite values.") if np.any(A <= 0): raise ValueError("AHP matrix values must be strictly positive.") if not np.allclose(np.diag(A), 1.0, rtol=0.0, atol=1e-8): raise ValueError("AHP matrix diagonal must contain only 1 values.") if not np.allclose(A * A.T, np.ones_like(A), rtol=1e-6): raise ValueError("Matrix is not reciprocal (A[i,j] must equal 1/A[j,i]).") eigvals, eigvecs = np.linalg.eig(A) k = int(np.argmax(eigvals.real)) if abs(eigvals[k].imag) > 1e-8: raise ValueError("Principal eigenvalue is unexpectedly complex.") w = eigvecs[:, k].real if w.sum() < 0: w = -w if np.any(w <= 0): raise ValueError("Principal eigenvector did not produce positive weights.") w /= w.sum() lam_max = eigvals[k].real ci = max(0.0, (lam_max - n) / (n - 1)) if n > 2 else 0.0 cr = ci / RI[n] if RI.get(n, 0) > 0 else 0.0 return w, float(cr) def main() -> None: """Read a headerless CSV matrix and print weights plus consistency.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("matrix", type=Path) args = parser.parse_args() matrix = np.loadtxt(args.matrix, delimiter=",") weights, cr = ahp_weights(matrix) for i, wi in enumerate(weights): print(f"criterion_{i + 1}: {wi:.4f}") verdict = "OK" if cr < 0.10 else "REVISE — inconsistent judgments" print(f"CR = {cr:.4f} [{verdict}]") if __name__ == "__main__": main()
-
-
SKILL.md 5.8 KB
--- name: mcda-suitability-analysis description: >- Always invoke for spatial suitability, site selection, AHP, criteria weights, or weighted-overlay work, including audits of inconsistent pairwise judgments and requests for only a final map. Covers consistency, standardization, constraints, ranked surfaces, shortlists, and sensitivity. Route travel-time placement and location-allocation to network-accessibility-analysis. license: MIT metadata: author: Muhammed Enes Duran --- # MCDA & Suitability Analysis Purpose: produce suitability maps whose weights, scales, and assumptions are explicit, consistent, and stress-tested. A suitability map without a sensitivity analysis is an opinion with a legend. ## Workflow 1. **Structure**: goal → criteria (factors) → constraints. Constraints are binary masks (legal exclusions, water bodies, slope > threshold) applied at the END by multiplication; factors are continuous and weighted. Keep them apart — encoding a constraint as a heavily-weighted factor is a classic error that lets forbidden areas score "acceptable". 2. **Criteria layers**: each factor as a raster on a COMMON grid (same CRS, extent, cell size, snap). Resample categorical layers with nearest, continuous with bilinear; document each. 3. **Standardization** to a common suitability scale (0-1 or 0-255): - Linear min-max for monotonic "more is better/worse". - Fuzzy membership (sigmoid/linear with control points) when suitability saturates — justify control points from domain knowledge. - Categorical layers: explicit reclass table, shown to the user. Direction check: confirm for EVERY layer whether high raw value means high or low suitability (slope: low=good; distance-to-road: usually low=good). Direction bugs survive to the final map invisibly. 4. **Weights** (AHP below, or direct/ranked methods with rationale). 5. **Aggregation**: weighted linear combination (WLC) default; OWA when the decision-maker's risk attitude (AND-like vs OR-like) matters. 6. **Constraint mask** multiply; classify the result (equal interval or quantiles — say which and why); **sensitivity analysis**; validate against known good/bad sites if any exist. ## AHP with consistency enforcement Pairwise comparisons on Saaty's 1-9 scale; weights from the principal eigenvector; consistency ratio (CR) must be < 0.10 or the matrix goes back for revision. Run `scripts/ahp_weights.py` to compute weights + CR from a reciprocal comparison matrix (it validates reciprocity and reports λ_max). Practices: elicit comparisons pair by pair with verbal anchors ("moderately more important" = 3); with multiple experts, aggregate judgments by geometric mean BEFORE computing weights; report the full matrix, weights, λ_max and CR in the deliverable. If CR ≥ 0.10, identify the most inconsistent triad and ask the expert to revisit it — do not silently massage numbers. ## Aggregation ```python suit = np.zeros_like(factors[0], dtype="float32") for w_i, f in zip(weights, factors): # factors already standardized 0-1 suit += w_i * f suit *= constraint_mask # binary 0/1, applied last ``` OWA variant: sort factor values per cell and apply order weights — full AND (min) to full OR (max) continuum; use when stakeholders disagree on risk tolerance and show 2-3 scenarios. ## Sensitivity analysis — mandatory A result that flips with a small weight change is not a result: - **One-at-a-time**: perturb each weight ±20% (renormalize), recompute, report % of area changing suitability class and a stability map (cells that never change class across perturbations). - **Scenario**: 2-3 alternative weight sets from different stakeholder priorities; present side-by-side. - If a Monte Carlo budget exists: sample weights from Dirichlet around the AHP vector; per-cell probability of "highly suitable" is a far stronger product than a single map. ## Deliverable standard Suitability map (classified + continuous), constraint mask map, weights table with CR, standardization functions per criterion (with direction), sensitivity/stability summary, and limitations paragraph (data currency, resolution, criteria omitted). Route cartography to `cartography-geoviz`; network-access criteria come from `network-accessibility-analysis`. ## Pitfalls checklist - Direction inversion on a criterion (the silent killer — double-check distance-based factors). - Mixing resolutions without declaring the resampling rule. - CR ignored or unreported. - Constraints blended as weights → forbidden zones scored medium. - Classifying with quantiles then reading them as absolute suitability. - No sensitivity analysis; single map presented as truth. ## Execution contract - **Workflow:** define decision and stakeholders; separate constraints from factors; standardize criteria; elicit and validate weights; aggregate; test sensitivity; communicate uncertainty. - **Decision rules:** use MCDA for transparent criteria-ranked surfaces, network analysis for route-constrained access, and optimization when discrete placement or capacity decisions dominate. - **Verification protocol:** check criterion direction and alignment, AHP consistency, constraint enforcement, weight and threshold perturbations, and stable-versus-fragile areas. - **Failure modes:** reject the model when criteria double-count the same construct, weights lack provenance, constraints leak into compensation, or rankings collapse under plausible perturbations. - **Deliverables:** continuous and classified suitability maps, constraints, criteria transformations, weights and consistency ratio, sensitivity results, and limitations. - **Source freshness:** consult [the authoritative source registry](references/authoritative-sources.md) before applying methods or implementation APIs and record the checked date.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.