fair-lending-screener
Statistical lending-disparity screening for public HMDA mortgage data, using FFIEC-standard controls — open-sourced for community advocates and investigative journalists.
What This Tool Does / Does Not Do
DOES:
- Run adjusted denial disparity analysis on public HMDA mortgage data
- Use binary logistic regression with FFIEC-standard controls (income, LTV, DTI, property value, MSA)
- Report adjusted odds ratios with 95% confidence intervals and p-values
- Generate journalist-legible Markdown reports explaining what was found and what it means
- Cite the FFIEC fair-lending risk-factor framework it is informed by (Interagency Fair Lending Examination Procedures, 2009)
- Tell you clearly what it cannot conclude
DOES NOT:
- Prove discrimination — it identifies statistical screening signals warranting further review
- Include credit score, AUS recommendations, or asset data (not in public HMDA)
- Analyze Section 1071 small business lending (different statute, different data)
- Replace a full fair lending examination by federal regulators with access to internal lender data
- Use black-box ML — every result is an auditable logistic regression you can reproduce
Alpha release (v0.2.2). Methodology peer review by an external fair lending expert is planned before v1.0.0. Use as a screening tool to identify cases warranting further analysis, not as a basis for enforcement or accusation.
Why It Exists
Community advocates, investigative journalists, and fair housing nonprofits routinely need to answer the question: "Are these denial rate disparities statistically suspicious after controlling for income, loan size, and geography?"
Currently that analysis requires either:
- $50K+ commercial software (ComplianceTech, RiskExec, RATA Comply, Abrigo) — out of reach for nonprofits
- Stata fluency — out of reach for most journalists
- Months of methodology work — The Markup spent months building their 2021 analysis
This package makes that analysis installable in 30 seconds.
Limitations — Read This First
Public HMDA data does not include:
- Credit score — The most predictive underwriting variable, not collected in public HMDA data. Its absence means results are upper-bound estimates of the unexplained disparity.
- AUS recommendations — Fannie/Freddie DU/LP decisions, the primary underwriting tool, are not public.
- Asset and reserve data — Not reported in HMDA.
- Employment history — Not reported in HMDA.
- Underwriter override data — Discretionary overrides are internal lender data.
A statistically significant adjusted disparity does not mean the lender discriminated. It means the disparity warrants further review with full loan-file data. See docs/limitations.md for the complete list.
Installation
pip install fair-lending-screener
Both import styles work:
import fair_lending_screener
import fairlendingscreener # alias — identical
Quickstart
import warnings
import fair_lending_screener as fls
# Load HMDA data from the CFPB public API (real data; requires internet)
df_raw = fls.load_from_api(year=2023, state="IL", limit=20_000)
# Apply FFIEC-standard dataset filters:
# conventional, first-lien, home purchase, site-built 1-4 unit,
# principal residence, LTV ≤ 100%
with warnings.catch_warnings():
warnings.simplefilter("ignore")
df = fls.prepare_for_analysis(df_raw)
# Run adjusted denial disparity analysis:
# logistic regression with income, LTV, DTI, property value, MSA controls
result = fls.adjusted_denial_disparity(
df,
protected_class="Black or African American",
comparison_class="White",
data_year=2023, # required; recorded in the provenance dict
)
# Key numbers
print(f"Unadjusted odds ratio: {result.unadjusted_odds_ratio:.2f}×")
print(f"Adjusted odds ratio: {result.adjusted_odds_ratio:.2f}×")
print(f"95% CI: {result.confidence_interval_95[0]:.2f}–{result.confidence_interval_95[1]:.2f}×")
print(f"p-value: {result.p_value:.4f}")
print(f"Statistically significant: {result.is_statistically_significant}")
print(f"Sample size: {result.sample_size:,}")
# Generate a journalist-legible Markdown report
report = fls.generate_disparity_report(
result,
lender_name="First Midwest Bank", # optional — suppressed if result is not statistically significant
geography="Illinois",
year=2023,
)
print(report)
Using Synthetic Sample Data (no internet required)
import warnings
import fair_lending_screener as fls
# Synthetic data for testing — NOT real HMDA, NOT for conclusions
raw = fls.load_sample(n=2000, seed=42)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
df = fls.prepare_for_analysis(raw)
result = fls.adjusted_denial_disparity(
df,
protected_class="Black or African American",
comparison_class="White",
data_year=2023, # required; recorded in the provenance dict
)
print(f"Adjusted OR: {result.adjusted_odds_ratio:.2f}×, p={result.p_value:.4f}")
Understanding the Output
Unadjusted vs. Adjusted Odds Ratio
- Unadjusted: Raw denial rate disparity — no controls for income, loan size, or geography
- Adjusted: Disparity after statistically holding constant income (log), loan amount (log), LTV, DTI, property value (log), and MSA fixed effects
An adjusted odds ratio of 1.8× means: among applicants who look similar on paper — same income, loan size, LTV, property value, and MSA — Black applicants faced 80% higher odds of denial than White applicants.
The difference between the two ratios shows how much of the raw disparity is explained by the available controls.
What "Statistically Significant" Means
A result is flagged as statistically significant when:
- p-value < 0.05 (the disparity is unlikely to be due to chance)
- 95% CI excludes 1.0 (the direction of the disparity is reliable)
Both conditions must hold. A large odds ratio with p = 0.08 is not reported as significant.
What Controls Are Used
Controls (FFIEC-standard borrower and loan characteristics):
| Control | Notes |
|---|---|
log(applicant_income) |
Ability-to-repay; log-transformed for skew |
log(loan_amount) |
Loan size |
loan_to_value_ratio |
Collateral coverage |
debt_to_income_ratio |
Binned: ≤35%, 36–42%, 43–49%, ≥50%, missing |
log(property_value) |
Collateral value |
| MSA fixed effects | ~300–400 dummies for local market conditions |
How This Compares to Commercial Tools
| fair-lending-screener | Commercial tools (ComplianceTech, RATA Comply, etc.) | |
|---|---|---|
| Cost | Free, open-source | $20K–$100K+/year |
| Data | Public HMDA only | Internal lender data + HMDA |
| Credit score | Not available (public HMDA) | Available via lender data feed |
| AUS data | Not available | Available |
| Methodology | Published, citable, auditable | Proprietary |
| Target user | Advocates, journalists, researchers | Lender compliance teams |
| Intended use | Screening signals for advocacy/research | Regulatory compliance management |
Methodology
Full methodology documentation is in docs/methodology.md. Every statistical decision cites a regulatory or academic source.
Short version:
- Binary logistic regression (
statsmodels.api.Logit) — not sklearn, not ML - Outcome:
action_taken == 3(denied) vs.action_taken == 1(originated) - Protected class:
derived_race(self-reported per Regulation C) - Controls: FFIEC standard set (income, LTV, DTI, property value, MSA)
- Dataset filters: conventional, first-lien, home purchase, site-built 1–4 unit, owner-occupied, LTV ≤ 100%
- Calibration target: The Markup (2021) found 1.8× adjusted OR for Black vs. White applicants nationally. Expected range from this tool: 1.6–2.2× (above The Markup's figure because we omit AUS and credit score — known upward-bias direction per Wooldridge 2019 §3.3)
Regulatory context: This tool is informed by the FFIEC Interagency Fair Lending Examination Procedures (2009), which identify lending disparities as fair-lending risk factors warranting further review. It is not the methodology examiners use: fair-lending examiners work from full loan files — including credit scores and AUS records obtained under supervisory authority — and, for lenders using credit scoring or with significant volume, follow specialized agency statistical procedures rather than a public-data model. This is a public-data screening tool, not a supervisory examination.
Roadmap
Under consideration:
- Extended control set from public HMDA (census-tract demographics, additional loan attributes)
- BISG race/ethnicity proxy for non-HMDA products (auto, student)
- Redlining geographic analysis (census-tract lender presence)
- Peer benchmarking (lender vs. market comparison)
- Multilevel/hierarchical MSA modeling
Evaluated and deferred:
- Pricing / rate-spread disparity — public HMDA lacks the credit-risk control needed for a defensible adjusted estimate (evaluated in a multi-round methodology review).
- AUS / credit-model / full-loan-file controls — not available in public HMDA.
Scope
Mortgage lending (HMDA-reportable transactions) only.
- Does NOT analyze Section 1071 small business lending
- Does NOT include credit score (not in public HMDA)
- Does NOT constitute a finding of discrimination
- DOES identify statistically significant adjusted disparities warranting further review
Methodology Feedback
Open a GitHub issue tagged methodology with specific concerns and citations. All methodology changes are versioned and documented in CHANGELOG.md.
Citation
If you use this tool in research or journalism, please cite it:
Patel, Jay (2026). fair-lending-screener (v0.2.2). MIT License.
https://github.com/Jaypatel1511/fair-lending-screener
See CITATION.cff for full citation metadata.
License
MIT License. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fair_lending_screener-0.2.2.tar.gz.
File metadata
- Download URL: fair_lending_screener-0.2.2.tar.gz
- Upload date:
- Size: 208.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f57a12dfa31c856f58a93952bd2d0fddc72ab82ef0ac0c2818ec8ee2f31e434a
|
|
| MD5 |
10c784ba92b4e7ff0c687b3528fba302
|
|
| BLAKE2b-256 |
e8b9a0abefe791cf959df32ceae51c20198f12f3dd8ab7bb78cec3fecd666489
|
Provenance
The following attestation bundles were made for fair_lending_screener-0.2.2.tar.gz:
Publisher:
release.yml on Jaypatel1511/fair-lending-screener
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fair_lending_screener-0.2.2.tar.gz -
Subject digest:
f57a12dfa31c856f58a93952bd2d0fddc72ab82ef0ac0c2818ec8ee2f31e434a - Sigstore transparency entry: 2188026124
- Sigstore integration time:
-
Permalink:
Jaypatel1511/fair-lending-screener@1a319a28a7e9d04cd129f65f5689b732740f879f -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/Jaypatel1511
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1a319a28a7e9d04cd129f65f5689b732740f879f -
Trigger Event:
push
-
Statement type:
File details
Details for the file fair_lending_screener-0.2.2-py3-none-any.whl.
File metadata
- Download URL: fair_lending_screener-0.2.2-py3-none-any.whl
- Upload date:
- Size: 51.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edc2100880eb7cba8e5a7772e61b5d97a7b138799a1ded84e52e1d03896be1a8
|
|
| MD5 |
33b111f630a0a2bc66d05183291d32c9
|
|
| BLAKE2b-256 |
55a3f99483006a656828e90572c9b9671f19701930d842046538400efcf41f62
|
Provenance
The following attestation bundles were made for fair_lending_screener-0.2.2-py3-none-any.whl:
Publisher:
release.yml on Jaypatel1511/fair-lending-screener
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fair_lending_screener-0.2.2-py3-none-any.whl -
Subject digest:
edc2100880eb7cba8e5a7772e61b5d97a7b138799a1ded84e52e1d03896be1a8 - Sigstore transparency entry: 2188026143
- Sigstore integration time:
-
Permalink:
Jaypatel1511/fair-lending-screener@1a319a28a7e9d04cd129f65f5689b732740f879f -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/Jaypatel1511
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1a319a28a7e9d04cd129f65f5689b732740f879f -
Trigger Event:
push
-
Statement type: