What's needed
Same pattern as the other CRM bridges in philanthropy.ingest (CiviCRM, Raiser's Edge, NPSP): a thin, CRM-specific header-alias map plus a commitment/payment exclusion filter, delegating the roll-up to the shared aggregator in philanthropy/ingest/_civicrm.py. This issue adds the bridge for DonorPerfect.
Where to look
| File |
Purpose |
philanthropy/ingest/_raisers_edge.py |
Closest existing example: header aliasing, a documented default exclusion set, delegation to the shared aggregator |
philanthropy/ingest/_civicrm.py |
The shared aggregator (civicrm_contributions_to_features, read_civicrm_contributions) and its private helpers |
tests/test_raisers_edge.py |
The test depth expected of your new tests/test_donorperfect.py |
philanthropy/cli.py |
_FEATURE_SOURCES and the features --source dispatch — wire your new source in the same shape, unless the read_gifts preset registry (a separate, in-flight PR) has already landed, in which case register there instead |
What to build
philanthropy/ingest/_donorperfect.py: a header-alias map from DonorPerfect's real export labels (its "gifts" table export) and its API field names onto the canonical contact_id / receive_date / total_amount / gift_type names; a documented default exclusion parameter for whatever DonorPerfect's pledge-vs-payment split actually is (DonorPerfect's data model separates pledges from gifts differently than Raiser's Edge or NPSP — find out exactly how from the docs); a donorperfect_gifts_to_features(...) function; and a read_donorperfect_gifts(path) reader delegating to read_civicrm_contributions.
- Verify every field name and every commitment/payment vocabulary term against DonorPerfect's own public documentation before writing it down. Cite the doc URLs you actually used in your PR body, the way PR #233 does for NPSP. This issue does not assert any field name as fact — confirm everything yourself.
- Export the new names from
philanthropy/ingest/__init__.py (__all__, alphabetical) and add a stability-tier row to docs/reference/index.md (Tier 2/Beta — a test enforces every public symbol has a row).
tests/test_donorperfect.py at the depth of tests/test_raisers_edge.py: header aliasing from every spelling the docs name, the commitment/payment double-count trap and its exclusion, the exclusion set being documented/overridable/disabled by None, currency parsing, schema/dtypes, empty input, missing required columns.
CHANGELOG.md entry under ## [Unreleased], and add yourself to CONTRIBUTORS.md if you'd like.
Hard constraints (same as the rest of this ingest series)
- Dependencies stay scikit-learn/pandas/numpy/joblib only — no new package, no API client, no network code anywhere in
philanthropy/. File-export reader only, never a live API connector.
- No new estimator class.
- Build the exclusion logic from DonorPerfect's own public docs only — never from an institution's actual configuration or data.
- Follow AGENTS.md's order: implement, export, verify with
python -c "...", then write tests, then make ci.
Verifying
python -m pip install -e ".[dev]"
python -c "from philanthropy.ingest import donorperfect_gifts_to_features, read_donorperfect_gifts; print('OK')"
make ci
make riskcov
Claim this issue with a comment before starting; a maintainer assigns within 24 hours.
What's needed
Same pattern as the other CRM bridges in
philanthropy.ingest(CiviCRM, Raiser's Edge, NPSP): a thin, CRM-specific header-alias map plus a commitment/payment exclusion filter, delegating the roll-up to the shared aggregator inphilanthropy/ingest/_civicrm.py. This issue adds the bridge for DonorPerfect.Where to look
philanthropy/ingest/_raisers_edge.pyphilanthropy/ingest/_civicrm.pycivicrm_contributions_to_features,read_civicrm_contributions) and its private helperstests/test_raisers_edge.pytests/test_donorperfect.pyphilanthropy/cli.py_FEATURE_SOURCESand thefeatures --sourcedispatch — wire your new source in the same shape, unless theread_giftspreset registry (a separate, in-flight PR) has already landed, in which case register there insteadWhat to build
philanthropy/ingest/_donorperfect.py: a header-alias map from DonorPerfect's real export labels (its "gifts" table export) and its API field names onto the canonicalcontact_id/receive_date/total_amount/gift_typenames; a documented default exclusion parameter for whatever DonorPerfect's pledge-vs-payment split actually is (DonorPerfect's data model separates pledges from gifts differently than Raiser's Edge or NPSP — find out exactly how from the docs); adonorperfect_gifts_to_features(...)function; and aread_donorperfect_gifts(path)reader delegating toread_civicrm_contributions.philanthropy/ingest/__init__.py(__all__, alphabetical) and add a stability-tier row todocs/reference/index.md(Tier 2/Beta — a test enforces every public symbol has a row).tests/test_donorperfect.pyat the depth oftests/test_raisers_edge.py: header aliasing from every spelling the docs name, the commitment/payment double-count trap and its exclusion, the exclusion set being documented/overridable/disabled byNone, currency parsing, schema/dtypes, empty input, missing required columns.CHANGELOG.mdentry under## [Unreleased], and add yourself toCONTRIBUTORS.mdif you'd like.Hard constraints (same as the rest of this ingest series)
philanthropy/. File-export reader only, never a live API connector.python -c "...", then write tests, thenmake ci.Verifying
Claim this issue with a comment before starting; a maintainer assigns within 24 hours.