"""Compliance helpers for CAD/IFC-driven MCP server.""" from __future__ import annotations from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Optional import json import yaml @dataclass class ToolResult: title: str summary: str artifacts: Dict[str, str] class IfcMetricsTool: """Parse simplified IFC-like JSON to extract metrics used by rules.""" def extract_metrics(self, path: str) -> ToolResult: data = json.loads(Path(path).read_text()) metrics = { "height_m": float(data.get("height_m", 0)), "footprint_area_m2": float(data.get("footprint_area_m2", 0)), "num_floors": int(data.get("num_floors", 0)), } summary = f"Height: {metrics['height_m']} m, area: {metrics['footprint_area_m2']} m^2, floors: {metrics['num_floors']}" return ToolResult(title="IFC metrics", summary=summary, artifacts={"metrics": json.dumps(metrics, indent=2)}) class RuleEvaluator: def evaluate(self, metrics: Dict[str, float], rules_path: str) -> ToolResult: rule_pack = yaml.safe_load(Path(rules_path).read_text()) results: List[Dict[str, object]] = [] passed = True for rule in rule_pack.get("rules", []): field = rule["field"] op = rule.get("op", "<=") threshold = rule.get("value", 0) observed = metrics.get(field, 0) if op == "<=": ok = observed <= threshold elif op == ">=": ok = observed >= threshold else: ok = False results.append({"rule": rule.get("name", field), "pass": ok, "observed": observed, "threshold": threshold}) passed = passed and ok summary = "All rules passed" if passed else "Rule violations detected" return ToolResult( title="Compliance evaluation", summary=summary, artifacts={"results": json.dumps(results, indent=2), "passed": json.dumps(passed)}, ) class GeminiDocTool: def draft_prompt(self, description: str, missing_items: List[str]) -> ToolResult: checklist = "\n".join([f"- {item}" for item in missing_items]) prompt = ( "You are a permitting reviewer. Summarize the project description and list missing artifacts.\n" f"Description: {description}\nMissing items:\n{checklist}" ) return ToolResult( title="Gemini document prompt", summary="Prompt prepared for Gemini-powered review.", artifacts={"prompt": prompt}, ) class StandardCatalogTool: """Search ISO/IEC/EN standard snippets for quick lookups.""" def __init__(self, catalog_path: Optional[str] = None) -> None: base = Path(__file__).resolve().parent.parent self.catalog_path = Path(catalog_path or base / "standards" / "catalog.yaml") def search(self, keyword: str, jurisdiction: Optional[str] = None, tag: Optional[str] = None) -> ToolResult: catalog = yaml.safe_load(self.catalog_path.read_text()) or {} entries: List[Dict[str, str]] = catalog.get("standards", []) keyword_lower = keyword.lower() filtered = [] for entry in entries: title = entry.get("title", "") desc = entry.get("summary", "") jurisdiction_match = True if not jurisdiction else jurisdiction.lower() in entry.get("jurisdiction", "").lower() tag_match = True if not tag else tag.lower() in " ".join(entry.get("tags", [])).lower() keyword_match = keyword_lower in title.lower() or keyword_lower in desc.lower() or keyword_lower in entry.get("id", "").lower() if jurisdiction_match and tag_match and keyword_match: filtered.append(entry) summary = f"Found {len(filtered)} standards matching '{keyword}'" return ToolResult(title="Standards lookup", summary=summary, artifacts={"standards": json.dumps(filtered, indent=2)}) class BuildingCodeChecklistTool: """Generate building-code and ISO alignment checklist for a project brief.""" def generate(self, occupancy: str, construction_type: str, height_m: float) -> ToolResult: checklist = [ { "item": "IBC/IRC occupancy confirmation", "details": f"Verify occupancy group '{occupancy}' against Chapter 3 use definitions.", }, { "item": "Construction type vs height", "details": ( f"Check {construction_type} height {height_m} m against IBC Table 504 allowances; flag podium separations if needed." ), }, { "item": "Fire resistance & egress", "details": "Confirm fire ratings, stair/exit widths, and travel distance per IBC Chapters 7, 10; align with NFPA 101 where applicable.", }, { "item": "Accessibility", "details": "Coordinate ANSI A117.1 and ISO 21542 clearances for doors, ramps, and toilets; ensure jurisdictional amendments are captured.", }, { "item": "Energy + sustainability", "details": "Map envelope/MEP targets to IECC/ASHRAE 90.1 plus ISO 52000 energy performance indicators.", }, { "item": "Digital delivery", "details": "Ensure BIM deliverables follow ISO 19650 naming and exchange information requirements (EIR/BEP).", }, ] return ToolResult( title="Building code checklist", summary="Checklist drafted with IBC/ISO references.", artifacts={"checklist": json.dumps(checklist, indent=2)}, ) class IsoMappingTool: """Map project artifacts to relevant ISO/EN references for documentation.""" def __init__(self, mapping_path: Optional[str] = None) -> None: base = Path(__file__).resolve().parent.parent self.mapping_path = Path(mapping_path or base / "standards" / "iso_mapping.yaml") def map_artifacts(self, artifact_types: List[str]) -> ToolResult: mapping_doc = yaml.safe_load(self.mapping_path.read_text()) or {} mappings = mapping_doc.get("mappings", []) matches: List[Dict[str, str]] = [] lower_artifacts = [a.lower() for a in artifact_types] for entry in mappings: if any(token in lower_artifacts for token in [entry.get("artifact", "").lower()] + [t.lower() for t in entry.get("aliases", [])]): matches.append(entry) summary = f"Matched {len(matches)} artifact types to ISO/EN references" return ToolResult( title="ISO/EN mappings", summary=summary, artifacts={"mappings": json.dumps(matches, indent=2)}, ) class HealthcareSpaceProgramTool: """Check healthcare department spaces against minimum areas.""" def __init__(self, space_path: Optional[str] = None) -> None: base = Path(__file__).resolve().parent.parent self.space_path = Path(space_path or base / "standards" / "healthcare_spaces.yaml") def evaluate(self, program: List[Dict[str, object]]) -> ToolResult: config = yaml.safe_load(self.space_path.read_text()) or {} standards = {entry.get("space", "").lower(): entry for entry in config.get("spaces", [])} results: List[Dict[str, object]] = [] for item in program: name = str(item.get("space", "")).lower() provided = float(item.get("area_m2", 0)) std = standards.get(name) if std: min_area = float(std.get("min_area_m2", 0)) ok = provided >= min_area results.append( { "space": item.get("space", ""), "provided_m2": provided, "min_m2": min_area, "pass": ok, "notes": std.get("notes", ""), } ) else: results.append( { "space": item.get("space", ""), "provided_m2": provided, "min_m2": None, "pass": None, "notes": "No healthcare baseline found", } ) summary = "Healthcare program validated" if all(r.get("pass", True) is not False for r in results) else "Program needs attention" return ToolResult( title="Healthcare space check", summary=summary, artifacts={"results": json.dumps(results, indent=2)}, ) class HealthcareCodeBundleTool: """Return key US healthcare code touchpoints for a facility type.""" def summarize(self, facility_type: str, risk_category: Optional[str] = None) -> ToolResult: ft_lower = facility_type.lower() bundle: List[Dict[str, str]] = [ { "code": "IBC 2021 - Group I-2", "focus": "Occupancy definition, smoke compartment sizing, egress width for patient care areas.", }, { "code": "NFPA 101 Life Safety Code", "focus": "Horizontal/vertical separation, defend-in-place strategies, healthcare corridor requirements.", }, { "code": "NFPA 99 Health Care Facilities Code", "focus": "Medical gas categories, electrical systems, ITM for critical branches.", }, { "code": "FGI 2022 Guidelines", "focus": "Space sizes, clearances, nurse station visibility, sterile/soiled flows.", }, ] if "outpatient" in ft_lower: bundle.append( { "code": "CMS ASC (41 CFR 416)", "focus": "Ambulatory surgical center infection control, anesthesia gas storage, recovery bays.", } ) else: bundle.append( { "code": "CMS Conditions of Participation (42 CFR 482)", "focus": "Emergency services, patient rights, physical environment maintenance for hospitals.", } ) if risk_category: bundle.append( { "code": "ASCE 7 Risk Category", "focus": f"Confirm essential facility structural/Seismic Design Category for risk category {risk_category}.", } ) summary = f"Healthcare code bundle for {facility_type}" return ToolResult(title="Healthcare codes", summary=summary, artifacts={"bundle": json.dumps(bundle, indent=2)}) __all__ = [ "ToolResult", "IfcMetricsTool", "RuleEvaluator", "GeminiDocTool", "StandardCatalogTool", "BuildingCodeChecklistTool", "IsoMappingTool", "HealthcareSpaceProgramTool", "HealthcareCodeBundleTool", ]