> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wegive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Person Mapping

> Detailed mapping between WeGive donors and Planning Center person records with contact information

# Person Mapping

This document details the real mapping between WeGive donor records and Planning Center Person objects, including contact information. The mapping is narrower than a full CRM sync — no demographic fields, no household/family relationships, and matching is by correlation ID only.

## Core Person Mapping

### Primary Fields

| WeGive Field | Planning Center Field | Direction | Notes |
| - | - | - | - |
| `planning_center_id` | `id` | Both | The **only** correlation/matching key |
| `first_name` | `first_name` | Both | Sent as literal `"FNU"` on push if blank; restored to `null` on pull if it equals `"FNU"` |
| `last_name` | `last_name` | Both | Same `"LNU"` placeholder behavior |

<Note>
  There is no `middle_name` pulled back (it's push-only), and no `suffix`, `birthdate`, `anniversary`, `gender`, `created_at`, or `updated_at` field mapping. Planning Center's own `status` attribute is pulled and mapped to WeGive's `enabled` flag (`status !== 'inactive'`) — that's the only other person-level field synced.
</Note>

## Contact Information Mapping

### Email Address Management

**Fields synced:** up to 3 emails, positional — `email_1`, `email_2`, `email_3`.

**Push behavior:**

* Only runs when a donor is first created, or on every update via `syncDonorContactInfo()`
* Fetches the person's current Planning Center emails first; an email already present (exact string match) is skipped, not re-sent
* New emails are posted with `primary: true` only for `email_1`; `email_2`/`email_3` are `primary: false`
* Location is always the literal string `"Email"` — there's no "home"/"work"/"other" categorization
* A 409 or 422 response (Planning Center considers the email a duplicate) is treated as success and skipped silently — not an error

**Pull behavior:** imports up to 3 emails from Planning Center by array position (not by any "primary" designation) into `email_1`/`email_2`/`email_3`.

### Phone Number Management

**Fields synced:** up to 2 phone numbers, positional — `mobile_phone`, `office_phone`. There is no `home_phone`/`work_phone` distinction anywhere in the code.

**Push behavior:**

* Same current-value-check-then-post pattern as email
* `primary: true` only for `mobile_phone`
* Location is always the literal string `"Phone Number"` — no "mobile"/"home"/"work" categorization
* Same 409/422-is-a-silent-skip behavior as email

**Pull behavior:** imports up to 2 phone numbers by position into `mobile_phone`/`office_phone`.

### Address Management

**Fields synced:** one mailing address only — `address_1` (from `street_line_1`), `city`, `state`, `zip`. There's no `street_2`, no country field mapped on push, and no address `type`/categorization beyond the fixed location string `"Mailing Address"`.

**Push behavior:**

* Only syncs if the address has non-empty `address_1`, `city`, `state`, **and** `zip` — an incomplete address is skipped entirely, not partially synced
* Finds the existing address marked `primary` in Planning Center and PATCHes it if found, otherwise POSTs a new one
* Always sent with `primary: true`

**Pull behavior:** imports the first address in the response array (`addresses.0`) into WeGive's mailing address, with a country default of `"US"` if Planning Center doesn't provide one.

## Data Transformation

### Person Creation Processing (Push)

1. Acquire a per-donor Redis lock (600s) to prevent duplicate creation under concurrent pushes
2. Create the Person with `first_name`/`last_name` only (`"FNU"`/`"LNU"` placeholders if blank)
3. Store the returned `id` as `planning_center_id`
4. Call `syncDonorContactInfo()` to add emails/phones/address

### Person Update Processing (Push)

1. PATCH the Person's `first_name`/`last_name` by `planning_center_id`
2. If the PATCH returns 404 (person deleted in Planning Center), clear the stale `planning_center_id` and create a new Person instead — this is a full re-create, not a narrower "confirm and skip" check
3. Call `syncDonorContactInfo()` again regardless

### Person Import Processing (Pull)

1. Match by `planning_center_id`; create a new WeGive Donor if no match
2. Map `first_name`/`last_name`/`middle_name`/`status` and the positional email/phone arrays
3. Create or update the mailing address from `addresses.0`

## Matching and Deduplication

### Person Matching Strategy

**The only matching mechanism, on push and pull alike, is `planning_center_id`.** There is no email-based, name-based, or partial-name-plus-phone matching anywhere in this integration. A donor without a stored `planning_center_id` always results in a new Planning Center Person being created — even if a person with the same name or email already exists there.

<Note>
  This matters for duplicate-contact tickets: the fix isn't "improve matching logic" (there is none to improve) — it's ensuring the donor gets linked to the correct existing Planning Center person (via `planning_center_id`) before its first push.
</Note>

### Conflict Resolution

* **Push:** WeGive's `first_name`/`last_name` always overwrite Planning Center's on update; contact info is additive (existing values aren't removed, missing values are added if not already present)
* **Pull:** Planning Center's data always overwrites WeGive's mapped fields

## Validation Rules

* Planning Center requires at least one of `first_name`/`last_name` — WeGive satisfies this unconditionally via the `"FNU"`/`"LNU"` placeholders, so a blank-name donor never fails Person creation
* Email/phone addition is skipped gracefully (not treated as an error) if Planning Center reports it as a duplicate
* An address with any of `address_1`/`city`/`state`/`zip` empty is skipped entirely on push — there's no partial-address sync

## Known Gaps (not implemented)

* Demographic fields: birthdate, anniversary, gender
* Household/family relationships of any kind
* Suffix, nickname, or middle-name-on-pull
* More than 3 emails or 2 phone numbers
* More than one address, or any address type beyond "mailing"
* Communication/opt-out preference sync
* Any performance/caching optimization beyond the existing-value check before each push


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.