Looking for the step-by-step? This page explains why the demo is shaped the way it is. The handbook is the manual — prerequisites with verification commands, the full annotated file map, what every file does, how information flows, and the daily, weekly and monthly routines for keeping it running. The package itself also ships SETUP.md (step by step) and MAINTENANCE.md (how to live with it).
Package:
Demo13_funding_radar.zip · 270 KB · 35–45 minutes
Don’t rebuild the scraper
Fetching, prefiltering, deduping and scoring are deterministic. Python does them faster, cheaper, reproducibly, and with tests. Turning a scraper into agent prompts makes it slower and non-reproducible — the wrong side of Demo 8’s workflow-versus-agent line.
So this demo adds the two things a scraper structurally cannot do: read the actual solicitation, and remember what you decided last time.
The one-line diagnosis. Here is the schema of a real funding-radar project:
CREATE TABLE items (uid, feed, source, title, url, org, score, rationale, ...);
CREATE TABLE feedback (uid TEXT, vote INTEGER, ts TEXT);
items: 90 rows. feedback: 0 rows.
The slot for the decision exists and is empty — because “passed,
the $200k cap is below what the work costs” does not fit in an
INTEGER. So the reason goes nowhere, and next year the same call
comes round and you read it from scratch.
The half the scraper never sees
inbox/ holds six solicitations you dropped in
yourself — four PDFs and two saved web pages. The scraper only
ever saw their titles and abstracts. The traps are all two pages in.
| Score | Call | What reading it reveals |
|---|---|---|
| 96 | NSF 26-512 — AI Institutes | Ineligible. Three dates, and the one on the cover is not binding: the institutional down-select closed in October, the required LOI closed in December. |
| 91 | USDA-NIFA — Crop Resilience | Already decided. The document says it reissues last year’s call unchanged, and your log says you passed in February 2025 — with the reason. |
| 88 | NIH PAR-26-118 | Superseded, in a banner at the top. The useful answer is not “dead” — it is go and read PAR-27-042. |
| 85 | NSF 26-534 — Foundation Models | Six of your keyword domains, all genuinely present. 25% cost share required, and the life sciences are explicitly out of scope. |
| 38 | DOE DE-FOA-0003291 | Submit. The only live call in the set. |
The profile has two halves, and nobody writes the second one
Interests say what you would like. Constraints say what you can actually do, and they are what decides:
Uncommitted effort 0.35 FTE
Indirect rate 54.5% on MTDC → a $200k *total* cap is ~$129k direct
Smallest viable project one student for two years ≈ $180k direct
Cost share the college does not commit it. A requirement is a decline.
Limited submissions down-select closes 8–10 weeks before an NSF LOI
Equipment no wet lab
Four of the five traps are caught by that table alone. A profile listing only research interests cannot produce a triage — it can only produce enthusiasm, which you already had.
The one at the bottom
The DOE call scored 38, lowest in the set, and the scorer was honest about why: the document never says foundation model, machine learning or deep learning. Then read its first area of interest against the profile’s first standing question:
| Profile, standing question 1 “Cross-species transfer for expression prediction — a model trained on reference species, evaluated honestly on a feedstock genome it has not seen. This is the one I most want funded.” |
DOE, AOI 1 “…building models that predict expression from sequence in genotypes for which no measurement exists. Applications proposing to transfer models trained in reference species to feedstock genomes are encouraged to address the generalisation problem explicitly.” |
The same sentence, written by two people who have never met. All three of its areas of interest map onto the profile, and it clears every constraint.
That is the finding — and the fix is not a lower threshold. A keyword filter cannot match a description of your work that avoids your words. No amount of tuning produces this one; a reader does.
One way, and it never eats a decision
pipeline/radar.db system of record for WHAT EXISTS read-only, denied
↓ export_vault.py — one way, idempotent
opportunities/*.md one note per call
↓
inbox/*.pdf the document itself
↓
YOU: decision + because ← the record that does not exist today
↓
memos/*.md the deliverable
The exporter refreshes every note from the database and copies your
## Decision block through verbatim. Run it twice; nothing you wrote
is lost. settings.json denies writes to pipeline/, to
profile/, to inbox/, and reads of .env*
— a system of record an agent can edit is no longer a record.
Three agents: recurrence (no Write
tool) checks the log before anything else; triage answers
is this worth an afternoon? in the order eligibility → deadlines
→ money → scope → science; fitmemo writes the memo,
and refuses to write one for a call you cannot submit to — a well-argued
memo for an ineligible call is worse than nothing, because it is persuasive.
What is verified and what is not. Every trap was checked mechanically against the documents: the submission limit, all three NSF dates, the cost-share line, the out-of-scope paragraph, the supersession banner, the reissue notice, the two conflicting deadlines, and the budget arithmetic the funder states itself. The exporter was run repeatedly to confirm it preserves a decision block — an earlier version did not, and that bug was found and fixed by this check. The agent’s behaviour was not measured. The solicitations are synthetic: the programmes, numbers, agencies’ specific calls and deadlines do not exist. Do not act on anything in the package.