Skip to content

feat: add a Salesforce Nonprofit Cloud gift export reader (ingest preset) #234

Description

@shivamlalakiya

What's needed

philanthropy.ingest has three donor-CRM gift bridges today: CiviCRM, Blackbaud Raiser's Edge, and Salesforce NPSP. Each is a thin, CRM-specific header-alias map plus a commitment/payment exclusion filter (a pledge and the payment against it must never both count toward total_gift_amount), delegating the actual roll-up to the shared aggregator in philanthropy/ingest/_civicrm.py. This issue adds the same bridge for Salesforce Nonprofit Cloud — Salesforce's newer nonprofit data model (Gift, Payment, and related objects), distinct from the older NPSP package this repo already supports.

Where to look

File Purpose
philanthropy/ingest/_npsp.py Closest existing example: same underlying platform (Salesforce), header aliasing, a documented default exclusion set, delegation to the shared aggregator
philanthropy/ingest/_raisers_edge.py A second example of the same pattern
philanthropy/ingest/_civicrm.py The shared aggregator (civicrm_contributions_to_features, read_civicrm_contributions) and the private helpers (_REQUIRED, _canonical, _normalise_headers, _to_frame, _empty_feature_frame) both examples import and reuse
tests/test_npsp.py The test depth expected of your new tests/test_nonprofit_cloud.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 extracting the shared aggregator into a source-keyed dispatcher) has already landed by the time you start, in which case register there instead

What to build

  1. philanthropy/ingest/_nonprofit_cloud.py: a header-alias map from Nonprofit Cloud's real export labels and API field names onto the canonical contact_id / receive_date / total_amount / gift_type names _civicrm.py already understands; a documented default exclusion parameter for whatever this data model's pledge/commitment-vs-payment split actually is (Nonprofit Cloud separates Gift and Payment as related objects rather than NPSP's stage-on-one-Opportunity approach — find out exactly how from the docs, don't assume it matches NPSP); a nonprofit_cloud_gifts_to_features(...) function; and a read_nonprofit_cloud_gifts(path) reader delegating to read_civicrm_contributions.
  2. Verify every field name and every commitment/payment vocabulary term against Salesforce's own public Nonprofit Cloud documentation before writing it down — do not guess or recall from memory. Cite the doc URLs you actually used in your PR body, the way PR #233 does for NPSP. Start from Salesforce's own help site and Trailhead, searching for "Nonprofit Cloud" data model / Gift / Payment objects — this issue does not assert any field name as fact, confirm everything yourself.
  3. 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, same reasoning as the other three bridges — a test enforces every public symbol has a row).
  4. tests/test_nonprofit_cloud.py at the depth of tests/test_npsp.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.
  5. 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/. This is a file-export reader (CSV in, DataFrame out), never a live API connector.
  • No new estimator class.
  • Build the exclusion logic from Salesforce'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 "from philanthropy.ingest import nonprofit_cloud_gifts_to_features, read_nonprofit_cloud_gifts; print('OK')", then write tests, then make ci.

Verifying

python -m pip install -e ".[dev]"
python -c "from philanthropy.ingest import nonprofit_cloud_gifts_to_features, read_nonprofit_cloud_gifts; print('OK')"
make ci
make riskcov

Claim this issue with a comment before starting (see CONTRIBUTING.md); a maintainer assigns within 24 hours.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions