Celery Beat Schedule¶
Daily scheduled tasks that run automatically via Celery Beat.
Task Schedule¶
| Task | Schedule | Time | Purpose |
|---|---|---|---|
| Interest accrual | Daily | --- | Accrue daily interest on all active loans |
| Amortization period transitions | Daily | --- | Transition period statuses (future → due → overdue) |
| Delinquency engine | Daily | 2:30 AM | Recalculate DPD, update buckets, fire webhook events |
| Default detection | Daily | --- | Auto-transition loans exceeding DPD threshold to defaulted |
| Late fee assessment | Daily | --- | Assess late fees per fee schedule rules |
| Broken promise detection | Daily | --- | Check for unfulfilled promise-to-pay commitments |
| Dunning execution | Daily | 4:00 AM | Send dunning communications based on DPD triggers |
| Scheduled fee assessment | Daily | --- | Assess recurring servicing fees |
| Variable rate adjustment | Daily | --- | Update variable-rate loan rates from latest index |
| Portfolio snapshots | Daily | --- | Capture aggregate portfolio metrics |
| Lien expiration check | Daily | --- | Alert on liens expiring within 180 days |
| Forbearance expiry | Daily | --- | Auto-complete expired forbearance agreements |
| Non-accrual evaluation | Daily | --- | Place/clear loans on non-accrual based on DPD threshold |
| Collection queue assignment | Daily | --- | Auto-assign delinquent loans to collection queues |
Dispatch Pattern¶
Beat dispatch tasks run in the public schema and fan out to individual tenants:
@shared_task
def dispatch_daily_interest_accrual() -> None:
"""Fan out interest accrual to all active tenants."""
for tenant in Tenant.objects.filter(status="active"):
# Each tenant task uses TenantTask base to activate the schema
accrue_interest_for_tenant.delay(schema_name=f"tenant_{tenant.slug}")
Each per-tenant task uses the TenantTask base class, which activates the correct PostgreSQL schema before execution.
Task Dependencies¶
Some tasks have implicit ordering requirements:
- Interest accrual should run before delinquency engine (ensures accrued interest is current)
- Delinquency engine should run before late fee assessment (DPD must be current for fee triggers)
- Delinquency engine should run before dunning execution (bucket must be current for dunning triggers)
- Amortization period transitions should run before delinquency engine (period statuses must be current)
These dependencies are handled through task scheduling (ordering by time) rather than explicit Celery chains.
Configuration¶
The beat schedule is defined in config/celery.py:
app.conf.beat_schedule = {
"accrue-daily-interest": {
"task": "apps.loans.tasks.dispatch_daily_interest_accrual",
"schedule": crontab(hour=1, minute=0),
},
"run-delinquency-engine": {
"task": "apps.collections.tasks.dispatch_delinquency_engine",
"schedule": crontab(hour=2, minute=30),
},
# ... additional tasks
}
Monitoring¶
- Tasks log start/completion timestamps for auditing
- Failed tasks are retried based on individual task retry configuration
- Celery Flower or similar monitoring tools can track task execution
See Also¶
- Cross-Module Communication --- Celery Canvas workflows
- Async Patterns --- TenantTask and async dispatch
- Collections --- Delinquency engine details