Coverage for atlas_ecommerce/models/conversion.py: 100%
33 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-25 16:36 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-25 16:36 +0000
1from datetime import UTC, datetime
2from enum import StrEnum
4from beanie import Document
5from pydantic import Field
6from pymongo import IndexModel
9class Destination(StrEnum):
10 LINKEDIN = "linkedin"
11 X = "x"
12 MIXPANEL = "mixpanel"
13 META = "meta"
14 GOOGLE_ADS = "google_ads"
17class DispatchStatus(StrEnum):
18 PENDING = "pending"
19 PROCESSING = "processing"
20 SENT = "sent"
21 SKIPPED = "skipped"
22 FAILED = "failed"
25class ConversionDispatchDb(Document):
26 """One purchase conversion owed to one destination for one order.
28 Payloads are rebuilt from the order at delivery time rather than stored, so
29 this collection never holds anything but ids, status and errors.
30 """
32 provider: str
33 order_id: str
34 destination: Destination
35 status: DispatchStatus = DispatchStatus.PENDING
36 skip_reason: str | None = None
37 attempts: int = 0
38 last_error: str | None = None
39 next_attempt_at: datetime | None = None
40 sent_at: datetime | None = None
41 claimed_at: datetime | None = None
42 claim_token: str | None = None
43 created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
45 class Settings:
46 name = "conversion_dispatches"
47 indexes = [
48 IndexModel(
49 [("provider", 1), ("order_id", 1), ("destination", 1)],
50 unique=True,
51 name="uniq_order_destination",
52 ),
53 # Both claim predicates start with the destinations this replica can deliver, so the
54 # pending backlog of an unconfigured destination is never scanned to find due work.
55 IndexModel(
56 [("destination", 1), ("status", 1), ("next_attempt_at", 1), ("attempts", 1)],
57 name="dispatch_queue",
58 ),
59 IndexModel([("destination", 1), ("status", 1), ("claimed_at", 1)], name="dispatch_leases"),
60 ]