alpacalabsllc/epd-parser
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.
npx skills add https://github.com/AlpacaLabsLLC/skills-for-architects --skill epd-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.
The user provides EPD PDFs in one of these ways:
.pdf files)Use the canonical 42-column contract in 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.
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.
EPDs contain fields that don't have dedicated columns. Append these to Notes:
Type: Product-specific or Type: Industry-average or Type: SectorVerified by: [verifier name]EN 15804+A1 or EN 15804+A2 (important for comparability)Source: holcim-readymix-epd.pdfLCA: GaBi or LCA: SimaPro or LCA: openLCAExample Notes cell: Type: Product-specific | Verified by: Underwriters Laboratories | EN 15804+A2 | LCA: GaBi | Source: holcim-readymix-epd.pdf
Parse the user's input to identify PDF file(s) and output preferences.
.pdf files and report countUse PyMuPDF (fitz) to extract text from each PDF. Run this Python script via Bash:
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.
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:
Some EPDs declare impacts for multiple products, product groups, or concrete mix designs. Create one row per product/variant:
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.
Do not create a file by default. Only when the user explicitly asks to save reusable records:
PROJECT.md; the target is <project-root>/epd-library.csv.schema/epd-schema.md, with Parsed At as an ISO 8601 timestamp and Source as epd-parser.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 <batch.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.
EXPIRED — valid to YYYY-MM-DD. Still parse the data.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.
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.]
Take alpacalabsllc/epd-parser from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.