Delivery Model Completion Progress and Alerts
This document describes the business rules behind Delivery Model completion tracking and milestone alerts. The system reads Zoho CRM Delivery Models, maps Zoho Projects timelogs into each model's scope, calculates progress, and optionally writes progress state back to CRM and sends milestone emails.
Scope
Implemented:
- Active Delivery Model selection for normal progress calculations.
- Project, tasklist, task, and phase scope matching for Zoho Projects timelogs.
- One-time completion progress based on total logged time.
- Recurring completion progress based on current-month and overall progress.
- CRM writeback for completion fields and
Cumulated_Amount. - Milestone detection at 30%, 50%, 90%, and 100%.
- Milestone email generation through the CRM Delivery Models
send_mailaction. - Static alert recipients plus project timelog-user recipients.
- Candidate writeback deferral when alert email handling fails or recipients are missing, so unsent milestones are not consumed.
- State-sync route for progress writeback without alert emails.
Not implemented:
- Durable alert outbox or retry queue.
- Per-milestone history records.
- Alert suppression preferences per customer or project.
- A separate business object for monthly completion periods.
Code Map
-
app/automations/delivery_model_completion_calculations.pyOwns one-time and recurring progress calculations, threshold-crossing rules, current-month reset logic, and prepared CRM update payloads. -
app/automations/delivery_model_completion.pyCombines one-time and recurring rows and exposes the state-sync workflow. -
app/delivery_models/scopes.pyFilters eligible Delivery Models and normalizes scope records. -
app/delivery_models/timelog_mapping.pyMaps Zoho Projects timelog rows to the best Delivery Model scope. -
app/delivery_models/timelog_monthly.pyAggregates recurring timelogs by Delivery Model and month. -
app/delivery_models/alerts.pyOrchestrates alert evaluation, CRM writeback, email sending, skipped reasons, and failure handling. -
app/delivery_models/alert_recipients.pyResolves alert recipients from configured static recipients and project timelog users. -
app/delivery_models/alert_email.pyBuilds the Delivery Model milestone email payload and HTML content. -
app/blueprints/delivery_model_automations.pyExposes the token-protected automation routes used by cron or Zoho callers.
CRM Setup
Delivery Model Fields
The progress calculation expects Delivery Models to have:
Revenue_ModelScope_Typeproject_idtasklist_id, whenScope_Typeis tasklisttask_id, whenScope_Typeis taskphase_id, whenScope_Typeis phaseLabour_AmountLabour_UnitDelivery_Model_AmountValid_FromValid_UntilLast_Completion_PctLast_Completion_Updated_AtCurrent_Month_Completion_PctCurrent_Month_Tracking_MonthCumulated_Amount
Revenue_Model determines the progress model:
- values containing
recurrare treated as recurring; - all other values are treated as one-time.
Labour_Unit determines expected minutes:
| Labour unit | Expected minutes |
|---|---|
Hour / Hours |
Labour_Amount * 60 |
Day / Days |
Labour_Amount * 7.6 * 60 |
Active Model Rules
Normal alert and state-sync runs include only active, calculable Delivery Models:
project_idmust be present.Scope_Typemust be one of project, tasklist, task, or phase.- Scope-specific IDs must be present for tasklist, task, and phase scopes.
Labour_AmountandLabour_Unitmust resolve to expected minutes.Valid_Untilmust be blank, today, or in the future.
The state-sync route can evaluate a specific inactive Delivery Model only when called through the path that explicitly allows inactive filtered records.
Timelog Mapping Rules
Timelogs come from Zoho Projects user/task/day reporting. Each timelog row is matched to the best Delivery Model scope for the related task:
- Task-level Delivery Model.
- Phase-level Delivery Model.
- Tasklist-level Delivery Model.
- Project-level Delivery Model.
When multiple Delivery Models match the same scope, the model with the latest
Valid_From wins. Ties are resolved by Delivery Model ID for deterministic
output.
Only matched timelogs contribute to completion calculations. Timelog values are converted to minutes before aggregation.
Completion Rules
Shared Rules
Completion percentages are normalized before comparison:
- blank or invalid values become
null; - negative values become
0; - decimal percentages are truncated to an integer.
CRM update payloads are prepared only when calculated state differs from stored CRM state.
One-Time Delivery Models
One-time progress is cumulative across all matched timelogs:
completion_pct = total_logged_minutes / expected_minutes * 100
The stored comparison field is Last_Completion_Pct.
When progress changes, CRM writeback prepares:
Last_Completion_PctLast_Completion_Updated_AtCumulated_Amount, when the calculated amount differs from stored CRM value
For one-time models, Cumulated_Amount is the normalized
Delivery_Model_Amount.
Recurring Delivery Models
Recurring models maintain two progress concepts:
- current-month progress, used for alert milestones;
- overall progress, used for broad delivery visibility.
Current-month progress uses only timelogs in the current calendar month:
current_month_completion_pct =
current_month_logged_minutes / expected_minutes_month * 100
Overall progress uses all monthly rows up to the current month:
overall_completion_pct =
all_logged_minutes_to_date / (expected_minutes_month * active_months_to_date) * 100
The stored current-month comparison fields are:
Current_Month_Tracking_MonthCurrent_Month_Completion_Pct
If Current_Month_Tracking_Month is not the current month, the row is treated
as a new monthly period. The first writeback for that period resets the current
month state to the new month before storing the calculated current-month value.
The stored overall comparison field is Last_Completion_Pct.
When recurring state changes, CRM writeback may prepare:
Current_Month_Tracking_MonthCurrent_Month_Completion_PctLast_Completion_PctLast_Completion_Updated_AtCumulated_Amount, when other recurring updates are being written and the calculated amount differs from stored CRM value
For recurring models:
Cumulated_Amount = Delivery_Model_Amount * active_months_to_date
Alert Rules
Milestone Detection
Alerts are based on newly crossed thresholds, not every percentage change.
Milestone thresholds are:
30%, 50%, 90%, 100%
A threshold is newly crossed when:
stored_completion_pct < threshold <= calculated_completion_pct
If multiple thresholds are crossed in one run, the highest crossed threshold is reported for that Delivery Model.
For one-time models, milestones compare Last_Completion_Pct to calculated
overall completion.
For recurring models, milestones compare Current_Month_Completion_Pct to
calculated current-month completion. Overall recurring completion can change
without sending an alert.
Email Sending
The alert-evaluation route sends email only when all of these are true:
send_alertsis true;apply_updatesis true;- at least one Delivery Model newly crossed a milestone;
- at least one recipient is resolved;
- alert email handling succeeds.
The email is sent through the CRM Delivery Models send_mail action and is
attached to the first candidate Delivery Model record. Candidate email items are
sorted by highest threshold first, then company name, then project/model name.
The email subject is:
Delivery milestone alert (<n> projects)
Recipients
Recipients are the deduplicated union of:
- configured static recipients from
ALERT_RECIPIENT_EMAILS; - users who have logged time on projects represented by the alert candidates.
If project timelog recipient lookup fails, the lookup error is reported but static recipients can still receive the alert.
If no recipients are available, the alert is skipped and candidate CRM writeback is deferred.
CRM Writeback and Alert Safety
Milestone candidates are not written to CRM until alert handling succeeds. This prevents the system from consuming a milestone before the email is sent.
If alert handling fails, the route:
- writes non-candidate CRM updates;
- defers candidate CRM updates;
- returns HTTP status
207; - includes
deferred_candidate_delivery_model_idsin the JSON response.
If email sending succeeds but CRM writeback later fails, the route still returns
207. This may allow a future duplicate alert, but it avoids silently losing an
unsent milestone.
Routes and Runtime
Alert Evaluation
POST /zoho/delivery-models/alert-evaluation
Typical cron payload:
{"apply_updates": true, "send_alerts": true}
Important response fields:
active_delivery_modelsevaluated_delivery_modelsupdated_modelsupdate_failuresemail_sentemail_erroremail_skipped_reasonrecipient_lookup_erroralert_candidate_delivery_model_idsdeferred_candidate_delivery_model_idsrows
Common skipped reasons:
| Reason | Meaning |
|---|---|
send_alerts_requires_apply_updates |
Alerts were requested without CRM writeback. |
missing_alert_recipient_emails |
A milestone exists, but no recipients were resolved. |
no_new_milestones |
The run updated or evaluated progress, but no threshold was newly crossed. |
State Sync
POST /zoho/delivery-models/state-sync
Typical payload:
{"apply_updates": true}
State sync calculates the same progress rows but does not send milestone email. It is useful for correcting CRM completion state or backfilling progress.
Test Email
POST /zoho/delivery-models/test-alert-email
This route sends a sample Delivery Model milestone email to validate CRM mail permissions, sender configuration, and rendering.
Operational Notes
- Alerts are expected to be sparse. A run can update many Delivery Models and still send no email when no thresholds were crossed.
no_new_milestonesis normal after a milestone was already written to CRM.- Repeated HTTP 500s in the cron log should be investigated with application
logs. The alert path now returns
207for known email-prep/send failures so unsent milestone candidates remain retryable. - A model already above 100% can still continue to update CRM without new alerts unless the relevant stored value was below a threshold before the run.
- Recurring alerts reset monthly because they use current-month completion, but one-time alerts are lifetime-to-date for the model.
References
- Code:
app/automations/delivery_model_completion_calculations.py - Code:
app/delivery_models/alerts.py - Code:
app/delivery_models/scopes.py - Code:
app/delivery_models/timelog_mapping.py - Code:
app/delivery_models/alert_recipients.py - Routes:
POST /zoho/delivery-models/alert-evaluation,POST /zoho/delivery-models/state-sync,POST /zoho/delivery-models/test-alert-email