fix: resolve remaining 5 QA audit findings (#3, #8, #10, #11, #12)

#3 — Duplicate prospect handling: add_lead now checks (campaign_id, url)
     before insert; bulk_add_leads skips existing URLs.
#8 — Atomic rate limiting: try_increment_* methods atomically check cap
     and increment in a single session; router uses these before send.
#10 — Reply matching via Message-ID: sender generates Message-ID header,
     stored on OutreachAttempt; reply monitor parses In-Reply-To/References;
     poll_replies matches by message_id first, falls back to from_email.
#11 — Save-to-campaign uses existing store results instead of
      re-running expensive deepDiscover.
#12 — Lead status Literal type: Pydantic models enforce valid status
      values; backend validates via LEAD_VALID_STATUSES frozenset;
      frontend API typed as LeadStatus union.
This commit is contained in:
ajaysi
2026-06-03 20:06:11 +05:30
parent 259194c289
commit 8699ffc27d
9 changed files with 226 additions and 103 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from pydantic import BaseModel, Field, HttpUrl
from typing import Dict, List, Optional
from typing_extensions import Literal
class BacklinkKeywordInput(BaseModel):
@@ -93,7 +94,7 @@ class LeadListResponse(BaseModel):
class LeadStatusUpdateRequest(BaseModel):
status: str = Field(..., min_length=1)
status: Literal["discovered", "contacted", "replied", "placed", "bounced", "unsubscribed"]
notes: Optional[str] = None
campaign_id: Optional[str] = Field(default=None, min_length=1)
@@ -329,7 +330,7 @@ class ConversionFunnelResponse(BaseModel):
class BulkStatusUpdateRequest(BaseModel):
lead_ids: List[str] = Field(..., min_length=1)
status: str = Field(..., min_length=1)
status: Literal["discovered", "contacted", "replied", "placed", "bounced", "unsubscribed"]
notes: Optional[str] = None
campaign_id: Optional[str] = Field(default=None, min_length=1)