de/netzbetreiber-preisblatt
Find the published §14a Preisblatt on a silent grid operator's own website, download it, and extract the module figures — the collection path for operators who never answered the email.
Netzbetreiber Preisblatt hunt
Campaign dir: campaigns/netzbetreiber/. Read the country CLAUDE.md first,
rule 3 especially — this task runs under its second exception and nowhere
outside it.
Why this task exists
Three rounds of email and a final wave (2026-09-01) left ~370 operators silent. Stefanie then phoned a sample of them and got the same answer every time: "das finden Sie auf unserer Website." They are not refusing the data — §14a figures are publication-obliged under EnWG — they are declining to retype it for us. So we read the website.
That is the whole mandate, and its edge is sharp: we read what an operator
chose to publish. We never get past anything they put in the way. No captcha
is solved, no login passed, no form submitted, no honeypot filled, and
browserless's stealth flag stays off. A page that will not open on its own
goes to the human queue.
The host rule is not yours to judge
Every URL fetched must sit on the operator's own domain or on
enwg-veroeffentlichungen.de. That check lives in hunt_preisblatt.mjs, which
is why you run the script instead of curling a URL yourself — hand-fetching
bypasses the check that makes this legitimate. This applies to search hits too:
a PDF you found via WebSearch goes through --url, and if the script refuses
it as wrong_host, that is the answer, not an obstacle.
1. Pass one — plain GET (fast, do this first)
node campaigns/netzbetreiber/hunt_preisblatt.mjs --limit 60
Walks each operator's homepage two levels deep looking for a Preisblatt, on a
1.5 s delay and a 10-fetch budget per operator. Pool = operators with a website,
not do_not_contact, whose findings are still incomplete, not already hunted.
Every miss is queued for the browser — including a page that returned a bland
200 with no links, because that is what a JavaScript shell looks like to a GET.
Start with --dry if you want to see the batch first. It is safe to run
repeatedly: an operator already hunted is skipped unless you pass --retry.
2. Pass two — browser (slow, only what pass one could not read)
node campaigns/netzbetreiber/fetch_browser.mjs --limit 25
Same drainer the reply path uses; it recognises the hunt's rows by
discovery: "website_hunt" and does its own two-level walk for them. Budget
roughly a minute per row and do not run it on the whole queue in one go.
A row it marks needs_human is finished as far as this task is concerned —
that operator goes on the call list, which is the outcome we agreed to accept.
3. Extract
node campaigns/netzbetreiber/pending_docs.mjs --out /tmp/pending-docs.json
Lists every downloaded document nobody has read yet. Read each one and append a
findings.jsonl record exactly as netzbetreiber-replies SKILL.md step 4
specifies — same fields, same normalized block, same conventions (positive
magnitudes, netto/brutto with vat_derived, canonical off-peak windows). Do not
invent a second format for this task.
Two things differ from the reply path, and both go in the source:
kind: "pdf",detail: "<the URL> (<document title>, gültig ab …; von der Website des Betreibers, nicht aus einer Antwort)". A reader must be able to tell a document we were sent from one we went and found.- There is no
message_id. Leave it out; do not fabricate one.
Write no triage row — nobody wrote to us.
Check the document is actually the right one before you extract from it. The scorer picks by filename and link text, and it is wrong often enough to matter: on the first live pass it returned an Anwendungsbedingungen PDF, a Messstellenbetrieb sheet and a 2008 price list. Verify two things in the file itself — it names Modul 1/2/3 (or an explicit §14a section), and its validity date is current. If either fails:
node campaigns/netzbetreiber/hunt_preisblatt.mjs --mastr SNB… --retry
which skips every document already fetched for that operator and takes the next candidate. Record nothing from the wrong document — country hard rule 1 — and never carry a figure across from a neighbouring year.
4. Web search, for what is left
For operators still empty after both passes, WebSearch is allowed as a
locator only: search in German (<operator name> Preisblatt Netzentgelte 2026,
<name> § 14a EnWG). Then hand the URL to the script:
node campaigns/netzbetreiber/hunt_preisblatt.mjs --mastr SNB… --url https://…
It applies the host check and downloads. wrong_host means the hit lives
somewhere else — a portal, a consultancy, a neighbouring utility. Do not fetch
it, do not quote figures out of a search snippet, and go back to looking on the
operator's own site instead.
5. What is left over
An operator with no website, or one both passes and the search could not read,
is a phone-queue row and nothing more. Name them in the summary. Do not call
anyone, do not write to anyone: this task never contacts an operator (country
hard rule 2 — the sending exception belongs to netzbetreiber-replies, not
here).
Finish
node campaigns/netzbetreiber/export_findings.mjsnode campaigns/netzbetreiber/push_sheet.mjs(skip without failing the run if credentials are missing)- Journal entry per country
CLAUDE.md. - Final message: operators hunted, documents found by GET vs by browser, findings extracted, how many are new completions, how many went to the phone queue, and any operator whose document looked wrong enough to be worth a human's eye. Numbers in the past tense — what this run did, not what it started.