/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:
- File paths — one or more PDF file paths
- Folder path — a directory containing PDFs (will process all
.pdf files)
- Just invoked — ask the user for file paths or a folder
Also ask (or use defaults):
- Output destination — Google Sheet, local CSV, or markdown (default: ask)
Output Schema
EPD data uses a 42-column schema — separate from the 33-column FF&E product schema. When writing to CSV, use the same column order.
Product Identity (columns A-H)
| Col |
Field |
Description |
Format |
| A |
EPD Link |
URL to original EPD document |
=HYPERLINK(url, "EPD") or blank for local PDFs |
| B |
Manufacturer |
Company that makes the product |
Title Case |
| C |
Product Name |
Declared product or product group |
Title Case |
| D |
Description |
Brief product description |
Sentence case |
| E |
Declared Unit |
Functional/declared unit (e.g., "1 m2", "1 kg", "1 m3") |
As stated in EPD |
| F |
Functional Unit |
Functional unit with RSL if different from declared |
As stated, blank if same |
| G |
CSI Division |
MasterFormat division number |
03, 05, 07, 08, 09, etc. |
| H |
Material Category |
Normalized material type |
See vocabulary below |
EPD Metadata (columns I-P)
| Col |
Field |
Description |
Format |
| I |
EPD Registration No. |
Unique EPD identifier |
As published |
| J |
Program Operator |
Certifying body |
UL, NSF, SCS, IBU, Environdec, etc. |
| K |
PCR Reference |
Product Category Rule reference |
Full citation |
| L |
PCR Expiry |
PCR expiration date |
YYYY-MM-DD |
| M |
Standard |
Governing standard |
ISO 14025, ISO 21930, EN 15804+A2, etc. |
| N |
System Boundary |
Scope of LCA |
Cradle-to-gate, Cradle-to-grave, Cradle-to-gate with options |
| O |
Valid From |
EPD publication date |
YYYY-MM-DD |
| P |
Valid To |
EPD expiration date |
YYYY-MM-DD |
Impact Indicators — Product Stage A1-A3 (columns Q-V)
| Col |
Field |
Description |
Unit |
| Q |
GWP-total (A1-A3) |
Global Warming Potential, total |
kg CO2e |
| R |
GWP-fossil (A1-A3) |
GWP from fossil sources |
kg CO2e |
| S |
GWP-biogenic (A1-A3) |
GWP from biogenic sources |
kg CO2e |
| T |
ODP (A1-A3) |
Ozone Depletion Potential |
kg CFC-11e |
| U |
AP (A1-A3) |
Acidification Potential |
kg SO2e |
| V |
EP (A1-A3) |
Eutrophication Potential |
kg PO4e |
Impact Indicators — Other Stages (columns W-AB)
| Col |
Field |
Description |
Unit |
| W |
GWP (A4-A5) |
Construction stage GWP |
kg CO2e |
| X |
GWP (B1-B7) |
Use stage GWP |
kg CO2e |
| Y |
GWP (C1-C4) |
End-of-life GWP |
kg CO2e |
| Z |
GWP (D) |
Beyond system boundary GWP |
kg CO2e |
| AA |
GWP-total (all stages) |
Sum of all declared stages |
kg CO2e |
| AB |
POCP (A1-A3) |
Photochemical Ozone Creation Potential |
kg C2H4e |
Resource Use (columns AC-AH)
| Col |
Field |
Description |
Unit |
| AC |
PERE (A1-A3) |
Primary Energy, Renewable, energy use |
MJ |
| AD |
PENRE (A1-A3) |
Primary Energy, Non-Renewable, energy use |
MJ |
| AE |
Total Energy (A1-A3) |
PERE + PENRE |
MJ |
| AF |
FW (A1-A3) |
Fresh Water Use |
m3 |
| AG |
Recycled Content |
Percentage of recycled content |
% |
| AH |
Waste (A1-A3) |
Total waste generated |
kg |
Tracking (columns AI-AP)
| Col |
Field |
Description |
Format |
| AI |
LEED Eligible |
MRc2 compliance flag |
Yes, No, Partial |
| AJ |
EC3 ID |
Building Transparency EC3 identifier |
As listed, blank if unknown |
| AK |
Plant/Facility |
Manufacturing plant or facility name |
As stated |
| AL |
Country |
Manufacturing country |
ISO 3166-1 alpha-2 |
| AM |
Parsed At |
Timestamp of parsing |
ISO 8601 |
| AN |
Tags |
User-assigned tags |
Comma-separated |
| AO |
Notes |
Additional context |
Free text — see below |
| AP |
Source |
Which skill created this row |
epd-parser |
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:
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 save the JSON output.
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:
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
Extract product identity first — manufacturer, product name, declared unit, functional unit. These are always on page 1-2.
Extract EPD metadata — registration number, program operator, PCR, standard, dates, system boundary. Usually on page 1 or in a header/sidebar.
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).
Extract resource use — PERE, PENRE, fresh water, waste. Usually in a separate table following the impact indicators.
Extract additional data — recycled content %, manufacturing plant, country of origin, LCA software, verifier name.
Determine LEED eligibility — based on EPD type and verification status.
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)
Ask: "Does this look correct? Should I adjust anything before saving?"
Step 5: Write output
Ask the user (if not already specified): "Where should I save this?"
Options:
- EPD Google Sheet — append rows to a dedicated EPD spreadsheet (separate from the FF&E product library). Ask for spreadsheet ID if not already known.
- Local CSV — save to a specified path (default:
./epd-data-YYYY-MM-DD.csv)
- Just the table — leave as markdown in the conversation
CSV Format
When saving to CSV, use the 42-column header:
EPD Link,Manufacturer,Product Name,Description,Declared Unit,Functional Unit,CSI Division,Material Category,EPD Registration No.,Program Operator,PCR Reference,PCR Expiry,Standard,System Boundary,Valid From,Valid To,GWP-total (A1-A3),GWP-fossil (A1-A3),GWP-biogenic (A1-A3),ODP (A1-A3),AP (A1-A3),EP (A1-A3),GWP (A4-A5),GWP (B1-B7),GWP (C1-C4),GWP (D),GWP-total (all stages),POCP (A1-A3),PERE (A1-A3),PENRE (A1-A3),Total Energy (A1-A3),FW (A1-A3),Recycled Content,Waste (A1-A3),LEED Eligible,EC3 ID,Plant/Facility,Country,Parsed At,Tags,Notes,Source
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."
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.]
1---2name: epd-parser3description: Parse EPD (Environmental Product Declaration) PDF documents to extract structured environmental impact data — GWP, life cycle stages, certifications, and compliance metrics.4---5
6# /epd-parser — EPD PDF Parser
7
8Extract 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.
9
10EPDs 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.
11
12## Input
13
14The user provides EPD PDFs in one of these ways:
15
161. **File paths** — one or more PDF file paths
172. **Folder path** — a directory containing PDFs (will process all `.pdf` files)
183. **Just invoked** — ask the user for file paths or a folder
19
20Also ask (or use defaults):
21
22- **Output destination** — Google Sheet, local CSV, or markdown (default: ask)
23
24## Output Schema
25
26EPD data uses a **42-column schema** — separate from the 33-column FF&E product schema. When writing to CSV, use the same column order.
27
28### Product Identity (columns A-H)
29
30| Col | Field | Description | Format |
31|-----|-------|-------------|--------|
32| A | EPD Link | URL to original EPD document | `=HYPERLINK(url, "EPD")` or blank for local PDFs |
33| B | Manufacturer | Company that makes the product | Title Case |
34| C | Product Name | Declared product or product group | Title Case |
35| D | Description | Brief product description | Sentence case |
36| E | Declared Unit | Functional/declared unit (e.g., "1 m2", "1 kg", "1 m3") | As stated in EPD |
37| F | Functional Unit | Functional unit with RSL if different from declared | As stated, blank if same |
38| G | CSI Division | MasterFormat division number | `03`, `05`, `07`, `08`, `09`, etc. |
39| H | Material Category | Normalized material type | See vocabulary below |
40
41### EPD Metadata (columns I-P)
42
43| Col | Field | Description | Format |
44|-----|-------|-------------|--------|
45| I | EPD Registration No. | Unique EPD identifier | As published |
46| J | Program Operator | Certifying body | `UL`, `NSF`, `SCS`, `IBU`, `Environdec`, etc. |
47| K | PCR Reference | Product Category Rule reference | Full citation |
48| L | PCR Expiry | PCR expiration date | YYYY-MM-DD |
49| M | Standard | Governing standard | `ISO 14025`, `ISO 21930`, `EN 15804+A2`, etc. |
50| N | System Boundary | Scope of LCA | `Cradle-to-gate`, `Cradle-to-grave`, `Cradle-to-gate with options` |
51| O | Valid From | EPD publication date | YYYY-MM-DD |
52| P | Valid To | EPD expiration date | YYYY-MM-DD |
53
54### Impact Indicators — Product Stage A1-A3 (columns Q-V)
55
56| Col | Field | Description | Unit |
57|-----|-------|-------------|------|
58| Q | GWP-total (A1-A3) | Global Warming Potential, total | kg CO2e |
59| R | GWP-fossil (A1-A3) | GWP from fossil sources | kg CO2e |
60| S | GWP-biogenic (A1-A3) | GWP from biogenic sources | kg CO2e |
61| T | ODP (A1-A3) | Ozone Depletion Potential | kg CFC-11e |
62| U | AP (A1-A3) | Acidification Potential | kg SO2e |
63| V | EP (A1-A3) | Eutrophication Potential | kg PO4e |
64
65### Impact Indicators — Other Stages (columns W-AB)
66
67| Col | Field | Description | Unit |
68|-----|-------|-------------|------|
69| W | GWP (A4-A5) | Construction stage GWP | kg CO2e |
70| X | GWP (B1-B7) | Use stage GWP | kg CO2e |
71| Y | GWP (C1-C4) | End-of-life GWP | kg CO2e |
72| Z | GWP (D) | Beyond system boundary GWP | kg CO2e |
73| AA | GWP-total (all stages) | Sum of all declared stages | kg CO2e |
74| AB | POCP (A1-A3) | Photochemical Ozone Creation Potential | kg C2H4e |
75
76### Resource Use (columns AC-AH)
77
78| Col | Field | Description | Unit |
79|-----|-------|-------------|------|
80| AC | PERE (A1-A3) | Primary Energy, Renewable, energy use | MJ |
81| AD | PENRE (A1-A3) | Primary Energy, Non-Renewable, energy use | MJ |
82| AE | Total Energy (A1-A3) | PERE + PENRE | MJ |
83| AF | FW (A1-A3) | Fresh Water Use | m3 |
84| AG | Recycled Content | Percentage of recycled content | % |
85| AH | Waste (A1-A3) | Total waste generated | kg |
86
87### Tracking (columns AI-AP)
88
89| Col | Field | Description | Format |
90|-----|-------|-------------|--------|
91| AI | LEED Eligible | MRc2 compliance flag | `Yes`, `No`, `Partial` |
92| AJ | EC3 ID | Building Transparency EC3 identifier | As listed, blank if unknown |
93| AK | Plant/Facility | Manufacturing plant or facility name | As stated |
94| AL | Country | Manufacturing country | ISO 3166-1 alpha-2 |
95| AM | Parsed At | Timestamp of parsing | ISO 8601 |
96| AN | Tags | User-assigned tags | Comma-separated |
97| AO | Notes | Additional context | Free text — see below |
98| AP | Source | Which skill created this row | `epd-parser` |
99
100### Material Category Vocabulary
101
102Use 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.
103
104### EPD-specific data in Notes (col AO)
105
106EPDs contain fields that don't have dedicated columns. Append these to Notes:
107
108- **EPD Type**: `Type: Product-specific` or `Type: Industry-average` or `Type: Sector`
109- **Verification**: `Verified by: [verifier name]`
110- **EN version**: `EN 15804+A1` or `EN 15804+A2` (important for comparability)
111- **Source File**: `Source: holcim-readymix-epd.pdf`
112- **LCA Software**: `LCA: GaBi` or `LCA: SimaPro` or `LCA: openLCA`
113- **Biogenic carbon note**: If the EPD reports biogenic carbon storage separately
114
115Example Notes cell: `Type: Product-specific | Verified by: Underwriters Laboratories | EN 15804+A2 | LCA: GaBi | Source: holcim-readymix-epd.pdf`
116
117### LEED Eligibility Logic (col AI)
118
119- **Yes**: Product-specific, third-party verified EPD conforming to ISO 14025
120- **Partial**: Industry-average or sector EPD (counts for LEED Option 1 but not Option 2)
121- **No**: Self-declared, unverified, or non-conforming
122
123## Workflow
124
125### Step 1: Get input
126
127Parse the user's input to identify PDF file(s) and output preferences.
128
129- If given a folder, list all `.pdf` files and report count
130- If no PDFs found or path is invalid, ask the user
131- Report: "Found N EPD PDF(s) to process."
132
133### Step 2: Extract text from PDF
134
135Use PyMuPDF (fitz) to extract text from each PDF. Run this Python script via Bash:
136
137```python
138import fitz
139import sys
140import json
141
142pdf_path = sys.argv[1]
143doc = fitz.open(pdf_path)
144pages = []
145for i, page in enumerate(doc):
146 text = page.get_text()
147 pages.append({"page": i + 1, "text": text})
148doc.close()
149
150print(json.dumps({"filename": pdf_path.split("/")[-1], "total_pages": len(pages), "pages": pages}))
151```
152
153For each PDF, extract all pages and save the JSON output.
154
155### Step 3: Parse EPD data
156
157Read the extracted text and identify all environmental impact data. This is the core intelligence step.
158
159**For small EPDs (<=30 pages):** Process all pages at once.
160
161**For large EPDs (>30 pages):** Process in chunks of 15 pages. Carry forward context between chunks.
162
163**Parsing instructions:**
164
1651. **Identify the EPD structure** — EPDs typically have these sections:
166 - General information (pages 1-2): manufacturer, product, declared unit, program operator, validity
167 - Product description (pages 2-3): materials, manufacturing process, application
168 - LCA information: system boundary, data sources, allocation rules
169 - Impact indicator tables: the core data — usually 1-3 pages of tables
170 - Resource use and waste tables
171 - Additional information: scenarios, biogenic carbon, recycled content
172
1732. **Extract product identity first** — manufacturer, product name, declared unit, functional unit. These are always on page 1-2.
174
1753. **Extract EPD metadata** — registration number, program operator, PCR, standard, dates, system boundary. Usually on page 1 or in a header/sidebar.
176
1774. **Find and parse impact indicator tables** — This is the most critical step:
178 - Look for tables with life cycle stage columns (A1, A2, A3 or A1-A3 combined, A4, A5, B1-B7, C1-C4, D)
179 - Impact categories are rows: GWP, ODP, AP, EP, POCP, ADP (or ADPE/ADPF)
180 - **EN 15804+A2 EPDs** split GWP into: GWP-total, GWP-fossil, GWP-biogenic, GWP-luluc. Capture all that exist.
181 - **EN 15804+A1 EPDs** report a single GWP row. Put this value in GWP-total (A1-A3).
182 - If A1, A2, A3 are reported separately (not combined), sum them for the A1-A3 total.
183 - 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).
184
1855. **Extract resource use** — PERE, PENRE, fresh water, waste. Usually in a separate table following the impact indicators.
186
1876. **Extract additional data** — recycled content %, manufacturing plant, country of origin, LCA software, verifier name.
188
1897. **Determine LEED eligibility** — based on EPD type and verification status.
190
1918. **Leave fields blank rather than guessing** — if a field isn't in the EPD, leave it empty.
192
193### Multi-product EPDs
194
195Some EPDs declare impacts for multiple products, product groups, or concrete mix designs. Create **one row per product/variant**:
196
197- If the EPD covers "Product A" and "Product B" with separate impact tables, create two rows
198- If the EPD covers multiple concrete mixes (e.g., 3000 PSI, 4000 PSI, 5000 PSI), create one row per mix
199- The product name should distinguish variants: "ReadyMix Concrete — 4000 PSI"
200
201### Step 4: Present results
202
203Show a summary table for each parsed EPD:
204
205```
206## EPD Parse Results
207
208### holcim-readymix-epd.pdf
209| Field | Value |
210|-------|-------|
211| Product | ReadyMix Concrete — 4000 PSI |
212| Manufacturer | Holcim |
213| Declared Unit | 1 m3 |
214| GWP (A1-A3) | 312 kg CO2e |
215| System Boundary | Cradle-to-gate |
216| Program Operator | NSF |
217| Valid | 2024-01-15 to 2029-01-15 |
218| LEED Eligible | Yes |
219
220Products extracted: 3 (3000 PSI, 4000 PSI, 5000 PSI)
221```
222
223Ask: **"Does this look correct? Should I adjust anything before saving?"**
224
225### Step 5: Write output
226
227Ask the user (if not already specified): **"Where should I save this?"**
228
229Options:
230- **EPD Google Sheet** — append rows to a dedicated EPD spreadsheet (separate from the FF&E product library). Ask for spreadsheet ID if not already known.
231- **Local CSV** — save to a specified path (default: `./epd-data-YYYY-MM-DD.csv`)
232- **Just the table** — leave as markdown in the conversation
233
234## CSV Format
235
236When saving to CSV, use the 42-column header:
237
238```csv
239EPD Link,Manufacturer,Product Name,Description,Declared Unit,Functional Unit,CSI Division,Material Category,EPD Registration No.,Program Operator,PCR Reference,PCR Expiry,Standard,System Boundary,Valid From,Valid To,GWP-total (A1-A3),GWP-fossil (A1-A3),GWP-biogenic (A1-A3),ODP (A1-A3),AP (A1-A3),EP (A1-A3),GWP (A4-A5),GWP (B1-B7),GWP (C1-C4),GWP (D),GWP-total (all stages),POCP (A1-A3),PERE (A1-A3),PENRE (A1-A3),Total Energy (A1-A3),FW (A1-A3),Recycled Content,Waste (A1-A3),LEED Eligible,EC3 ID,Plant/Facility,Country,Parsed At,Tags,Notes,Source
240```
241
242## Edge Cases
243
244- **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."
245- **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.
246- **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.
247- **Multi-product EPDs**: One row per product/variant. See Multi-product EPDs section above.
248- **Expired EPDs**: Flag in Notes: `EXPIRED — valid to YYYY-MM-DD`. Still parse the data.
249- **Password-protected PDFs**: PyMuPDF will fail to open. Catch the error and tell the user.
250- **Very large PDFs (50+ pages)**: Process in 15-page chunks. Give progress updates.
251- **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."
252
253## Error Reporting
254
255After processing, always report:
256
257```
258Parsed: X products from Y EPD PDF(s)
259- filename.pdf: N products extracted
260- filename2.pdf: M products extracted
261Issues: [list any problems — expired, scanned, missing tables, etc.]
262```