Where every figure comes from
One page for the sources and methodology behind San Francisco’s budget, payees, contracts and payroll — the datasets, their perimeters, the privacy rules and the choices we made reading them. Each content page keeps its per-section source line; the full method lives here.
- The chart of accounts switched (legacy numeric codes → modern mnemonics), and FY2018 itself mixes both systems — so this site shows citywide, service-area and fund totals for FY2018, but no department or category breakdown.
- Program detail collapses: before 2018, hundreds of real per-department programs; from FY2019, ~10 generic activity tags (Operating, Capital, Administrative…).
- Contract numbers and nonprofit flags only begin in FY2018 — earlier payments can’t be tied to contracts or nonprofit status.
- Vendor-level granularity changes across the break — long payee series need care.
A chart that kinks at 2018 reflects the system migration, not a change in policy. Department-level history is shown from FY2019 onward; service-area (organization group) history is comparable across the whole range.
Perimeter: Adopted budget (Annual Appropriation Ordinance), ALL funds, citywide, NET of the dataset's embedded negative 'Transfer Adjustment' rows — those lines are carried explicitly (never silently netted) as transfer_adjustment_usd at every altitude and flagged in the dept × character cells.
This year’s status: FY2025 is a closed fiscal year: the adopted budget and the year-end accounting are both final.
Two-year budgeting: San Francisco adopts a balanced two-year budget: revenue equals spending every year (tested within $10 in our pipeline), and the newest two fiscal years are adopted in the same AAO cycle.
This dataset kinks at FY2018 — the PeopleSoft systems break ↑
Display names & glosses: Department display names and category explanations are an editorial layer written for this site (provenance-flagged in the pipeline seeds, 2026-07-16). The source’s own labels are always carried alongside and never overwritten — amounts come exclusively from the source.
Population denominator: 826,079 residents (July 1, 2025 estimate) — per-resident figures exist only for fiscal years with a same-year Census estimate. U.S. Census Bureau, Population Estimates Program (PEP), Vintage 2025 — Subcounty Resident Population Estimates ↗
Sources: Budget (xdgd-c79v) — SF Controller's Office ↗ · Spending and Revenue (actuals) ↗
Data generated July 21, 2026 · source data as of July 13, 2026 · configs/countries/us.yaml → sync_socrata.py + sync_census_popest.py → raw.us_sf_* → dbt_us_staging → dbt_us_analytics → dbt_us_marts → export_us_sf.py
Source: Vendor Payments (Vouchers) data ↗ · Dataset updated July 13, 2026 · refreshed weekly by the SF Controller
Perimeter: ALL payments through the City's financial system — including flows to related government units (pension benefits, health service system premiums, community college district, citywide debt service). The city vs related split is exported per year in totals and related_top_departments; it is never netted silently.
Classification: Two manual in-session batches (2026-07-16): the top FY2025 payees, then the measured per-FY top-30 union + all-time top-200 + six study-named individual landlords (232 exact vendor strings). A classified string carries its bucket into every fiscal year it appears in; bucket is null for unclassified names. Per-FY coverage of dollars is exported as bucket_coverage_pct — strong FY2024+ (62-66%), weak pre-2018 (~26-46%), where the page shows a coverage badge. Buckets categorize payees for DISPLAY — every amount comes from the voucher dataset itself.
Default view: The page's default ranking excludes fiscal agents / debt-service banks and payroll pass-throughs (money flowing THROUGH the payee), re-includable via the page toggle. 'person' rows are never featured. A naive top-payees ranking is dominated by banks and fiscal agents (debt service and pass-through flows), NOT by service providers — read payees through their bucket.
Grant lens: Payments under contracts San Francisco classifies as 'Grant Contracts (City as Grantor)' — voucher lines joined to the contract register by contract number. Contract numbers exist FY2018+ only; 98-99% of carried contract dollars match the register (measured 2026-07-16). The Controller's nonprofit flag exists from FY2018 only (measured: zero flagged rows 2007-2017) — the nonprofit slice starts there. No name-based backfill: it would cover only 13.8% of pre-2018 dollars.
Individuals: Individual payees (mostly landlords) are published in the source data. This page labels them “individual payee” and never features them in rankings, suggestions or examples.
This dataset kinks at FY2018 — the PeopleSoft systems break ↑
Data generated July 21, 2026 · configs/countries/us.yaml → sync_socrata.py + sync_census_popest.py → raw.us_sf_* → dbt_us_staging → dbt_us_analytics → dbt_us_marts → export_us_sf.py
Source: Supplier Contracts · cqi5-hm2d · · Source updated: July 13, 2026
This dataset kinks at FY2018 — the PeopleSoft systems break ↑
- One row per contract: amounts are summed over the register’s prime-contractor rows (40 contracts carry two prime rows that net additive amendments); 75 register entries have only subcontractor rows and are excluded from money totals.
- The register’s own consumed/remaining columns fail basic arithmetic and are never published here. “Remaining” anywhere on this page is agreed minus paid, floored at zero.
- Payments can exceed the agreed amount (1,053 active contracts): payments accumulate across modifications while agreed reflects the base document.
- Per-contract payment curves come from the City’s voucher data, joined on contract number — 98.8% of voucher dollars carrying a contract number match this register; detail begins FY2018.
- 370 contracts have no recorded end date and are never counted as active; 25 placeholder end dates (year 2200) were removed.
Small groups: No dollar aggregate in this file covers fewer than 5 employees. Groups under 5 people are pooled into visible 'Other roles' rows per department; pools that would themselves stay under the threshold are folded into the department total with no row. Departments under the threshold (the Law Library in practice, 2-3 people) are included in citywide totals but not listed. Pooling moves ~1.2% of total dollars into visible 'Other roles' rows; the department-level fold is under $600k/year (<0.01% of compensation). Nothing is removed from totals. Counts of employees above fixed thresholds (>$200k…>$500k) are published exactly, including values under 5: they attach no dollar amount to any group. The source itself is public at row level (pseudonymized); this site publishes aggregates only.
Accounting basis: SF fiscal year N runs July 1 (N-1) to June 30 N. All figures are FISCAL-year accounting only — the source publishes every row under BOTH Calendar and Fiscal accountings and mixing them double-counts (the dataset's #1 trap).
Median: median/avg are per-person annual totals (an employee's rows are summed across jobs and departments first). The median is exact, not an approximation.
Overtime counter: n_ot_exceeds_salary_floored counts employees whose overtime pay exceeded their base salary, requiring salary > $1,000. Without the floor (n_ot_exceeds_salary_naive), FY2019 shows a spike that is an artifact of job-change rows carrying near-zero salaries.
For this payroll dataset specifically: department labels change format at FY2017 (every series here is keyed on stable department codes instead), and employment-type/hours fields only exist from FY2017 on — totals are unaffected.
This dataset kinks at FY2018 — the PeopleSoft systems break ↑
Population denominator: July 1 Census estimate. per_resident_usd values exist only for fiscal years with a Vintage 2025 estimate (2020-2025); SF fiscal year N ends June 30 of year N. U.S. Census Bureau, Population Estimates Program (PEP), Vintage 2025 — Subcounty Resident Population Estimates ↗
Source: Employee Compensation (88g8-5mnd) ↗ — SF Controller's Office
Data generated July 21, 2026 · configs/countries/us.yaml → sync_socrata.py + sync_census_popest.py → raw.us_sf_employee_comp → dbt_us_staging (+ seeds seed_us_sf_job_family_display / seed_us_sf_job_reclass) → dbt_us_intermediate → dbt_us_analytics → dbt_us_marts → export_us_sf_payroll.py