# Phase Handover — Loyalty 7.2 Point Engine ## Metadata | Field | Value | | --- | --- | | Phase ID | loyalty-7.2 | | Title | Point Engine (immutable ledger) | | Status | Complete | | Service(s) | loyalty (`loyalty-service`, port 8004) | | Version | 0.7.2.0 | | Date | 2026-07-26 | | ADR(s) | ADR-001, ADR-003, ADR-006, ADR-011 | ## Reusable Components | Component | Location | Reuse notes | | --- | --- | --- | | PointLedgerEntry | `app/models/ledger.py` | Append-only pattern for wallet/gift (7.6/7.7) | | PointLedgerRepository | `app/repositories/ledger.py` | Balance sum + filtered list | | PointEngineService | `app/services/point_engine.py` | Earn/redeem/adjust/expire orchestration | | Ledger validators | `app/validators/ledger.py` | Signed amounts + open-account checks | ## Public APIs | Method | Path | Auth / Permission | Notes | | --- | --- | --- | --- | | GET | `/api/v1/point-accounts/{id}/balance` | `loyalty.points.view` | Computed balance | | GET | `/api/v1/point-accounts/{id}/ledger` | `loyalty.points.view` | Paginated entries | | POST | `/api/v1/point-accounts/{id}/earn` | `loyalty.points.earn` | Idempotent optional | | POST | `/api/v1/point-accounts/{id}/redeem` | `loyalty.points.redeem` | Rejects insufficient | | POST | `/api/v1/point-accounts/{id}/adjust` | `loyalty.points.adjust` | Signed delta | | POST | `/api/v1/point-accounts/{id}/expire-points` | `loyalty.points.expire` | Debit expire | ## Events | Event type | Domain / Integration | Payload summary | Version | | --- | --- | --- | --- | | `loyalty.points.earned` | Domain | account, amount, balance_after | 1 | | `loyalty.points.redeemed` | Domain | account, amount, balance_after | 1 | | `loyalty.points.adjusted` | Domain | account, amount, balance_after | 1 | | `loyalty.points.expired` | Domain | account, amount, balance_after | 1 | ## Extension Points | Extension point | How to extend | Forbidden uses | | --- | --- | --- | | Ledger entry types | Add enum + validators + tests | Mutate existing rows | | Balance source | Always SUM(ledger) | Add mutable balance column | | Reward redemption | Call PointEngine from 7.3 | Bypass ledger for rewards | ## Known Limitations - No scheduled expire worker - No per-lot FIFO expiration - Member transfer does not move ledger rows ## Migration Notes | Item | Detail | | --- | --- | | Alembic revision(s) | `0003_phase_72_points` | | Upgrade steps | `alembic upgrade head` in loyalty service | | Downgrade support | Yes (drops table) | | Data backfill | None | | Breaking changes | None (additive) | ## Dependencies | Dependency | Type | Required for | | --- | --- | --- | | loyalty-7.1 | Phase | Membership + point accounts | | shared-lib | Library | Events, security, exceptions | ## Next Phase Entry 1. This handover + [loyalty-phase-7-2.md](../loyalty-phase-7-2.md) 2. Service snapshot + manifests 3. Build Rewards Engine on top of PointEngine redeem — do not mutate balances directly 4. Suggested next: `loyalty-7.3` Rewards Engine | Field | Value | | --- | --- | | Recommended next phase | loyalty-7.3 | | Blockers for next phase | None | | Entry checklist | Read 7.2 handover; keep ledger immutable; reuse PointEngineService | ## Completion Sign-Off - [x] Quality gates passed - [x] Tests green (61) - [x] Documentation updated - [x] Progress / next-steps / registries updated - [x] Service Snapshot regenerated - [x] No TODO for claimed deliverables ## Related Documents - [loyalty-phase-7-2.md](../loyalty-phase-7-2.md) - [loyalty-phase-7-1.md](../loyalty-phase-7-1.md) - [ADR-011](../architecture/adr/ADR-011.md)