Repository navigation
Data model for provider attribution (source + "Provided By") #38
Description
Description
The ProvidedBy component shipped in phlask-map (#692), and Billy raised in a recent Data Circle meeting that many scraped resources lack provider attribution or a logo. Kevin proposed (#phlask_dev) assigning providers automatically at scrape time to cut down manual work.
The display layer exists. The data model is what's missing. This issue scopes the schema so attribution can be assigned, reviewed, and audited.
Current state (measured against production)
source.type MIGRATION 441 (46%) ← not in DataSourceType!
MANUAL 313 (33%)
WEB_SCRAPE 198 (21%)
attribution WEB_SCRAPE 163/198 attributed (82%)
MANUAL 1/313 attributed (0%)
MIGRATION 0/441 attributed (0%)
providers on file 2 (Share Food Program; "Test Provider" w/ no logo)
source.url domains sharefoodprogram.org (167), sharingexcess.com (31), "sdfsdfsdf" (1)
Important nuance: the headline "788 resources lack attribution" overstates the problem. Broken out by type, the unattributed are FORAGE 310, WATER 253, BATHROOM 171, FOOD 54 — and most foraging spots (wild trees) and public fountains legitimately have no provider. The scrapers are already doing well (82%). So the real problems are narrower:
- We can't tell "has no provider" apart from "provider unknown / needs research" — absence of a row means both. Without that distinction there's no way to build a "needs attribution" work queue or know when we're done.
- Attribution can't flow through the review pipeline —
resource_providersis a join table, andresource_edits(added in Add resource_edits review pipeline: holding table, queues, approve/reject/rollback #24) only mirrors theresourcescolumns. A crowdsourced edit cannot add or correct a provider. - Scrapers have no stable key to resolve an org by; matching on
nameis fragile. DataSourceTypeis"MANUAL" | "WEB_SCRAPE", but 46% of live rows areMIGRATION. The type is wrong, andsource.urlis unvalidated (see thesdfsdfsdfrow).
Proposed data model
1. providers — add a stable machine key
alter table providers
add column slug text unique not null, -- 'share-food-program'
add column source_domain text; -- 'sharefoodprogram.org'slug gives scrapers a stable identifier instead of name matching. source_domain does double duty: scrapers resolve their own provider, and it enables backfill by matching existing source.url domains (which would immediately attribute the remaining ~35 scraped resources).
2. resource_providers — add integrity + provenance
alter table resource_providers
add column created_at timestamptz not null default now(),
add column attributed_via text, -- SCRAPER | ADMIN | CROWDSOURCE
add column is_primary boolean not null default false,
add constraint resource_providers_uniq unique (resource_id, provider_id);attributed_via answers "where did this attribution come from" — the same provenance question source answers for the resource itself. is_primary decides which logo renders first (the ProvidedBy component already supports multiple with "+X more", and one resource currently has 2 providers). The unique constraint prevents duplicate links.
3. Distinguish "no provider" from "unknown" ⭐ the main decision
alter table resources
add column attribution_status text not null default 'UNKNOWN'
check (attribution_status in ('ATTRIBUTED', 'NONE', 'UNKNOWN'));ATTRIBUTED— has provider link(s)NONE— reviewed, genuinely has no provider (public fountain, wild fruit tree)UNKNOWN— not yet reviewed → this is the admin work queue
Without this, "no rows in resource_providers" is ambiguous and the backlog is unmeasurable. This is the one real judgment call in the proposal — flagging it for team sign-off, since an alternative is a simpler attribution_reviewed_at timestamptz (null = needs review).
4. Let edits carry attribution (closes the review-flow gap)
alter table resource_edits
add column suggested_provider_ids bigint[]; -- null = no change proposedSemantics matter here:
NULL→ the edit proposes no attribution change (leaveresource_providersalone)- non-null → replace the resource's provider set with exactly this list
{}→ explicitly remove all attribution
approve_edit() syncs resource_providers when the column is non-null. An array (rather than a parallel resource_edit_providers join table) keeps the current-vs-suggested diff trivial to compute and render in the compare view.
5. Fix the source type
- Add
MIGRATIONtoDataSourceTypeinapp/types/ResourceEntry.ts— it describes 46% of live rows - Validate
source.urlon write
Scope
- Migration for 1–4 above
- Update
approve_edit()to syncresource_providersfromsuggested_provider_ids -
DataSourceTypefix +source.urlvalidation - Backfill: map
source.urldomain →providers.source_domainfor the ~35 unattributed scraped resources - Seed real providers (Share Food Program, Sharing Excess); remove "Test Provider"
- Set
attribution_status = 'NONE'in bulk where a provider is not applicable (likely mostFORAGE)
Out of scope / related
- Assigning providers inside the scrapers belongs with phlask-map #727 / #697 — this issue just gives those scrapers the
slug/source_domainto resolve against - Display is already done (phlask-map#692)
Reference
Builds on #24 (resource_edits review pipeline) and #3 (schemas & views).
Activity
vontell commentedon Jul 27, 2026
Closing — the existing model already covers this.
Checked what the frontend actually reads (
getResourceProviders):ProvidedByneeds onlyproviders.name,logo_url, andwebsite_urljoined throughresource_providers— all of which exist. It never readsresources.source. No schema change is needed for attribution to display.What's left is data, not structure: only 2 provider rows exist (one is a "Test Provider" with no logo), and most unattributed resources are
FORAGE/WATER, which legitimately have no provider.Generated by Claude Code