Use when the user asks to run a competitive positioning analysis, rank a subject property against market comparables using MCDA, determine how competitive a listing is, calculate what rent reduction is needed to achieve Top 3 positioning, analyze a CoStar or broker package for competitive ranking, or generate a relative valuation report. Trigger phrases include: "relative valuation", "competitive positioning", "rank this property", "how competitive is this listing", "run relative valuation", "/mcda-lease-comparison".
How this skill is triggered — by the user, by Claude, or both
Slash command
/mcda-lease-comparison:mcda-lease-comparisonThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Automated PDF → JSON → Python → Report workflow for Multi-Criteria Decision Analysis (MCDA)**
inputs/schema_template.jsonreferences/RANKING_METHODOLOGY.mdreferences/SCHEMA.mdreferences/WEIGHTS_CONFIG_GUIDE.mdreferences/pdf_style.cssscripts/__pycache__/statistics_module.cpython-313.pycscripts/calculate_distances.pyscripts/relative_valuation_calculator.pyscripts/statistics_module.pyscripts/weights_config.jsonscripts/weights_config_schema.jsonscripts/weights_loader.pyAutomated PDF → JSON → Python → Report workflow for Multi-Criteria Decision Analysis (MCDA)
You are executing the /mcda-lease-comparison skill. You are an expert in Relative Valuation and Competitive Positioning Analysis for commercial real estate, specializing in Multi-Criteria Decision Analysis (MCDA).
Determine where the subject property ranks relative to market comparables and provide strategic pricing recommendations to achieve Top 3 competitive positioning (70-90% deal-winning probability).
Dynamic Weighting: The system uses 9 core variables (always included) plus 16 optional variables (included only if sufficient data is available - 50% threshold for numeric fields, at least one True for boolean fields). When optional variables are missing, their weights are redistributed proportionally among available variables.
| Variable | Weight | Type | Rationale |
|---|---|---|---|
| Net Asking Rent | 11% | Core | Most critical - Direct impact on tenant budget |
| Parking Ratio | 10% | Core | Second most critical - Often deal-breaker for industrial/office |
| TMI | 9% | Core | Affects total occupancy cost |
| Clear Height | 7% | Core | Critical for industrial operations |
| % Office Space | 7% | Core | Mix affects usability |
| Distance | 7% | Core | Location convenience |
| Area Difference | 7% | Core | Size match to tenant needs |
| Building Age | 4% | Core | Replaces Year Built - more intuitive condition proxy |
| Class | 5% | Core | A/B/C quality tier |
| Bay Depth | 5% | Optional | Racking efficiency, trailer access |
| Shipping Doors (TL) | 4% | Optional | Truck-level loading capacity |
| Lot Size (Acres) | 4% | Optional | Expansion potential, outdoor storage |
| Shipping Doors (DI) | 3% | Optional | Drive-in door access |
| Power | 3% | Optional | Electrical capacity (amps) |
| HVAC Coverage | 3% | Optional | Climate control for products/workers |
| Sprinkler Type | 3% | Optional | ESFR = insurance savings + high-piled storage |
| Trailer Parking | 2% | Optional | Trailer storage availability |
| Rail Access | 2% | Optional | Deal-breaker for bulk commodities |
| Crane | 2% | Optional | Heavy manufacturing essential |
| Occupancy Status | 0% | Optional | Vacant = immediate occupancy (low priority) |
| Grade Level Doors | 2% | Optional | Courier vans, small truck access |
| Days on Market | 2% | Optional | Landlord motivation indicator |
| Zoning | 2% | Optional | Permitted use restrictions |
| Secure Shipping | 0% | Optional | Secure loading areas (rarely available) |
| Excess Land | 0% | Optional | Expansion/outdoor storage (rarely available) |
| Rank | Status | Win Probability | Action Required |
|---|---|---|---|
| #1-3 | ✅ Highly Competitive | 70-90% | Maintain position |
| #4-10 | ⚠️ Moderately Competitive | 50-70% | Consider adjustments |
| #11+ | ❌ Not Competitive | <50% | Urgent price reduction needed |
The "Top 3 Rule": Must be Rank #1, #2, or #3 to win deals consistently.
Ascending (Lower = Rank 1): Net Asking Rent, TMI, Distance, Class (A=1 beats C=3), Area Difference
Descending (Higher = Rank 1): Clear Height, Parking Ratio, Year Built, % Office Space
Tie handling: Average rank method — (5 + 6 + 7) / 3 = 6.0 for a three-way tie at ranks 5-7.
| Tier | Strategy |
|---|---|
| Rank #1-3 | Defend pricing, highlight value proposition, weekly comp monitoring |
| Rank #4-10 | Calculate exact pricing to reach Rank #3, evaluate TI/free rent alternatives |
| Rank #11+ | Urgent repositioning — immediate rent reduction, consider below-market deal to secure tenant |
Calculate rent/TMI reduction to achieve Rank #3:
Formula: Points gap = Subject Score − Rank #3 Score → convert to required rent/TMI changes using variable weights.
TI allowance (+$10-20/SF effective rent reduction), free rent (3-6 months), TMI escalation caps (2-3%/year), lease flexibility (termination/expansion rights).
To Landlord:
To Tenant Rep:
Plugin root — run in Bash:
echo "${CLAUDE_PLUGIN_ROOT}"
If empty, find it:
find ~ -path "*/mcda-lease-comparison/skills/mcda-lease-comparison/SKILL.md" -maxdepth 8 2>/dev/null | head -1 | sed 's|/skills/mcda-lease-comparison/SKILL.md||'
If still empty, use: /home/reggiechan/021-CRE-150/plugins/mcda-lease-comparison
Workspace — current working directory. Create Reports/ if absent:
mkdir -p "$(pwd)/Reports"
Timestamp — run TZ=America/Toronto date +%Y-%m-%d_%H%M%S.
From the resolved plugin root, construct absolute paths:
SCRIPTS_DIR = <plugin_root>/skills/mcda-lease-comparison/scriptsREFERENCES_DIR = <plugin_root>/skills/mcda-lease-comparison/referencesINPUTS_DIR = <plugin_root>/skills/mcda-lease-comparison/inputsCALCULATOR = <SCRIPTS_DIR>/relative_valuation_calculator.pyDISTANCE_CALCULATOR = <SCRIPTS_DIR>/calculate_distances.pyPDF_STYLE = <REFERENCES_DIR>/pdf_style.cssSCHEMA_TEMPLATE = <INPUTS_DIR>/schema_template.jsonVerify the calculator exists:
ls "<CALCULATOR>"
If missing, report the error and stop.
Parse user arguments:
--full flag (show all competitors vs. top 10 only)--stats flag (append statistical analysis)--persona <name> (default, 3pl, manufacturing, office) — default: defaultUse the Agent tool to dispatch a subagent. This offloads vision reads, data extraction, and report generation to a fresh 200k context window, keeping the primary context clean.
If the Agent tool is unavailable, execute Steps A through G directly in the current context.
Substitute the resolved values for {{ }} placeholders, then call the Agent tool with description: "Relative valuation competitive positioning analysis" and the prompt below:
--- BEGIN SUBAGENT PROMPT ---
You are a relative valuation competitive positioning analysis agent for commercial real estate. Your job is to execute the full analysis pipeline below and return a structured result. Do not ask questions — execute all steps and report results.
Core variables (always included):
| Field | Encoding | Rank Direction |
|---|---|---|
net_asking_rent | $/SF/year (decimal) | Ascending (lower = better) |
parking_ratio | spaces per 1,000 SF (decimal) | Descending (higher = better) |
tmi | $/SF/year (decimal) | Ascending (lower = better) |
clear_height_ft | feet (decimal) | Descending (higher = better) |
pct_office_space | DECIMAL 0–1 (e.g., 0.11 not 11.0) | Descending (higher = better) |
distance_km | km from subject (0.0 for subject) | Ascending (lower = better) |
area_difference | abs(available_sf − subject_sf) / subject_sf | Ascending (lower = better) |
year_built / building_age_years | year integer / age auto-calculated | Descending year = better |
class | 1=A, 2=B, 3=C | Ascending (1=A is best) |
Optional variables (included if ≥50% of properties have data):
| Field | Encoding | Rank Direction |
|---|---|---|
bay_depth_ft | feet (decimal) | Descending |
shipping_doors_tl | integer count (truck-level) | Descending |
lot_size_acres | acres (decimal) | Descending |
shipping_doors_di | integer count (drive-in) | Descending |
power_amps | amps (integer) | Descending |
hvac_coverage | 1=Full, 2=Partial, 3=None | Ascending (1 is best) |
sprinkler_type | 1=ESFR, 2=Standard, 3=None | Ascending (1 is best) |
trailer_parking | boolean true/false | Descending (true is better) |
rail_access | boolean true/false | Descending |
crane | boolean true/false | Descending |
occupancy_status | 1=Vacant, 2=Tenant occupied | Ascending (1=Vacant is better) |
grade_level_doors | integer count | Descending |
days_on_market | integer | Ascending (lower = better) |
zoning | string (e.g., "M1") | No ranking (filter only) |
secure_shipping | boolean | Descending |
excess_land | boolean | Descending |
Address format (REQUIRED for distance API):
"Street Address, City, Province PostalCode, Country"
"2550 Stanfield Rd, Mississauga, ON L4Y 1S2, Canada"
unit field, NOT appended to addresspct_office_space conversion (CRITICAL):
pct_office_space = (100 − warehouse_pct) / 100Shipping doors: PDF format "X TL Y DI"
shipping_doors_tl, Y → shipping_doors_diBay depth: Parse from "Bay Size" field (e.g., "55 x 52" → take first number: 55.0)
Lot size: Convert to acres (from sq ft: ÷ 43,560; from hectares: × 2.471)
Subject property: Must have is_subject: true, distance_km: 0.0
All comparables: Must have is_subject: false
| Persona | Key emphasis |
|---|---|
default | Balanced — general industrial |
3pl | Bay depth 7%, clear height 10%, shipping doors TL 6%, trailer parking 4% |
manufacturing | Clear height 9%, power 5%, crane 5%, rail access 4%, bay depth 6% |
office | Office space 12%, parking 12%, rent 13%, HVAC 6%, class 8%, distance 10% |
| Rank | Status | Win Probability |
|---|---|---|
| #1-3 | Highly Competitive | 70-90% |
| #4-10 | Moderately Competitive | 50-70% |
| #11+ | Not Competitive | <50% |
If the input file is a JSON file (.json), skip to Step C.
Read the PDF using vision. For large PDFs (>5 pages), read in batches of 20 pages to stay within context limits.
Extract all properties — subject + comparables — using the extraction rules above. Capture all available optional fields in addition to the 9 core fields.
Identify the subject property: it is typically highlighted, listed first, or described as the "subject" or "availability" being analyzed. Set is_subject: true, distance_km: 0.0.
Build the input JSON following this structure:
{
"analysis_date": "YYYY-MM-DD",
"market": "Market Name - Property Type",
"subject_property": {
"address": "Complete geocodable address",
"unit": "",
"year_built": 2005,
"clear_height_ft": 32.0,
"pct_office_space": 0.11,
"parking_ratio": 2.0,
"available_sf": 50000,
"distance_km": 0.0,
"net_asking_rent": 9.50,
"tmi": 4.75,
"class": 2,
"is_subject": true,
"landlord": ""
},
"comparables": [
{ /* same structure as subject_property with is_subject: false */ }
],
"filters": {},
"weights": {}
}
Save to: {{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_input.json
Verify the JSON before proceeding — the calculator cannot self-correct if the input is malformed. Run:
python3 - << 'PYEOF'
import json, sys
with open("{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_input.json") as f:
data = json.load(f)
errors = []
if "subject_property" not in data:
errors.append("Missing 'subject_property'")
if not data.get("comparables"):
errors.append("Missing or empty 'comparables' array")
if "analysis_date" not in data:
errors.append("Missing 'analysis_date'")
sp = data.get("subject_property", {})
if not sp.get("is_subject"):
errors.append("subject_property.is_subject must be true")
if sp.get("distance_km", -1) != 0.0:
errors.append("subject_property.distance_km must be 0.0")
# Catch common pct_office_space mistake
for prop in [sp] + data.get("comparables", []):
pct = prop.get("pct_office_space", 0)
if pct > 1.0:
errors.append(f"{prop.get('address','unknown')}: pct_office_space={pct} — must be decimal (0.11 not 11.0)")
if errors:
print("VALIDATION FAILED:")
for e in errors:
print(f" - {e}")
sys.exit(1)
else:
print(f"Validation passed — {len(data.get('comparables', []))} comparables, subject: {sp.get('address','?')}")
PYEOF
If validation fails, correct the JSON errors and re-validate before proceeding to Step D.
if [ -z "$DISTANCEMATRIX_API_KEY" ]; then
echo "WARNING: DISTANCEMATRIX_API_KEY not set. Skipping distance calculations."
echo "Set API key: export DISTANCEMATRIX_API_KEY=your_key_here"
else
python3 "{{ DISTANCE_CALCULATOR }}" \
--input "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_input.json" \
--output "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_input.json" \
--verbose
fi
Note: cd to the scripts directory first — the calculator uses sibling imports (statistics_module, weights_loader) that resolve relative to CWD.
Build the command based on flags:
cd "{{ SCRIPTS_DIR }}" && python3 relative_valuation_calculator.py \
--input "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_input.json" \
--output "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_report.md" \
--output-json "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_output.json" \
{{ FLAGS }}
Where {{ FLAGS }} may include --full, --stats, --persona 3pl, etc.
Capture stdout/stderr. If the command exits non-zero, report the error and stop.
Read the output JSON from {{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_output.json and the markdown report. Identify:
--stats was used) — R², regression coefficients, outliersAppend the executive summary to the markdown report OR present inline. Format:
## EXECUTIVE SUMMARY: COMPETITIVE POSITIONING
**Subject Property**: [Address]
**Analysis Date**: [Date]
**Market**: [Market Name]
### Current Position
- **Rank**: #X out of Y properties
- **Score**: XX.XX (lower is better)
- **Status**: [Highly/Moderately/Not] Competitive
- **Deal-Winning Probability**: XX-XX%
### Key Findings
1. [Top strength]
2. [Key weakness]
3. [Critical insight]
### Strategic Recommendation
[Action-oriented recommendation for the rank tier]
### Next Steps
1. [Immediate action]
2. [Secondary action]
3. [Long-term]
pandoc "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_report.md" \
-o "{{ WORKSPACE }}/Reports/{{ TIMESTAMP }}_mcda_lease_comparison_report.pdf" \
--css "{{ PDF_STYLE }}" \
--pdf-engine=wkhtmltopdf \
--pdf-engine-opt=--orientation --pdf-engine-opt=Landscape \
--pdf-engine-opt=--margin-top --pdf-engine-opt=5mm \
--pdf-engine-opt=--margin-bottom --pdf-engine-opt=5mm \
--pdf-engine-opt=--margin-left --pdf-engine-opt=8mm \
--pdf-engine-opt=--margin-right --pdf-engine-opt=8mm
Landscape orientation is required — the competitor table has 13 columns. If wkhtmltopdf is unavailable, note in the return block and skip.
Return ONLY this block — no other commentary:
ANALYSIS_RESULT subject: rank: #X out of Y properties score: tier: <Highly Competitive / Moderately Competitive / Not Competitive> win_probability: <XX-XX%> report: Reports/.md json: Reports/.json pdf: Reports/.pdf (or "not generated — wkhtmltopdf unavailable") errors: <none, or description of issues>
--- END SUBAGENT PROMPT ---
When the subagent returns, parse the ANALYSIS_RESULT block and report:
✅ Relative valuation complete
📍 Subject: {subject}
🏆 Rank: {rank}
📊 Score: {score} (lower is better)
🎯 Status: {tier} ({win_probability})
📄 Report: {report}
📋 JSON: {json}
📑 PDF: {pdf}
If errors is not "none":
⚠️ Issues encountered: {errors}
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub reggiechan74/021-cre-150 --plugin mcda-lease-comparison