SKILL.md v7 sustainability
EPD Search Sources
Search supplier sites, Environdec, and EPD Hub; keep EPD-like candidates only.
No reviews yet 0 installs
epdsearchenvirondecepdhubsustainabilitylca
Versions
- v7 # Changelog ## 0.10.0 - The PCF/LCA tier's open-web fallback now names the supplier: `"<product>" "<supplier>" (PCF OR ...)`. That fallback fires exactly when the manufacturer's own site published nothing — the same condition in which the EPD tier failed — so it was the one query most in need of the supplier term. The `site:` pass is unchanged. - Vault candidates carry `attribution` and `declaredHolder`, and `vaultRejected` now reports an attribution conflict, naming the holder the document declared. ## 0.9.0 - Read `/workspace/work/code/epd-vault-index.json` (a missing index is empty, never an error — runs predating the vault step resume unchanged). A verified product-specific vault document (`epd-scan-vault`'s verdict) settles that product with **zero** broker queries: the result carries `vaultCandidates` and `searchSkipped: "vault_product_hit"`, and no `candidates`/`pcfCandidates`/`industrialCandidates` are populated. - A product-specific vault PCF/LCA whose boundary is **not** comparable to A1-A3 does NOT short-circuit — it is recorded in `vaultRejected` with the actual reason, naming the boundary TEXT the document declared when one was matched (e.g. `"declared boundary A1-C4 is not comparable to A1-A3"`, `"another vault document was preferred for this product"`), and the normal internet search still runs for that product. Each `vaultRejected` entry also carries `boundaryDetail` so downstream steps can report it without re-deriving it. - A category-scope vault document is offered to the industrial tier only (`vault_category_candidates`, `matchTier: "industrial_proxy"`) — it is an industrial proxy, not a tier-1 hit, and is folded into `industrialCandidates` alongside the registry-sourced ones. - The circuit breaker now counts only products actually **searched** (`searchable_total` excludes vault-covered products): a vault hit issues no query and must not consume one of the breaker's probe slots. - New PCF/LCA tier (`search_pcf_lca`): searches the supplier site first, then the open web only when the supplier site answers nothing (`pcfSource`: `supplier_website` | `open_web`, carried through on each candidate so build can grade an open-web PCF Low confidence). Candidates carry `matchTier: "pcf_lca"` and are offered via `pcfCandidates`. Each kept hit is also classified `documentType: "pcf" | "lca"` from its title/URL/pdfUrl — without this every hit defaulted to no `documentType`, and `epd-build-library`'s Document Type column labelled every one of them "PCF", even when the document was an LCA report. - `SKILL.md` updated: vault short-circuit, `vaultRejected`, the category-scope vault → industrial routing, the new PCF/LCA tier, and the searched-only circuit-breaker denominator. ## 0.8.0 - `SearchHealth` accumulator records every broker query outcome; `epd-candidates.json` gains a `searchHealth` block on every run. - Circuit breaker: when the first min(5, N) products produce only errored queries (no successes AND no genuine empties) the step writes the partial artifact, prints the `environment_unhealthy` signal and exits 4 (vendored `environment_health.py`). Broker exceptions are still swallowed per-query, but never silently. ## 0.7.0 - Industrial/sector signal now includes `industry-wide` (the NRMCA/CMHA label for sector EPDs) alongside industrial/industry-average/sector/generic/ representative. - Industrial registry searches drop hits whose URL is not on the queried registry's domain. `site:` is a hint, not a guarantee — talos answered the EPD Hub query with an epd.guide article (2026-09-30 run), which then carried the `industrial_epdhub` label into collect. ## 0.6.0 - Industrial/sector searches are keyed by the product's `materialCategory` (from epd-normalize-products), falling back to the leading token for pre-classification products JSON. Converts previously dead-end queries such as `Block`/`Mastic` into `concrete block`/`construction sealant`. - Industrial candidates carry `materialCategory`, `confidence` (high/medium/low or null on fallback) and `confidenceRationale`; candidates preserve `validFrom`/`validTo` from registry payloads when present. ## 0.5.0 - Add industrial/sector-average EPD registry search into `industrialCandidates` per product. - Product-specific `candidates` remain unchanged; industrial tier is separate. ## 0.4.0 - Emit per-product progress JSON on stdout during `search_all_products` (flushed) for long runs. ## 0.3.0 - `files`, `urls` and `companies` wrapper keys are now walked when collecting candidate objects, matching the keys the URL-only path already handled. A `{"files": [...]}` payload previously found no candidate dicts and fell through to the URL-only path, discarding every `title`. - Candidates now preserve `title` and `registrationNumber` from the search payload whenever present, including dict-wrapped results (`{"results": [...]}`) that previously lost both fields and left ranking unable to tell candidates apart. - When a candidate has no `registrationNumber`, one is derived from a registry URL's id (e.g. `.../library/epd906` → `epd906`). A `title` is still never synthesised from a URL. ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 10/13/2026
- v6 # Changelog ## 0.9.0 - Read `/workspace/work/code/epd-vault-index.json` (a missing index is empty, never an error — runs predating the vault step resume unchanged). A verified product-specific vault document (`epd-scan-vault`'s verdict) settles that product with **zero** broker queries: the result carries `vaultCandidates` and `searchSkipped: "vault_product_hit"`, and no `candidates`/`pcfCandidates`/`industrialCandidates` are populated. - A product-specific vault PCF/LCA whose boundary is **not** comparable to A1-A3 does NOT short-circuit — it is recorded in `vaultRejected` with the actual reason, naming the boundary TEXT the document declared when one was matched (e.g. `"declared boundary A1-C4 is not comparable to A1-A3"`, `"another vault document was preferred for this product"`), and the normal internet search still runs for that product. Each `vaultRejected` entry also carries `boundaryDetail` so downstream steps can report it without re-deriving it. - A category-scope vault document is offered to the industrial tier only (`vault_category_candidates`, `matchTier: "industrial_proxy"`) — it is an industrial proxy, not a tier-1 hit, and is folded into `industrialCandidates` alongside the registry-sourced ones. - The circuit breaker now counts only products actually **searched** (`searchable_total` excludes vault-covered products): a vault hit issues no query and must not consume one of the breaker's probe slots. - New PCF/LCA tier (`search_pcf_lca`): searches the supplier site first, then the open web only when the supplier site answers nothing (`pcfSource`: `supplier_website` | `open_web`, carried through on each candidate so build can grade an open-web PCF Low confidence). Candidates carry `matchTier: "pcf_lca"` and are offered via `pcfCandidates`. Each kept hit is also classified `documentType: "pcf" | "lca"` from its title/URL/pdfUrl — without this every hit defaulted to no `documentType`, and `epd-build-library`'s Document Type column labelled every one of them "PCF", even when the document was an LCA report. - `SKILL.md` updated: vault short-circuit, `vaultRejected`, the category-scope vault → industrial routing, the new PCF/LCA tier, and the searched-only circuit-breaker denominator. ## 0.8.0 - `SearchHealth` accumulator records every broker query outcome; `epd-candidates.json` gains a `searchHealth` block on every run. - Circuit breaker: when the first min(5, N) products produce only errored queries (no successes AND no genuine empties) the step writes the partial artifact, prints the `environment_unhealthy` signal and exits 4 (vendored `environment_health.py`). Broker exceptions are still swallowed per-query, but never silently. ## 0.7.0 - Industrial/sector signal now includes `industry-wide` (the NRMCA/CMHA label for sector EPDs) alongside industrial/industry-average/sector/generic/ representative. - Industrial registry searches drop hits whose URL is not on the queried registry's domain. `site:` is a hint, not a guarantee — talos answered the EPD Hub query with an epd.guide article (2026-09-30 run), which then carried the `industrial_epdhub` label into collect. ## 0.6.0 - Industrial/sector searches are keyed by the product's `materialCategory` (from epd-normalize-products), falling back to the leading token for pre-classification products JSON. Converts previously dead-end queries such as `Block`/`Mastic` into `concrete block`/`construction sealant`. - Industrial candidates carry `materialCategory`, `confidence` (high/medium/low or null on fallback) and `confidenceRationale`; candidates preserve `validFrom`/`validTo` from registry payloads when present. ## 0.5.0 - Add industrial/sector-average EPD registry search into `industrialCandidates` per product. - Product-specific `candidates` remain unchanged; industrial tier is separate. ## 0.4.0 - Emit per-product progress JSON on stdout during `search_all_products` (flushed) for long runs. ## 0.3.0 - `files`, `urls` and `companies` wrapper keys are now walked when collecting candidate objects, matching the keys the URL-only path already handled. A `{"files": [...]}` payload previously found no candidate dicts and fell through to the URL-only path, discarding every `title`. - Candidates now preserve `title` and `registrationNumber` from the search payload whenever present, including dict-wrapped results (`{"results": [...]}`) that previously lost both fields and left ranking unable to tell candidates apart. - When a candidate has no `registrationNumber`, one is derived from a registry URL's id (e.g. `.../library/epd906` → `epd906`). A `title` is still never synthesised from a URL. ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 10/13/2026
- v5 # Changelog ## 0.8.0 - `SearchHealth` accumulator records every broker query outcome; `epd-candidates.json` gains a `searchHealth` block on every run. - Circuit breaker: when the first min(5, N) products produce only errored queries (no successes AND no genuine empties) the step writes the partial artifact, prints the `environment_unhealthy` signal and exits 4 (vendored `environment_health.py`). Broker exceptions are still swallowed per-query, but never silently. ## 0.7.0 - Industrial/sector signal now includes `industry-wide` (the NRMCA/CMHA label for sector EPDs) alongside industrial/industry-average/sector/generic/ representative. - Industrial registry searches drop hits whose URL is not on the queried registry's domain. `site:` is a hint, not a guarantee — talos answered the EPD Hub query with an epd.guide article (2026-09-30 run), which then carried the `industrial_epdhub` label into collect. ## 0.6.0 - Industrial/sector searches are keyed by the product's `materialCategory` (from epd-normalize-products), falling back to the leading token for pre-classification products JSON. Converts previously dead-end queries such as `Block`/`Mastic` into `concrete block`/`construction sealant`. - Industrial candidates carry `materialCategory`, `confidence` (high/medium/low or null on fallback) and `confidenceRationale`; candidates preserve `validFrom`/`validTo` from registry payloads when present. ## 0.5.0 - Add industrial/sector-average EPD registry search into `industrialCandidates` per product. - Product-specific `candidates` remain unchanged; industrial tier is separate. ## 0.4.0 - Emit per-product progress JSON on stdout during `search_all_products` (flushed) for long runs. ## 0.3.0 - `files`, `urls` and `companies` wrapper keys are now walked when collecting candidate objects, matching the keys the URL-only path already handled. A `{"files": [...]}` payload previously found no candidate dicts and fell through to the URL-only path, discarding every `title`. - Candidates now preserve `title` and `registrationNumber` from the search payload whenever present, including dict-wrapped results (`{"results": [...]}`) that previously lost both fields and left ranking unable to tell candidates apart. - When a candidate has no `registrationNumber`, one is derived from a registry URL's id (e.g. `.../library/epd906` → `epd906`). A `title` is still never synthesised from a URL. ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 10/8/2026
- v4 # Changelog ## 0.5.0 - Add industrial/sector-average EPD registry search into `industrialCandidates` per product. - Product-specific `candidates` remain unchanged; industrial tier is separate. ## 0.4.0 - Emit per-product progress JSON on stdout during `search_all_products` (flushed) for long runs. ## 0.3.0 - `files`, `urls` and `companies` wrapper keys are now walked when collecting candidate objects, matching the keys the URL-only path already handled. A `{"files": [...]}` payload previously found no candidate dicts and fell through to the URL-only path, discarding every `title`. - Candidates now preserve `title` and `registrationNumber` from the search payload whenever present, including dict-wrapped results (`{"results": [...]}`) that previously lost both fields and left ranking unable to tell candidates apart. - When a candidate has no `registrationNumber`, one is derived from a registry URL's id (e.g. `.../library/epd906` → `epd906`). A `title` is still never synthesised from a URL. ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 9/22/2026
- v3 # Changelog ## 0.3.0 - `files`, `urls` and `companies` wrapper keys are now walked when collecting candidate objects, matching the keys the URL-only path already handled. A `{"files": [...]}` payload previously found no candidate dicts and fell through to the URL-only path, discarding every `title`. - Candidates now preserve `title` and `registrationNumber` from the search payload whenever present, including dict-wrapped results (`{"results": [...]}`) that previously lost both fields and left ranking unable to tell candidates apart. - When a candidate has no `registrationNumber`, one is derived from a registry URL's id (e.g. `.../library/epd906` → `epd906`). A `title` is still never synthesised from a URL. ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 9/1/2026
- v2 # Changelog ## 0.2.0 - **EPD-likeness filter** (`filter_epd_candidates`, `is_epd_like`): after ranking, drop non-EPD PDFs such as LEED requirement summaries and marketing brochures; require EPD signals in title/URL and distinctive product tokens when the normalized name has multiple tokens. Applied automatically in `search_all_products`. ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 8/28/2026
- v1 # Changelog ## 0.1.0 - Initial release: `lib/search_sources.py` reads `/workspace/work/code/epd-products.json` and `/workspace/work/code/epd-suppliers.json`, joins suppliers by `supplierKey`, and searches for EPD candidates per product in order: supplier website → [Environdec](https://www.environdec.com/home) → [EPD Hub](https://manage.epdhub.com/). - Writes `/workspace/work/code/epd-candidates.json` with merged, ranked candidates (`supplier_website`, `environdec`, `epdhub`). Uses normalized names only; empty candidate lists are valid; never invents EPD metadata or PDF URLs. 8/27/2026