Refactor campaign delivery decision paths

This commit is contained in:
2026-07-29 17:06:15 +02:00
parent 5240749ae1
commit dd9592a192
9 changed files with 1103 additions and 593 deletions
@@ -307,59 +307,40 @@ class _JobReportAggregate:
include_recent_failures: bool,
) -> None:
self.total += 1
self._add_statuses(job)
self._add_delivery_counts(job, retry_max_attempts=retry_max_attempts)
self._add_issues(job)
self._add_attachments(job)
self._add_recent_failure(job, include_recent_failures=include_recent_failures)
def _add_statuses(self, job: CampaignJob) -> None:
self.build[job.build_status or "unknown"] += 1
self.validation[job.validation_status or "unknown"] += 1
self.queue[job.queue_status or "unknown"] += 1
self.send[job.send_status or "unknown"] += 1
self.postbox[job.postbox_status or "unknown"] += 1
self.imap[job.imap_status or "unknown"] += 1
def _add_delivery_counts(self, job: CampaignJob, *, retry_max_attempts: int | None) -> None:
if job.send_status in {"queued", "claimed", "sending"}:
self.pending += 1
if (
job.validation_status in {"ready", "warning"}
and job.build_status == "built"
):
if _job_is_queueable(job):
self.queueable += 1
if (
job.attempt_count == 0
and job.postbox_attempt_count == 0
and job.send_status in {"not_queued", "cancelled"}
and job.validation_status in {"ready", "warning"}
and job.build_status == "built"
):
if _job_is_queueable_unattempted(job):
self.queueable_unattempted += 1
if (
job.send_status in {"failed_temporary", "partially_accepted"}
and (
retry_max_attempts is None
or job.attempt_count < retry_max_attempts
)
):
if _job_is_retryable(job, retry_max_attempts=retry_max_attempts):
self.retryable += 1
if job.send_status not in {
"skipped",
"smtp_accepted",
"postbox_accepted",
"delivered",
"partially_accepted",
"sent",
"outcome_unknown",
"claimed",
"sending",
"cancelled",
}:
if _job_is_cancellable(job):
self.cancellable += 1
if _job_needs_attention(job):
self.needs_attention += 1
self._add_issues(job)
self._add_attachments(job)
if include_recent_failures and _job_is_recent_failure(job):
self.recent_failures.append(job)
self.recent_failures.sort(
key=lambda item: item.updated_at or item.created_at,
reverse=True,
)
del self.recent_failures[20:]
def _add_recent_failure(self, job: CampaignJob, *, include_recent_failures: bool) -> None:
if not include_recent_failures or not _job_is_recent_failure(job):
return
self.recent_failures.append(job)
self.recent_failures.sort(key=lambda item: item.updated_at or item.created_at, reverse=True)
del self.recent_failures[20:]
def _add_issues(self, job: CampaignJob) -> None:
for issue in job.issues_snapshot or []:
@@ -391,6 +372,42 @@ class _JobReportAggregate:
self.attachment_ambiguous += 1
NON_CANCELLABLE_SEND_STATUSES = {
"skipped",
"smtp_accepted",
"postbox_accepted",
"delivered",
"partially_accepted",
"sent",
"outcome_unknown",
"claimed",
"sending",
"cancelled",
}
def _job_is_queueable(job: CampaignJob) -> bool:
return job.validation_status in {"ready", "warning"} and job.build_status == "built"
def _job_is_queueable_unattempted(job: CampaignJob) -> bool:
return (
job.attempt_count == 0
and job.postbox_attempt_count == 0
and job.send_status in {"not_queued", "cancelled"}
and _job_is_queueable(job)
)
def _job_is_retryable(job: CampaignJob, *, retry_max_attempts: int | None) -> bool:
attempts_available = retry_max_attempts is None or job.attempt_count < retry_max_attempts
return job.send_status in {"failed_temporary", "partially_accepted"} and attempts_available
def _job_is_cancellable(job: CampaignJob) -> bool:
return job.send_status not in NON_CANCELLABLE_SEND_STATUSES
def _job_needs_attention(job: CampaignJob) -> bool:
return (
job.validation_status in {"needs_review", "blocked"}
@@ -577,31 +594,48 @@ def _job_evidence_row(
"campaign_id": job.campaign_id,
"campaign_version_id": job.campaign_version_id,
"message_id_header": job.message_id_header,
"from": _address_summary(recipients.get("from")),
"to": _address_summary(recipients.get("to")),
"cc": _address_summary(recipients.get("cc")),
"bcc": _address_summary(recipients.get("bcc")),
"reply_to": _address_summary(recipients.get("reply_to")),
"postbox_targets": "; ".join(
str(target.get("address") or target.get("name") or target.get("postbox_id") or "")
for target in (
getattr(job, "resolved_postbox_targets", None) or []
)
if isinstance(target, dict)
),
**_job_evidence_addresses(recipients),
"postbox_targets": _job_postbox_target_summary(job),
"attachment_names": _attachment_names(job.resolved_attachments),
**_job_attempt_evidence(latest_smtp=latest_smtp, latest_imap=latest_imap),
})
return row
def _job_evidence_addresses(recipients: dict[str, Any]) -> dict[str, str]:
return {key: _address_summary(recipients.get(key)) for key in ("from", "to", "cc", "bcc", "reply_to")}
def _job_postbox_target_summary(job: CampaignJob) -> str:
targets = getattr(job, "resolved_postbox_targets", None) or []
return "; ".join(
str(target.get("address") or target.get("name") or target.get("postbox_id") or "")
for target in targets
if isinstance(target, dict)
)
def _iso_timestamp(value: datetime | None) -> str | None:
return value.isoformat() if value else None
def _job_attempt_evidence(
*,
latest_smtp: SendAttempt | None,
latest_imap: ImapAppendAttempt | None,
) -> dict[str, Any]:
return {
"latest_smtp_attempt_number": latest_smtp.attempt_number if latest_smtp else None,
"latest_smtp_status": latest_smtp.status if latest_smtp else None,
"latest_smtp_status_code": latest_smtp.smtp_status_code if latest_smtp else None,
"latest_smtp_started_at": latest_smtp.started_at.isoformat() if latest_smtp and latest_smtp.started_at else None,
"latest_smtp_finished_at": latest_smtp.finished_at.isoformat() if latest_smtp and latest_smtp.finished_at else None,
"latest_smtp_started_at": _iso_timestamp(latest_smtp.started_at) if latest_smtp else None,
"latest_smtp_finished_at": _iso_timestamp(latest_smtp.finished_at) if latest_smtp else None,
"latest_imap_attempt_number": latest_imap.attempt_number if latest_imap else None,
"latest_imap_status": latest_imap.status if latest_imap else None,
"latest_imap_folder": latest_imap.folder if latest_imap else None,
"latest_imap_created_at": latest_imap.created_at.isoformat() if latest_imap and latest_imap.created_at else None,
"latest_imap_updated_at": latest_imap.updated_at.isoformat() if latest_imap and latest_imap.updated_at else None,
})
return row
"latest_imap_created_at": _iso_timestamp(latest_imap.created_at) if latest_imap else None,
"latest_imap_updated_at": _iso_timestamp(latest_imap.updated_at) if latest_imap else None,
}
def generate_campaign_report(