Skip to content

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_mail action.
  • 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.py Owns one-time and recurring progress calculations, threshold-crossing rules, current-month reset logic, and prepared CRM update payloads.

  • app/automations/delivery_model_completion.py Combines one-time and recurring rows and exposes the state-sync workflow.

  • app/delivery_models/scopes.py Filters eligible Delivery Models and normalizes scope records.

  • app/delivery_models/timelog_mapping.py Maps Zoho Projects timelog rows to the best Delivery Model scope.

  • app/delivery_models/timelog_monthly.py Aggregates recurring timelogs by Delivery Model and month.

  • app/delivery_models/alerts.py Orchestrates alert evaluation, CRM writeback, email sending, skipped reasons, and failure handling.

  • app/delivery_models/alert_recipients.py Resolves alert recipients from configured static recipients and project timelog users.

  • app/delivery_models/alert_email.py Builds the Delivery Model milestone email payload and HTML content.

  • app/blueprints/delivery_model_automations.py Exposes 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_Model
  • Scope_Type
  • project_id
  • tasklist_id, when Scope_Type is tasklist
  • task_id, when Scope_Type is task
  • phase_id, when Scope_Type is phase
  • Labour_Amount
  • Labour_Unit
  • Delivery_Model_Amount
  • Valid_From
  • Valid_Until
  • Last_Completion_Pct
  • Last_Completion_Updated_At
  • Current_Month_Completion_Pct
  • Current_Month_Tracking_Month
  • Cumulated_Amount

Revenue_Model determines the progress model:

  • values containing recurr are 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_id must be present.
  • Scope_Type must be one of project, tasklist, task, or phase.
  • Scope-specific IDs must be present for tasklist, task, and phase scopes.
  • Labour_Amount and Labour_Unit must resolve to expected minutes.
  • Valid_Until must 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:

  1. Task-level Delivery Model.
  2. Phase-level Delivery Model.
  3. Tasklist-level Delivery Model.
  4. 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_Pct
  • Last_Completion_Updated_At
  • Cumulated_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_Month
  • Current_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_Month
  • Current_Month_Completion_Pct
  • Last_Completion_Pct
  • Last_Completion_Updated_At
  • Cumulated_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_alerts is true;
  • apply_updates is 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_ids in 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_models
  • evaluated_delivery_models
  • updated_models
  • update_failures
  • email_sent
  • email_error
  • email_skipped_reason
  • recipient_lookup_error
  • alert_candidate_delivery_model_ids
  • deferred_candidate_delivery_model_ids
  • rows

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_milestones is 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 207 for 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