feat: --discover mode for full-site seed enumeration - #9
Merged
Conversation
- pipeline/discover.py: discover_region_urls (homepage -> region URLs, allowlist of the 7 known region slugs, deduped, host-filtered) and discover_trust_seeds (region page -> (trust_url, region) seeds, exactly one path level below the region); discover_seeds orchestrates both scrapes - cli.py: --discover replaces --seed; discovery failures abort with a clear message and exit 1 (never silently fall back to DEFAULT_SEEDS) - fixtures: synthetic homepage + South East region page exercising duplicates, external hosts, self links, too-deep specialty links and wrong-region links - tests/test_discover.py: 13 offline tests covering both parsers, orchestration via a fake backend, CLI flag conflicts, the discovery failure path, and a full --discover run writing 4 golden records
Split the over-long comprehension in test_traps_are_excluded.
Ground-truthed against myplannedcare.nhs.uk: Midlands is /mids/ and North East and Yorkshire is /ney/ (e.g. /ney/north-cumbria/), not the /midlands/ and /neast/ slugs assumed earlier. The other five slugs (east, london, nwest, seast, swest) are confirmed correct. Without this, discovery would silently miss 2 of 7 regions on the live site.
…cal records Both discovered seeds serve the same fixture HTML, so the second trust's 4 records are exact duplicates of the first and normalise_records collapses them: 4 records, not 8. Assert that (dedupe across seeds is the correct behaviour) and add a distinct- records case so multi-seed accumulation stays covered.
The "weeks longer" replacement broke the waiting-time number parser, so Oxford contributed zero records (4 total, not 8). Two different URLs serving identical HTML dedupe to the same records, so the dedupe key's provider comes from the page <h1> — prepending text inside the h1 tag changes the provider (and thus the dedupe key) without touching any parsed numbers, and works regardless of the exact heading text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the "scrape the entire website" question: full-site runs previously required a hand-maintained
--seed URL=REGIONper trust (~137 acute providers).--discoverenumerates them from the live site itself. Follows on from #8.How it works
The site is structured
/<region-slug>/<trust>/. Discovery scrapes 8 pages total — the homepage plus 7 region pages — and emits the(url, region)seed pairsrun_pipelinealready consumes, then the normal pipeline (preflight → crawl → extract) runs unchanged.What's included
pipeline/discover.py— two pure parsers + one thin async orchestrator:discover_region_urls(html)— homepage anchors → region URLs. An allowlist of the 7 known region slugs (not "any single path segment") keeps utility pages like/find-my-hospital/out; deduped, host-filtered, document order preserveddiscover_trust_seeds(region_url, html)— region page anchors →(trust_url, region)seeds, where a trust link is exactly one path level below the region: excludes self links, specialty pages (two levels down), and other regions' trusts. Unknown slugs fall back to the slug as the region name — a newly added region still works, just unpretty until the map is updateddiscover_seeds(backend)— orchestrates the scrapes; raises on empty discovery (no regions / no trusts) because a silent empty seed list would produce a silently empty CSV — the exact failure mode the preflight probe exists to preventcli.py—--discoverflag, mutually exclusive with--seed. A failed discovery exits 1 with a clear message and never falls back toDEFAULT_SEEDS— silent degradation from a full-site run to a one-trust run would be worse than failing.Fixtures + tests (13) — synthetic homepage (7 regions + duplicate/external/utility-link traps) and South East region page (2 trusts + duplicate/self/too-deep/wrong-region traps). Tests cover both parsers, orchestration via a fake backend, empty-discovery errors, the CLI conflict path, the no-fallback failure path, and a full
--discoverrun producing 8 golden records from 2 discovered seeds.Verification
Parser verified in the sandbox against every trap pattern in the fixtures before pushing (dedupe, host filter, allowlist, self/deep/wrong-region exclusion, slug fallback, relative-URL resolution).
Design decisions to review
crawl()from the homepage would fetch every trust and specialty page just to throw the content away.scrape, notcrawl— keeps the retry policy (from feat: transient-error retry policy with failure telemetry #8) applied to discovery requests for free.