--- name: epd-parser description: Extract GWP, life-cycle stages, certifications, and impact metrics from an EPD PDF. Use when given a declaration to parse; not to find or compare EPDs. allowed-tools: - Read - Write - Edit - Bash - Glob - Grep - AskUserQuestion --- # /as:epd-parser — EPD PDF Parser Extract structured environmental impact data from EPD (Environmental Product Declaration) PDF files. Uses PyMuPDF for text extraction and Claude's reasoning to parse varying EPD formats into a standardized 42-column schema. EPDs follow ISO 14025 / ISO 21930 / EN 15804 and report life cycle environmental impacts of building products. This skill reads those PDFs and structures the data for comparison, specification, and LEED documentation. ## Input The user provides EPD PDFs in one of these ways: 1. **File paths** — one or more PDF file paths 2. **Folder path** — a directory containing PDFs (will process all `.pdf` files) 3. **Just invoked** — ask the user for file paths or a folder ## Output Schema Use the canonical 42-column contract in [`schema/epd-schema.md`](../../schema/epd-schema.md). It is separate from the 33-column FF&E product schema. Never substitute or persist an FF&E-shaped record as EPD data. Leave unavailable values empty and use a plain URL for `EPD Link`. ### Material Category Vocabulary Use ONE normalized term: Concrete, Steel, Aluminum, Wood/Timber, Insulation, Gypsum, Glass, Ceramic/Tile, Carpet, Resilient Flooring, Roofing Membrane, Sealant, Paint/Coating, Masonry, Stone, Composite Panel, Acoustic, Cladding, Rebar, Cement, Aggregate, Furniture, Other. ### EPD-specific data in Notes (col AO) EPDs contain fields that don't have dedicated columns. Append these to Notes: - **EPD Type**: `Type: Product-specific` or `Type: Industry-average` or `Type: Sector` - **Verification**: `Verified by: [verifier name]` - **EN version**: `EN 15804+A1` or `EN 15804+A2` (important for comparability) - **Source File**: `Source: holcim-readymix-epd.pdf` - **LCA Software**: `LCA: GaBi` or `LCA: SimaPro` or `LCA: openLCA` - **Biogenic carbon note**: If the EPD reports biogenic carbon storage separately Example Notes cell: `Type: Product-specific | Verified by: Underwriters Laboratories | EN 15804+A2 | LCA: GaBi | Source: holcim-readymix-epd.pdf` ### LEED Eligibility Logic (col AI) - **Yes**: Product-specific, third-party verified EPD conforming to ISO 14025 - **Partial**: Industry-average or sector EPD (counts for LEED Option 1 but not Option 2) - **No**: Self-declared, unverified, or non-conforming ## Workflow ### Step 1: Get input Parse the user's input to identify PDF file(s) and output preferences. - If given a folder, list all `.pdf` files and report count - If no PDFs found or path is invalid, ask the user - Report: "Found N EPD PDF(s) to process." ### Step 2: Extract text from PDF Use PyMuPDF (fitz) to extract text from each PDF. Run this Python script via Bash: ```python import fitz import sys import json pdf_path = sys.argv[1] doc = fitz.open(pdf_path) pages = [] for i, page in enumerate(doc): text = page.get_text() pages.append({"page": i + 1, "text": text}) doc.close() print(json.dumps({"filename": pdf_path.split("/")[-1], "total_pages": len(pages), "pages": pages})) ``` For each PDF, extract all pages and keep the JSON output transiently for parsing; do not create a persistent JSON artifact. ### Step 3: Parse EPD data Read the extracted text and identify all environmental impact data. This is the core intelligence step. **For small EPDs (<=30 pages):** Process all pages at once. **For large EPDs (>30 pages):** Process in chunks of 15 pages. Carry forward context between chunks. **Parsing instructions:** 1. **Identify the EPD structure** — EPDs typically have these sections: - General information (pages 1-2): manufacturer, product, declared unit, program operator, validity - Product description (pages 2-3): materials, manufacturing process, application - LCA information: system boundary, data sources, allocation rules - Impact indicator tables: the core data — usually 1-3 pages of tables - Resource use and waste tables - Additional information: scenarios, biogenic carbon, recycled content 2. **Extract product identity first** — manufacturer, product name, declared unit, functional unit. These are always on page 1-2. 3. **Extract EPD metadata** — registration number, program operator, PCR, standard, dates, system boundary. Usually on page 1 or in a header/sidebar. 4. **Find and parse impact indicator tables** — This is the most critical step: - Look for tables with life cycle stage columns (A1, A2, A3 or A1-A3 combined, A4, A5, B1-B7, C1-C4, D) - Impact categories are rows: GWP, ODP, AP, EP, POCP, ADP (or ADPE/ADPF) - **EN 15804+A2 EPDs** split GWP into: GWP-total, GWP-fossil, GWP-biogenic, GWP-luluc. Capture all that exist. - **EN 15804+A1 EPDs** report a single GWP row. Put this value in GWP-total (A1-A3). - If A1, A2, A3 are reported separately (not combined), sum them for the A1-A3 total. - Units are standardized: GWP = kg CO2e, ODP = kg CFC-11e, AP = kg SO2e (or mol H+ eq for +A2), EP = kg PO4e (or mol N eq / kg P eq for +A2), POCP = kg C2H4e (or kg NMVOC eq for +A2). 5. **Extract resource use** — PERE, PENRE, fresh water, waste. Usually in a separate table following the impact indicators. 6. **Extract additional data** — recycled content %, manufacturing plant, country of origin, LCA software, verifier name. 7. **Determine LEED eligibility** — based on EPD type and verification status. 8. **Leave fields blank rather than guessing** — if a field isn't in the EPD, leave it empty. ### Multi-product EPDs Some EPDs declare impacts for multiple products, product groups, or concrete mix designs. Create **one row per product/variant**: - If the EPD covers "Product A" and "Product B" with separate impact tables, create two rows - If the EPD covers multiple concrete mixes (e.g., 3000 PSI, 4000 PSI, 5000 PSI), create one row per mix - The product name should distinguish variants: "ReadyMix Concrete — 4000 PSI" ### Step 4: Present results Show a summary table for each parsed EPD: ``` ## EPD Parse Results ### holcim-readymix-epd.pdf | Field | Value | |-------|-------| | Product | ReadyMix Concrete — 4000 PSI | | Manufacturer | Holcim | | Declared Unit | 1 m3 | | GWP (A1-A3) | 312 kg CO2e | | System Boundary | Cradle-to-gate | | Program Operator | NSF | | Valid | 2024-01-15 to 2029-01-15 | | LEED Eligible | Yes | Products extracted: 3 (3000 PSI, 4000 PSI, 5000 PSI) ``` The preview is the default result. It can pass directly to `/as:epd-compare` or `/as:epd-to-spec` without creating a file. ### Step 5: Optional persistence Do not create a file by default. Only when the user explicitly asks to save reusable records: 1. Resolve the nearest ancestor containing `PROJECT.md`; the target is `/epd-library.csv`. 2. Build complete records using [`schema/epd-schema.md`](../../schema/epd-schema.md), with `Parsed At` as an ISO 8601 timestamp and `Source` as `epd-parser`. 3. Preview the records, target path, and whether this initializes or appends. 4. Use one confirmation gate; do not ask the same confirmation first in prose. 5. Serialize all approved records as one JSON array, preserving canonical field names. After approval, use `python3 "${CLAUDE_PLUGIN_ROOT}/skills/master-schedule/scripts/csv-library.py" init epd` if needed, then invoke `python3 "${CLAUDE_PLUGIN_ROOT}/skills/master-schedule/scripts/csv-library.py" append epd --row-json ` exactly once. Never append in a per-record loop. The helper validates the entire file, rejects FF&E or malformed headers, and writes the whole batch atomically. If no project root exists, keep the result in conversation and offer `/as:project init`; do not invent another CSV destination. ## Edge Cases - **Scanned PDFs (image-only)**: PyMuPDF will return empty or garbage text. Detect this (very short text relative to page count) and tell the user: "This PDF appears to be scanned/image-based. Text extraction won't work — consider using an OCR tool first." - **Non-English EPDs**: Common for European manufacturers (German, French, Spanish, Swedish). Impact indicator abbreviations (GWP, ODP, AP, EP, POCP) are the same internationally. Extract numeric data regardless; note the language in Notes. - **EN 15804+A1 vs +A2**: Older EPDs use +A1 (single GWP row, different EP/AP units). Newer use +A2 (split GWP, different units for AP/EP/POCP). Always note which version in Notes. Map to schema as closely as possible. - **Multi-product EPDs**: One row per product/variant. See Multi-product EPDs section above. - **Expired EPDs**: Flag in Notes: `EXPIRED — valid to YYYY-MM-DD`. Still parse the data. - **Password-protected PDFs**: PyMuPDF will fail to open. Catch the error and tell the user. - **Very large PDFs (50+ pages)**: Process in 15-page chunks. Give progress updates. - **EPDs with impact tables as images**: Detect missing numeric data in what should be table sections. Flag: "Impact tables may be embedded as images — manual extraction needed." ## GWP Baseline Policy This policy is shared by all four EPD skills (`epd-parser`, `epd-research`, `epd-compare`, `epd-to-spec`) and must read identically in each. Industry-average GWP baselines are allowed only when cited with a named source and publication year (e.g., "NRMCA Industry-Wide Member EPD v3.2, 2022" or "AISC Fabricated Hot-Rolled Structural Sections EPD, 2021"). Uncited baseline numbers recalled from memory or training data are banned. If no source-and-year citation is available, ask the user to provide a baseline EPD or find one with `/as:epd-research` — never guess a baseline. For this skill: report only values extracted from the parsed EPD itself. When contextualizing a parsed GWP against an industry average (e.g., in the parse-results summary), the baseline must carry a source-and-year citation or be omitted. ## Error Reporting After processing, always report: ``` Parsed: X products from Y EPD PDF(s) - filename.pdf: N products extracted - filename2.pdf: M products extracted Issues: [list any problems — expired, scanned, missing tables, etc.] ```