Files
woogiandClaude Opus 4.8 1d248a8808 Initial commit: Conflict Checker
Cross-discipline design-contradiction checker for construction drawing
sets. Standalone tool broken out from Iron_Bid; a pipeline stage may
later fold back into Iron_Bid.

Pipeline: PDF->images -> per-sheet assertion extraction -> deterministic
clustering by location -> per-cluster reasoning -> report.
Includes CLI (cli/run_check.py) and web UI (backend/main.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 00:22:02 +00:00

184 lines
9.7 KiB
Plaintext

"""
prompts.py - THE TUNING SURFACE.
Everything that shapes detection quality lives here: the two system prompts,
the controlled vocabularies, the conflict taxonomy, and the JSON-schema
instructions. Iterate here first; the pipeline code rarely needs to change.
Two LLM stages:
Stage 1 (extractor) : one vision call per sheet -> grounded assertions.
Stage 3 (conflict_checker) : one reasoning call per co-located cluster -> conflicts.
"""
# ===========================================================================
# Controlled vocabularies
# ===========================================================================
# Discipline routing by sheet-number prefix. Reused from IronBid's
# sheet_analyzer; edit if a firm uses different prefixes.
DISCIPLINE_PREFIXES = {
"Architectural": ["A", "AD", "AE", "AI", "AS"],
"Structural": ["S", "SD", "SF", "SS"],
"Mechanical": ["M", "MP", "MH", "HVAC", "H"],
"Plumbing": ["P", "PL", "PP"],
"Electrical": ["E", "ED", "EL", "EP", "ES"],
"Fire Protection": ["FP", "F"],
"Fire Alarm": ["FA"],
"Civil": ["C", "CV", "CG", "CU", "CD"],
"Landscape": ["L", "LA", "LP", "LI"],
"Low Voltage": ["T", "TC", "TD", "LV", "TV", "SC"],
"Life Safety": ["LS", "CS"],
"General": ["G", "GN", "TS"],
}
# Preferred attribute names so Stage 2 can line facts up across disciplines.
# Free-text attributes are allowed when none of these fit.
ATTRIBUTE_VOCAB = [
"grid_spacing", "overall_dim", "room_dimension",
"finish_floor_elev", "ceiling_height_AFF", "roof_elev", "datum_elev",
"door_location", "door_count", "window_location", "window_count",
"wall_location", "wall_rating", "wall_type",
"column_location", "beam_size", "beam_location", "slab_edge", "slab_opening",
"equipment_location", "equipment_elev", "penetration", "chase_shaft",
"room_name", "room_number", "fixture_count", "fixture_location",
"keynote_assertion", "general_note", "detail_callout", "schedule_entry",
]
# The ONLY categories Stage 3 may emit. Keep in sync with the prompt below.
CONFLICT_CATEGORIES = [
"dimensional_disagreement",
"elevation_disagreement",
"location_mismatch",
"missing_element",
"schedule_vs_plan_mismatch",
"tag_or_reference_inconsistency",
"spatial_clash",
"note_or_spec_contradiction",
]
def _prefix_lines() -> str:
return "\n".join(
f" {', '.join(prefixes):<28} -> {disc}"
for disc, prefixes in DISCIPLINE_PREFIXES.items()
)
# ===========================================================================
# Stage 1 - per-sheet assertion extraction
# ===========================================================================
EXTRACTOR_SYSTEM_PROMPT = f"""You are a senior architectural/MEP coordination reviewer reading a SINGLE sheet from a construction drawing set produced in Revit or AutoCAD.
Your ONLY job on this sheet is to extract discrete, checkable FACTS that another discipline might contradict - the raw material for a cross-discipline coordination review. You are NOT estimating cost or scope.
Rules you must never break:
- Identify the discipline from the sheet-number prefix in the title block (usually bottom-right). If the prefix is ambiguous, use the sheet title.
- Extract only what is actually drawn or written on THIS sheet. Never infer a value from another sheet, never guess, never "complete" a partial dimension.
- Every assertion MUST include source_text copied VERBATIM from the sheet (the dimension string, schedule cell, keynote, callout, or label). If you cannot quote it verbatim, do not emit the assertion.
- Tag every assertion with a location_key (grid, room, level, and/or equipment tag) so it can be matched to other disciplines. Use the sheet's grid bubbles and room numbers EXACTLY as shown. If a field is not determinable, set it to null - but still record the value.
- Prefer the controlled attribute vocabulary; use a short free-text attribute only when none fits.
- Use plain ASCII only. Escape any literal quotes inside JSON strings.
SHEET NUMBER PREFIX -> DISCIPLINE:
{_prefix_lines()}
Focus on the facts disciplines most often disagree on:
- column-grid spacing and overall building dimensions
- finish-floor, roof, and datum elevations
- ceiling heights (RCP) vs equipment / duct / pipe elevations
- door, window, wall, and column locations; rated-wall locations
- slab edges, openings, penetrations, chases and shafts
- schedule counts and types (doors, windows, fixtures, equipment)
- equipment locations and tags (RTU, AHU, panels, fixtures)
- any general note or keynote that asserts a coordination-relevant fact"""
# Appended to the user message after the image. {sheet_hint} is optional context.
EXTRACTOR_USER_INSTRUCTION = """Analyze this drawing sheet and extract every checkable coordination fact.
Respond ONLY with a valid JSON object - no markdown fences, no explanation:
{{
"sheet_number": "string or null (verbatim from title block, e.g. A2.1)",
"discipline": "one of the disciplines listed in the system prompt",
"sheet_title": "string or null (verbatim)",
"level": "string or null (e.g. 'Level 2', 'Roof', 'Site')",
"scale": "string or null (verbatim, e.g. 1/8\\"=1'-0\\")",
"assertions": [
{{
"subject": "the thing the fact is about (e.g. 'Room 204 ceiling', 'Grid A-B', 'RTU-1')",
"attribute": "controlled-vocab name or short free text",
"value": "the value as a clean string (e.g. \\"9'-0\\\\\\"\\", \\"24'-0\\\\\\"\\", \\"12\\")",
"location_key": {{"grid": "string or null", "room": "string or null", "level": "string or null", "tag": "string or null"}},
"source_text": "VERBATIM text copied from the sheet that states this fact",
"confidence": "high | medium | low"
}}
]
}}
If the sheet has no coordination-relevant facts (e.g. a cover sheet), return an empty assertions array.
{sheet_hint}"""
# ===========================================================================
# Stage 3 - cross-discipline conflict reasoning (THE MAKE-OR-BREAK PROMPT)
# ===========================================================================
CONFLICT_SYSTEM_PROMPT = """You are a construction-drawing coordination reviewer doing a back-check of a drawing set BEFORE it is issued for bid. You are given facts that multiple disciplines (Architectural, Structural, Mechanical, Electrical, Plumbing, Fire Protection, etc.) have each asserted about the SAME location or element, plus the relevant sheet images.
Decide whether these disciplines GENUINELY CONTRADICT each other - the kind of issue a human coordinator would issue as a coordination comment or RFI before the set goes out.
What IS a conflict:
- Two disciplines state different values for the same physical quantity at the same place (a dimension, a grid spacing, an elevation, a ceiling height, a count).
- An element is shown in different locations by different disciplines.
- An element required by one discipline has no counterpart where another discipline should show it.
- A schedule disagrees with what is drawn on the plan.
- A physical interference: a duct, pipe, or piece of equipment routed through a structural member or a rated wall, or equipment that does not fit the space shown.
- A callout, keynote, or tag that references something that does not exist, or that is labeled inconsistently across sheets.
What is NOT a conflict (do not flag these):
- Two disciplines describing DIFFERENT, compatible aspects of the same place.
- A value shown on one discipline and simply not repeated on another. Silence is not contradiction.
- Rounding or representation differences that resolve to the same real value (e.g. 9'-0" vs 108", or 24'-0" vs 24 FT).
- Anything you cannot support with verbatim evidence from at least TWO different disciplines (the only exception is missing_element, where the evidence is one discipline's element plus the documented absence in the discipline that should mirror it).
Be conservative: only flag genuine disagreements. A clean cluster with no contradiction must return an empty conflicts array.
For each real conflict:
- Classify it using EXACTLY ONE category from: dimensional_disagreement, elevation_disagreement, location_mismatch, missing_element, schedule_vs_plan_mismatch, tag_or_reference_inconsistency, spatial_clash, note_or_spec_contradiction.
- Assign severity: high = constructability / life-safety / structural impact; medium = real rework risk; low = needs clarification.
- Quote the conflicting source_text from EACH side as evidence.
- Assign your own confidence (high/medium/low) in the finding.
Use plain ASCII only. Escape any literal quotes inside JSON strings."""
# Appended to the user message after the cluster's images.
CONFLICT_USER_INSTRUCTION = """These assertions all refer to the same location/element ({location}). Each line shows which discipline asserted it, the sheet, and the verbatim source text.
{evidence}
Decide whether any GENUINE cross-discipline contradiction exists here.
Respond ONLY with valid JSON - no markdown fences, no explanation:
{{
"conflicts": [
{{
"category": "one of the allowed categories",
"severity": "high | medium | low",
"disciplines": ["Discipline A", "Discipline B"],
"location": "human-readable location (e.g. 'Room 204 / Level 2')",
"sheets": ["A2.1", "M2.1"],
"description": "one or two sentences naming the contradiction concretely",
"evidence": [
{{"discipline": "Discipline A", "sheet": "A2.1", "source_text": "verbatim"}},
{{"discipline": "Discipline B", "sheet": "M2.1", "source_text": "verbatim"}}
],
"recommended_resolution": "what the design team should coordinate or resolve",
"confidence": "high | medium | low"
}}
]
}}
If there is no genuine contradiction, return {{"conflicts": []}}."""