$npx -y skills add dleerdefi/claude-code-construction --skill schedule-extractorExtract tabular schedule data from construction drawings — door, window, finish, fixture, panel schedules — and output to Excel. Triggers: 'door schedule', 'extract schedule', 'schedule to Excel', 'panel schedule'.
| 1 | # Schedule Extractor |
| 2 | |
| 3 | Extracts tabular schedule data embedded in drawing sheets or spec pages and outputs structured Excel files. Schedules are typically one element among many on a sheet — or the entire sheet may be a schedule. |
| 4 | |
| 5 | ## Workflow |
| 6 | |
| 7 | ``` |
| 8 | Extraction Progress: |
| 9 | - [ ] Step 1: Discover schedule locations |
| 10 | - [ ] Step 2: Isolate (crop) the schedule region |
| 11 | - [ ] Step 3: Extract structured data (pdfplumber + vision) |
| 12 | - [ ] Step 4: Validate and clean data |
| 13 | - [ ] Step 5: Output to Excel |
| 14 | - [ ] Step 6: Write graph entry |
| 15 | ``` |
| 16 | |
| 17 | ### Step 1: Discover Schedule Locations |
| 18 | |
| 19 | Use a multi-source discovery approach, checking all available sources: |
| 20 | |
| 21 | #### Source A — Sheet titles in sheet index (primary, most reliable) |
| 22 | |
| 23 | Search `.construction/index/sheet_index.yaml` (or the sheet index you've built) for sheets with "SCHEDULE" in the title. Many important schedules occupy an **entire sheet** — the sheet title tells you exactly what it is: |
| 24 | - "DOOR SCHEDULE" → entire sheet is a door schedule |
| 25 | - "FINISH SCHEDULE" or "ROOM FINISH SCHEDULE" → entire sheet is finishes |
| 26 | - "WINDOW SCHEDULE" → entire sheet is windows |
| 27 | - "PANEL SCHEDULE" → electrical panel schedule |
| 28 | - "FIXTURE SCHEDULE" → plumbing fixtures |
| 29 | |
| 30 | For dedicated schedule sheets, the entire page is the extraction target — no need to crop. |
| 31 | |
| 32 | #### Source B — AgentCM database query (supplementary) |
| 33 | |
| 34 | If `.construction/database.yaml` exists, read `query_command` from it, then query known schedules: |
| 35 | ```bash |
| 36 | # Read query_command and project_id from .construction/database.yaml |
| 37 | {query_command} -c "SELECT id, schedule_type, title, sheet_id, bounding_region FROM schedules WHERE project_id = '{PROJECT_ID}'" |
| 38 | ``` |
| 39 | This returns all schedules already detected (including stubs from Group Review with bounding regions). For embedded schedules on non-schedule sheets, check the `bounding_region` column. |
| 40 | |
| 41 | **Note**: Schedule bounding region detection is still being refined — treat these as hints, not definitive boundaries. Always verify with vision. |
| 42 | |
| 43 | #### Source C — Discipline-based heuristics |
| 44 | |
| 45 | Schedules appear on specific sheet types: |
| 46 | - **Door schedule** → typically on A-0.XX or A-8.XX sheets, or a dedicated sheet |
| 47 | - **Window schedule** → same sheets as door schedule, or separate |
| 48 | - **Room finish schedule** → A-0.XX or interior sheets |
| 49 | - **Panel schedule** → E-X.XX electrical sheets |
| 50 | - **Fixture schedule** → P-X.XX plumbing sheets |
| 51 | - **Equipment schedule** → M-X.XX mechanical sheets |
| 52 | |
| 53 | #### Source D — Vision scan (fallback) |
| 54 | |
| 55 | If no index or graph is available: |
| 56 | ```bash |
| 57 | ${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/../../scripts/pdf/rasterize_page.py {pdf_path} {page} --dpi 150 --output full_sheet.png |
| 58 | ``` |
| 59 | |
| 60 | Use vision on the full sheet image: "Identify any tabular schedules on this drawing sheet. Report the approximate bounding box coordinates (top-left x,y and bottom-right x,y) as percentages of the image dimensions, the schedule type, and the column headers visible." |
| 61 | |
| 62 | ### Step 2: Isolate the Schedule Region |
| 63 | |
| 64 | **For dedicated schedule sheets** (entire page is a schedule): Skip cropping — use the full page. |
| 65 | |
| 66 | **For embedded schedules** (schedule is one element on a larger sheet): |
| 67 | |
| 68 | Crop the identified region with padding: |
| 69 | ```bash |
| 70 | ${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/../../scripts/pdf/crop_region.py full_sheet.png \ |
| 71 | --box {x1},{y1},{x2},{y2} \ |
| 72 | --padding 20 \ |
| 73 | --output schedule_crop.png |
| 74 | ``` |
| 75 | |
| 76 | Re-rasterize at higher DPI (300) for the cropped region to improve text clarity: |
| 77 | ```bash |
| 78 | ${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/../../scripts/pdf/rasterize_page.py {pdf_path} {page} \ |
| 79 | --dpi 300 \ |
| 80 | --crop {x1},{y1},{x2},{y2} \ |
| 81 | --output schedule_hires.png |
| 82 | ``` |
| 83 | |
| 84 | ### Step 3: Extract Structured Data |
| 85 | |
| 86 | Use a **try → validate → fallback** approach: |
| 87 | |
| 88 | #### Method A — pdfplumber table extraction (try first) |
| 89 | |
| 90 | Use pdfplumber's `extract_tables()` method on the target page. If multiple tables are found, select the largest one (most rows with the most columns — schedules are wide). The first row of the selected table contains headers; subsequent rows are data. If no tables are found or results look garbled, fall back to vision (Method B). |
| 91 | |
| 92 | #### Evaluate Method A — Quality Gate |
| 93 | |
| 94 | **Check these criteria before proceeding:** |
| 95 | |
| 96 | 1. **Row count**: Did pdfplumber extract at least 10 data rows? Most schedules have 20-200+ entries. |
| 97 | 2. **Column consistency**: Do ≥80% of rows have the same number of columns? Inconsistent columns = parsing error. |
| 98 | 3. **Non-empty cells**: Are >50% of cells non-empty? Mostly-empty extraction = parsing failed. |
| 99 | 4. **First column validity**: Does the first column contain recognizable IDs (door numbers, ro |