Add AAFP clinical recommendation scraper - #22
Conversation
| return any(_normalized(heading.text_content()) == _GUIDELINES_HEADING for heading in main.iter("h2")) | ||
|
|
||
|
|
||
| def _category_drawers(main: lxml_html.HtmlElement) -> Iterator[tuple[str, lxml_html.HtmlElement]]: |
There was a problem hiding this comment.
The _category_drawers helper in aafp.py, the source collector, opens categories only at accordion headings. Its drawer branch ignores drawers when no category is open.
An accepted fix recognizes drawers directly under the guidelines heading while retaining the boundary that excludes unrelated resources. A regression fixture should cover both arrangements and the unrelated section. Next we examine a source link inside an accepted drawer.
TLDR A page with the same recommendations arranged differently can contribute no documents.
|
|
||
|
|
||
| def _full_guideline_url(links: list[dict[str, str]]) -> str | None: | ||
| return next((link["url"] for link in links if _FULL_SOURCE_LINK_RE.search(link["text"])), None) |
There was a problem hiding this comment.
Moving down to _full_guideline_url in aafp.py, the metadata collector, the selection rule requires the literal full prefix. The drawer builder therefore leaves its dedicated guideline address empty for a link labelled Read the recommendation.
An accepted fix recognizes the source’s observed recommendation-link wording and tests it against unrelated references so a broader pattern does not choose the wrong link. Next we assemble the final decision.
TLDR The document keeps its link, but the dedicated field for finding the full guideline can stay empty.
What this adds
This PR adds an AAFP scraper for Clinical Insights pages.
It discovers eligible pages from the AAFP sitemap or accepts a specific page through
--url. Each clinical recommendation drawer is saved as a separateScrapedDocument. This avoids mixing recommendations with different populations, sources, dates, and AAFP positions into one document.The scraper preserves the recommendation content as Markdown and records useful metadata, including:
It also reports errors when discovery or page parsing fails instead of silently returning an empty result.
Files changed
datasets/amfv_datasets/scraping/aafp.pydatasets/amfv_datasets/scraping/cli.pyaafpas a scraper source.--source allwhen used with a single--url.datasets/test/test_scraping_aafp.pydatasets/test/test_scraping_cli.pyNo new dependencies were added.
How to test
Optional live smoke test:
uv run --frozen amfv-scrape \ --source aafp \ --url "https://www.aafp.org/clinical-insights/cardiometabolic-health/hypertension" \ --documents 2 \ --no-progress \ --output /tmp/aafp-smoke.jsonlResults
This PR contains scraper code and synthetic test fixtures only. It does not add scraped AAFP content.