Opportunity App¶
The opportunity app is the core domain of CommCare Connect. It manages paid programs where mobile users learn skills and deliver health services in exchange for payment.
Key Concepts¶
- Opportunity — A paid program created by an organization. It has a learn app (training), a deliver app (fieldwork), a budget, and a time window.
- OpportunityAccess — Links a user to an opportunity. Tracks their journey: invited → accepted → learning → claimed → delivering → paid.
- OpportunityClaim — Created when a user commits to delivering work. Allocates visit limits per payment unit.
- PaymentUnit — Defines what a user gets paid for and how much. Can be hierarchical (parent/child).
- DeliverUnit — A type of form submission within a delivery app (e.g., "household visit", "follow-up").
- Task — A named task within a learn/deliver app, distinct from a LearnModule.
- TaskUnit — A task module from the delivery app XML. A task unit links to a task.
- UserVisit — A single form submission from a mobile user during delivery.
- CompletedWork — Aggregates visits for a specific entity (beneficiary) and payment unit. Tracks approval status and payment.
- AssignedTask — Tracks completion of individual tasks by a user (assigned → completed).
- Payment — A confirmed payment to a user for approved work.
- PaymentInvoice — An invoice for a managed opportunity. Tracks status through NM/PM review workflow and links to completed work and payments.
User Journey¶
1. Invitation¶
An organization invites mobile users to an opportunity. This creates an access record and sends an SMS with an invite link.
2. Acceptance¶
The user clicks the invite link and accepts the opportunity.
3. Learning¶
The user opens the learn app in CommCare. Forms submitted in CommCare HQ are sent to Connect via the form receiver. Each completed module and assessment is recorded. Once all modules are complete, the user can proceed to claiming.
4. Claiming¶
After completing learning, the user claims the opportunity via the API. This allocates their share of the budget as visit limits per payment unit. A CommCareHQ mobile worker account is created if one doesn't exist.
5. Delivery¶
The user submits forms through the deliver app in CommCare. Each form submission is processed into a visit record, checked against verification flags, and aggregated into completed work. Payment amounts are calculated and cached.
6. Payment¶
Approved work accrues payment. Payments can be distributed manually via CSV upload or through the invoicing workflow for managed opportunities. See PAYMENTS.md for how accrual is calculated and how invoicing works.
Verification Flags¶
When a delivery form is received, it is checked against the opportunity's OpportunityVerificationFlags. Any failures are stored in UserVisit.flag_reason as a JSON list, and UserVisit.flagged is set to True.
| Flag | Check | Configured on |
|---|---|---|
duplicate |
Entity already visited | OpportunityVerificationFlags |
gps |
GPS location missing | OpportunityVerificationFlags |
location |
Visit too close to another visit | OpportunityVerificationFlags |
form_submission_period |
Outside allowed time window | OpportunityVerificationFlags |
duration |
Form completed too quickly | DeliverUnitFlagRules |
attachment_missing |
Required attachments missing | DeliverUnitFlagRules |
form_value_not_found |
Custom form validation failed | FormJsonValidationRules |
user_suspended |
User is suspended | OpportunityAccess.suspended |
pending_task |
Worker has an incomplete task | Always on (AssignedTask) |
Flag codes are defined in commcare_connect/utils/flags.py. It also keeps catchment, whose check has been removed, so that old flag_reason values still have a label. user_suspended and pending_task reject the visit outright; see the visit status table in ../form_receiver/README.md. If auto_approve_visits is enabled and the visit is not flagged, the visit is automatically approved.
Auto-Approval¶
Two levels of auto-approval can be enabled per opportunity:
auto_approve_visits— Unflagged visits are automatically set toapprovedauto_approve_payments—CompletedWorkis automatically approved when all visits are approved (runs viabulk_approve_completed_workCelery task)
Model Relationships¶
Organization
├── CommCareApp
│ ├── LearnModule
│ ├── Task
│ └── DeliverUnit → PaymentUnit
└── Opportunity (refs learn_app, deliver_app)
├── PaymentUnit (amount, limits)
│ └── PaymentUnit (child, optional)
├── OpportunityVerificationFlags (1:1)
├── DeliverUnitFlagRules (per DeliverUnit)
├── FormJsonValidationRules (per DeliverUnit, M2M)
├── PaymentInvoice
│ └── Payment (1:1)
└── OpportunityAccess (per user)
├── OpportunityClaim (1:1)
│ └── OpportunityClaimLimit (per PaymentUnit)
├── CompletedModule (per LearnModule)
├── AssignedTask (per Task)
├── Assessment (per attempt)
├── CompletedWork (per entity + PaymentUnit)
│ └── UserVisit (per form submission)
└── Payment (per PaymentUnit, for FLW payments)
Key Files¶
| File | Purpose |
|---|---|
models/ |
Models, one module per domain; import them from the package root |
api/views.py |
Mobile API: claim, progress, delivery |
views/ |
Web UI; invoice views in invoices.py, the rest in __init__.py |
tasks.py |
Celery tasks: invites, notifications, auto-approval, exports |
forms/ |
Django forms; invoice forms in invoices.py, the rest in __init__ |
tables/ |
Tables; invoice tables in invoices.py, the rest in __init__.py |
export.py |
Exports: visits, payments, user status, work status |
visit_import.py |
Bulk visit/payment import, exchange rates |
deletion.py |
delete_opportunity() for stale opportunity cleanup |
utils/completed_work.py |
CompletedWorkUpdater: status calculation and payment accrual |
utils/invoice.py |
Invoice number generation and start date utilities |
../form_receiver/processor.py |
Processes forms from CommCareHQ into modules, assessments, and visits |
views/, forms/ and tables/ are being split by feature. A feature module such as invoices.py imports shared
helpers from its package's __init__.py; the __init__.py never imports it back, so import its names from the
submodule itself.