# RALPH AR WORKFLOW E2E — Complete Email Reminders Automation Testing

**Mission:** Test, fix, enhance, and verify the ENTIRE email reminders automation workflow end-to-end across all angles — backend API, frontend UI, Celery tasks, AI services, communication logging, reply monitoring, approval workflows, and dashboard integration — until the completion promise is achieved: **every single test passes with real fixes applied and the entire workflow works flawlessly for a real user.**

**Completion Promise:** `<promise>AR WORKFLOW COMPLETE</promise>` — Only output this when ALL phases pass, ALL fixes are genuine (verified with multiple scenarios), ALL AI prompts perform at expert level, ALL frontend displays work correctly, and a real user could start using this workflow immediately without encountering any issues.

---

## CORE PRINCIPLES

### 1. NO SCRIPTS — MANUAL TESTING LIKE A REAL QA ENGINEER
- **ABSOLUTELY NO TEST SCRIPTS** — Do NOT write Python scripts, bash scripts, or any automated test runners
- Execute each test INDIVIDUALLY using direct `curl` commands or `python -c` one-liners
- Read each response yourself. Think about it. Verify the data. Then move to the next test
- For frontend tests, use Chrome MCP to navigate, click, fill forms, take screenshots, and verify UI visually

### 2. FIX THE CODE, NOT THE TEST
- If a test fails, FIX THE UNDERLYING CODE — never delete a feature to make a test pass
- If a service is missing, BUILD IT properly
- If a prompt is weak, ENHANCE IT with research and expertise
- If a frontend component is broken, FIX the component

### 3. VERIFY FIXES ARE GENUINE — NOT JUST PATCHED
After every fix:
- **Step 1**: Re-run the original failing test → must pass
- **Step 2**: Run 2-3 DIFFERENT scenarios of the same feature (different customer, different industry, different edge case) → all must pass
- **Step 3**: Verify adjacent features weren't broken (run related tests)
- **Step 4**: For frontend fixes — visually verify in the browser using Chrome MCP
- **Step 5**: Only after ALL steps pass → mark the fix as confirmed

### 4. ASSERT ON CONTENT, NOT JUST STATUS CODES
- A 200 response with wrong data is a FAILURE
- An AI-generated email that's generic/templated is a FAILURE — it must be personalized and contextual
- A frontend that shows mock data instead of real data is a FAILURE
- A Celery task that runs but doesn't produce correct results is a FAILURE

### 5. TEST ALL INDUSTRIES
Every major feature must be tested across at least 3 industries to ensure industry-aware behavior works:
- Construction (unique: retainage, AIA billing, lien waivers)
- Education (unique: academic terms, payment plans, registration holds)
- Wholesale/Distribution (unique: deduction management, EDI)
- Generic (baseline behavior without industry-specific features)

### 6. FULL AUTONOMY — NEVER WAIT, NEVER ASK, NEVER STOP
- **YOU ARE FULLY AUTONOMOUS.** Never pause to ask the user anything. Never wait for the user to start a server, fix a config, or approve a step.
- If the Django backend is NOT running → START IT YOURSELF: `cd singoa-api && nohup python manage.py runserver 0.0.0.0:8000 &`
- If Redis is NOT running → START IT: `redis-server --daemonize yes` or `sudo systemctl start redis`
- If Celery worker is NOT running → START IT: `cd singoa-api && nohup celery -A singoa_project worker -l info &`
- If Celery beat is NOT running → START IT: `cd singoa-api && nohup celery -A singoa_project beat -l info &`
- If the Next.js frontend is NOT running → START IT: `cd singoa-ui && nohup npm run dev &`
- If database migrations are needed → RUN THEM: `cd singoa-api && python manage.py migrate`
- If a Python package is missing → INSTALL IT: `cd singoa-api && pip install <package>`
- If a Node package is missing → INSTALL IT: `cd singoa-ui && npm install`
- If a port is already in use → FIND AND KILL the old process, then start fresh
- If an API endpoint returns 404 → check urls.py, check if the app is in INSTALLED_APPS, run migrations
- If a model field doesn't exist → check the actual model definition, adapt your queries
- **BOTTOM LINE: You have full root access. Fix ANY blocker yourself. The loop does NOT stop until ALL 270+ tests pass with 1000% certainty.**

### 7. SEQUENTIAL EXECUTION — NO PARALLEL AGENTS
- **ABSOLUTELY NO sub-agents or parallel Task tool delegation.** YOU must run every single test YOURSELF, one by one, in your own conversation.
- Do NOT use the Task tool to spawn background agents for testing. Each test must be executed by YOU directly.
- Run Phase 1 completely, then Phase 2 completely, then Phase 3, etc. — strictly sequential.
- Every test result must be visible in YOUR conversation output so the user can see exactly what happened.
- If you delegate to sub-agents, tests WILL be skipped and results WILL be biased/incomplete. This is unacceptable.
- The only exception is using Task tool for brief code exploration/search (Explore agent) — never for test execution.

### 8. NEVER STOP UNTIL 1000% COMPLETE
- Do NOT output the completion promise until you have run EVERY SINGLE TEST and ALL pass
- If even ONE test fails, fix it and re-run ALL related tests to confirm no regressions
- After all phases pass individually, do a FULL RE-RUN of critical tests from every phase to confirm nothing broke
- The standard is not "probably works" — it's "absolutely, undeniably, 1000% verified working"
- You must be able to say: "A real user can sign up, import invoices, and the entire AR automation cycle works perfectly from first reminder to final payment, across all 4 industries, with expert-level AI emails, complete logging, approval workflows, and a beautiful dashboard showing accurate real-time data"

---

## STATE MANAGEMENT

### Tracker File: `_extras/data/ar-workflow-e2e-state.json`

Read this file first. If it doesn't exist, create it:

```json
{
  "iteration": 0,
  "current_phase": 0,
  "total_phases": 23,
  "phases": {},
  "bugs_fixed": [],
  "enhancements_made": [],
  "test_users": {},
  "backend_url": "http://localhost:8000/api",
  "frontend_url": "http://localhost:3000",
  "started_at": "",
  "last_updated": ""
}
```

**MANDATORY: After completing EVERY phase, update this file with:**
```json
{
  "iteration": "<increment>",
  "current_phase": "<next phase>",
  "phases": {
    "<phase_number>": {
      "status": "PASS|FAIL",
      "tests_passed": "<count>",
      "tests_total": "<count>",
      "fixes": ["list of fixes applied"],
      "enhancements": ["list of enhancements made"],
      "notes": "any important observations"
    }
  }
}
```

---

## PHASE 0: ENVIRONMENT SETUP & DATA SEEDING

### Test 0.1: Backend Health Check

**Steps:**
1. Check if Django backend is running:
```bash
curl -s http://localhost:8000/api/core/health/intelligent-ar/ -H "Content-Type: application/json"
```
2. If not running, start it:
```bash
cd singoa-api && python manage.py runserver 0.0.0.0:8000
```
3. Also verify Redis is running (needed for Celery):
```bash
redis-cli ping
```
4. Verify Celery worker is running:
```bash
cd singoa-api && celery -A singoa_project inspect ping
```

**Assertions:**
- Health endpoint returns 200 with `{"status": "ok"}` or similar
- Redis responds with `PONG`
- Celery worker responds to ping
- If any service is down, start it and re-verify

---

### Test 0.2: Create Test Users (One Per Industry)

Create 4 test users. For EACH user, follow this exact sequence:

**User 1: Construction**
```bash
curl -s -X POST http://localhost:8000/api/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "artest.construction@singoa.com",
    "password": "TestPass123!@#",
    "password_confirm": "TestPass123!@#",
    "first_name": "John",
    "last_name": "Builder",
    "company_name": "BuildRight Construction Co",
    "phone": "+15551001001"
  }'
```

**Assertions for registration:**
- Returns 201 with user object containing `id`, `email`, `first_name`, `last_name`
- `email` matches exactly what was sent
- `is_active` may be false (pending admin approval)
- No duplicate user error on first run; if user exists, skip to login

**User 2: Education**
```bash
curl -s -X POST http://localhost:8000/api/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "artest.education@singoa.com",
    "password": "TestPass123!@#",
    "password_confirm": "TestPass123!@#",
    "first_name": "Sarah",
    "last_name": "Dean",
    "company_name": "State University Finance",
    "phone": "+15551002002"
  }'
```

**User 3: Wholesale**
```bash
curl -s -X POST http://localhost:8000/api/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "artest.wholesale@singoa.com",
    "password": "TestPass123!@#",
    "password_confirm": "TestPass123!@#",
    "first_name": "Mike",
    "last_name": "Distributor",
    "company_name": "Metro Distribution Inc",
    "phone": "+15551003003"
  }'
```

**User 4: Generic**
```bash
curl -s -X POST http://localhost:8000/api/auth/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "artest.generic@singoa.com",
    "password": "TestPass123!@#",
    "password_confirm": "TestPass123!@#",
    "first_name": "Alex",
    "last_name": "Manager",
    "company_name": "Acme Business Corp",
    "phone": "+15551004004"
  }'
```

---

### Test 0.3: Approve All Test Users

Users require admin approval. Use Django management shell:
```bash
cd singoa-api && python -c "
import django; import os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from authentication.models import User
for email in ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']:
    try:
        u = User.objects.get(email=email)
        u.is_active = True
        u.is_approved = True
        u.save()
        print(f'Approved: {email}')
    except User.DoesNotExist:
        print(f'NOT FOUND: {email}')
    except Exception as e:
        print(f'Error for {email}: {e}')
"
```

**Assertions:**
- All 4 users print "Approved: ..."
- No "NOT FOUND" or "Error" messages
- If `is_approved` field doesn't exist, check actual model fields and adapt

---

### Test 0.4: Login Each User and Save JWT Tokens

For EACH user, login and capture the access token:

```bash
# Construction user
curl -s -X POST http://localhost:8000/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"email": "artest.construction@singoa.com", "password": "TestPass123!@#"}'
```

**Assertions for EACH login:**
- Returns 200
- Response contains `access` or `access_token` (JWT token string)
- Response contains `refresh` or `refresh_token`
- Token is a valid JWT (3 dot-separated base64 segments)
- Save each token for subsequent tests — store in state tracker:
```json
{
  "test_users": {
    "construction": {
      "email": "artest.construction@singoa.com",
      "token": "<access_token>",
      "refresh": "<refresh_token>",
      "user_id": "<id from response>"
    },
    "education": { ... },
    "wholesale": { ... },
    "generic": { ... }
  }
}
```

Repeat for education, wholesale, generic users.

---

### Test 0.5: Set Industry Configuration for Each User

For EACH user, set their industry:

```bash
# Construction user
curl -s -X POST http://localhost:8000/api/core/user-config/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"industry": "construction"}'
```

```bash
# Education user
curl -s -X POST http://localhost:8000/api/core/user-config/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <education_token>" \
  -d '{"industry": "education"}'
```

```bash
# Wholesale user
curl -s -X POST http://localhost:8000/api/core/user-config/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <wholesale_token>" \
  -d '{"industry": "wholesale"}'
```

```bash
# Generic user
curl -s -X POST http://localhost:8000/api/core/user-config/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <generic_token>" \
  -d '{"industry": "general"}'
```

**Assertions for EACH:**
- Returns 200 or 201
- Response confirms `industry` is set correctly
- If endpoint doesn't exist or uses different field name, check `core/urls.py` and adapt
- Verify by GET request: `GET /api/core/user-config/` with same token → industry matches

---

### Test 0.6: Verify Subscription / Trial Status

For EACH user:
```bash
curl -s http://localhost:8000/api/auth/subscription/status/ \
  -H "Authorization: Bearer <token>"
```

**Assertions:**
- Returns 200
- `status` is "trial" or "active"
- `trial_days_remaining` > 0 (if trial)
- If no subscription endpoint, check if user has access to automations features by hitting:
  ```bash
  curl -s http://localhost:8000/api/automations/ar/workflow/status/ \
    -H "Authorization: Bearer <token>"
  ```
  Should return 200, NOT 403

---

### Test 0.7: Configure Business Rules for Each User

For EACH test user:
```bash
# Construction user — longer payment cycles expected in construction
curl -s -X POST http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "first_reminder_days": 1,
    "reminder_frequency_days": 7,
    "due_soon_days": 7,
    "overdue_2_start_days": 8,
    "overdue_3_start_days": 15,
    "critical_threshold_days": 30,
    "sms_threshold_days": 15,
    "call_threshold_days": 30,
    "tone_overdue_1": "friendly",
    "tone_overdue_2": "professional",
    "tone_overdue_3": "firm",
    "tone_critical": "urgent",
    "auto_match_tolerance_percent": 0,
    "send_emails_start_hour": 9,
    "send_emails_end_hour": 17,
    "working_days": [0,1,2,3,4],
    "promise_grace_days": 2,
    "dispute_resolution_days": 14,
    "default_payment_terms": 30,
    "late_fee_enabled": true,
    "late_fee_percent": 1.5,
    "late_fee_grace_days": 5,
    "ar_workflow_hour": 9,
    "reminder_window_1_hour": 10,
    "reminder_window_2_hour": 14
  }'
```

**Assertions for EACH user:**
- Returns 200 or 201
- Response contains ALL fields that were sent
- `tone_overdue_1` = "friendly"
- `tone_overdue_2` = "professional"
- `tone_overdue_3` = "firm"
- `tone_critical` = "urgent"
- `working_days` = [0,1,2,3,4] (Monday-Friday)
- `send_emails_start_hour` = 9
- `send_emails_end_hour` = 17
- If POST fails because rules already exist, use PUT instead
- Verify with GET: `GET /api/automations/ar/business-rules/` → all fields match

Repeat with same values for education, wholesale, generic users.

---

### Test 0.8: Configure Communication Settings for Each User

For EACH user:

```bash
# Construction user
curl -s -X PUT http://localhost:8000/api/auth/settings/communication/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "business_tone": "professional",
    "escalation_speed": 50,
    "sender_name": "John at BuildRight",
    "email_signature": "Best regards,\nJohn Smith\nAccounts Receivable\nBuildRight Construction Co\n(555) 100-1001",
    "custom_instructions": "Always reference project names and PO numbers when available. Use construction-appropriate language."
  }'
```

```bash
# Education user
curl -s -X PUT http://localhost:8000/api/auth/settings/communication/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <education_token>" \
  -d '{
    "business_tone": "professional",
    "escalation_speed": 40,
    "sender_name": "Sarah Dean - Student Finance",
    "email_signature": "Best regards,\nSarah Dean\nStudent Financial Services\nState University\n(555) 100-2002",
    "custom_instructions": "Use student-friendly language. Reference semester deadlines. Mention financial aid options when applicable."
  }'
```

```bash
# Wholesale user
curl -s -X PUT http://localhost:8000/api/auth/settings/communication/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <wholesale_token>" \
  -d '{
    "business_tone": "professional",
    "escalation_speed": 60,
    "sender_name": "Mike at Metro Distribution",
    "email_signature": "Regards,\nMike Thompson\nAccounts Receivable\nMetro Distribution Inc\n(555) 100-3003",
    "custom_instructions": "Reference PO numbers and order numbers. Understand that deduction disputes are common in wholesale."
  }'
```

```bash
# Generic user
curl -s -X PUT http://localhost:8000/api/auth/settings/communication/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <generic_token>" \
  -d '{
    "business_tone": "professional",
    "escalation_speed": 50,
    "sender_name": "Alex at Acme Business",
    "email_signature": "Best regards,\nAlex Manager\nAccounts Receivable\nAcme Business Corp\n(555) 100-4004",
    "custom_instructions": ""
  }'
```

**Assertions for EACH:**
- Returns 200
- `sender_name` matches what was sent
- `email_signature` matches what was sent (including newlines)
- `custom_instructions` matches
- Verify with GET: `GET /api/auth/settings/communication/` → all fields correct

---

### Test 0.9: Seed Customers for Each Test User — HEAVY REALISTIC DATA

For EACH test user, create **15 customers** covering all segments with realistic business names. Use the invoices API or Django shell (whichever works). This is a HEAVY demo environment — the system must handle real-world volume.

**Use a Django management script for speed (create all 15 per user in one shot):**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from invoices.models import Customer
from authentication.models import User

CUSTOMERS = [
    # --- VIP / PROMPT (3 customers) ---
    {'customer_name': 'Meridian Holdings LLC', 'customer_email': 'ap@meridianholdings.com', 'phone': '+15559001001', 'address': '100 Corporate Blvd, Suite 400, Austin, TX 78701', 'payment_terms': 30, 'notes': 'VIP platinum client. 5-year relationship. Always pays 5-10 days early. Annual contract \$180K.'},
    {'customer_name': 'Titan Infrastructure Group', 'customer_email': 'accounts@titaninfra.com', 'phone': '+15559001002', 'address': '2500 Industrial Pkwy, Houston, TX 77002', 'payment_terms': 30, 'notes': 'VIP client, government contractor. Net-30 always honored. Contact: Maria Torres, CFO.'},
    {'customer_name': 'Summit Capital Partners', 'customer_email': 'finance@summitcapital.com', 'phone': '+15559001003', 'address': '88 Wall Street, 12th Floor, New York, NY 10005', 'payment_terms': 15, 'notes': 'VIP early payer. Net-15 terms. Auto-pay configured. Relationship since 2021.'},

    # --- STANDARD / DELAYED (4 customers) ---
    {'customer_name': 'Cascade Supply Co', 'customer_email': 'billing@cascadesupply.com', 'phone': '+15559002001', 'address': '1200 Commerce Dr, Portland, OR 97201', 'payment_terms': 30, 'notes': 'Reliable but slow. Typically pays 10-20 days late. Contact: James Wu, AP Manager.'},
    {'customer_name': 'Redwood Services Inc', 'customer_email': 'payments@redwoodservices.com', 'phone': '+15559002002', 'address': '340 Oak Ave, Sacramento, CA 95814', 'payment_terms': 30, 'notes': 'Pays 15-25 days late on average. Good communication, always responds to reminders.'},
    {'customer_name': 'Pioneer Manufacturing Ltd', 'customer_email': 'ar@pioneermfg.com', 'phone': '+15559002003', 'address': '7800 Factory Rd, Detroit, MI 48201', 'payment_terms': 45, 'notes': 'Net-45 terms. Usually pays around day 55-60. Large orders \$25K-\$50K.'},
    {'customer_name': 'Coastal Logistics Group', 'customer_email': 'finance@coastallogistics.com', 'phone': '+15559002004', 'address': '950 Harbor Blvd, Long Beach, CA 90802', 'payment_terms': 30, 'notes': 'Pays late but always pays. Average 18 days late. Contact: David Chen, Controller.'},

    # --- RISK / ERRATIC (3 customers) ---
    {'customer_name': 'Apex Ventures International', 'customer_email': 'info@apexventures.com', 'phone': '+15559003001', 'address': '600 Startup Row, San Francisco, CA 94105', 'payment_terms': 30, 'notes': 'ERRATIC payer. Sometimes pays in 10 days, sometimes 90+. Cash flow dependent on their funding rounds.'},
    {'customer_name': 'Blackstone Building Corp', 'customer_email': 'payables@blackstonebldg.com', 'phone': '+15559003002', 'address': '1500 Construction Ave, Chicago, IL 60601', 'payment_terms': 30, 'notes': 'Risk client. 3 invoices went to 60+ days last quarter. Disputes frequently. Contact: Robert Kim.'},
    {'customer_name': 'Evergreen Technical Solutions', 'customer_email': 'billing@evergreentech.com', 'phone': '+15559003003', 'address': '222 Tech Park, Denver, CO 80202', 'payment_terms': 30, 'notes': 'Erratic. Paid 2 invoices on time, then ghosted on 3 others for 45+ days. Unreliable.'},

    # --- DELINQUENT / NON_PAYER (2 customers) ---
    {'customer_name': 'Phantom Enterprises LLC', 'customer_email': 'office@phantomenterprises.com', 'phone': '+15559004001', 'address': '999 Shadow Ln, Las Vegas, NV 89101', 'payment_terms': 30, 'notes': 'DELINQUENT. 4 invoices unpaid 90+ days. No response to 8 emails and 3 phone calls. Collections candidate.'},
    {'customer_name': 'Zenith Global Trading', 'customer_email': 'accounts@zenithglobal.com', 'phone': '+15559004002', 'address': '444 International Blvd, Miami, FL 33101', 'payment_terms': 30, 'notes': 'NON-PAYER. \$47K outstanding 120+ days. Last contact was a bounced email. Skip trace recommended.'},

    # --- SPECIAL CASES (3 customers) ---
    {'customer_name': 'NovaTech Startups Inc', 'customer_email': 'shivambindal155@gmail.com', 'phone': '+15559005001', 'address': '15 Innovation Way, Palo Alto, CA 94301', 'payment_terms': 30, 'notes': 'REAL EMAIL TEST CUSTOMER. Uses shivambindal155@gmail.com for live email delivery verification. NEW customer. First invoice just sent. No payment history. Startup — monitor closely.'},
    {'customer_name': 'Heritage Partners Group', 'customer_email': 'finance@heritagepartners.com', 'phone': '+15559005002', 'address': '700 Promise Pkwy, Atlanta, GA 30301', 'payment_terms': 30, 'notes': 'Makes payment promises frequently but misses 40% of them. Currently has 2 active promises.'},
    {'customer_name': 'Atlas Dispute Resolution', 'customer_email': 'disputes@atlasdr.com', 'phone': '+15559005003', 'address': '333 Legal Center Dr, Washington, DC 20001', 'payment_terms': 30, 'notes': 'FREQUENT DISPUTER. 3 of last 5 invoices disputed. Claims quality issues. 1 active dispute now.'},
]

SEGMENTS = {
    'Meridian Holdings LLC': ('VIP', 'PROMPT'),
    'Titan Infrastructure Group': ('VIP', 'PROMPT'),
    'Summit Capital Partners': ('VIP', 'PROMPT'),
    'Cascade Supply Co': ('STANDARD', 'DELAYED'),
    'Redwood Services Inc': ('STANDARD', 'DELAYED'),
    'Pioneer Manufacturing Ltd': ('STANDARD', 'DELAYED'),
    'Coastal Logistics Group': ('STANDARD', 'DELAYED'),
    'Apex Ventures International': ('RISK', 'ERRATIC'),
    'Blackstone Building Corp': ('RISK', 'ERRATIC'),
    'Evergreen Technical Solutions': ('RISK', 'ERRATIC'),
    'Phantom Enterprises LLC': ('DELINQUENT', 'NON_PAYER'),
    'Zenith Global Trading': ('DELINQUENT', 'NON_PAYER'),
    'NovaTech Startups Inc': ('NEW', 'UNKNOWN'),
    'Heritage Partners Group': ('STANDARD', 'DELAYED'),
    'Atlas Dispute Resolution': ('RISK', 'ERRATIC'),
}

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']

for user_email in test_emails:
    u = User.objects.get(email=user_email)
    for cdata in CUSTOMERS:
        c, created = Customer.objects.get_or_create(user=u, customer_name=cdata['customer_name'], defaults=cdata)
        seg, pat = SEGMENTS[cdata['customer_name']]
        c.segment = seg
        c.payment_pattern = pat
        c.save()
        print(f'{\"CREATED\" if created else \"EXISTS\"}: {user_email} -> {cdata[\"customer_name\"]} ({seg}/{pat})')

print(f'Total customers: {Customer.objects.filter(user__email__in=test_emails).count()}')
"
```

**Assertions:**
- 15 customers created per user = **60 total customers**
- Each customer has correct segment and payment pattern
- Mix: 3 VIP, 4 Standard, 3 Risk, 2 Delinquent, 1 New, 1 Promise-maker, 1 Disputer

---

### Test 0.10: Seed Invoices — HEAVY REALISTIC DATA (25-30 per user)

Create realistic invoices for EVERY customer with varied statuses, amounts ($500 - $75,000), dates spanning 6 months. This creates a production-like environment.

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from invoices.models import Customer, Invoice
from authentication.models import User
from django.utils import timezone
from datetime import timedelta
from decimal import Decimal
import random

random.seed(42)  # Reproducible

def d(days_ago):
    return (timezone.now() - timedelta(days=days_ago)).date()

def future(days):
    return (timezone.now() + timedelta(days=days)).date()

# Invoice templates per customer type
INVOICE_PLANS = {
    # VIP customers: 8-12 invoices each, ALL paid on time or early
    'Meridian Holdings LLC': [
        ('INV-MER-001', 15000, d(180), 'paid', 15000, d(175), 'Annual contract Q3 - Phase 1'),
        ('INV-MER-002', 15000, d(150), 'paid', 15000, d(148), 'Annual contract Q3 - Phase 2'),
        ('INV-MER-003', 15000, d(120), 'paid', 15000, d(115), 'Annual contract Q4 - Phase 1'),
        ('INV-MER-004', 15000, d(90), 'paid', 15000, d(87), 'Annual contract Q4 - Phase 2'),
        ('INV-MER-005', 15000, d(60), 'paid', 15000, d(55), 'Annual contract Q1 2026 - Phase 1'),
        ('INV-MER-006', 15000, d(30), 'paid', 15000, d(28), 'Annual contract Q1 2026 - Phase 2'),
        ('INV-MER-007', 15000, d(5), 'sent', 0, None, 'Annual contract Q1 2026 - Phase 3'),  # Due soon, not yet overdue
        ('INV-MER-008', 18500, future(20), 'sent', 0, None, 'Annual contract renewal - not due yet'),
    ],
    'Titan Infrastructure Group': [
        ('INV-TIT-001', 45000, d(150), 'paid', 45000, d(145), 'Highway bridge subcontract - milestone 1'),
        ('INV-TIT-002', 45000, d(120), 'paid', 45000, d(118), 'Highway bridge subcontract - milestone 2'),
        ('INV-TIT-003', 32000, d(90), 'paid', 32000, d(85), 'Water treatment plant - electrical'),
        ('INV-TIT-004', 38000, d(60), 'paid', 38000, d(58), 'Water treatment plant - plumbing'),
        ('INV-TIT-005', 52000, d(30), 'paid', 52000, d(25), 'New school building - foundation'),
        ('INV-TIT-006', 48000, d(3), 'sent', 0, None, 'New school building - framing (due in 27 days)'),
    ],
    'Summit Capital Partners': [
        ('INV-SUM-001', 8500, d(120), 'paid', 8500, d(125), 'Advisory Q3 - paid EARLY'),
        ('INV-SUM-002', 8500, d(90), 'paid', 8500, d(95), 'Advisory Q4 - paid EARLY'),
        ('INV-SUM-003', 8500, d(60), 'paid', 8500, d(63), 'Advisory Jan - paid EARLY'),
        ('INV-SUM-004', 8500, d(30), 'paid', 8500, d(33), 'Advisory Feb - paid EARLY'),
        ('INV-SUM-005', 9200, d(2), 'sent', 0, None, 'Advisory Mar - due soon'),
    ],

    # STANDARD/DELAYED: mix of paid late + overdue
    'Cascade Supply Co': [
        ('INV-CAS-001', 3200, d(120), 'paid', 3200, d(105), 'Office supplies Q3 - paid 15d late'),
        ('INV-CAS-002', 4100, d(90), 'paid', 4100, d(72), 'Office supplies Q4 - paid 18d late'),
        ('INV-CAS-003', 2800, d(60), 'paid', 2800, d(48), 'Cleaning supplies - paid 12d late'),
        ('INV-CAS-004', 5500, d(30), 'paid', 5500, d(10), 'IT equipment - paid 20d late'),
        ('INV-CAS-005', 3900, d(8), 'overdue', 0, None, 'Monthly supplies Mar - 8 days overdue'),
        ('INV-CAS-006', 4200, d(1), 'overdue', 0, None, 'Printer cartridges - 1 day overdue'),
        ('INV-CAS-007', 6100, future(5), 'sent', 0, None, 'Furniture order - due in 5 days'),
    ],
    'Redwood Services Inc': [
        ('INV-RED-001', 7500, d(90), 'paid', 7500, d(70), 'Maintenance Q4 - paid 20d late'),
        ('INV-RED-002', 7500, d(60), 'paid', 7500, d(45), 'Maintenance Jan - paid 15d late'),
        ('INV-RED-003', 8200, d(30), 'paid', 8200, d(5), 'Maintenance Feb - paid 25d late'),
        ('INV-RED-004', 7500, d(15), 'overdue', 0, None, 'Maintenance Mar - 15 days overdue'),
        ('INV-RED-005', 3200, d(3), 'overdue', 0, None, 'Emergency repair - 3 days overdue'),
    ],
    'Pioneer Manufacturing Ltd': [
        ('INV-PIO-001', 28000, d(150), 'paid', 28000, d(90), 'Custom fabrication lot 1 - paid 15d late on net-45'),
        ('INV-PIO-002', 35000, d(120), 'paid', 35000, d(65), 'Custom fabrication lot 2 - paid 10d late'),
        ('INV-PIO-003', 42000, d(75), 'paid', 42000, d(25), 'Custom fabrication lot 3 - paid 5d late'),
        ('INV-PIO-004', 38500, d(30), 'overdue', 0, None, 'Custom fabrication lot 4 - 30 days overdue (net-45 was 75d ago)'),
        ('INV-PIO-005', 51000, d(10), 'overdue', 0, None, 'Rush order - 10 days overdue'),
        ('INV-PIO-006', 22000, future(15), 'sent', 0, None, 'Next lot - not due yet'),
    ],
    'Coastal Logistics Group': [
        ('INV-COA-001', 12000, d(90), 'paid', 12000, d(72), 'Freight Q4 - paid 18d late'),
        ('INV-COA-002', 14500, d(60), 'paid', 14500, d(40), 'Freight Jan - paid 20d late'),
        ('INV-COA-003', 11800, d(30), 'overdue', 6000, d(15), 'Freight Feb - PARTIAL payment \$6K of \$11.8K'),
        ('INV-COA-004', 13200, d(7), 'overdue', 0, None, 'Freight Mar week 1 - 7 days overdue'),
        ('INV-COA-005', 9800, d(1), 'overdue', 0, None, 'Freight Mar week 2 - 1 day overdue'),
    ],

    # RISK/ERRATIC: unpredictable patterns
    'Apex Ventures International': [
        ('INV-APX-001', 18000, d(150), 'paid', 18000, d(145), 'Consulting Q3 - paid on time!'),
        ('INV-APX-002', 22000, d(120), 'paid', 22000, d(50), 'Consulting Q4 - paid 70 DAYS LATE'),
        ('INV-APX-003', 15000, d(90), 'paid', 15000, d(88), 'Platform license - paid on time'),
        ('INV-APX-004', 25000, d(45), 'overdue', 0, None, 'Consulting Jan - 45 days overdue, no response'),
        ('INV-APX-005', 19500, d(15), 'overdue', 0, None, 'Consulting Feb - 15 days overdue'),
        ('INV-APX-006', 31000, d(5), 'overdue', 0, None, 'Platform renewal - 5 days overdue'),
    ],
    'Blackstone Building Corp': [
        ('INV-BLK-001', 55000, d(120), 'paid', 55000, d(60), 'Foundation work - paid 60d late after dispute'),
        ('INV-BLK-002', 42000, d(90), 'paid', 42000, d(88), 'Framing phase - paid on time (rare)'),
        ('INV-BLK-003', 67000, d(35), 'overdue', 0, None, 'Electrical phase - 35 days overdue, claims defects'),
        ('INV-BLK-004', 38000, d(12), 'overdue', 0, None, 'Plumbing phase - 12 days overdue'),
        ('INV-BLK-005', 29000, d(3), 'overdue', 0, None, 'Materials delivery - 3 days overdue'),
    ],
    'Evergreen Technical Solutions': [
        ('INV-EVG-001', 9500, d(120), 'paid', 9500, d(118), 'IT support Q4 - paid on time'),
        ('INV-EVG-002', 11000, d(90), 'paid', 11000, d(30), 'IT support Jan - paid 60 DAYS LATE'),
        ('INV-EVG-003', 8700, d(60), 'overdue', 0, None, 'Server maintenance - 60 days overdue, ghosting'),
        ('INV-EVG-004', 13500, d(20), 'overdue', 0, None, 'Cloud migration - 20 days overdue'),
        ('INV-EVG-005', 7200, d(5), 'overdue', 0, None, 'Emergency support - 5 days overdue'),
    ],

    # DELINQUENT/NON-PAYER: severe overdue
    'Phantom Enterprises LLC': [
        ('INV-PHN-001', 12500, d(180), 'overdue', 0, None, 'Project Alpha - 180 DAYS overdue. 6 reminders sent.'),
        ('INV-PHN-002', 8900, d(150), 'overdue', 0, None, 'Project Beta - 150 days overdue. No response ever.'),
        ('INV-PHN-003', 15200, d(120), 'overdue', 0, None, 'Consulting block - 120 days overdue.'),
        ('INV-PHN-004', 6800, d(90), 'overdue', 0, None, 'Materials fee - 90 days overdue.'),
        ('INV-PHN-005', 11000, d(45), 'overdue', 0, None, 'Latest project - 45 days overdue. Collections warning sent.'),
    ],
    'Zenith Global Trading': [
        ('INV-ZEN-001', 22000, d(200), 'overdue', 0, None, 'Import consulting - 200 DAYS overdue. Bounced emails.'),
        ('INV-ZEN-002', 18500, d(160), 'overdue', 0, None, 'Trade compliance - 160 days overdue.'),
        ('INV-ZEN-003', 7200, d(130), 'overdue', 2000, d(100), 'Partial \$2K of \$7.2K received, then silence.'),
        ('INV-ZEN-004', 15800, d(95), 'overdue', 0, None, 'Customs brokerage - 95 days overdue.'),
    ],

    # SPECIAL: New customer
    'NovaTech Startups Inc': [
        ('INV-NOV-001', 4500, future(25), 'sent', 0, None, 'Onboarding package - first invoice, not due yet'),
    ],

    # SPECIAL: Promise maker
    'Heritage Partners Group': [
        ('INV-HER-001', 9000, d(90), 'paid', 9000, d(60), 'Consulting Q4 - paid 30d late after promise'),
        ('INV-HER-002', 11500, d(60), 'paid', 11500, d(35), 'Consulting Jan - paid 25d late after 2 promises'),
        ('INV-HER-003', 8200, d(10), 'overdue', 0, None, 'Consulting Feb - ACTIVE PROMISE: says will pay in 3 days'),
        ('INV-HER-004', 14000, d(25), 'overdue', 0, None, 'Consulting Mar - BROKEN PROMISE: promised 5 days ago, nothing'),
        ('INV-HER-005', 6500, d(3), 'overdue', 0, None, 'Ad-hoc work - 3 days overdue, no promise yet'),
    ],

    # SPECIAL: Disputer
    'Atlas Dispute Resolution': [
        ('INV-ATL-001', 18000, d(90), 'paid', 18000, d(70), 'Legal research Q4 - paid after dispute resolved'),
        ('INV-ATL-002', 12500, d(60), 'paid', 9000, d(45), 'Compliance audit - disputed \$3.5K, paid \$9K of \$12.5K'),
        ('INV-ATL-003', 21000, d(30), 'overdue', 0, None, 'Risk assessment - ACTIVELY DISPUTED. Claims scope mismatch.'),
        ('INV-ATL-004', 8800, d(10), 'overdue', 0, None, 'Document review - 10 days overdue, threatening to dispute'),
        ('INV-ATL-005', 15500, d(2), 'overdue', 0, None, 'Expert witness prep - 2 days overdue'),
    ],
}

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']
total_created = 0

for user_email in test_emails:
    u = User.objects.get(email=user_email)
    user_count = 0
    for cust_name, inv_list in INVOICE_PLANS.items():
        c = Customer.objects.get(user=u, customer_name=cust_name)
        for (inv_num, amount, due_date, status, paid_amt, paid_date, desc) in inv_list:
            inv, created = Invoice.objects.get_or_create(
                user=u, customer=c, invoice_number=inv_num,
                defaults={
                    'amount': Decimal(str(amount)),
                    'due_date': due_date,
                    'status': status,
                    'paid_amount': Decimal(str(paid_amt)),
                    'description': desc,
                }
            )
            if paid_date and created:
                inv.paid_date = paid_date
                inv.save()
            if created:
                total_created += 1
                user_count += 1
    print(f'{user_email}: {user_count} invoices created')

print(f'TOTAL invoices created: {total_created}')
print(f'TOTAL invoices in DB for test users: {Invoice.objects.filter(user__email__in=test_emails).count()}')
"
```

**Also set lifecycle_status for special invoices:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from invoices.models import Invoice
from django.utils import timezone
from datetime import timedelta

# Set payment promises for Heritage Partners
for inv in Invoice.objects.filter(invoice_number='INV-HER-003'):
    inv.lifecycle_status = 'promise_to_pay'
    inv.payment_promise_date = (timezone.now() + timedelta(days=3)).date()
    inv.save()
    print(f'{inv.invoice_number}: Active promise, due in 3 days')

for inv in Invoice.objects.filter(invoice_number='INV-HER-004'):
    inv.lifecycle_status = 'promise_to_pay'
    inv.payment_promise_date = (timezone.now() - timedelta(days=5)).date()
    inv.save()
    print(f'{inv.invoice_number}: BROKEN promise from 5 days ago')

# Set dispute status for Atlas
for inv in Invoice.objects.filter(invoice_number='INV-ATL-003'):
    inv.lifecycle_status = 'disputed'
    inv.save()
    print(f'{inv.invoice_number}: Actively disputed')

print('Special statuses set.')
"
```

**Assertions:**
- **60 customers** across 4 users (15 per user)
- **~300+ invoices** across 4 users (~75 per user)
- Mix of: paid on time, paid late, overdue (1d to 200d), partial payments, disputed, promised, sent (not due yet)
- Amount range: $2,800 to $67,000 — realistic business invoices
- Dates spanning 6 months of history
- Industry-appropriate descriptions (construction projects, IT services, supplies, consulting)

---

### Test 0.11: Seed Communication History for Returning Customers

For realistic testing, seed past communication records:

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from django.utils import timezone
from datetime import timedelta
from automations.models import AutomationLog
from invoices.models import Customer, Invoice
from authentication.models import User

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']
total_logs = 0

for user_email in test_emails:
    u = User.objects.get(email=user_email)

    # Cascade Supply - 2 past reminders (20 and 10 days ago)
    c = Customer.objects.get(user=u, customer_name='Cascade Supply Co')
    inv = Invoice.objects.filter(customer=c, status='overdue').first()
    if inv:
        for days_ago in [20, 10]:
            AutomationLog.objects.get_or_create(
                user=u, invoice=inv, client=c, trigger_type='OVERDUE_REMINDER',
                sent_at=timezone.now() - timedelta(days=days_ago),
                defaults={'status': 'SENT', 'subject': f'Payment Reminder: {inv.invoice_number}',
                          'body': f'Reminder sent {days_ago} days ago...', 'recipient_email': c.customer_email}
            )
            total_logs += 1

    # Phantom Enterprises - 6 past reminders (150, 120, 90, 60, 45, 30 days ago) — ALL IGNORED
    c = Customer.objects.get(user=u, customer_name='Phantom Enterprises LLC')
    inv = Invoice.objects.filter(customer=c, status='overdue').order_by('due_date').first()
    if inv:
        for i, days_ago in enumerate([150, 120, 90, 60, 45, 30]):
            tones = ['friendly', 'friendly', 'professional', 'firm', 'urgent', 'urgent']
            AutomationLog.objects.get_or_create(
                user=u, invoice=inv, client=c, trigger_type='OVERDUE_REMINDER',
                sent_at=timezone.now() - timedelta(days=days_ago),
                defaults={'status': 'SENT', 'subject': f'Reminder #{i+1}: {inv.invoice_number}',
                          'body': f'{tones[i].title()} reminder #{i+1}...', 'recipient_email': c.customer_email}
            )
            total_logs += 1

    # Zenith Global - 4 past reminders, 2 BOUNCED
    c = Customer.objects.get(user=u, customer_name='Zenith Global Trading')
    inv = Invoice.objects.filter(customer=c, status='overdue').order_by('due_date').first()
    if inv:
        for i, (days_ago, status) in enumerate([(160, 'SENT'), (130, 'SENT'), (100, 'BOUNCED'), (70, 'BOUNCED')]):
            AutomationLog.objects.get_or_create(
                user=u, invoice=inv, client=c, trigger_type='OVERDUE_REMINDER',
                sent_at=timezone.now() - timedelta(days=days_ago),
                defaults={'status': status, 'subject': f'Reminder: {inv.invoice_number}',
                          'body': f'Reminder attempt...', 'recipient_email': c.customer_email,
                          'error_message': 'Email bounced - address not found' if status == 'BOUNCED' else ''}
            )
            total_logs += 1

    # Redwood Services - set last_customer_response to 2 days ago (they replied)
    Customer.objects.filter(user=u, customer_name='Redwood Services Inc').update(
        last_customer_response=timezone.now() - timedelta(days=2)
    )

    # Coastal Logistics - set last_customer_response to 1 day ago (recent payer)
    Customer.objects.filter(user=u, customer_name='Coastal Logistics Group').update(
        last_customer_response=timezone.now() - timedelta(days=1)
    )

    # Heritage Partners - past reminder 12 days ago
    c = Customer.objects.get(user=u, customer_name='Heritage Partners Group')
    inv = Invoice.objects.filter(customer=c, status='overdue').first()
    if inv:
        AutomationLog.objects.get_or_create(
            user=u, invoice=inv, client=c, trigger_type='OVERDUE_REMINDER',
            sent_at=timezone.now() - timedelta(days=12),
            defaults={'status': 'SENT', 'subject': f'Payment Reminder: {inv.invoice_number}',
                      'body': 'Professional reminder...', 'recipient_email': c.customer_email}
        )
        total_logs += 1

print(f'Total automation logs seeded: {total_logs}')
print(f'Total logs in DB: {AutomationLog.objects.filter(user__email__in=test_emails).count()}')
"
```

**Assertions:**
- Communication history seeded for 5+ customer types per user
- Mix of: SENT (successful), BOUNCED (failed delivery), with varying dates
- `last_customer_response` set for 2 customers (testing recent-response skip logic)
- Phantom has 6 past reminders (testing escalation history reference in emails)
- Zenith has bounced emails (testing email validity detection)

---

### Test 0.12: Verify Total Seeded Data — HEAVY Environment

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'singoa_project.settings')
django.setup()
from authentication.models import User
from invoices.models import Customer, Invoice
from automations.models import AutomationLog

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']
users = User.objects.filter(email__in=test_emails)
print(f'Test users: {users.count()} (expected: 4)')
print()

for u in users:
    customers = Customer.objects.filter(user=u)
    invoices = Invoice.objects.filter(user=u)
    logs = AutomationLog.objects.filter(user=u)
    overdue = invoices.filter(status='overdue')
    paid = invoices.filter(status='paid')
    sent = invoices.filter(status='sent')
    partial = invoices.filter(paid_amount__gt=0, status='overdue')
    disputed = invoices.filter(lifecycle_status='disputed')
    promised = invoices.filter(lifecycle_status='promise_to_pay')
    from django.db.models import Sum
    outstanding = invoices.exclude(status='paid').aggregate(total=Sum('amount'))['total'] or 0
    print(f'{u.email}:')
    print(f'  Customers: {customers.count()} | Invoices: {invoices.count()}')
    print(f'  Overdue: {overdue.count()} | Paid: {paid.count()} | Sent: {sent.count()}')
    print(f'  Partial payments: {partial.count()} | Disputed: {disputed.count()} | Promised: {promised.count()}')
    print(f'  Automation logs: {logs.count()} | Outstanding: \${outstanding:,.2f}')
    print()

total_c = Customer.objects.filter(user__in=users).count()
total_i = Invoice.objects.filter(user__in=users).count()
total_l = AutomationLog.objects.filter(user__in=users).count()
print(f'=== TOTALS ===')
print(f'Customers: {total_c} (min 60)')
print(f'Invoices: {total_i} (min 280)')
print(f'Automation Logs: {total_l} (min 40)')

assert total_c >= 60, f'FAIL: Only {total_c} customers, need 60+'
assert total_i >= 280, f'FAIL: Only {total_i} invoices, need 280+'
print('ALL SEEDING ASSERTIONS PASSED')
"
```

**Assertions:**
- **4 test users** (1 per industry)
- **60+ customers** (15 per user)
- **280+ invoices** (70+ per user)
- Mix includes: overdue (various stages), paid, sent, partial, disputed, promised
- **40+ automation logs** (communication history)
- Total outstanding amounts are realistic ($100K-$500K per user)

**Phase 0 is PASS when ALL of the above are verified. Update state tracker.**

---

## PHASE 1: AR STRATEGY PLANNER

### Test 1.1: Manual Trigger — Basic Planning (Per User)

For EACH test user, trigger the AR workflow and verify planning results:

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/workflow/run/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"workflow_type": "full"}'
```

**Assertions:**
- Returns 200
- Response has `success: true`
- Response includes `results` object with:
  - `actions_planned` > 0 (should be ≥ number of overdue invoices)
  - `actions_executed` may be 0 (planning only on first run)
- Response has `workflow_type: "full"` or `status: "completed"` or `status: "queued"`
- If status is "queued", wait 10-15 seconds and check status:
  ```bash
  curl -s http://localhost:8000/api/automations/ar/workflow/status/ \
    -H "Authorization: Bearer <construction_token>"
  ```

**Repeat for ALL 4 users. Each should return similar structure.**

---

### Test 1.2: Verify PlannedAction Records Created

After running workflow, verify PlannedActions were created:

```bash
curl -s http://localhost:8000/api/automations/scheduled/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `tasks` is a non-empty list
- Each task object contains:
  - `id` (int)
  - `action_type` or `task_type` (string — e.g., "send_reminder", "email_reminder")
  - `status` (string — "planned" or "scheduled" or "pending")
  - `scheduled_for` or `scheduled_time` (datetime — must be in the future or current day)
- **NO PlannedActions for paid invoices** — check that none reference AlwaysPays Corp invoices (all paid)
- **NO PlannedActions for NewCustomer Ltd** — their invoice isn't overdue yet
- **PlannedActions exist for**: SlowPayer (overdue invoices), Unreliable (overdue), GhostClient (overdue), PromiseMaker (broken promise invoice), RecentPayer (overdue invoice if not recently responded)
- Count total PlannedActions — should be ≥ number of overdue invoices minus those with valid skip reasons

---

### Test 1.3: Verify Tone Escalation Logic

Check that each PlannedAction has the correct tone based on days overdue:

```bash
curl -s http://localhost:8000/api/automations/scheduled/ \
  -H "Authorization: Bearer <construction_token>" | python3 -c "
import json, sys
data = json.load(sys.stdin)
tasks = data.get('tasks', data.get('data', []))
for t in tasks:
    print(f'Invoice: {t.get(\"invoice_number\", \"?\")}, Days Overdue: {t.get(\"days_overdue\", \"?\")}, Tone: {t.get(\"tone\", t.get(\"priority\", \"?\"))}, Scheduled: {t.get(\"scheduled_for\", t.get(\"scheduled_time\", \"?\"))}')
"
```

**Assertions — verify EACH planned action:**
- Invoice 1 day overdue → tone = "friendly"
- Invoice 8 days overdue → tone = "professional"
- Invoice 15 days overdue → tone = "firm"
- Invoice 30+ days overdue → tone = "urgent"
- Invoice 90+ days overdue → tone = "urgent" (critical)
- Invoice 120+ days overdue → tone = "urgent" (critical/final)
- Tone field might be in `parameters`, `context_data`, or `metadata` — check the actual response structure
- If tone is missing from response, check the PlannedAction model directly:
  ```bash
  cd singoa-api && python -c "
  import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
  from automations.models import PlannedAction
  from authentication.models import User
  u = User.objects.get(email='artest.construction@singoa.com')
  for pa in PlannedAction.objects.filter(user=u).order_by('scheduled_for'):
      print(f'ID:{pa.id} Invoice:{pa.invoice.invoice_number if pa.invoice else \"N/A\"} Type:{pa.action_type} Tone:{getattr(pa,\"tone\",\"N/A\")} Status:{pa.status} Scheduled:{pa.scheduled_for}')
  "
  ```

---

### Test 1.4: Verify Business Hours Alignment

**Assertions for EVERY PlannedAction:**
- `scheduled_for` time component is between 09:00 and 17:00 (user's configured business hours)
- `scheduled_for` day is NOT Saturday (5) or Sunday (6) — only Monday-Friday (0-4)
- If current time is outside business hours, tasks should be scheduled for NEXT business day at start hour
- Verify with:
  ```bash
  cd singoa-api && python -c "
  import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
  from automations.models import PlannedAction
  from authentication.models import User
  u = User.objects.get(email='artest.construction@singoa.com')
  for pa in PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']):
      hour = pa.scheduled_for.hour
      weekday = pa.scheduled_for.weekday()
      ok = 9 <= hour <= 17 and weekday < 5
      print(f'ID:{pa.id} Scheduled:{pa.scheduled_for} Hour:{hour} Weekday:{weekday} ValidWindow:{ok}')
      if not ok:
          print(f'  *** FAILURE: Outside business hours! ***')
  "
  ```
- ZERO failures expected. If any task is outside business hours → BUG → FIX the `_align_to_reminder_window` method in `strategy_planner.py`

---

### Test 1.5: Verify Customer Segment Awareness

Trigger customer segmentation:
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/customers/segment/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Response contains `results` with segment counts
- Verify segments are correct:
  ```bash
  curl -s http://localhost:8000/api/automations/ar/customers/segments/ \
    -H "Authorization: Bearer <construction_token>"
  ```
- Response lists customers grouped by segment:
  - VIP segment contains AlwaysPays Corp
  - STANDARD segment contains SlowPayer LLC, RecentPayer Inc
  - RISK segment contains Unreliable Inc, DisputeKing LLC
  - DELINQUENT segment contains GhostClient Co
- Each customer entry has: `id`, `customer_name`, `segment`, `payment_pattern`
- If segmentation changes our manual assignments, that's OK as long as logic is correct
- If segmentation uses on_time_payment_rate, verify the calculation is accurate based on seeded data

---

### Test 1.6: Verify Skip Logic — Paid Invoices NOT Planned

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
paid_invoices = Invoice.objects.filter(user=u, status='paid')
for inv in paid_invoices:
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    if actions.exists():
        print(f'*** FAILURE: Paid invoice {inv.invoice_number} has {actions.count()} planned actions! ***')
    else:
        print(f'OK: Paid invoice {inv.invoice_number} has no planned actions')
"
```

**Assertions:**
- ALL paid invoices print "OK" — ZERO planned actions for paid invoices
- If any print "FAILURE" → BUG → fix the skip logic in strategy planner

---

### Test 1.7: Verify Skip Logic — Disputed Invoices BLOCKED

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction, ActionQueueItem
from invoices.models import Invoice, Customer
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
dispute_customer = Customer.objects.get(user=u, customer_name='DisputeKing LLC')
disputed_invoices = Invoice.objects.filter(customer=dispute_customer, lifecycle_status='disputed')

for inv in disputed_invoices:
    # Should NOT have planned actions
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    if actions.exists():
        print(f'*** FAILURE: Disputed invoice {inv.invoice_number} has {actions.count()} planned actions! ***')
    else:
        print(f'OK: Disputed invoice {inv.invoice_number} blocked from automation')

    # SHOULD have ActionQueueItem for human review
    queue_items = ActionQueueItem.objects.filter(invoice=inv)
    if queue_items.exists():
        print(f'  OK: ActionQueueItem exists for disputed invoice (type: {queue_items.first().action_type})')
    else:
        print(f'  WARNING: No ActionQueueItem for disputed invoice (may need manual review workflow)')
"
```

**Assertions:**
- Disputed invoices have NO planned actions
- Disputed invoices have ActionQueueItem entries (or at least a warning/blocker)
- If disputed invoices DO have planned actions → FIX the dispute check in strategy planner

---

### Test 1.8: Verify Skip Logic — Valid Payment Promise Within Grace Period

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice, Customer
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
promise_customer = Customer.objects.get(user=u, customer_name='PromiseMaker Corp')
invoices = Invoice.objects.filter(customer=promise_customer)

for inv in invoices:
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    promise_date = getattr(inv, 'payment_promise_date', None)
    lifecycle = getattr(inv, 'lifecycle_status', None)
    print(f'Invoice: {inv.invoice_number}, Status: {inv.status}, Lifecycle: {lifecycle}, Promise: {promise_date}, PlannedActions: {actions.count()}')
"
```

**Assertions:**
- Invoice with VALID promise (future date within grace) → NO planned actions (skipped)
- Invoice with BROKEN promise (past date beyond grace) → HAS planned actions (should be reminded)
- Invoice without promise → normal planning applies

---

### Test 1.9: Verify Skip Logic — Recent Customer Response

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice, Customer
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
recent_customer = Customer.objects.get(user=u, customer_name='RecentPayer Inc')
print(f'Last response: {recent_customer.last_customer_response}')

invoices = Invoice.objects.filter(customer=recent_customer, status='overdue')
for inv in invoices:
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    print(f'Invoice: {inv.invoice_number}, Overdue: {inv.status}, PlannedActions: {actions.count()}')
"
```

**Assertions:**
- RecentPayer's overdue invoices should have FEWER or NO planned actions because `last_customer_response` was set to 1 day ago
- The system should wait before sending another reminder after a customer just responded
- If planned actions exist despite recent response → check if the 3-day cooldown logic exists in strategy planner

---

### Test 1.10: Verify Skip Logic — Reminder Already Sent Within Frequency Window

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction, AutomationLog
from invoices.models import Invoice, Customer
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
slow_customer = Customer.objects.get(user=u, customer_name='SlowPayer LLC')

# Check if we seeded a reminder 10 days ago — frequency is 7 days, so new reminder should be allowed
invoices = Invoice.objects.filter(customer=slow_customer, status='overdue')
for inv in invoices:
    last_log = AutomationLog.objects.filter(invoice=inv, status='SENT').order_by('-sent_at').first()
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    last_sent = last_log.sent_at if last_log else 'Never'
    print(f'Invoice: {inv.invoice_number}, Last Sent: {last_sent}, PlannedActions: {actions.count()}')
"
```

**Assertions:**
- If last reminder was sent > `reminder_frequency_days` ago (7 days) → new planned action should exist
- If last reminder was sent < `reminder_frequency_days` ago → NO new planned action (too soon)
- Seeded data: SlowPayer had reminder 10 days ago → new reminder IS due (10 > 7)

---

### Test 1.11: Verify ActionQueueItems for System Blockers

```bash
curl -s http://localhost:8000/api/automations/ar/action-queue/?status=pending \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `items` is a list (may be empty if no blockers)
- Check for expected blocker types:
  - If a customer has invalid email → ActionQueueItem with `action_type` containing "email" or "invalid"
  - If user has no email integration → ActionQueueItem warning about email setup
  - If disputed invoice → ActionQueueItem for human review
- Each item has: `id`, `action_type`, `priority`, `status`, `title`, `description`, `customer_name`
- `quick_actions` list should be non-empty (suggested resolution actions)

---

### Test 1.12: Industry-Specific Planning — Construction

For the construction user specifically:
```bash
curl -s http://localhost:8000/api/automations/ar/customers/<construction_vip_customer_id>/strategy/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `strategy` object contains industry-aware recommendations
- If construction-specific fields exist (retainage awareness, lien deadlines), they should appear in strategy
- Tone recommendations should account for longer construction payment cycles
- If strategy is completely generic with no industry awareness → NOTE for Phase 4 enhancement

---

### Test 1.13: Industry-Specific Planning — Education

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<education_customer_id>/strategy/ \
  -H "Authorization: Bearer <education_token>"
```

**Assertions:**
- Strategy mentions academic context if applicable
- Tone is adjusted for education sector
- Payment plan awareness exists

---

### Test 1.14: Industry-Specific Planning — Wholesale

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<wholesale_customer_id>/strategy/ \
  -H "Authorization: Bearer <wholesale_token>"
```

**Assertions:**
- Strategy accounts for wholesale volume
- Deduction-aware if applicable
- Different escalation paths for wholesale

---

### Test 1.15: Activity Feed Logging

```bash
curl -s http://localhost:8000/api/automations/ar/activity-feed/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `items` list is non-empty
- Contains entries for:
  - Workflow run started/completed
  - Planning decisions (X actions planned)
  - Segment updates (if segmentation ran)
  - Blocked actions (disputed invoices, etc.)
- Each item has: `id`, `activity_type`, `title`, `description`, `created_at`
- `activity_type` values include things like "workflow_run", "action_planned", "segment_updated"
- Items are ordered by `created_at` descending (newest first)

---

### Test 1.16: Audit Trail Verification

```bash
curl -s http://localhost:8000/api/automations/ar/audit-log/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `logs` list is non-empty
- Each log entry has:
  - `id` (int)
  - `action` (string — e.g., "workflow_executed", "action_planned")
  - `actor` or `actor_display` (string — user who triggered)
  - `description` (string — human-readable description)
  - `created_at` (datetime)
  - `entry_hash` (string — for chain integrity)
- Entries trace back to the workflow run we just triggered

**Verify audit chain integrity:**
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/audit-log/verify/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `chain_valid: true`
- If `chain_valid: false` → BUG → fix the hash chain logic

---

### Test 1.17: Edge Case — Duplicate Run Prevention

Run the workflow TWICE in rapid succession:
```bash
# First run
curl -s -X POST http://localhost:8000/api/automations/ar/workflow/run/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"

# Immediate second run (within 1 second)
curl -s -X POST http://localhost:8000/api/automations/ar/workflow/run/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Then count PlannedActions:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from authentication.models import User
u = User.objects.get(email='artest.construction@singoa.com')
total = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).count()
print(f'Total active PlannedActions: {total}')

# Check for duplicates (same invoice, same action_type, overlapping schedule)
from django.db.models import Count
dupes = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).values('invoice_id','action_type').annotate(cnt=Count('id')).filter(cnt__gt=1)
for d in dupes:
    print(f'*** DUPLICATE: Invoice {d[\"invoice_id\"]}, Type {d[\"action_type\"]}, Count: {d[\"cnt\"]} ***')
if not dupes:
    print('OK: No duplicates found')
"
```

**Assertions:**
- "OK: No duplicates found" — no duplicate PlannedActions for same invoice
- If duplicates exist → BUG → fix deduplication logic in strategy planner

---

### Test 1.18: Edge Case — No Overdue Invoices

Create a temporary user with only paid invoices and run the workflow:
```bash
# Use the generic user and temporarily pay all their overdue invoices
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User
u = User.objects.get(email='artest.generic@singoa.com')
# First, count overdue
overdue_count = Invoice.objects.filter(user=u, status='overdue').count()
print(f'Overdue before: {overdue_count}')
"
```

If there are overdue invoices, this test requires a fresh user or a specific scenario. Alternative approach — just verify the workflow handles 0 overdue gracefully by checking response of workflow run for a user with only paid invoices.

**Assertions:**
- Workflow completes without error (200, success: true)
- `actions_planned` = 0
- No crash, no 500 error

---

### Test 1.19: Edge Case — User With No Business Rules

Check what happens if business rules don't exist:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import BusinessRuleSet
from authentication.models import User
# Check if business rules exist for a test user
u = User.objects.get(email='artest.construction@singoa.com')
rules = BusinessRuleSet.objects.filter(user=u)
print(f'Rules exist: {rules.exists()}, Count: {rules.count()}')
if rules.exists():
    r = rules.first()
    print(f'  first_reminder_days: {r.first_reminder_days}')
    print(f'  reminder_frequency_days: {r.reminder_frequency_days}')
    print(f'  tone_overdue_1: {r.tone_overdue_1}')
"
```

**Assertions:**
- If rules exist → workflow uses them correctly
- If rules don't exist → workflow should use default values OR return a clear error
- Should NOT crash with 500

---

### Test 1.20: Edge Case — User With No Email Integration

```bash
curl -s http://localhost:8000/api/integrations/email/status/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns status of email integration (likely not connected for test users)
- When email is not connected:
  - Workflow still plans actions (planning doesn't require email)
  - BUT an ActionQueueItem or warning should be created about missing email
  - Check action queue for email-related warnings:
    ```bash
    curl -s "http://localhost:8000/api/automations/ar/action-queue/?status=pending" \
      -H "Authorization: Bearer <construction_token>" | python3 -c "
    import json, sys
    data = json.load(sys.stdin)
    items = data.get('items', [])
    for item in items:
        if 'email' in str(item).lower() or 'integration' in str(item).lower():
            print(f'Found email warning: {item.get(\"title\", item.get(\"description\", \"?\"))}')
    "
    ```

**Phase 1 is PASS when ALL 20 tests above pass. Update state tracker.**

---

## PHASE 2: EVENT SCHEDULING & CELERY INTEGRATION

### Test 2.1: Verify PlannedAction Record Structure

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
actions = PlannedAction.objects.filter(user=u).order_by('scheduled_for')[:5]
for a in actions:
    print(f'--- PlannedAction ID: {a.id} ---')
    print(f'  action_type: {a.action_type}')
    print(f'  status: {a.status}')
    print(f'  scheduled_for: {a.scheduled_for}')
    print(f'  invoice: {a.invoice.invoice_number if a.invoice else None}')
    print(f'  customer: {a.invoice.customer.customer_name if a.invoice and a.invoice.customer else None}')
    print(f'  sequence_number: {getattr(a, \"sequence_number\", \"N/A\")}')
    print(f'  parent_action: {getattr(a, \"parent_action_id\", \"N/A\")}')
    print(f'  execution_lock: {getattr(a, \"execution_lock\", \"N/A\")}')
    print(f'  tone: {getattr(a, \"tone\", getattr(a, \"parameters\", {}).get(\"tone\", \"N/A\"))}')
"
```

**Assertions for EACH PlannedAction:**
- `action_type` is a valid value (e.g., "send_reminder", "email_reminder", "escalation")
- `status` is "planned", "scheduled", or "pending"
- `scheduled_for` is a valid datetime in the future (or today within business hours)
- `invoice` links to a valid overdue invoice
- `sequence_number` increments for same invoice (1st reminder = 1, 2nd = 2, etc.)
- `execution_lock` is None/null (not currently being executed)

---

### Test 2.2: Verify Scheduled Tasks via API

```bash
curl -s http://localhost:8000/api/automations/scheduled/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `tasks` list contains all PlannedActions for this user
- Each task has: scheduled time, status, type, linked invoice info
- Tasks are sorted by `scheduled_for` ascending (earliest first)
- No tasks for paid invoices
- No tasks for disputed invoices
- Tasks span correct date range based on business rules

---

### Test 2.3: Verify Scheduled Tasks By Invoice

Pick a specific overdue invoice and check its tasks:
```bash
curl -s http://localhost:8000/api/automations/scheduled/invoice/<invoice_id>/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with tasks specific to this invoice
- Tasks show the escalation sequence (reminder 1, 2, 3, etc.)
- Each subsequent task has a later `scheduled_for` date
- Tone escalates: friendly → professional → firm → urgent

---

### Test 2.4: Frontend — Scheduled Tasks View (Chrome MCP)

```
1. Navigate to http://localhost:3000 in Chrome
2. Login as construction user (artest.construction@singoa.com / TestPass123!@#)
3. Navigate to /automations (or wherever scheduled tasks are shown)
4. Take snapshot of the page
```

**Assertions:**
- Page loads without errors (no blank screen, no console errors)
- Scheduled tasks are visible in a list or timeline view
- Each task shows: customer name, invoice number, scheduled time, tone/priority, status
- Tasks can be filtered by status (planned, completed, failed)
- Tasks can be filtered by customer
- Tasks can be filtered by date range
- Calendar view (if exists) shows tasks on correct dates
- Take screenshot for visual verification

---

### Test 2.5: Manual Execution Trigger — Only Due Tasks

```bash
curl -s -X POST http://localhost:8000/api/automations/scheduled/execute/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Response includes counts: `executed_count` or similar
- Only tasks where `scheduled_for <= now()` are executed
- Future tasks remain in "planned"/"scheduled" status
- Verify by re-checking scheduled tasks:
  ```bash
  curl -s http://localhost:8000/api/automations/scheduled/ \
    -H "Authorization: Bearer <construction_token>" | python3 -c "
  import json, sys
  data = json.load(sys.stdin)
  tasks = data.get('tasks', [])
  for t in tasks:
      print(f'Status: {t.get(\"status\")}, Scheduled: {t.get(\"scheduled_for\", t.get(\"scheduled_time\"))}')
  "
  ```
- Executed tasks now have status "completed" or "executed"
- Future tasks still have status "planned" or "scheduled"

---

### Test 2.6: Task Cancellation for Specific Invoice

Pick an invoice with planned tasks and cancel them:
```bash
curl -s -X POST http://localhost:8000/api/automations/scheduled/invoice/<invoice_id>/cancel/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `cancelled_count` > 0
- Re-check tasks for this invoice:
  ```bash
  curl -s http://localhost:8000/api/automations/scheduled/invoice/<invoice_id>/ \
    -H "Authorization: Bearer <construction_token>"
  ```
- All future tasks for this invoice now have status "cancelled"
- Already-completed tasks (if any) remain unchanged (status still "completed")
- Activity feed shows cancellation entry:
  ```bash
  curl -s http://localhost:8000/api/automations/ar/activity-feed/?limit=5 \
    -H "Authorization: Bearer <construction_token>"
  ```
  Should contain "cancelled" or "tasks cancelled" entry
- Audit log shows cancellation with user attribution

---

### Test 2.7: Celery Beat Schedule Verification

Verify the Celery beat tasks are properly configured:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from django_celery_beat.models import PeriodicTask
for task in PeriodicTask.objects.filter(enabled=True).order_by('name'):
    print(f'Task: {task.name}')
    print(f'  Schedule: {task.interval or task.crontab or task.solar}')
    print(f'  Task: {task.task}')
    print(f'  Enabled: {task.enabled}')
    print()
" 2>/dev/null || echo "django_celery_beat not installed or no periodic tasks"
```

**Also check celery config directly:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from singoa_project.celery import app
beat_schedule = app.conf.beat_schedule or {}
for name, config in beat_schedule.items():
    print(f'Task: {name}')
    print(f'  Function: {config.get(\"task\")}')
    print(f'  Schedule: {config.get(\"schedule\")}')
    print()
"
```

**Assertions:**
- `execute_due_planned_actions` — runs every 5 minutes (300 seconds)
- `run_strategy_planner_for_all_users` — runs hourly (3600 seconds or crontab)
- `send_scheduled_collection_reminders` — has a schedule defined
- `update_customer_segments` — runs daily
- `release_stale_execution_locks` — has a schedule defined
- All tasks are enabled
- If any critical task is missing → BUG → add it to beat_schedule

---

### Test 2.8: Manual Celery Task Trigger — execute_due_planned_actions

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import execute_due_planned_actions
result = execute_due_planned_actions()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Returns a result dict or None (no crash)
- If tasks were due, they are now executed
- If no tasks were due, returns 0 executed count
- Check no 500-level errors in Django logs

---

### Test 2.9: Manual Celery Task Trigger — run_strategy_planner_for_all_users

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import run_strategy_planner_for_all_users
result = run_strategy_planner_for_all_users()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error for all 4 test users
- Each user's overdue invoices are analyzed
- New PlannedActions created where needed
- No duplicates created (if actions already exist)

---

### Test 2.10: Manual Celery Task Trigger — update_customer_segments

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import update_customer_segments
result = update_customer_segments()
print(f'Result: {result}')
"
```

**Assertions:**
- Returns dict with `total`, `updated`, `unchanged`, `failed`
- `failed` = 0
- Segments are recalculated based on actual payment history
- ActionQueueItems created for escalations (RISK/DELINQUENT customers needing attention)

---

### Test 2.11: Execution Lock Mechanism — No Duplicate Execution

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from django.utils import timezone
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')

# Find a pending task and manually set execution_lock
task = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).first()
if task:
    task.execution_lock = timezone.now()
    task.save()
    print(f'Locked task {task.id}')

    # Now try to execute — this task should be SKIPPED
    from automations.tasks.celery_tasks import execute_due_planned_actions
    result = execute_due_planned_actions()
    print(f'Execution result: {result}')

    # Verify the locked task was NOT executed
    task.refresh_from_db()
    print(f'Task {task.id} status after execution: {task.status}')
    print(f'Task {task.id} lock still set: {task.execution_lock is not None}')

    # Clean up — remove lock
    task.execution_lock = None
    task.save()
    print(f'Lock removed from task {task.id}')
else:
    print('No pending tasks found to test lock mechanism')
"
```

**Assertions:**
- Locked task is NOT re-executed (status remains unchanged)
- Other unlocked due tasks (if any) are still executed
- No errors or crashes when encountering locked tasks

---

### Test 2.12: Stale Lock Release

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from django.utils import timezone
from datetime import timedelta
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')

# Find a pending task and set an OLD lock (>30 min ago)
task = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).first()
if task:
    task.execution_lock = timezone.now() - timedelta(minutes=45)
    task.save()
    print(f'Set stale lock on task {task.id} (45 min old)')

    # Run stale lock release
    from automations.tasks.celery_tasks import release_stale_execution_locks
    result = release_stale_execution_locks()
    print(f'Release result: {result}')

    # Verify lock was released
    task.refresh_from_db()
    print(f'Task {task.id} lock after release: {task.execution_lock}')
    if task.execution_lock is None:
        print('OK: Stale lock released successfully')
    else:
        print('*** FAILURE: Stale lock NOT released ***')
else:
    print('No pending tasks found to test stale lock release')
"
```

**Assertions:**
- Stale lock (>30 min old) is released
- Task is now available for execution again
- If `release_stale_execution_locks` task doesn't exist → BUG → implement it

---

### Test 2.13: Decision Engine for Specific Invoice

```bash
curl -s http://localhost:8000/api/automations/ar/decision/<overdue_invoice_id>/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Response contains:
  - `action` (string — recommended action)
  - `priority` (string — "low", "medium", "high", "critical")
  - `reasoning` (string — human-readable explanation)
  - `confidence` (float — 0.0 to 1.0)
  - `requires_human` (bool — whether human review is needed)
- For 1-day overdue invoice → action should be "send_reminder", priority "low", requires_human false
- For 90+ day overdue → action should be "escalate" or "final_notice", priority "critical"
- For disputed invoice → `requires_human: true`

---

### Test 2.14: Verify Pause/Resume Invoice Automation

```bash
# Pause automation for a specific invoice
curl -s -X POST http://localhost:8000/api/automations/pause/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"invoice_id": <overdue_invoice_id>}'
```

**Assertions:**
- Returns 200 with `success: true`
- Future scheduled tasks for this invoice are paused (not cancelled, just paused)
- Executor skips paused invoices
- Verify by running executor and checking the invoice's tasks aren't executed

**Resume (if endpoint exists):**
```bash
curl -s -X POST http://localhost:8000/api/automations/resume/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"invoice_id": <overdue_invoice_id>}'
```

---

**Phase 2 is PASS when ALL 14 tests pass. Update state tracker.**

---

## PHASE 3: PRE-EXECUTION VALIDATION (EVENT EXECUTOR)

The Event Executor (`scheduled_executor.py`) runs 6+ pre-checks before sending any email. Each test below simulates a specific scenario and verifies the executor handles it correctly.

### Test 3.1: Invoice Already Paid — Executor Skips

**Setup:** Take an overdue invoice that has a PlannedAction, then mark it as paid:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
# Find a planned action for an overdue invoice
pa = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending'], invoice__status='overdue').first()
if pa:
    inv = pa.invoice
    print(f'Target: PlannedAction {pa.id} for Invoice {inv.invoice_number} (status: {inv.status})')

    # Mark invoice as paid
    inv.status = 'paid'
    inv.paid_amount = inv.amount
    inv.paid_date = timezone.now().date()
    inv.save()
    print(f'Invoice {inv.invoice_number} marked as PAID')

    # Now make this task due immediately
    pa.scheduled_for = timezone.now()
    pa.save()
    print(f'PlannedAction {pa.id} scheduled for NOW')
else:
    print('No suitable PlannedAction found')
"
```

**Execute:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import execute_due_planned_actions
result = execute_due_planned_actions()
print(f'Result: {result}')
"
```

**Verify:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
pa = PlannedAction.objects.get(id=<pa_id>)
print(f'Status: {pa.status}')
print(f'Skip reason: {getattr(pa, \"skip_reason\", getattr(pa, \"notes\", \"N/A\"))}')
"
```

**Assertions:**
- PlannedAction status is "skipped" or "cancelled" (NOT "completed" or "executed")
- Skip reason contains "paid" or "invoice_already_paid"
- NO email was sent (check AutomationLog — no new SENT entry for this invoice)
- Activity feed shows "skipped because paid" entry

**Cleanup:** Revert the invoice back to overdue for subsequent tests:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
inv = Invoice.objects.get(invoice_number='<invoice_number>')
inv.status = 'overdue'
inv.paid_amount = 0
inv.paid_date = None
inv.save()
print('Invoice reverted to overdue')
"
```

---

### Test 3.2: Recent Payment Received — Executor Skips

**Setup:** Create a payment for an overdue invoice (partial or full, received yesterday):
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from payment_matching.models import Payment
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')
inv = Invoice.objects.filter(user=u, status='overdue').first()
if inv:
    # Create a recent payment
    Payment.objects.create(
        user=u,
        invoice=inv,
        amount=inv.amount / 2,  # partial payment
        payment_date=timezone.now().date() - timedelta(days=1),
        status='MATCHED',
        reference='TEST-RECENT-PAY-001'
    )
    print(f'Created recent payment for {inv.invoice_number}')
"
```

**Execute the executor, then verify:**

**Assertions:**
- PlannedAction for this invoice is SKIPPED
- Skip reason mentions "recent_payment_received" or "payment_detected"
- No email sent for this invoice
- If executor doesn't check for recent payments → BUG → add the check to `_should_execute_task()` in `scheduled_executor.py`

---

### Test 3.3: Customer Recently Responded — Executor Skips

**Setup:** Set `last_customer_response` to 1 day ago for a customer:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
from django.utils import timezone
from datetime import timedelta

c = Customer.objects.filter(customer_name='SlowPayer LLC').first()
c.last_customer_response = timezone.now() - timedelta(days=1)
c.save()
print(f'Set last_customer_response for {c.customer_name} to 1 day ago')
"
```

**Execute, then verify the PlannedAction for this customer's invoices:**

**Assertions:**
- PlannedAction status is "skipped"
- Skip reason mentions "recent_customer_response" or "customer_responded"
- No email sent
- The cooldown period should be configurable (default 3 days or from business rules)

---

### Test 3.4: Active Dispute — Executor BLOCKS (Creates ActionQueueItem)

**Setup:** Ensure DisputeKing's disputed invoice has a PlannedAction (re-run planner if needed):
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction, ActionQueueItem
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
dispute_customer = Customer.objects.get(user=u, customer_name='DisputeKing LLC')
disputed_inv = Invoice.objects.filter(customer=dispute_customer, lifecycle_status='disputed').first()

if disputed_inv:
    # Create a PlannedAction if none exists
    pa, created = PlannedAction.objects.get_or_create(
        user=u,
        invoice=disputed_inv,
        action_type='send_reminder',
        defaults={
            'status': 'planned',
            'scheduled_for': timezone.now(),
        }
    )
    print(f'PlannedAction {pa.id} for disputed invoice {disputed_inv.invoice_number} (created: {created})')
"
```

**Execute, then verify:**

**Assertions:**
- PlannedAction is BLOCKED (status = "blocked" or "failed" with reason)
- An ActionQueueItem is created with:
  - `action_type` containing "dispute" or "review"
  - `priority` = "high" or "medium"
  - `description` mentioning the dispute
  - `customer` linked to DisputeKing LLC
  - `invoice` linked to the disputed invoice
- NO email sent to disputed customer
- Human review is required before automation can proceed

---

### Test 3.5: Payment Promise — Within Grace Period (SKIP) vs Expired (EXECUTE)

**Test 3.5a: Valid Promise (within grace period) — Should SKIP:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
promise_customer = Customer.objects.get(user=u, customer_name='PromiseMaker Corp')

# Find the invoice with valid promise (future date)
valid_promise_inv = Invoice.objects.filter(
    customer=promise_customer,
    lifecycle_status='promise_to_pay'
).exclude(payment_promise_date__lt=timezone.now().date()).first()

if valid_promise_inv:
    pa = PlannedAction.objects.filter(invoice=valid_promise_inv, status__in=['planned','scheduled','pending']).first()
    if pa:
        pa.scheduled_for = timezone.now()
        pa.save()
        print(f'PlannedAction {pa.id} for valid-promise invoice {valid_promise_inv.invoice_number} scheduled for NOW')
        print(f'Promise date: {valid_promise_inv.payment_promise_date}')
    else:
        print(f'No PlannedAction for valid-promise invoice')
"
```

Execute and verify: Task should be SKIPPED (within grace period).

**Test 3.5b: Broken Promise (expired) — Should EXECUTE:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
promise_customer = Customer.objects.get(user=u, customer_name='PromiseMaker Corp')

# Find the invoice with broken promise (past date)
broken_promise_inv = Invoice.objects.filter(
    customer=promise_customer,
    lifecycle_status='promise_to_pay',
    payment_promise_date__lt=timezone.now().date()
).first()

if broken_promise_inv:
    pa = PlannedAction.objects.filter(invoice=broken_promise_inv, status__in=['planned','scheduled','pending']).first()
    if pa:
        pa.scheduled_for = timezone.now()
        pa.save()
        print(f'PlannedAction {pa.id} for broken-promise invoice {broken_promise_inv.invoice_number} scheduled for NOW')
        print(f'Promise date: {broken_promise_inv.payment_promise_date} (PAST)')
"
```

Execute and verify: Task should EXECUTE (grace period expired, broken promise).

**Assertions:**
- 3.5a: Valid promise → SKIPPED, reason = "active_payment_promise" or "promise_within_grace"
- 3.5b: Broken promise → EXECUTED, email sent or attempted
- Grace period = `promise_grace_days` from business rules (we set 2 days)

---

### Test 3.6: Invalid Email Address — Executor BLOCKS

**Setup:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer, Invoice
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')

# Temporarily set a customer's email to invalid
c = Customer.objects.filter(user=u, customer_name='SlowPayer LLC').first()
original_email = c.customer_email
c.customer_email = 'notavalidemail'
c.save()
print(f'Set {c.customer_name} email to: {c.customer_email}')

# Ensure there's a due PlannedAction
inv = Invoice.objects.filter(customer=c, status='overdue').first()
if inv:
    pa, _ = PlannedAction.objects.get_or_create(
        user=u, invoice=inv, action_type='send_reminder',
        defaults={'status': 'planned', 'scheduled_for': timezone.now()}
    )
    pa.scheduled_for = timezone.now()
    pa.status = 'planned'
    pa.save()
    print(f'PlannedAction {pa.id} ready')
"
```

Execute, then verify:

**Assertions:**
- Task is BLOCKED, NOT executed
- ActionQueueItem created asking user to fix the email
- ActionQueueItem has `quick_actions` including "update_email"
- No email send attempted (would fail anyway)

**Cleanup:** Restore original email.

---

### Test 3.7: Missing Email Address — Executor BLOCKS

Same as 3.6 but set email to empty string or None:
```bash
c.customer_email = ''  # or None
```

**Assertions:**
- Same as 3.6 — BLOCKED, ActionQueueItem created
- Error message mentions "missing email" not "invalid email"

---

### Test 3.8: Automation Paused by User — All Tasks Skipped

```bash
# Pause automation for a specific invoice
curl -s -X POST http://localhost:8000/api/automations/pause/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"invoice_id": <overdue_invoice_id>}'
```

Execute the task executor:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import execute_due_planned_actions
result = execute_due_planned_actions()
print(f'Result: {result}')
"
```

**Assertions:**
- Paused invoice's tasks are SKIPPED
- Other (non-paused) tasks execute normally
- Skip reason mentions "automation_paused" or "user_paused"
- Paused state persists across executor runs until explicitly resumed

---

### Test 3.9: Re-validation After Conditions Change (Race Condition)

**Scenario:** Task was planned when invoice was overdue, but customer paid between planning and execution.

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Invoice
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')

# Get a planned action
pa = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending'], invoice__status='overdue').first()
if pa:
    inv = pa.invoice
    # Make it due now
    pa.scheduled_for = timezone.now()
    pa.save()

    # Simulate payment arriving between planning and execution
    inv.status = 'paid'
    inv.paid_amount = inv.amount
    inv.save()
    print(f'Invoice {inv.invoice_number} paid AFTER planning, BEFORE execution')

    # Execute
    from automations.tasks.celery_tasks import execute_due_planned_actions
    result = execute_due_planned_actions()
    print(f'Execution result: {result}')

    # Check
    pa.refresh_from_db()
    print(f'PlannedAction {pa.id} status: {pa.status}')

    # Cleanup
    inv.status = 'overdue'
    inv.paid_amount = 0
    inv.save()
"
```

**Assertions:**
- Executor detects that invoice is now paid AT EXECUTION TIME
- Task is SKIPPED despite being planned when overdue
- No email sent
- This confirms the executor does fresh validation, not just trusting the plan

---

### Test 3.10: Concurrent Execution Prevention

**Simulate two executors running simultaneously:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction, AutomationLog
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')

# Count emails before
before_count = AutomationLog.objects.filter(user=u, status='SENT').count()

# Run executor twice 'simultaneously' (in sequence but checking for locks)
from automations.tasks.celery_tasks import execute_due_planned_actions
result1 = execute_due_planned_actions()
result2 = execute_due_planned_actions()

# Count emails after
after_count = AutomationLog.objects.filter(user=u, status='SENT').count()
new_emails = after_count - before_count

print(f'Run 1: {result1}')
print(f'Run 2: {result2}')
print(f'New emails sent: {new_emails}')

# Check for duplicate sends (same invoice, same timestamp within 1 minute)
from django.db.models import Count
from datetime import timedelta
recent_logs = AutomationLog.objects.filter(
    user=u,
    status='SENT',
    sent_at__gte=timezone.now() - timedelta(minutes=5)
)
dupes = recent_logs.values('invoice_id').annotate(cnt=Count('id')).filter(cnt__gt=1)
for d in dupes:
    print(f'*** DUPLICATE SEND: Invoice {d[\"invoice_id\"]} sent {d[\"cnt\"]} times ***')
if not dupes:
    print('OK: No duplicate sends detected')
"
```

**Assertions:**
- No duplicate email sends for the same invoice
- Execution locks prevent double-processing
- Second run finds tasks already locked/completed and skips them

---

### Test 3.11: Email Integration Not Connected — Graceful Handling

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')

# Ensure no email integration exists
from integrations.models import EmailIntegration
EmailIntegration.objects.filter(user=u).delete()
print('Removed all email integrations for test user')

# Make a task due now
pa = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).first()
if pa:
    pa.scheduled_for = timezone.now()
    pa.save()

    # Execute
    from automations.tasks.celery_tasks import execute_due_planned_actions
    result = execute_due_planned_actions()
    print(f'Result: {result}')

    pa.refresh_from_db()
    print(f'Task status: {pa.status}')
"
```

**Assertions:**
- Task does NOT crash with 500 error
- Task is marked as "failed" or "blocked" with reason "no_email_integration"
- ActionQueueItem created prompting user to connect email
- System does NOT silently succeed (claiming email was sent when it wasn't)

---

### Test 3.12: Working Hours Enforcement at Execution Time

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')

# Schedule a task for 2 AM (outside business hours)
pa = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).first()
if pa:
    # Set to 2 AM today
    now = timezone.now()
    outside_hours = now.replace(hour=2, minute=0, second=0)
    if outside_hours < now:
        outside_hours += timedelta(days=1)
    pa.scheduled_for = outside_hours
    pa.save()
    print(f'Task {pa.id} scheduled for {outside_hours} (outside business hours)')
    print(f'Executor should either skip this or reschedule to next business hour window')
"
```

**Assertions:**
- Executor does NOT send emails at 2 AM
- Task is either rescheduled to 9 AM or held until business hours
- If executor blindly sends at any hour → BUG → add working hours check

**Phase 3 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 4: COLLECTION PSYCHOLOGIST — AI PROMPT ENGINEERING

### Test 4.1: Assess Current Collection Psychologist Implementation

**Read the source code:**
```bash
# Read the file to understand current state
cat singoa-api/automations/services/collection_psychologist.py
```

**Assess and document:**
- Is email generation using actual AI (Gemini/Claude) or rule-based templates?
- Does `generate_collection_email()` call `GeminiAIService` or just return templated text?
- Does `ai_personalizer.py` exist? What does it do?
- Does the prompt include customer context (payment history, segment, relationship)?
- Rate current quality on 1-10 scale

**Assertions:**
- If rule-based only → MUST be enhanced to use AI (Phase 4.2)
- If AI is used but prompt is generic → MUST be enhanced with expert-level prompt
- Document current state in state tracker

---

### Test 4.2: ENHANCE Collection Psychologist to Expert Level

**This is the most critical enhancement in the entire workflow.**

If the current implementation is rule-based or uses a weak AI prompt, ENHANCE it. The enhanced Collection Psychologist MUST:

**Step 1: Research collection email psychology best practices**
Read these concepts and incorporate into the prompt:
- Cialdini's 6 Principles of Influence (reciprocity, commitment, social proof, authority, liking, scarcity)
- Loss aversion (Kahneman & Tversky) — people fear losses more than they value gains
- The Zeigarnik Effect — incomplete tasks create psychological tension
- DISC personality framework for communication styles
- Motivational Interviewing techniques for resistant communicators

**Step 2: Build the expert prompt**
The AI prompt must include ALL of the following context variables:
```python
prompt_context = {
    # Invoice Details
    "invoice_number": str,
    "invoice_amount": Decimal,
    "outstanding_amount": Decimal,
    "due_date": date,
    "days_overdue": int,
    "invoice_description": str,

    # Customer Profile
    "customer_name": str,
    "customer_segment": str,  # VIP, STANDARD, RISK, DELINQUENT
    "payment_pattern": str,  # PROMPT, DELAYED, ERRATIC, NON_PAYER
    "on_time_rate": float,  # percentage of invoices paid on time
    "avg_days_to_pay": int,  # average days to payment
    "total_relationship_value": Decimal,  # lifetime invoice total
    "relationship_months": int,  # how long they've been a customer
    "total_outstanding": Decimal,  # all unpaid across invoices
    "previous_reminders_sent": int,  # for THIS invoice
    "last_reminder_date": date,
    "last_customer_response": date,  # when they last replied
    "has_disputes": bool,
    "has_payment_promises": bool,

    # Communication Preferences
    "preferred_tone": str,  # from business rules based on overdue stage
    "sender_name": str,
    "email_signature": str,
    "custom_instructions": str,  # user's special instructions

    # Industry Context
    "industry": str,
    "industry_terminology": dict,  # industry-specific terms to use

    # Historical Context
    "past_email_subjects": list,  # avoid repeating same subject lines
    "past_email_tones": list,  # show escalation history
}
```

**Step 3: The AI system prompt should be:**
```
You are an expert Accounts Receivable communication specialist with 20+ years of experience in B2B collections. You combine the psychology of Robert Cialdini, the empathy of a skilled negotiator, and the precision of a financial professional.

Your task: Write a personalized collection email that maximizes the probability of payment while preserving the business relationship.

RULES:
1. NEVER use generic templates — every email must be uniquely crafted for this specific customer and situation
2. NEVER include AI artifacts ("As an AI...", markdown formatting, etc.)
3. ALWAYS reference specific invoice details (number, amount, date)
4. ALWAYS match the tone to the customer's segment and overdue stage
5. ALWAYS include a clear, specific call to action
6. ALWAYS include the sender's signature
7. Use psychological principles subtly — never be manipulative or aggressive
8. Keep emails concise (150-300 words for body)
9. Write subject lines that are 5-10 words, create urgency without alarm
10. If the customer has a history, reference it naturally

TONE SPECTRUM:
- VIP + Overdue 1-7d: "Warm nudge" — assume oversight, express appreciation, gentle reminder
- STANDARD + Overdue 1-7d: "Friendly professional" — clear reminder, payment options, helpful
- STANDARD + Overdue 8-14d: "Direct professional" — note the delay, reference terms, request prompt action
- RISK + Overdue 15-29d: "Firm but fair" — acknowledge pattern, state consequences, offer resolution path
- DELINQUENT + Overdue 30+d: "Urgent and consequential" — reference previous attempts, state escalation timeline, final opportunity
- ANY + Overdue 90+d: "Final notice" — reference all past attempts, state imminent escalation (collections/legal), deadline for response

PSYCHOLOGICAL TECHNIQUES (use 1-2 per email, matched to situation):
- Reciprocity: Reference past flexibility or discounts you've offered
- Social proof: "Most of our clients settle accounts within 30 days"
- Commitment: Reference their agreed payment terms
- Loss aversion: "To avoid late fees" / "To maintain your preferred pricing"
- Future framing: "To keep your account in good standing for upcoming projects"
- The Zeigarnik Effect: "Let's close this open item so we can focus on [future project]"

INDUSTRY-SPECIFIC LANGUAGE:
{industry_terminology}

CUSTOM INSTRUCTIONS FROM USER:
{custom_instructions}

OUTPUT FORMAT (JSON):
{
  "subject": "5-10 word subject line",
  "body": "Plain text email body with proper formatting",
  "html_body": "HTML version with basic formatting (paragraphs, bold for key amounts)",
  "tone_used": "description of tone approach",
  "psychological_technique": "which technique was primary",
  "confidence": 0.0-1.0
}
```

**Step 4: Implement the enhanced service**
- Modify `collection_psychologist.py` to call `GeminiAIService.generate_collection_email()` with full context
- Ensure `data_masking_service` masks PII before sending to AI
- Ensure AI response is unmasked before sending to customer
- Add fallback: if AI fails → use rule-based template (never fail to send)
- Add A/B variant support: generate 2 versions if configured

**Assertions after enhancement:**
- `collection_psychologist.py` calls AI service, not just returns templates
- Prompt includes ALL context variables listed above
- Data masking is applied
- Fallback exists
- Unit-test by calling the service directly and inspecting output

---

### Test 4.3: Generate Email for VIP Customer (AlwaysPays Corp)

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<vip_customer_id>/strategy/ \
  -H "Authorization: Bearer <construction_token>"
```

AND test direct email generation:
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<vip_overdue_invoice_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — VIP Email Quality:**
- [ ] Subject line is warm and non-alarming (e.g., "Quick reminder regarding Invoice INV-VIP-001")
- [ ] Opening acknowledges their loyalty ("As a valued long-term partner..." or "Thank you for your continued business...")
- [ ] Tone is warm and appreciative — NOT demanding
- [ ] References their excellent payment history subtly ("We know this is unusual for your account...")
- [ ] Uses soft language: "gentle reminder", "for your records", "at your earliest convenience"
- [ ] Does NOT use threatening language — no mention of fees, consequences, collections
- [ ] Includes specific invoice number, amount, and due date
- [ ] Has clear but gentle CTA ("Please let us know if you have any questions about this invoice")
- [ ] Includes sender's signature (from communication settings)
- [ ] Reads naturally — not robotic or templated
- [ ] No AI artifacts ("As an AI...", markdown syntax, etc.)
- [ ] Body is 150-250 words
- [ ] HTML version has proper formatting

---

### Test 4.4: Generate Email for Standard Delayed Customer (SlowPayer LLC)

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<slowpayer_8day_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — Standard Delayed Email:**
- [ ] Subject line is professional and direct (e.g., "Payment Due: Invoice INV-SLOW-006 — 8 Days Past Due")
- [ ] Tone is professional but not aggressive
- [ ] References the specific overdue period ("This invoice was due on [date], 8 days ago")
- [ ] Mentions payment options or portal link
- [ ] Provides clear next steps ("Please submit payment by [date] or contact us to discuss")
- [ ] May mention late fee policy if applicable ("Per our terms, a late fee of X% may apply after Y days")
- [ ] Includes specific amount outstanding
- [ ] Professional but firmer than VIP email
- [ ] Clear CTA with deadline
- [ ] Includes signature

---

### Test 4.5: Generate Email for Risk/Erratic Customer (Unreliable Inc)

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<unreliable_15day_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — Risk Customer Email:**
- [ ] Subject line creates urgency ("Action Required: Invoice INV-RISK-005 — 15 Days Overdue")
- [ ] Tone is firm but professional — NOT hostile
- [ ] References their payment pattern ("We've noticed some inconsistency in payment timing...")
- [ ] Clearly states consequences ("To avoid service interruption..." / "Late fees will apply...")
- [ ] Offers resolution path ("If you're experiencing cash flow challenges, we can discuss a payment plan")
- [ ] Uses loss aversion or scarcity framing
- [ ] Specific amount and deadline
- [ ] Escalation warning if pattern continues
- [ ] CTA is urgent but not threatening

---

### Test 4.6: Generate Email for Delinquent Customer (GhostClient Co)

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<ghost_90day_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — Delinquent Email:**
- [ ] Subject line is urgent ("FINAL NOTICE: Invoice INV-GHOST-001 — 120 Days Past Due")
- [ ] References ALL previous communication attempts ("We have sent 3 previous reminders on [dates]...")
- [ ] States escalation path clearly ("Without payment by [deadline], we will be compelled to...")
- [ ] Mentions specific consequences: collections agency, credit reporting, legal action
- [ ] Still professional — NOT threatening, hostile, or personal
- [ ] Provides ONE FINAL opportunity to resolve ("We would prefer to resolve this directly...")
- [ ] Includes a hard deadline (specific date)
- [ ] Total outstanding across all invoices mentioned
- [ ] CTA is unmistakably urgent
- [ ] No room for misinterpretation — consequences are crystal clear

---

### Test 4.7: Generate Email for New Customer (NewCustomer Ltd)

For new customer with only a sent (not yet overdue) invoice — this tests the "due soon" reminder:
```bash
# First, make the invoice due within 7 days (due_soon)
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')
new_customer = Customer.objects.get(user=u, customer_name='NewCustomer Ltd')
inv = Invoice.objects.filter(customer=new_customer).first()
if inv:
    inv.due_date = (timezone.now() + timedelta(days=3)).date()
    inv.save()
    print(f'Invoice {inv.invoice_number} due in 3 days')
"
```

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<new_customer_invoice_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — New Customer Email:**
- [ ] Subject line is gentle ("Upcoming Payment: Invoice INV-NEW-001 Due March 4")
- [ ] Tone is introductory and welcoming
- [ ] Does NOT reference past patterns (there are none)
- [ ] Assumes positive intent ("Just a friendly heads-up...")
- [ ] Provides helpful payment information (how to pay, portal link)
- [ ] Builds relationship ("We look forward to a great working relationship...")
- [ ] Short and friendly — not overwhelming
- [ ] No urgency language (invoice isn't overdue yet)

---

### Test 4.8: Industry-Specific Email — Construction

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<construction_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions — Construction Industry:**
- [ ] Uses construction terminology where appropriate (pay application, retainage, SOV, lien waiver)
- [ ] Understands longer payment cycles are industry standard
- [ ] If invoice involves retainage, acknowledges the retention schedule
- [ ] References project names if available in invoice description
- [ ] Mentions lien rights awareness if approaching deadline (for very overdue)
- [ ] Language feels natural to someone in construction finance

---

### Test 4.9: Industry-Specific Email — Education

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<education_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <education_token>"
```

**Assertions — Education Industry:**
- [ ] Uses student-appropriate language (not corporate jargon)
- [ ] References academic term deadlines if applicable
- [ ] Mentions financial aid options when relevant
- [ ] Registration hold warning for severely overdue
- [ ] Empathetic tone — understanding that students may have financial difficulties
- [ ] References payment plan options

---

### Test 4.10: Industry-Specific Email — Wholesale

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<wholesale_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <wholesale_token>"
```

**Assertions — Wholesale Industry:**
- [ ] References PO numbers and order numbers
- [ ] Understands deduction disputes are common
- [ ] Volume/relationship-aware messaging
- [ ] May reference credit terms or credit limit implications
- [ ] Business-to-business language appropriate for distribution

---

### Test 4.11: Email Distinctness Test — Same Invoice, Different Tones

Generate 4 emails for the same overdue invoice but with different forced tones:
```bash
for tone in friendly professional firm urgent; do
  echo "=== TONE: $tone ==="
  curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<invoice_id>/reminder/ \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <construction_token>" \
    -d "{\"tone\": \"$tone\"}"
  echo ""
done
```

**Assertions:**
- All 4 emails are DISTINCTLY different in language, approach, and urgency
- Friendly email is warm, professional is balanced, firm is direct, urgent is consequential
- They don't just change one word — the ENTIRE email structure and psychology changes
- If all 4 look the same → the AI prompt or tone handling is broken → FIX

---

### Test 4.12: Data Masking Verification for AI Calls

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from core.services.data_masking import DataMaskingService

masking = DataMaskingService()

# Test masking
test_data = {
    'customer_name': 'John Smith',
    'customer_email': 'john@example.com',
    'phone': '+15551234567',
    'company_name': 'Acme Corp',
    'invoice_number': 'INV-001',
    'amount': 5000.00,
    'days_overdue': 15
}

result = masking.mask_customer_data(test_data)
masked = result.get('masked_data', result)
session_id = result.get('session_id', None)

print('=== MASKED DATA ===')
for k, v in masked.items():
    print(f'  {k}: {v}')

print()

# Verify PII is masked
assert 'John Smith' not in str(masked), 'FAILURE: Customer name NOT masked!'
assert 'john@example.com' not in str(masked), 'FAILURE: Email NOT masked!'
assert '+15551234567' not in str(masked), 'FAILURE: Phone NOT masked!'
assert 'Acme Corp' not in str(masked), 'FAILURE: Company NOT masked!'

# Verify non-PII is preserved
assert masked.get('invoice_number') == 'INV-001', 'FAILURE: Invoice number should NOT be masked!'
assert masked.get('amount') == 5000.00, 'FAILURE: Amount should NOT be masked!'
assert masked.get('days_overdue') == 15, 'FAILURE: Days overdue should NOT be masked!'

print('OK: PII masked, non-PII preserved')

# Test unmasking
if session_id:
    test_text = f\"\"\"Dear {masked.get('customer_name', 'CUST_XXXXX')}, your invoice {masked.get('invoice_number')} is overdue.\"\"\"
    unmasked = masking.unmask_text(test_text, session_id)
    print(f'Unmasked: {unmasked}')
    assert 'John Smith' in unmasked or 'CUST_' not in unmasked, 'FAILURE: Unmasking failed!'
    print('OK: Unmasking works')
"
```

**Assertions:**
- Customer names → masked to CUST_XXXXX tokens
- Email addresses → masked to EMAIL_XXXXX tokens
- Phone numbers → masked to PHONE_XXXXX tokens
- Company names → masked to COMPANY_XXXXX tokens
- Invoice numbers → NOT masked (needed for AI context)
- Amounts → NOT masked (needed for AI context)
- Days overdue → NOT masked (needed for analysis)
- Unmasking correctly restores original values
- If masking service doesn't exist or is broken → BUG → fix/implement it

---

### Test 4.13: AI Fallback When Primary Provider Fails

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from core.services.gemini_service import GeminiAIService
from unittest.mock import patch

ai = GeminiAIService()

# Mock Gemini to fail
with patch.object(ai, '_call_gemini', side_effect=Exception('Gemini API down')):
    try:
        result = ai.generate_collection_email(
            invoice_data={'invoice_number': 'TEST-001', 'amount': 5000, 'days_overdue': 10},
            customer_behavior={'segment': 'STANDARD', 'payment_pattern': 'DELAYED'},
            tone='professional',
            sender_name='Test User'
        )
        print(f'Fallback result: {result}')
        if result and result.get('subject') and result.get('body'):
            print('OK: Fallback produced email content')
        else:
            print('FAILURE: Fallback did not produce email content')
    except Exception as e:
        print(f'FAILURE: No fallback - crashed with: {e}')
"
```

**Assertions:**
- When Gemini fails → system falls back to Claude (or template-based)
- Fallback email still has subject and body
- Email quality may be lower but it EXISTS (never fail to send)
- No unhandled exception reaches the user
- If no fallback exists → BUG → implement Claude fallback → template fallback

---

### Test 4.14: Email Quality Human-Like Assessment Checklist

For EACH email generated in tests 4.3-4.10, verify ALL of these:
- [ ] Reads naturally (a human would not suspect it's AI-generated)
- [ ] Proper greeting (Dear [Name], Hi [Name], etc.)
- [ ] Proper sign-off (Best regards, Regards, Sincerely, etc.)
- [ ] References specific details (invoice number, amount, date — not generic placeholders)
- [ ] Appropriate length (150-300 words for body)
- [ ] Clear single call to action (not multiple confusing asks)
- [ ] Professional formatting (paragraphs, not walls of text)
- [ ] User's signature included exactly as configured
- [ ] Reply instructions clear (email to reply to, phone number, portal link)
- [ ] No AI artifacts: no "As an AI...", no "I'm a language model...", no markdown in email body
- [ ] No template artifacts: no {{variable}}, no [PLACEHOLDER], no TODO
- [ ] Proper grammar and spelling
- [ ] Appropriate use of the customer's name (not overused, not missing)
- [ ] Subject line is 5-10 words, creates appropriate urgency level
- [ ] HTML version renders properly (no broken tags)

**If ANY email fails 3+ of these checks → the prompt needs improvement → FIX IT.**

---

### Test 4.15: A/B Variant Generation (If Supported)

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<invoice_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"generate_variants": true}'
```

**Assertions:**
- If supported: returns 2 email variants (different subject lines, different approaches)
- Variants are meaningfully different (not just word substitutions)
- Both variants pass the quality checklist above
- If not supported: note for future enhancement, not a blocking failure

**Phase 4 is PASS when ALL 15 tests pass AND email quality is expert-level. Update state tracker.**

---

## PHASE 5: EMAIL EXECUTION & SENDING

### Test 5.1: Send Reminder for Specific Invoice via API

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<slowpayer_8day_overdue_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Response contains `result` object with:
  - `channel`: "email" (or "sms" if configured)
  - `message_id`: non-null string (tracking ID)
  - `tone`: the tone used for generation
  - `success`: true
  - `error`: null or absent
- If `skipped: true` with `skip_reason` → verify the reason is valid (e.g., recently sent)
- Email content is returned in response (subject, body preview)

---

### Test 5.2: Send Reminder — Verify Email Content Structure

Examine the actual email that was generated:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
log = AutomationLog.objects.filter(user=u, status='SENT').order_by('-sent_at').first()
if log:
    print(f'Subject: {log.subject}')
    print(f'Recipient: {log.recipient_email}')
    print(f'Status: {log.status}')
    print(f'Sent at: {log.sent_at}')
    print(f'Body preview: {log.body[:500]}')
    print(f'Invoice: {log.invoice.invoice_number if log.invoice else \"N/A\"}')
    print(f'Customer: {log.client.customer_name if log.client else \"N/A\"}')
"
```

**Assertions:**
- Subject line is present and relevant (not empty, not generic)
- Recipient email matches the customer's email
- Body contains: invoice number, amount, due date
- Body contains user's email signature
- Body is NOT empty and NOT just a template placeholder
- Sent at timestamp is recent (within last few minutes)
- Linked to correct invoice and customer

---

### Test 5.3: Execute Collection Campaign — Batch Processing

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/collection/campaign/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Response contains `results` with:
  - `emails_sent`: count of emails actually sent (≥ 1)
  - `sms_sent`: count of SMS (may be 0)
  - Any `errors` list should be empty or contain expected skips
- All overdue invoices for this user were processed
- Invoices that should be skipped (paid, disputed, recent response) are NOT included in emails_sent
- Verify total by counting AutomationLog entries:
  ```bash
  cd singoa-api && python -c "
  import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
  from automations.models import AutomationLog
  from authentication.models import User
  from django.utils import timezone
  from datetime import timedelta
  u = User.objects.get(email='artest.construction@singoa.com')
  recent = AutomationLog.objects.filter(user=u, sent_at__gte=timezone.now()-timedelta(minutes=10))
  print(f'Emails sent in last 10 min: {recent.filter(status=\"SENT\").count()}')
  print(f'Skipped: {recent.filter(status=\"SKIPPED\").count()}')
  print(f'Failed: {recent.filter(status=\"FAILED\").count()}')
  for log in recent:
      print(f'  {log.client.customer_name if log.client else \"?\"}: {log.status} - {log.subject[:50]}')
  "
  ```

---

### Test 5.4: Verify Invoice Updated After Sending

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
for inv in Invoice.objects.filter(user=u, status='overdue'):
    last_sent = getattr(inv, 'last_reminder_sent', None)
    reminder_count = getattr(inv, 'reminder_count', 'N/A')
    print(f'{inv.invoice_number}: last_reminder_sent={last_sent}, reminder_count={reminder_count}')
"
```

**Assertions:**
- `last_reminder_sent` is updated to a recent timestamp for invoices that were reminded
- `reminder_count` is incremented (if field exists)
- Invoices that were skipped have unchanged timestamps

---

### Test 5.5: Send Without Email Integration — Graceful Failure

```bash
# Ensure no email integration for this test
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<invoice_id>/reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- If email integration is not connected:
  - Response indicates failure gracefully (not 500 crash)
  - Error message mentions "email integration not connected" or similar
  - ActionQueueItem created prompting user to connect email
  - Email is NOT silently dropped (user must know it wasn't sent)
- If email integration IS connected → verify email was actually dispatched

---

### Test 5.6: Rate Limiting — High Volume Batch

```bash
# Create many overdue invoices for stress testing
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.generic@singoa.com')
customer = Customer.objects.filter(user=u, customer_name='SlowPayer LLC').first()

# Create 25 overdue invoices for batch test
for i in range(25):
    Invoice.objects.get_or_create(
        user=u,
        customer=customer,
        invoice_number=f'INV-BATCH-{i+1:03d}',
        defaults={
            'amount': 1000 + i * 100,
            'due_date': (timezone.now() - timedelta(days=10+i)).date(),
            'status': 'overdue',
            'description': f'Batch test invoice {i+1}'
        }
    )
print(f'Created batch invoices. Total overdue: {Invoice.objects.filter(user=u, status=\"overdue\").count()}')
"
```

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/collection/campaign/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <generic_token>"
```

**Assertions:**
- Campaign completes without timeout (< 60 seconds)
- No 500 errors
- Rate limiting prevents API throttling (if sending via Gmail/Outlook)
- All emails are queued and eventually sent (may be batched)
- No memory issues or crashes
- Clean up batch invoices after test

---

### Test 5.7: Send Reminder via Alternative Endpoint

```bash
curl -s -X POST http://localhost:8000/api/automations/send-reminder/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"invoice_id": <invoice_id>}'
```

**Assertions:**
- Returns 200 with success
- Email generated and logged
- Verify this endpoint produces same quality as `/ar/invoice/<id>/reminder/`

---

### Test 5.8: Send Custom Email to Customer

```bash
curl -s -X POST http://localhost:8000/api/automations/send-customer-email/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "customer_id": <customer_id>,
    "subject": "Account Update from BuildRight",
    "message": "Dear Customer, we wanted to update you on your account status. Please contact us at your convenience."
  }'
```

**Assertions:**
- Returns 200 with success
- Custom subject and message are sent as-is (not AI-modified)
- Communication logged in history
- This tests manual override capability

**Phase 5 is PASS when ALL 8 tests pass. Update state tracker.**

---

## PHASE 6: COMMUNICATION LOGGING & TRACKING

### Test 6.1: Verify AutomationLog Entry After Sending

```bash
curl -s http://localhost:8000/api/automations/logs/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200
- Response contains list of log entries
- Most recent entry has:
  - `status`: "SENT"
  - `subject`: non-empty string matching what was sent
  - `body`: full email body text
  - `recipient_email`: customer's email address
  - `sent_at`: recent timestamp
  - `invoice_number`: correct invoice
  - `client_name`: correct customer name
  - `trigger_type`: "OVERDUE_REMINDER" or similar
- Filter by status works:
  ```bash
  curl -s "http://localhost:8000/api/automations/logs/?status=SENT" \
    -H "Authorization: Bearer <construction_token>"
  ```
- Filter by days works:
  ```bash
  curl -s "http://localhost:8000/api/automations/logs/?days=1" \
    -H "Authorization: Bearer <construction_token>"
  ```

---

### Test 6.2: Verify ActivityFeedItem After Sending

```bash
curl -s http://localhost:8000/api/automations/ar/activity-feed/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Contains entry for the email send with:
  - `activity_type`: "email_sent" or "reminder_sent" or similar
  - `title`: "Email reminder sent to [customer name]" or similar
  - `description`: includes invoice reference
  - `related_invoice`: correct invoice ID
  - `related_customer`: correct customer ID
  - `created_at`: recent timestamp
  - `is_read`: false (new entry)
- Mark as read:
  ```bash
  curl -s -X POST http://localhost:8000/api/automations/ar/activity-feed/mark-read/ \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <construction_token>" \
    -d '{"item_ids": [<activity_id>]}'
  ```
  Verify: `is_read` now true

---

### Test 6.3: Verify Customer Communication History

```bash
curl -s http://localhost:8000/api/communications/customer/<slowpayer_customer_id>/history/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `communications` list contains the sent email entry with:
  - `type`: "email"
  - `direction`: "outbound"
  - `subject`: the email subject line
  - `body_preview`: first ~200 chars of email body
  - `timestamp`: matches sent_at
- History includes both outbound (sent) and inbound (received) communications
- Entries are ordered by timestamp descending

---

### Test 6.4: Verify Audit Trail Entry for Email Send

```bash
curl -s http://localhost:8000/api/automations/ar/audit-log/?limit=10 \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Contains entry for email send with:
  - `action`: "email_sent" or "reminder_sent"
  - `actor_display`: the user who triggered it (or "system" if automated)
  - `description`: includes customer name, invoice number
  - `metadata`: includes email details (subject, recipient)
  - `entry_hash`: non-empty (chain integrity)
  - `created_at`: matches sent time

---

### Test 6.5: Verify Hub Message Not Created for Outbound

Outbound emails should NOT appear as hub messages (hub is for inbound):
```bash
curl -s http://localhost:8000/api/communications/hub/messages/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- No new hub message created for our outbound reminder
- Hub messages only contain inbound/external communications
- If outbound emails DO appear in hub → clarify if that's intended behavior

---

### Test 6.6: Frontend — Dashboard Activity Feed (Chrome MCP)

```
1. Navigate to http://localhost:3000/dashboard (logged in as construction user)
2. Take snapshot
3. Look for activity feed widget/section
```

**Assertions:**
- Activity feed section is visible on dashboard
- Shows recent entries including "Email reminder sent to [customer]"
- Each entry has: icon, title, timestamp, description
- Clicking an entry navigates to detail or expands inline
- "See all" link works (navigates to full activity feed)
- No console errors on dashboard page
- Take screenshot for evidence

---

### Test 6.7: Frontend — Customer Detail Communication Timeline (Chrome MCP)

```
1. Navigate to /customers/<slowpayer_customer_id>
2. Take snapshot
3. Look for communication history/timeline section
```

**Assertions:**
- Communication timeline section exists on customer detail page
- Shows sent email with: date, subject, status badge ("Sent")
- May show response status ("Awaiting reply" or "No response")
- Email content is viewable (click to expand or modal)
- Timeline is chronologically ordered
- Includes both automated and manual communications
- Take screenshot for evidence

---

### Test 6.8: Verify Logging Completeness — Cross-Reference All Logs

For ONE specific email send, verify it appears in ALL logging locations:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog, ActivityFeedItem, ImmutableAuditLog
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')
since = timezone.now() - timedelta(hours=1)

# 1. AutomationLog
auto_logs = AutomationLog.objects.filter(user=u, sent_at__gte=since, status='SENT').count()
print(f'1. AutomationLog SENT entries: {auto_logs}')

# 2. ActivityFeedItem
activity = ActivityFeedItem.objects.filter(user=u, created_at__gte=since).count()
print(f'2. ActivityFeedItem entries: {activity}')

# 3. ImmutableAuditLog
audit = ImmutableAuditLog.objects.filter(actor_id=u.id, created_at__gte=since).count()
print(f'3. ImmutableAuditLog entries: {audit}')

# Cross-reference: each SENT email should appear in all 3
if auto_logs > 0 and activity > 0 and audit > 0:
    print('OK: All logging systems have entries')
else:
    if auto_logs == 0: print('*** FAILURE: Missing AutomationLog entries ***')
    if activity == 0: print('*** FAILURE: Missing ActivityFeedItem entries ***')
    if audit == 0: print('*** FAILURE: Missing AuditLog entries ***')
"
```

**Assertions:**
- ALL THREE logging systems have entries for each email send
- If any system is missing entries → BUG → add the missing logging calls

**Phase 6 is PASS when ALL 8 tests pass. Update state tracker.**

---

## PHASE 7: REPLY MONITORING & HANDLING

### Test 7.1: Email Sync Service — Trigger Sync

```bash
curl -s -X POST http://localhost:8000/api/integrations/emails/sync/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 (even if no new emails to sync)
- Response indicates sync status: "completed", "no_integration", or "synced"
- If email integration is connected → shows count of synced messages
- If not connected → graceful message (not 500 crash)
- Check what email sync model stores:
  ```bash
  cd singoa-api && python -c "
  import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
  from integrations.models import EmailMessage, EmailThread
  from authentication.models import User
  u = User.objects.get(email='artest.construction@singoa.com')
  messages = EmailMessage.objects.filter(user=u).count()
  threads = EmailThread.objects.filter(user=u).count()
  print(f'Email messages: {messages}, Threads: {threads}')
  "
  ```

---

### Test 7.2: Simulate Customer Reply — Inbound Email Webhook

Simulate a customer reply using the SendGrid inbound webhook:
```bash
curl -s -X POST http://localhost:8000/api/communications/email-inbound/ \
  -H "Content-Type: application/json" \
  -d '{
    "from": "billing@slowpayer.com",
    "to": "artest.construction@singoa.com",
    "subject": "Re: Payment Reminder: Invoice INV-SLOW-006",
    "text": "Hi John, I apologize for the delay. We are processing the payment now and it should be sent by end of this week. Thank you for your patience.",
    "html": "<p>Hi John, I apologize for the delay. We are processing the payment now and it should be sent by end of this week. Thank you for your patience.</p>"
  }'
```

**Assertions:**
- Returns 200 with `success: true` and `processed: true`
- Reply is stored in the system
- Reply is linked to the correct customer (SlowPayer LLC)
- Reply is linked to the correct invoice thread (INV-SLOW-006)
- `last_customer_response` on the customer is updated to now

---

### Test 7.3: Verify Hub Message Created for Inbound

```bash
curl -s http://localhost:8000/api/communications/hub/messages/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with message list
- Contains entry for the simulated reply with:
  - `sender_name`: "billing@slowpayer.com" or "SlowPayer LLC"
  - `sender_email`: "billing@slowpayer.com"
  - `subject`: "Re: Payment Reminder: Invoice INV-SLOW-006"
  - `preview`: first ~100 chars of the message
  - `classification`: "external_ar" or "payment_response" (NOT "internal")
  - `classification_confidence`: > 0.5
  - `status`: "pending" or "new"
  - `customer_name`: "SlowPayer LLC" (linked)
- Click for detail:
  ```bash
  curl -s http://localhost:8000/api/communications/hub/messages/<message_id>/ \
    -H "Authorization: Bearer <construction_token>"
  ```
  Shows full content, AI analysis, suggested action

---

### Test 7.4: Verify AI Classification of Reply

The hub message detail should include AI analysis:

**Assertions on detail response:**
- `ai_summary`: summarizes the reply (e.g., "Customer promises payment by end of week")
- `ai_suggested_action`: recommended next step (e.g., "Log payment promise, reschedule reminders")
- `request_type`: classified type (e.g., "payment_promise", "dispute", "information_request")
- `extracted_entities`: parsed details (e.g., payment date, reference numbers)
- If AI classification is missing or generic → enhance the classification prompt

---

### Test 7.5: Simulate Payment Confirmation Reply

```bash
curl -s -X POST http://localhost:8000/api/communications/email-inbound/ \
  -H "Content-Type: application/json" \
  -d '{
    "from": "pay@recentpayer.com",
    "to": "artest.construction@singoa.com",
    "subject": "Re: Invoice INV-RECENT-005",
    "text": "Payment has been sent via wire transfer. Reference number: WT-2026-03-001. Please confirm receipt.",
    "html": ""
  }'
```

**Assertions:**
- Reply is classified as "payment_confirmation" or "payment_notification"
- System detects payment intent
- Extracted entities include reference number "WT-2026-03-001"
- Creates notification or approval for user to confirm the payment
- If auto-approve is enabled → automatically logs a payment promise
- Future scheduled reminders for this invoice should be reconsidered

---

### Test 7.6: Simulate Dispute Reply

```bash
curl -s -X POST http://localhost:8000/api/communications/email-inbound/ \
  -H "Content-Type: application/json" \
  -d '{
    "from": "disputes@disputeking.com",
    "to": "artest.construction@singoa.com",
    "subject": "Re: Invoice INV-DISPUTE-002",
    "text": "We are disputing this invoice. The materials delivered were not as specified in the purchase order. We request a credit memo for $3,000 of the $9,000 total.",
    "html": ""
  }'
```

**Assertions:**
- Reply classified as "dispute" or "billing_dispute"
- Creates ActionQueueItem for human review (disputes can't be auto-resolved)
- Invoice lifecycle_status should be updated to "disputed" (or flagged)
- Future automated reminders for this invoice should be BLOCKED
- Notification sent to user about the dispute
- AI summary captures the dispute amount ($3,000 of $9,000)

---

### Test 7.7: Event Rescheduling After Customer Reply

When a customer replies, future scheduled reminders should be reconsidered:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from invoices.models import Customer
from authentication.models import User

u = User.objects.get(email='artest.construction@singoa.com')
slow = Customer.objects.get(user=u, customer_name='SlowPayer LLC')

# Check if future tasks were rescheduled/cancelled after the reply in Test 7.2
actions = PlannedAction.objects.filter(
    user=u,
    invoice__customer=slow,
    status__in=['planned','scheduled','pending']
)
print(f'Remaining planned actions for SlowPayer: {actions.count()}')
for a in actions:
    print(f'  ID:{a.id} Scheduled:{a.scheduled_for} Status:{a.status}')

# The customer replied with a promise to pay by end of week
# So future reminders should be rescheduled or put on hold
"
```

**Assertions:**
- After customer reply with payment promise → future reminders are paused/rescheduled
- The system doesn't send another reminder tomorrow when customer just replied today
- If no rescheduling happens → the executor's pre-check (Test 3.3) should catch it at execution time

---

### Test 7.8: Process Customer Response via API

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<invoice_id>/response/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"response_text": "Will pay by Friday, sorry for the delay."}'
```

**Assertions:**
- Returns 200 with `success: true`
- `analysis` object contains:
  - Detected intent (payment promise)
  - Suggested actions
  - Confidence score
- Customer's response is logged in communication history
- Future reminders adjusted based on the response

---

### Test 7.9: Frontend — Hub/Communications Page (Chrome MCP)

```
1. Navigate to /hub (logged in as construction user)
2. Take snapshot
```

**Assertions:**
- Hub page loads with message list
- Incoming messages displayed with:
  - Sender name/email
  - Subject line
  - Preview text
  - Classification badge (e.g., "AR Related", "Payment Promise", "Dispute")
  - Time received
  - Status (New, Reviewed, Responded)
- Click a message → shows full content with:
  - Complete message text
  - AI analysis sidebar or section
  - Suggested response
  - Action buttons (Reply, Classify, Route, Dismiss)
- Reply functionality works (type response, click send)
- Reclassify works (change classification category)
- Take screenshot for evidence

---

### Test 7.10: Hub Statistics

```bash
curl -s http://localhost:8000/api/communications/hub/stats/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- Stats include: `total_messages`, `pending_messages`, `classified_messages`
- `by_classification` breaks down by type
- `response_rate` is calculated correctly
- Numbers match the actual messages we've processed

**Phase 7 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 8: APPROVAL & REVIEW WORKFLOWS

### Test 8.1: List Action Queue — Pending Items

```bash
curl -s "http://localhost:8000/api/automations/ar/action-queue/?status=pending" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- `items` list contains pending items from previous phases:
  - Disputed invoice items (from Phase 3/7)
  - Email-related warnings (from Phase 1/3)
  - Escalation items (from segmentation)
- Each item has ALL expected fields:
  - `id`, `action_type`, `action_type_display`
  - `priority` (low/medium/high/critical), `priority_display`
  - `status` = "pending"
  - `title` — human-readable title
  - `description` — detailed description
  - `ai_recommendation` — AI suggested action
  - `ai_confidence` — confidence score (0-1)
  - `quick_actions` — list of quick resolution options
  - `customer_name`, `invoice_number` (if applicable)
  - `total_outstanding` — amount at stake
  - `created_at`, `expires_at`
- Items are sorted by priority (critical first)

---

### Test 8.2: Get Action Queue Item Detail

```bash
curl -s http://localhost:8000/api/automations/ar/action-queue/<item_id>/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with full item details
- `context_data` provides additional context (related invoices, customer history)
- `ai_reasoning` explains why this item was created
- `quick_actions` provides actionable options:
  ```json
  [
    {"action": "update_email", "label": "Update Email Address"},
    {"action": "schedule_call", "label": "Schedule Phone Call"},
    {"action": "payment_plan", "label": "Offer Payment Plan"}
  ]
  ```

---

### Test 8.3: Resolve Action Queue Item

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/action-queue/<item_id>/resolve/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "resolution": "Contacted customer by phone. Payment confirmed for next week.",
    "notes": "Spoke with Sarah in AP dept. Wire transfer expected by March 7."
  }'
```

**Assertions:**
- Returns 200 with `success: true`
- Item status changed to "completed"
- `resolved_by` = current user
- `resolved_at` = current timestamp
- `resolution` matches what was sent
- `resolution_notes` matches what was sent
- Activity feed shows resolution entry
- Audit log shows resolution entry
- If this was blocking a PlannedAction → that action can now proceed

---

### Test 8.4: Dismiss Action Queue Item

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/action-queue/<another_item_id>/dismiss/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"reason": "Customer has been contacted offline, no further action needed."}'
```

**Assertions:**
- Returns 200 with `success: true`
- Item status changed to "dismissed"
- Reason is logged
- Related planned actions remain as-is (not automatically cancelled)

---

### Test 8.5: Quick Action — Update Customer Email

If there's an item about invalid email:
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/action-queue/<email_item_id>/quick-action/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "action": "update_email",
    "data": {"email": "newemail@customer.com"}
  }'
```

**Assertions:**
- Returns 200 with `success: true`
- Customer's email is actually updated in the database
- Item is resolved automatically
- Previously blocked PlannedAction is now unblocked
- Activity feed shows the email update

---

### Test 8.6: Review Queue — Unified View

```bash
curl -s http://localhost:8000/api/automations/review-tasks/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns combined view of ActionQueueItems + ApprovalRequests
- Each task has: type, source, priority, title, actions available
- Sorted by priority and urgency

---

### Test 8.7: Test Notification on Approval-Required Events

```bash
# Trigger a test approval notification
curl -s -X POST http://localhost:8000/api/notifications/test-approval/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with `success: true`
- In-app notification created:
  ```bash
  curl -s http://localhost:8000/api/notifications/?is_read=false \
    -H "Authorization: Bearer <construction_token>"
  ```
  Should contain the approval notification with action buttons
- If email notifications enabled → email would be sent
- Notification contains action tokens for approve/reject

---

### Test 8.8: Execute Notification Action via Token

Get the notification with actions, then use the action token:
```bash
# Get notification detail with action tokens
curl -s http://localhost:8000/api/notifications/<notification_id>/ \
  -H "Authorization: Bearer <construction_token>"
```

Extract action token, then:
```bash
curl -s -X POST http://localhost:8000/api/notifications/action/<action_token>/ \
  -H "Content-Type: application/json"
```

**Assertions:**
- Returns 200 with `success: true`
- Action is executed (approve/reject based on token type)
- Token is marked as used (can't be reused)
- Related items are updated accordingly
- Audit trail updated

---

### Test 8.9: Notification Preferences — Quiet Hours

```bash
# Set quiet hours
curl -s -X PUT http://localhost:8000/api/notifications/preferences/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "quiet_hours_enabled": true,
    "quiet_hours_start": "22:00",
    "quiet_hours_end": "07:00",
    "quiet_hours_timezone": "America/New_York"
  }'
```

**Assertions:**
- Preferences saved correctly
- Verify by GET:
  ```bash
  curl -s http://localhost:8000/api/notifications/preferences/ \
    -H "Authorization: Bearer <construction_token>"
  ```
  `quiet_hours_enabled`: true, start/end times match
- Notifications during quiet hours should be queued (not sent immediately)
- Notifications outside quiet hours sent normally

---

### Test 8.10: Frontend — Review Queue Page (Chrome MCP)

```
1. Navigate to /automations/review-queue
2. Take snapshot
```

**Assertions:**
- Page loads with list of pending review items
- Each item shows: type icon, title, priority badge, customer, description
- Approve button visible and functional
- Reject/Dismiss button visible and functional
- Bulk selection checkbox exists
- Bulk approve button works
- Filter by status works (pending, completed, dismissed)
- Filter by type works
- Filter by confidence works
- Search functionality exists
- Item count matches API response
- Take screenshot for evidence

---

### Test 8.11: Bulk Approve Multiple Items

```bash
curl -s -X POST http://localhost:8000/api/automations/review-queue/bulk-approve/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"item_ids": ["<id1>", "<id2>"]}'
```

**Assertions:**
- Returns 200 with `approved_count: 2`
- All specified items are now approved/completed
- Each approval logged in audit trail
- Activity feed updated for each

---

### Test 8.12: Unread Notification Count

```bash
curl -s http://localhost:8000/api/notifications/unread_count/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns `count` matching actual unread notifications
- After marking all read:
  ```bash
  curl -s -X POST http://localhost:8000/api/notifications/mark-all-read/ \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <construction_token>"
  ```
  Count drops to 0

**Phase 8 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 9: SETTINGS & CONFIGURATION

### Test 9.1: Get Business Rules

```bash
curl -s http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with full BusinessRuleSet
- ALL fields present:
  - `due_soon_days`: 7
  - `overdue_2_start_days`: 8
  - `overdue_3_start_days`: 15
  - `critical_threshold_days`: 30
  - `first_reminder_days`: 1
  - `reminder_frequency_days`: 7
  - `tone_overdue_1`: "friendly", `tone_overdue_1_display`: human-readable
  - `tone_overdue_2`: "professional"
  - `tone_overdue_3`: "firm"
  - `tone_critical`: "urgent"
  - `send_emails_start_hour`: 9
  - `send_emails_end_hour`: 17
  - `working_days`: [0,1,2,3,4]
  - `promise_grace_days`: 2
  - `late_fee_enabled`, `late_fee_percent`, `late_fee_grace_days`
  - `ar_workflow_hour`, `reminder_window_1_hour`, `reminder_window_2_hour`

---

### Test 9.2: Update Business Rules

```bash
curl -s -X PUT http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "reminder_frequency_days": 5,
    "critical_threshold_days": 45,
    "send_emails_start_hour": 8,
    "send_emails_end_hour": 18
  }'
```

**Assertions:**
- Returns 200
- Updated fields reflect new values
- Non-updated fields retain original values
- Verify: `GET /api/automations/ar/business-rules/` shows new values
- Next planner run uses updated rules (re-run planner and check)

**Revert changes after test:**
```bash
curl -s -X PUT http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"reminder_frequency_days": 7, "critical_threshold_days": 30, "send_emails_start_hour": 9, "send_emails_end_hour": 17}'
```

---

### Test 9.3: Business Rules Validation — Reject Invalid Values

```bash
# Negative days
curl -s -X PUT http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"first_reminder_days": -1}'
```

**Assertions:** Returns 400 with validation error

```bash
# overdue_2 < overdue_1 threshold (illogical)
curl -s -X PUT http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"overdue_2_start_days": 3, "overdue_3_start_days": 2}'
```

**Assertions:** Returns 400 (stage 3 can't start before stage 2)

```bash
# start_hour > end_hour
curl -s -X PUT http://localhost:8000/api/automations/ar/business-rules/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"send_emails_start_hour": 20, "send_emails_end_hour": 8}'
```

**Assertions:** Returns 400 or handles gracefully (overnight window)

---

### Test 9.4: Industry Default Business Rules

```bash
curl -s "http://localhost:8000/api/automations/ar/business-rules/industry-defaults/?industry=construction" \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with industry-specific defaults
- Construction defaults differ from generic (e.g., longer payment cycles)
- Test for each industry:
  ```bash
  for industry in construction education wholesale general; do
    echo "=== $industry ==="
    curl -s "http://localhost:8000/api/automations/ar/business-rules/industry-defaults/?industry=$industry" \
      -H "Authorization: Bearer <construction_token>"
    echo ""
  done
  ```
- Each industry has reasonable, distinct defaults
- If all industries return identical defaults → enhance with industry-specific values

---

### Test 9.5: Get & Update Communication Settings

```bash
curl -s http://localhost:8000/api/auth/settings/communication/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with all communication settings
- `sender_name`, `email_signature`, `business_tone`, `custom_instructions` all present and matching what we set in Phase 0

**Update and verify:**
```bash
curl -s -X PUT http://localhost:8000/api/auth/settings/communication/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "sender_name": "John Builder - AR Dept",
    "custom_instructions": "Always mention our 2% early payment discount for invoices paid within 10 days."
  }'
```

**Assertions:**
- Returns 200 with updated values
- `sender_name` now shows "John Builder - AR Dept"
- `custom_instructions` updated
- Next generated email reflects these changes (run a quick reminder and check)

---

### Test 9.6: Get & Update Notification Preferences

```bash
curl -s http://localhost:8000/api/notifications/preferences/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with all preference fields
- `enable_websocket`, `enable_email`, `enable_sms`, `enable_push` all present
- `quiet_hours_enabled`, `quiet_hours_start`, `quiet_hours_end` present

**Update:**
```bash
curl -s -X PUT http://localhost:8000/api/notifications/preferences/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "enable_email": true,
    "enable_sms": false,
    "email_digest_enabled": true,
    "email_digest_frequency": "daily",
    "digest_send_hour": 8
  }'
```

**Assertions:**
- Returns 200 with updated preferences
- Changes are saved and returned correctly

---

### Test 9.7: Collection Stages Configuration

```bash
curl -s http://localhost:8000/api/automations/ar/collection-stages/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with list of collection stages
- Stages represent the escalation path (e.g., Friendly → Professional → Firm → Urgent → Collections)
- Each stage has: name, days threshold, tone, actions

---

### Test 9.8: Frontend — AI Automation Settings Page (Chrome MCP)

```
1. Navigate to /settings/ai-automation
2. Take snapshot
```

**Assertions:**
- Page loads with current automation rules
- Natural language chat interface visible (for creating rules)
- Can type a rule like "Send a reminder 3 days after an invoice is overdue"
  - AI parses the rule
  - Confirmation shown
  - Rule is saved
- Existing rules listed with activate/deactivate toggles
- Templates section shows pre-built rule templates
- Can create rule from template
- All toggles and buttons functional
- No console errors
- Take screenshot

---

### Test 9.9: Frontend — Notification Settings Page (Chrome MCP)

```
1. Navigate to /settings/notifications
2. Take snapshot
```

**Assertions:**
- Channel configuration visible (Email, SMS, Push, WebSocket toggles)
- Toggle switches work (click to enable/disable)
- Quiet hours section with start/end time pickers
- Digest settings (frequency dropdown, send hour)
- Changes save when clicking save button
- No console errors
- Take screenshot

---

### Test 9.10: Natural Language Rule Creation

```bash
curl -s -X POST http://localhost:8000/api/automations/rules/parse/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{"rule_text": "Send a friendly reminder 3 days after an invoice becomes overdue"}'
```

**Assertions:**
- Returns 200 with `success: true`
- `parsed_rule` contains structured rule data:
  - Trigger: "invoice_overdue"
  - Delay: 3 days
  - Action: "send_reminder"
  - Tone: "friendly"
- Rule can be saved and activated

**Phase 9 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 10: DASHBOARD & FRONTEND INTEGRATION

### Test 10.1: Dashboard KPIs Accuracy (Chrome MCP)

```
1. Navigate to /dashboard
2. Take snapshot
3. Note all KPI values displayed
```

**Verify KPIs against actual data:**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User
from django.db.models import Sum, Count
from decimal import Decimal

u = User.objects.get(email='artest.construction@singoa.com')
invoices = Invoice.objects.filter(user=u)

total_outstanding = invoices.exclude(status='paid').aggregate(total=Sum('amount'))['total'] or 0
overdue_amount = invoices.filter(status='overdue').aggregate(total=Sum('amount'))['total'] or 0
overdue_count = invoices.filter(status='overdue').count()
paid_count = invoices.filter(status='paid').count()
total_count = invoices.count()

print(f'Total Outstanding: \${total_outstanding}')
print(f'Overdue Amount: \${overdue_amount}')
print(f'Overdue Count: {overdue_count}')
print(f'Paid Count: {paid_count}')
print(f'Total Invoices: {total_count}')
print(f'Collection Rate: {paid_count/total_count*100:.1f}%' if total_count else 'N/A')
"
```

**Assertions:**
- Dashboard "Total Outstanding" matches calculated total_outstanding
- Dashboard "Overdue Amount" matches calculated overdue_amount
- Dashboard "Overdue Invoices" count matches overdue_count
- DSO calculation is present and reasonable
- Aging buckets (0-30, 31-60, 61-90, 90+) add up to total overdue
- At-risk customers list matches RISK/DELINQUENT segments
- If any KPI is wrong → verify the dashboard API endpoint and fix

---

### Test 10.2: Dashboard API Endpoint

```bash
curl -s http://localhost:8000/api/dashboard/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns 200 with dashboard data
- Contains: outstanding amounts, overdue counts, aging breakdown, recent activity
- Numbers match the manual calculation above

---

### Test 10.3: Activity Feed on Dashboard (Chrome MCP)

**Assertions:**
- Activity feed widget visible on dashboard
- Shows recent events: emails sent, replies received, actions resolved
- Each entry has timestamp (relative: "5 minutes ago")
- Clickable entries navigate to relevant detail page
- "View all" link works

---

### Test 10.4: Scheduled Actions Widget on Dashboard

**Assertions:**
- Upcoming scheduled actions shown (next 5-10)
- Each shows: action type, customer name, scheduled time
- Completed actions NOT shown in upcoming
- Overdue/past-due actions highlighted

---

### Test 10.5: AI Insights Widget

```bash
curl -s http://localhost:8000/api/automations/agent/insights/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Returns insights list (may be empty if not generated yet)
- Trigger generation if needed:
  ```bash
  cd singoa-api && python -c "
  import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
  from automations.tasks.celery_tasks import generate_daily_ai_insights
  result = generate_daily_ai_insights()
  print(f'Result: {result}')
  "
  ```
- Insights are contextual (reference actual customer data, not generic advice)
- Recommendations are actionable
- On dashboard: insights widget shows top 3-5 insights

---

### Test 10.6: Customer Detail Page — Full View (Chrome MCP)

```
1. Navigate to /customers/<slowpayer_customer_id>
2. Take snapshot
```

**Assertions:**
- Customer info displayed: name, email, phone, segment badge
- Outstanding amount shown
- Invoice list with statuses
- Communication timeline with:
  - Sent reminders (with dates and content preview)
  - Customer replies (with classification)
  - Status changes
- Payment history
- AI insights for this customer
- No broken layouts or missing data
- Take screenshot

---

### Test 10.7: Invoice Detail Page (Chrome MCP)

```
1. Navigate to /invoices (list)
2. Click on a specific overdue invoice
3. Take snapshot
```

**Assertions:**
- Invoice details: number, amount, due date, status badge, customer
- Reminder history section showing:
  - Each reminder sent (date, tone, subject)
  - Customer responses (if any)
  - Next scheduled reminder
- Payment history (if any partial payments)
- Action buttons: Send Reminder, Pause Automation, Mark as Paid
- Take screenshot

---

### Test 10.8: Automations Page — Complete View (Chrome MCP)

```
1. Navigate to /automations or relevant automations tab
2. Take snapshot of each section/tab
```

**Assertions:**
- Timeline/Scheduled tab: shows all planned actions with dates
- Action Queue tab: shows items needing review
- Activity Feed tab: shows recent automation events
- Rules/Settings tab: shows configured automation rules
- All tabs load without errors
- Data matches API responses
- Navigation between tabs works smoothly
- Take screenshots of each tab

---

### Test 10.9: Frontend Console Error Check (Chrome MCP)

```
1. Navigate through ALL key pages: /dashboard, /invoices, /customers, /automations/review-queue, /settings/ai-automation, /settings/notifications, /hub
2. For each page, check console for errors
```

**Assertions:**
- ZERO JavaScript errors on any page
- ZERO failed API calls (network tab shows all 200s)
- No "undefined" or "null" displayed where data should be
- No broken images or missing assets
- All pages render within 3 seconds
- If errors found → document and fix

**Phase 10 is PASS when ALL 9 tests pass. Update state tracker.**

---

## PHASE 11: CONTINUOUS CYCLE & EDGE CASES

### Test 11.1: Full Cycle End-to-End (Single Invoice Journey)

Execute the COMPLETE lifecycle for ONE invoice through the entire system:

**Step 1: Create a new overdue invoice**
```bash
curl -s -X POST http://localhost:8000/api/invoices/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>" \
  -d '{
    "customer": <slowpayer_id>,
    "invoice_number": "INV-E2E-CYCLE-001",
    "amount": 7500.00,
    "due_date": "<5_days_ago>",
    "status": "overdue",
    "description": "Full E2E cycle test invoice"
  }'
```
**Assert:** Invoice created (201), appears in list.

**Step 2: Run AR workflow**
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/workflow/run/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```
**Assert:** PlannedAction created for this invoice with tone "friendly" (5 days overdue).

**Step 3: Execute due tasks**
```bash
curl -s -X POST http://localhost:8000/api/automations/scheduled/execute/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <construction_token>"
```
**Assert:** Task executed (or attempted), status changes to completed.

**Step 4: Verify email was generated by Collection Psychologist**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog
log = AutomationLog.objects.filter(invoice__invoice_number='INV-E2E-CYCLE-001').last()
if log:
    print(f'Status: {log.status}')
    print(f'Subject: {log.subject}')
    print(f'Body preview: {log.body[:200]}')
"
```
**Assert:** Email generated with personalized content, correct tone.

**Step 5: Verify communication logged everywhere**
Check: AutomationLog, ActivityFeed, AuditLog, customer history.
**Assert:** All 4 logging systems have entries.

**Step 6: Simulate customer reply**
```bash
curl -s -X POST http://localhost:8000/api/communications/email-inbound/ \
  -H "Content-Type: application/json" \
  -d '{
    "from": "billing@slowpayer.com",
    "to": "artest.construction@singoa.com",
    "subject": "Re: INV-E2E-CYCLE-001",
    "text": "We will process payment by end of this week.",
    "html": ""
  }'
```
**Assert:** Reply detected, classified, logged in hub.

**Step 7: Verify schedule updated**
**Assert:** Future reminders for this invoice are reconsidered.

**Step 8: Simulate payment received**
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from django.utils import timezone
inv = Invoice.objects.get(invoice_number='INV-E2E-CYCLE-001')
inv.status = 'paid'
inv.paid_amount = inv.amount
inv.paid_date = timezone.now().date()
inv.save()
print(f'Invoice marked as paid')
"
```
**Assert:** All remaining planned actions cancelled. Dashboard metrics updated.

**Step 9: Verify dashboard reflects everything**
Check dashboard KPIs — outstanding reduced, paid count increased.

**Step 10: Full cycle complete**
**Assert:** Invoice went from creation → planning → execution → email → reply → payment → closed, with full audit trail at every step.

---

### Test 11.2: Invoice Status Transition Full Escalation

Create an invoice and simulate it aging through ALL escalation stages:

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice, Customer
from automations.models import PlannedAction, AutomationLog
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')
c = Customer.objects.get(user=u, customer_name='SlowPayer LLC')

# Create test invoice
inv, _ = Invoice.objects.get_or_create(
    user=u, customer=c, invoice_number='INV-ESCALATION-001',
    defaults={'amount': 5000, 'due_date': timezone.now().date() - timedelta(days=1), 'status': 'overdue'}
)

stages = [
    (1, 'friendly', 'Due Soon / Just Overdue'),
    (8, 'professional', 'Overdue Stage 2'),
    (15, 'firm', 'Overdue Stage 3'),
    (30, 'urgent', 'Critical'),
    (90, 'urgent', 'Final Notice / Collections'),
]

for days, expected_tone, stage_name in stages:
    inv.due_date = (timezone.now() - timedelta(days=days)).date()
    inv.save()

    # Run planner
    from automations.services.strategy_planner import ARStrategyPlanner
    planner = ARStrategyPlanner(u)
    result = planner.analyze_and_plan()

    # Check what was planned
    pa = PlannedAction.objects.filter(invoice=inv).order_by('-created_at').first()
    actual_tone = getattr(pa, 'tone', 'N/A') if pa else 'NO ACTION'
    print(f'{stage_name} ({days}d overdue): Expected={expected_tone}, Got={actual_tone}, Match={actual_tone==expected_tone}')
"
```

**Assertions:**
- Each stage produces the correct tone
- Escalation is progressive (never goes from urgent back to friendly)
- At each stage, email content is appropriate for the urgency level

---

### Test 11.3: Payment Received Mid-Cycle — Cancels Future Reminders

**Setup:** Invoice with scheduled reminders → receives payment:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
inv = Invoice.objects.filter(user=u, status='overdue').first()

# Count planned actions before
before = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending']).count()

# Pay the invoice
inv.status = 'paid'
inv.paid_amount = inv.amount
inv.paid_date = timezone.now().date()
inv.save()

# Run executor (should detect payment and cancel)
from automations.tasks.celery_tasks import execute_due_planned_actions
execute_due_planned_actions()

# Count after
after = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending']).count()
cancelled = PlannedAction.objects.filter(invoice=inv, status='cancelled').count()

print(f'Before payment: {before} planned actions')
print(f'After payment: {after} planned, {cancelled} cancelled')

# Revert for other tests
inv.status = 'overdue'
inv.paid_amount = 0
inv.save()
"
```

**Assertions:**
- All planned actions for paid invoice are cancelled (after=0)
- Cancelled count matches before count
- No email sent after payment

---

### Test 11.4: Partial Payment Handling

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')
inv = Invoice.objects.filter(user=u, status='overdue', amount__gte=5000).first()
if inv:
    # Partial payment: 60% of total
    inv.paid_amount = inv.amount * 0.6
    inv.status = 'partial'
    inv.save()
    print(f'Invoice {inv.invoice_number}: Amount={inv.amount}, Paid={inv.paid_amount}, Remaining={inv.amount - inv.paid_amount}')

    # Run workflow
    from automations.services.strategy_planner import ARStrategyPlanner
    planner = ARStrategyPlanner(u)
    result = planner.analyze_and_plan()
    print(f'Plan result: {result}')

    # Check — reminders should continue for remaining amount
    from automations.models import PlannedAction
    actions = PlannedAction.objects.filter(invoice=inv, status__in=['planned','scheduled','pending'])
    print(f'Planned actions for partial invoice: {actions.count()}')
"
```

**Assertions:**
- Invoice status updated to "partial"
- Reminders continue for the REMAINING amount (not full amount)
- Generated email references the partial payment ("We received $3,000 of your $5,000 invoice...")
- Outstanding amount in email is correct ($2,000 remaining)

---

### Test 11.5: Multiple Invoices for Same Customer — Consolidation

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice, Customer
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone
from datetime import timedelta

u = User.objects.get(email='artest.construction@singoa.com')
c = Customer.objects.get(user=u, customer_name='Unreliable Inc')

# Ensure multiple overdue invoices exist
overdue = Invoice.objects.filter(customer=c, status='overdue').count()
print(f'Overdue invoices for Unreliable Inc: {overdue}')

# Run planner
from automations.services.strategy_planner import ARStrategyPlanner
planner = ARStrategyPlanner(u)
result = planner.analyze_and_plan()

# Check — should we get 1 consolidated reminder or multiple?
actions = PlannedAction.objects.filter(
    user=u,
    invoice__customer=c,
    status__in=['planned','scheduled','pending']
)
print(f'Planned actions: {actions.count()}')
for a in actions:
    print(f'  Invoice: {a.invoice.invoice_number}, Scheduled: {a.scheduled_for}')
"
```

**Assertions:**
- Either: ONE consolidated email mentioning ALL overdue invoices (preferred)
- Or: Separate emails but properly spaced (not all at same time)
- Verify email content mentions all overdue amounts if consolidated
- Customer doesn't receive 5 separate emails in 5 minutes

---

### Test 11.6: Timezone Handling

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction, BusinessRuleSet
from authentication.models import User
from django.utils import timezone
import pytz

u = User.objects.get(email='artest.construction@singoa.com')
rules = BusinessRuleSet.objects.get(user=u)

# Check timezone handling
print(f'Server timezone: {timezone.get_current_timezone()}')
print(f'Business hours: {rules.send_emails_start_hour}:00 - {rules.send_emails_end_hour}:00')

actions = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending'])[:5]
for a in actions:
    local_time = timezone.localtime(a.scheduled_for)
    print(f'Action {a.id}: UTC={a.scheduled_for}, Local={local_time}, Hour={local_time.hour}')
    if not (rules.send_emails_start_hour <= local_time.hour <= rules.send_emails_end_hour):
        print(f'  *** WARNING: Outside business hours in local time ***')
"
```

**Assertions:**
- All scheduled times are stored as UTC in database
- When converted to user's local timezone, they fall within business hours
- Frontend displays times in user's timezone (not UTC)

---

### Test 11.7: High Volume Stress Test

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice, Customer
from authentication.models import User
from django.utils import timezone
from datetime import timedelta
import time

u = User.objects.get(email='artest.generic@singoa.com')
c = Customer.objects.filter(user=u).first()

# Create 50 overdue invoices
for i in range(50):
    Invoice.objects.get_or_create(
        user=u, customer=c, invoice_number=f'INV-STRESS-{i+1:03d}',
        defaults={
            'amount': 1000 + i * 50,
            'due_date': (timezone.now() - timedelta(days=5+i%30)).date(),
            'status': 'overdue',
        }
    )

total = Invoice.objects.filter(user=u, status='overdue').count()
print(f'Total overdue invoices: {total}')

# Time the workflow
start = time.time()
from automations.services.strategy_planner import ARStrategyPlanner
planner = ARStrategyPlanner(u)
result = planner.analyze_and_plan()
elapsed = time.time() - start

print(f'Planning took: {elapsed:.2f} seconds')
print(f'Result: {result}')

# Assert performance
if elapsed > 60:
    print(f'*** FAILURE: Planning took {elapsed:.0f}s (limit: 60s) ***')
else:
    print(f'OK: Planning completed in {elapsed:.1f}s')
"
```

**Assertions:**
- Planning 50+ invoices completes in < 60 seconds
- No timeout errors
- No memory issues
- All invoices planned correctly
- Clean up stress test data after

---

### Test 11.8: Error Recovery — Failed Email Retry

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import PlannedAction
from authentication.models import User
from django.utils import timezone

u = User.objects.get(email='artest.construction@singoa.com')

# Find a failed task (or create one)
pa = PlannedAction.objects.filter(user=u, status='failed').first()
if not pa:
    pa = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled']).first()
    if pa:
        pa.status = 'failed'
        pa.save()
        print(f'Marked task {pa.id} as failed for retry test')

if pa:
    # Retry mechanism should pick up failed tasks
    pa.status = 'planned'  # Reset to allow retry
    pa.execution_lock = None
    pa.scheduled_for = timezone.now()
    pa.save()

    from automations.tasks.celery_tasks import execute_due_planned_actions
    result = execute_due_planned_actions()
    print(f'Retry result: {result}')

    pa.refresh_from_db()
    print(f'Task {pa.id} status after retry: {pa.status}')
"
```

**Assertions:**
- Failed tasks can be retried
- Retry doesn't create duplicate sends
- If task fails again, it's marked failed (not stuck in loop)
- Maximum retry count should be enforced (e.g., max 3 retries)

---

### Test 11.9: Data Integrity Under Concurrent Operations

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from automations.models import PlannedAction, AutomationLog
from authentication.models import User
from django.utils import timezone
from django.db import IntegrityError

u = User.objects.get(email='artest.construction@singoa.com')

# Verify no orphaned records
orphaned_actions = PlannedAction.objects.filter(user=u, invoice__isnull=True).count()
orphaned_logs = AutomationLog.objects.filter(user=u, invoice__isnull=True).count()

print(f'Orphaned PlannedActions (no invoice): {orphaned_actions}')
print(f'Orphaned AutomationLogs (no invoice): {orphaned_logs}')

# Verify no duplicate PlannedActions
from django.db.models import Count
dupes = PlannedAction.objects.filter(user=u, status__in=['planned','scheduled','pending']).values('invoice_id','action_type').annotate(cnt=Count('id')).filter(cnt__gt=1)
print(f'Duplicate PlannedActions: {dupes.count()}')
for d in dupes:
    print(f'  Invoice {d[\"invoice_id\"]}: {d[\"cnt\"]} duplicates')

if orphaned_actions == 0 and orphaned_logs == 0 and dupes.count() == 0:
    print('OK: Data integrity verified')
else:
    print('*** FAILURES detected — investigate and fix ***')
"
```

**Assertions:**
- Zero orphaned records
- Zero duplicate planned actions
- All foreign keys valid
- No data corruption after all our testing

---

### Test 11.10: Daily Digest Generation

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks.celery_tasks import generate_daily_ai_insights
result = generate_daily_ai_insights()
print(f'Insights result: {result}')
"
```

```bash
curl -s http://localhost:8000/api/notifications/digests/ \
  -H "Authorization: Bearer <construction_token>"
```

**Assertions:**
- Digest is generated or can be generated
- Contains summary of: emails sent, payments matched, approvals created
- `is_empty` is false (we have activity)
- Digest detail shows actionable summary

**Phase 11 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 12: FINAL VERIFICATION & PRODUCTION READINESS

### Test 12.1: Complete Workflow Walkthrough — Construction User

Walk through the ENTIRE workflow as the construction user. Using BOTH API calls and Chrome MCP frontend:

1. **Login** → `POST /api/auth/login/` → get token → Navigate to /dashboard in Chrome
2. **Verify dashboard** → KPIs correct, activity feed populated, scheduled actions visible
3. **Check overdue invoices** → Navigate to /invoices → filter by overdue → all present with correct statuses
4. **Check automation settings** → Navigate to /settings/ai-automation → rules configured
5. **Run AR workflow** → `POST /api/automations/ar/workflow/run/` → plans created
6. **View planned actions** → Navigate to automations page → timeline shows correct schedule
7. **Execute tasks** → `POST /api/automations/scheduled/execute/` → emails sent
8. **Check email quality** → Read the generated emails → verify construction-specific language, personalization
9. **View communication log** → Navigate to customer detail → communication timeline shows sent emails
10. **Simulate reply** → `POST /api/communications/email-inbound/` → reply detected and classified
11. **Check approval queue** → Navigate to /automations/review-queue → items requiring attention
12. **Resolve items** → Click approve/resolve → items cleared
13. **Verify dashboard updated** → Metrics reflect all changes

**Assertions:** Each step completes without error. Data is consistent across API and frontend. Take screenshots at each step.

---

### Test 12.2: Complete Workflow Walkthrough — Education User

Same 13-step walkthrough but verify education-specific features:
- Email language is student-appropriate
- Academic term awareness in planning/messaging
- Registration hold mentions for severely overdue
- Payment plan references

---

### Test 12.3: Complete Workflow Walkthrough — Wholesale User

Same 13-step walkthrough but verify wholesale-specific features:
- PO number references in emails
- Volume/relationship-aware messaging
- Deduction dispute handling
- Higher volume invoice processing

---

### Test 12.4: Complete Workflow Walkthrough — Generic User

Same 13-step walkthrough as baseline:
- No industry-specific language (generic professional)
- All features work without industry specialization
- Default business rules applied correctly

---

### Test 12.5: Security Verification

```bash
# Test 1: Unauthenticated access denied
curl -s http://localhost:8000/api/automations/ar/workflow/status/
```
**Assert:** Returns 401 Unauthorized

```bash
# Test 2: Cross-user data isolation — user A can't see user B's data
curl -s http://localhost:8000/api/invoices/ \
  -H "Authorization: Bearer <construction_token>" | python3 -c "
import json, sys
data = json.load(sys.stdin)
invoices = data.get('results', data.get('data', []))
for inv in invoices:
    user = inv.get('user', inv.get('user_id', 'unknown'))
    print(f'Invoice {inv.get(\"invoice_number\")}: user={user}')
"
```
**Assert:** All invoices belong to construction user — NONE from other test users.

```bash
# Test 3: Invalid token rejected
curl -s http://localhost:8000/api/automations/ar/workflow/status/ \
  -H "Authorization: Bearer invalid.token.here"
```
**Assert:** Returns 401

```bash
# Test 4: Expired token rejected (if you have one from earlier that expired)
```
**Assert:** Returns 401 with "token_expired" or similar

---

### Test 12.6: UI/UX Verification — All Pages (Chrome MCP)

Navigate to EVERY key page and verify:

**Pages to check:**
1. `/dashboard` — KPIs, widgets, activity feed
2. `/invoices` — Invoice list with filters
3. `/customers` — Customer list
4. `/customers/<id>` — Customer detail with communication history
5. `/automations/review-queue` — Review queue
6. `/settings/ai-automation` — AI automation settings
7. `/settings/notifications` — Notification settings
8. `/hub` — Communications hub

**For EACH page assert:**
- [ ] Page loads without blank screen
- [ ] No JavaScript console errors
- [ ] All data is real (not mock/placeholder)
- [ ] No "undefined", "null", or "NaN" displayed
- [ ] No lorem ipsum or placeholder text
- [ ] All buttons are clickable and functional
- [ ] Loading states work (spinner shown while loading)
- [ ] Error states display properly (if API fails)
- [ ] Layout is not broken (no overlapping elements)
- [ ] Take screenshot for evidence

---

### Test 12.7: Performance Verification

```bash
# Test API response times
for endpoint in \
  "/api/dashboard/" \
  "/api/invoices/" \
  "/api/automations/ar/action-queue/" \
  "/api/automations/ar/activity-feed/" \
  "/api/automations/scheduled/" \
  "/api/automations/ar/business-rules/" \
  "/api/notifications/"; do
  time=$(curl -s -o /dev/null -w "%{time_total}" "http://localhost:8000$endpoint" \
    -H "Authorization: Bearer <construction_token>")
  echo "$endpoint: ${time}s"
  if (( $(echo "$time > 2.0" | bc -l) )); then
    echo "  *** SLOW: Over 2 second threshold ***"
  fi
done
```

**Assertions:**
- ALL API endpoints respond in < 2 seconds
- Dashboard page loads in < 3 seconds (Chrome MCP timing)
- No endpoint takes > 5 seconds (critical failure)

---

### Test 12.8: Final Completeness Checklist

Run this final verification:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from authentication.models import User
from invoices.models import Customer, Invoice
from automations.models import PlannedAction, AutomationLog, ActionQueueItem, ActivityFeedItem, ImmutableAuditLog, BusinessRuleSet

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']
users = User.objects.filter(email__in=test_emails)

print('=== FINAL VERIFICATION ===')
print()

checks_passed = 0
checks_total = 0

for u in users:
    print(f'--- {u.email} ---')

    # Business rules exist
    checks_total += 1
    rules = BusinessRuleSet.objects.filter(user=u).exists()
    print(f'  Business rules: {\"PASS\" if rules else \"FAIL\"}')
    if rules: checks_passed += 1

    # Has customers
    checks_total += 1
    customers = Customer.objects.filter(user=u).count()
    ok = customers >= 8
    print(f'  Customers: {customers} ({\"PASS\" if ok else \"FAIL - need 8\"})')
    if ok: checks_passed += 1

    # Has invoices
    checks_total += 1
    invoices = Invoice.objects.filter(user=u).count()
    ok = invoices >= 15
    print(f'  Invoices: {invoices} ({\"PASS\" if ok else \"FAIL - need 15\"})')
    if ok: checks_passed += 1

    # Has automation logs (emails were sent)
    checks_total += 1
    logs = AutomationLog.objects.filter(user=u, status='SENT').count()
    ok = logs >= 1
    print(f'  Emails sent: {logs} ({\"PASS\" if ok else \"FAIL - need >=1\"})')
    if ok: checks_passed += 1

    # Has activity feed entries
    checks_total += 1
    activity = ActivityFeedItem.objects.filter(user=u).count()
    ok = activity >= 1
    print(f'  Activity feed: {activity} ({\"PASS\" if ok else \"FAIL\"})')
    if ok: checks_passed += 1

    # Has audit log entries
    checks_total += 1
    audit = ImmutableAuditLog.objects.filter(actor_id=u.id).count()
    ok = audit >= 1
    print(f'  Audit log: {audit} ({\"PASS\" if ok else \"FAIL\"})')
    if ok: checks_passed += 1

    print()

print(f'=== RESULT: {checks_passed}/{checks_total} checks passed ===')
if checks_passed == checks_total:
    print('ALL CHECKS PASSED!')
else:
    print(f'FAILURES: {checks_total - checks_passed} checks failed')
"
```

**This is the FINAL gate. ALL checks must pass before the completion promise can be output.**

---

## PHASE 13: EMAIL INTEGRATION & REAL DELIVERY

This phase tests the entire email integration pipeline — Gmail/Outlook OAuth, token management, real email delivery to shivambindal155@gmail.com, and email sync.

### Test 13.1: Check Email Integration Status

```bash
curl -s http://localhost:8000/api/integrations/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
print(json.dumps(data, indent=2))
# Look for any email integrations (Gmail, Outlook, SendGrid)
"
```

**Assertions:**
- Response 200
- Note which email integrations are available
- If NO email integration exists, check settings.py EMAIL_BACKEND
- In dev mode, console backend is acceptable BUT real delivery test (13.5) must use actual service

---

### Test 13.2: Gmail OAuth Integration — Connect Account

```bash
# Check if Gmail OAuth is configured
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from django.conf import settings
print('GOOGLE_CLIENT_ID:', 'SET' if settings.GOOGLE_CLIENT_ID else 'NOT SET')
print('GOOGLE_CLIENT_SECRET:', 'SET' if settings.GOOGLE_CLIENT_SECRET else 'NOT SET')
print('GOOGLE_REDIRECT_URI:', settings.GOOGLE_REDIRECT_URI)
from integrations.models import Integration
gmail_integrations = Integration.objects.filter(provider='gmail')
print(f'Existing Gmail integrations: {gmail_integrations.count()}')
for g in gmail_integrations:
    print(f'  User: {g.user.email}, Status: {g.status}, Connected: {g.is_connected}')
"
```

**Assertions:**
- Google OAuth credentials are configured (or note if not available for testing)
- If Gmail integrations exist, verify their status
- If tokens are expired, verify the refresh mechanism works

---

### Test 13.3: Email Service Provider Verification

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from django.conf import settings
print('EMAIL_BACKEND:', settings.EMAIL_BACKEND)
print('EMAIL_HOST:', settings.EMAIL_HOST)
print('DEFAULT_FROM_EMAIL:', settings.DEFAULT_FROM_EMAIL)
print()
# Check SendGrid
sendgrid_key = os.getenv('SENDGRID_API_KEY', '')
print('SENDGRID_API_KEY:', 'SET' if sendgrid_key else 'NOT SET')
print()
# Check what happens when we try to send
from django.core.mail import send_mail
try:
    result = send_mail(
        'Test Subject - Singoa AR Test',
        'This is a test email from Singoa AR Workflow E2E testing.',
        settings.DEFAULT_FROM_EMAIL,
        ['shivambindal155@gmail.com'],
        fail_silently=False,
    )
    print(f'send_mail result: {result}')
except Exception as e:
    print(f'send_mail error: {type(e).__name__}: {e}')
"
```

**Assertions:**
- Email backend is identified (SendGrid, console, SMTP)
- If console backend: emails appear in Django console output
- If SendGrid: API key is configured and send succeeds
- Document the email delivery method for subsequent tests

---

### Test 13.4: Send Test Email via Gmail API Service

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from integrations.models import Integration
from integrations.services.email_service import GmailService

# Find a Gmail integration
gmail = Integration.objects.filter(provider='gmail', status='active').first()
if gmail:
    print(f'Using Gmail integration for: {gmail.user.email}')
    service = GmailService(gmail.get_access_token())
    result = service.send_email(
        to='shivambindal155@gmail.com',
        subject='[SINGOA TEST] Gmail API Direct Send Test',
        body='This is a test email sent via Gmail API from Singoa AR E2E testing.'
    )
    print(f'Result: {result}')
else:
    print('No active Gmail integration found.')
    print('Testing via Django send_mail instead...')
    from django.core.mail import send_mail
    send_mail(
        '[SINGOA TEST] Django Mail Test',
        'This is a test email from Singoa AR E2E testing via Django mail.',
        'noreply@singoa.com',
        ['shivambindal155@gmail.com'],
    )
    print('Email sent via Django mail backend')
"
```

**Assertions:**
- Email is actually sent (check shivambindal155@gmail.com inbox)
- If Gmail API: verify API call succeeds
- If Django mail: verify console output shows email content
- Note which method works for real delivery

---

### Test 13.5: REAL Email Delivery to shivambindal155@gmail.com via AR Workflow

This is the most important email test — trigger the actual AR workflow for the NovaTech customer (which uses shivambindal155@gmail.com) and verify a REAL email arrives.

```bash
# First, find the NovaTech customer and an overdue invoice
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer, Invoice
from authentication.models import User

# Find NovaTech across all test users
for email in ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']:
    try:
        user = User.objects.get(email=email)
        nova = Customer.objects.filter(user=user, customer_email='shivambindal155@gmail.com').first()
        if nova:
            print(f'Found NovaTech: {nova.customer_name} (ID: {nova.id}) under {email}')
            invoices = Invoice.objects.filter(customer=nova, status__in=['overdue', 'sent']).order_by('due_date')
            for inv in invoices[:5]:
                print(f'  Invoice: {inv.invoice_number} | \${inv.total_amount} | Due: {inv.due_date} | Status: {inv.status}')
    except User.DoesNotExist:
        pass
"
```

Then trigger a reminder for NovaTech's overdue invoice:
```bash
curl -s -X POST http://localhost:8000/api/automations/ar/invoice/<INVOICE_ID>/reminder/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Reminder API returns 200/201
- Check shivambindal155@gmail.com inbox for the ACTUAL email
- Email subject references the invoice number/amount
- Email body is personalized (not generic template)
- Email is from the expected sender address
- If email doesn't arrive: investigate email backend, check logs, fix the delivery pipeline

---

### Test 13.6: Email Sync — Fetch Inbound Emails

```bash
curl -s -X POST http://localhost:8000/api/integrations/emails/sync/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"days_back": 7}' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Response 200
- Reports how many emails synced
- No errors in sync process
- Synced emails appear in communications hub

---

### Test 13.7: OAuth Token Refresh Mechanism

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from integrations.models import Integration
from datetime import datetime, timedelta
from django.utils import timezone

# Check token expiry for all integrations
for intg in Integration.objects.filter(provider__in=['gmail', 'outlook']):
    print(f'Provider: {intg.provider}, User: {intg.user.email}')
    print(f'  Status: {intg.status}')
    print(f'  Token expires: {intg.token_expires_at}')
    if intg.token_expires_at:
        remaining = intg.token_expires_at - timezone.now()
        print(f'  Time remaining: {remaining}')
        if remaining.total_seconds() < 3600:
            print('  WARNING: Token expires in < 1 hour')
    print(f'  Refresh token exists: {bool(intg.refresh_token)}')
    print()
"
```

**Assertions:**
- Token expiry times are tracked
- Refresh tokens exist for OAuth integrations
- Proactive refresh task is scheduled in Celery beat
- Expired tokens trigger automatic refresh (not user intervention)

---

### Test 13.8: Email Delivery Failure Handling

```bash
# Test with an invalid email to verify error handling
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
from authentication.models import User

# Temporarily set a customer email to invalid
user = User.objects.get(email='artest.generic@singoa.com')
customer = Customer.objects.filter(user=user).first()
original_email = customer.customer_email
customer.customer_email = 'invalid-email-address'
customer.save()
print(f'Set {customer.customer_name} email to: {customer.customer_email}')
print(f'Now trigger a reminder and verify it fails gracefully...')
print(f'Original email (restore after test): {original_email}')
"
```

Then try to send:
```bash
curl -s -X POST http://localhost:8000/api/automations/send-customer-email/ \
  -H "Authorization: Bearer $GENERIC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"customer_id": "<CUSTOMER_ID>", "subject": "Test", "body": "Test"}' | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))"
```

Then restore:
```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
from authentication.models import User
user = User.objects.get(email='artest.generic@singoa.com')
customer = Customer.objects.filter(user=user).first()
customer.customer_email = '<ORIGINAL_EMAIL>'
customer.save()
print(f'Restored email: {customer.customer_email}')
"
```

**Assertions:**
- Send attempt fails gracefully (not 500 error)
- Error message explains the issue (invalid email)
- ActionQueueItem created for manual review
- No crash or unhandled exception
- Email restored after test

---

### Test 13.9: Email Rate Limiting per Customer

```bash
# Send 5 rapid emails to same customer — verify rate limiting kicks in
for i in $(seq 1 5); do
  echo "--- Attempt $i ---"
  curl -s -X POST http://localhost:8000/api/automations/send-customer-email/ \
    -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"customer_id": "<VIP_CUSTOMER_ID>", "subject": "Test $i", "body": "Rate limit test $i", "email_type": "reminder"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status',''), d.get('error',''), d.get('message',''))"
  sleep 1
done
```

**Assertions:**
- First 1-3 emails succeed (depending on max_emails_per_day setting)
- Subsequent emails are rate-limited with clear error message
- Rate limit is per-customer, not global
- Rate limit respects BusinessRuleSet.max_emails_per_day

---

### Test 13.10: Email Thread Tracking

```bash
# After sending a reminder, verify the email thread is tracked
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
logs = AutomationLog.objects.filter(user=user, status='SENT').order_by('-created_at')[:5]
for log in logs:
    print(f'Log ID: {log.id}')
    print(f'  Customer: {log.customer.customer_name if log.customer else \"N/A\"}')
    print(f'  Invoice: {log.invoice.invoice_number if log.invoice else \"N/A\"}')
    print(f'  Action Type: {log.action_type}')
    print(f'  Details: {log.details}')
    print(f'  Created: {log.created_at}')
    print()
"
```

**Assertions:**
- Each sent email has a corresponding AutomationLog
- Log includes customer reference, invoice reference, action type
- Details contain email subject/preview
- Chronological ordering is correct

---

### Test 13.11: Bounced Email Detection

```bash
# Simulate a bounced email for Zenith Global Trading (known bounced email in seed data)
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog, ActionQueueItem
from invoices.models import Customer
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
zenith = Customer.objects.filter(user=user, customer_name__icontains='Zenith').first()
if zenith:
    print(f'Zenith customer: {zenith.customer_name} ({zenith.customer_email})')
    # Check if there are bounced logs
    bounced = AutomationLog.objects.filter(customer=zenith, status='BOUNCED')
    print(f'Bounced logs: {bounced.count()}')
    # Check if action queue item was created for bounced email
    queue = ActionQueueItem.objects.filter(customer=zenith, action_type__icontains='email')
    print(f'Queue items for Zenith: {queue.count()}')
    for q in queue:
        print(f'  Type: {q.action_type}, Status: {q.status}, Notes: {q.notes[:100] if q.notes else \"\"}')
"
```

**Assertions:**
- Bounced emails from seed data are tracked
- ActionQueueItem exists for bounced email customers
- System doesn't keep trying to send to bounced addresses
- Email resolution service flags invalid emails

---

### Test 13.12: Email Template Management

```bash
# List email templates
curl -s http://localhost:8000/api/automations/email-templates/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
if isinstance(data, list):
    print(f'Templates found: {len(data)}')
    for t in data[:5]:
        print(f'  - {t.get(\"name\",\"\")} | Type: {t.get(\"template_type\",\"\")} | Industry: {t.get(\"industry\",\"\")}')
elif isinstance(data, dict) and 'results' in data:
    print(f'Templates found: {len(data[\"results\"])}')
    for t in data['results'][:5]:
        print(f'  - {t.get(\"name\",\"\")} | Type: {t.get(\"template_type\",\"\")} | Industry: {t.get(\"industry\",\"\")}')
else:
    print(json.dumps(data, indent=2))
"
```

**Assertions:**
- Template endpoint responds (200)
- Templates exist for different email types (reminder, follow-up, escalation, final notice)
- Templates have industry-specific variants if applicable
- Templates include merge fields/variables

---

### Test 13.13: Template Preview with Real Customer Data

```bash
curl -s -X POST http://localhost:8000/api/automations/template-preview/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "<TEMPLATE_ID>",
    "customer_id": "<CUSTOMER_ID>",
    "invoice_id": "<INVOICE_ID>"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Preview renders with real customer name, invoice amount, due date
- No unresolved {{variables}} or placeholders
- Professional formatting
- Industry-appropriate language

---

### Test 13.14: Email Open/Click Tracking (If Implemented)

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import AutomationLog, CommunicationEffectivenessLog
from authentication.models import User

# Check if effectiveness tracking exists
user = User.objects.get(email='artest.construction@singoa.com')
effectiveness = CommunicationEffectivenessLog.objects.filter(user=user)
print(f'Effectiveness logs: {effectiveness.count()}')
for e in effectiveness[:5]:
    print(f'  Tone: {e.tone}, Segment: {e.customer_segment}')
    print(f'  Opened: {e.opened}, Replied: {e.replied}, Paid: {e.resulted_in_payment}')
    print()
"
```

**Assertions:**
- CommunicationEffectivenessLog model exists and is tracked
- After sending emails, effectiveness entries are created
- Fields track open rates, reply rates, payment conversion

---

### Test 13.15: Multi-Channel Delivery Preference

```bash
# Check if customer has channel preferences (email vs SMS vs slack)
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
customers = Customer.objects.filter(user=user)[:5]
for c in customers:
    print(f'{c.customer_name}:')
    print(f'  Email: {c.customer_email}')
    print(f'  Phone: {c.phone}')
    # Check for communication preferences
    prefs = getattr(c, 'communication_preferences', None) or getattr(c, 'preferred_channel', None)
    print(f'  Preferred channel: {prefs or \"default (email)\"}')
    print()
"
```

**Assertions:**
- Customer model supports communication channel preference
- Default channel is email
- System respects channel preference when sending

**Phase 13 is PASS when ALL 15 tests pass. Update state tracker.**

---

## PHASE 14: AI AGENT & CONVERSATIONS

This phase tests the AI agent system — the conversational interface where users can ask questions, get insights, and manage their AR workflow through natural language.

### Test 14.1: List AI Agent Conversations

```bash
curl -s http://localhost:8000/api/automations/agent/conversations/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
print(json.dumps(data, indent=2)[:2000])
"
```

**Assertions:**
- Response 200
- Returns list of conversations (may be empty initially)
- Each conversation has: id, title, created_at, message_count

---

### Test 14.2: Start New Agent Conversation

```bash
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What are my most overdue invoices right now?",
    "conversation_id": null
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Response 200
- Returns a conversation_id
- Agent response mentions actual invoice data (not generic/placeholder)
- Response references real customer names and amounts from seeded data
- Response is contextually relevant to the user's AR data

---

### Test 14.3: Continue Existing Conversation

```bash
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Send a reminder to the most delinquent one",
    "conversation_id": "<CONVERSATION_ID_FROM_14.2>"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Agent understands context from previous message
- References the specific delinquent customer identified in 14.2
- Either executes the action or explains what needs approval
- Conversation history is maintained

---

### Test 14.4: Agent Context Awareness — Industry Specific

```bash
# Construction user asking about retainage
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "Do any of my invoices have retainage holds?"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

```bash
# Education user asking about academic terms
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $EDUCATION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "Show me overdue student accounts for this semester"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Construction agent understands retainage, lien waivers, AIA billing
- Education agent understands semesters, student accounts, financial aid
- Responses use industry-appropriate terminology
- Data returned is filtered by the user's industry context

---

### Test 14.5: Agent Action Confirmation Flow

```bash
# Ask agent to do something that requires confirmation
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "Write off the invoice for Phantom Enterprises — it is uncollectable"}' | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Agent does NOT immediately execute write-off
- Agent requests confirmation with details (customer name, amount, implications)
- Agent creates a pending approval or shows confirmation dialog
- Only after explicit confirm does the action execute

---

### Test 14.6: Agent Confirm Action

```bash
curl -s -X POST http://localhost:8000/api/automations/agent/confirm/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action_id": "<ACTION_ID_FROM_14.5>",
    "confirmed": true
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Confirmation response acknowledges the action
- Action is executed (or queued for execution)
- Audit log entry created for the confirmed action

---

### Test 14.7: Agent Insights & Recommendations

```bash
curl -s http://localhost:8000/api/automations/agent/insights/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Returns AI-generated insights about the user's AR data
- Insights are data-driven (reference actual numbers)
- Includes recommendations for next actions
- Industry-relevant insights

---

### Test 14.8: Agent Performance Metrics

```bash
curl -s http://localhost:8000/api/automations/agent/performance/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns agent performance stats
- Includes: total conversations, actions taken, accuracy, response time
- Metrics are realistic (not zeros or placeholders)

---

### Test 14.9: Agent Tools List

```bash
curl -s http://localhost:8000/api/automations/agent/tools/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Returns list of tools the agent can use
- Tools include: send_reminder, check_invoice, create_payment_plan, write_off, etc.
- Each tool has description and required parameters

---

### Test 14.10: Agent Memory & Preferences

```bash
# Get AI preferences
curl -s http://localhost:8000/api/automations/ai/preferences/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"

# Get AI context
curl -s http://localhost:8000/api/automations/ai/context/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:2000])"
```

**Assertions:**
- Preferences endpoint returns user's AI settings
- Context endpoint returns current AI context (customer data summary, recent actions)
- Context is accurate and reflects actual database state

---

### Test 14.11: Agent Cross-User Isolation

```bash
# Construction user's agent should NOT see education user's data
curl -s -X POST http://localhost:8000/api/automations/agent/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "Show me all customers"}' | python3 -c "
import sys,json; data=json.load(sys.stdin)
response = data.get('response', data.get('message', str(data)))
# Check that education-only customers don't appear
print(response[:2000])
"
```

**Assertions:**
- Construction user only sees construction customers
- No data leakage between users
- Agent respects user isolation boundaries

---

### Test 14.12: Frontend — AI Chat Interface (Chrome MCP)

**Steps:**
1. Navigate to `/dashboard` or `/automations` (wherever AI chat is)
2. Look for AI chat widget/button
3. Click to open chat
4. Type a question and send
5. Verify response renders properly
6. Take screenshot

**Assertions:**
- Chat interface is accessible and functional
- Messages render with proper formatting
- Loading indicator shows during AI response
- Chat history persists across page refreshes
- No console errors

**Phase 14 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 15: LEARNING, SUGGESTIONS & A/B EXPERIMENTS

This phase tests the AI learning system — how the system learns from user behavior, generates suggestions, and runs A/B experiments to optimize email effectiveness.

### Test 15.1: Generate AI Suggestions

```bash
curl -s -X POST http://localhost:8000/api/automations/suggestions/generate/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Response 200/201
- Suggestions are generated based on user's actual data
- Each suggestion has: id, type, description, confidence, reasoning

---

### Test 15.2: List Suggestions

```bash
curl -s http://localhost:8000/api/automations/suggestions/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Returns list of suggestions
- Suggestions are relevant to the user's AR data
- Different types: tone adjustment, timing change, escalation recommendation

---

### Test 15.3: Accept Suggestion

```bash
curl -s -X POST http://localhost:8000/api/automations/suggestions/<SUGGESTION_ID>/accept/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Suggestion marked as accepted
- Related settings/rules updated based on suggestion
- Activity feed entry created
- Audit log entry created

---

### Test 15.4: Dismiss Suggestion

```bash
curl -s -X POST http://localhost:8000/api/automations/suggestions/<SUGGESTION_ID>/dismiss/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Not applicable to my business"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Suggestion marked as dismissed
- Dismissal reason stored
- System learns from dismissal (future suggestions adjusted)

---

### Test 15.5: Approval Patterns Analysis

```bash
curl -s http://localhost:8000/api/automations/approval-patterns/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:2000])"
```

**Assertions:**
- Returns approval pattern analysis
- Shows which action types user typically approves/rejects
- Includes confidence scores for auto-approval recommendations

---

### Test 15.6: Communication Effectiveness Metrics

```bash
curl -s http://localhost:8000/api/automations/communication-effectiveness/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:2000])"
```

**Assertions:**
- Returns effectiveness data by tone, segment, industry
- Metrics include: open rate, reply rate, payment conversion rate
- Data reflects actual email sending results

---

### Test 15.7: Start A/B Experiment

```bash
curl -s -X POST http://localhost:8000/api/automations/experiments/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tone Test - Professional vs Friendly for VIP",
    "description": "Test whether professional or friendly tone works better for VIP customers",
    "variant_a": {"tone": "professional", "segment": "VIP"},
    "variant_b": {"tone": "friendly", "segment": "VIP"},
    "target_sample_size": 20,
    "metric": "payment_rate"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Experiment created with status "draft" or "running"
- Returns experiment ID
- Both variants are properly configured

---

### Test 15.8: Get Experiment Details

```bash
curl -s http://localhost:8000/api/automations/experiments/<EXPERIMENT_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns full experiment details
- Includes variant definitions, current progress, status
- Sample sizes are tracked

---

### Test 15.9: Experiment Results

```bash
curl -s http://localhost:8000/api/automations/experiments/<EXPERIMENT_ID>/results/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns results (may be preliminary if not enough data)
- Shows performance metrics for each variant
- Statistical significance indicator (if enough data)

---

### Test 15.10: Pause & Cancel Experiment

```bash
# Pause
curl -s -X POST http://localhost:8000/api/automations/experiments/<EXPERIMENT_ID>/pause/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"

# Cancel
curl -s -X POST http://localhost:8000/api/automations/experiments/<EXPERIMENT_ID>/cancel/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Pause: experiment status changes to "paused"
- Cancel: experiment status changes to "cancelled"
- No more emails sent under cancelled experiment

---

### Test 15.11: Daily AI Insights Generation (Celery Task)

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks_learning import generate_daily_ai_insights
result = generate_daily_ai_insights()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Generates insights for all active users
- Insights are based on actual data trends

---

### Test 15.12: Learning Digest Email (Celery Task)

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks_learning import send_learning_digest
result = send_learning_digest()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Digest email generated for users with activity
- Digest content includes: actions taken, suggestions, performance metrics

**Phase 15 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 16: ADVANCED AUTHORIZATION & AI BOUNDARIES

This phase tests the AI action boundary system — limits on what the AI can do autonomously vs what requires human approval.

### Test 16.1: List AI Boundaries

```bash
curl -s http://localhost:8000/api/automations/boundaries/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Response 200
- Returns list of boundaries (may include defaults)
- Each boundary has: type, threshold, action_on_violation

---

### Test 16.2: Create AI Boundary — Dollar Limit

```bash
curl -s -X POST http://localhost:8000/api/automations/boundaries/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Max Auto-Send Amount",
    "description": "Require approval for emails about invoices over $10,000",
    "boundary_type": "amount_threshold",
    "threshold_value": 10000,
    "action_on_violation": "require_approval",
    "applies_to": ["send_reminder", "send_email"]
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Boundary created successfully
- Returns boundary ID
- Boundary is active immediately

---

### Test 16.3: Verify Boundary Enforcement — Below Threshold (Auto-Proceed)

```bash
# Find an invoice under $10K and trigger reminder
curl -s -X POST http://localhost:8000/api/automations/boundaries/check/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action_type": "send_reminder",
    "invoice_id": "<LOW_AMOUNT_INVOICE_ID>",
    "amount": 5000
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Response indicates action is ALLOWED (no boundary violation)
- No approval required for invoices under threshold

---

### Test 16.4: Verify Boundary Enforcement — Above Threshold (Require Approval)

```bash
# Check an invoice over $10K
curl -s -X POST http://localhost:8000/api/automations/boundaries/check/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action_type": "send_reminder",
    "invoice_id": "<HIGH_AMOUNT_INVOICE_ID>",
    "amount": 25000
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Response indicates action is BLOCKED (boundary violation)
- Violation details include which boundary was triggered
- Approval request created automatically

---

### Test 16.5: Parse Boundary from Natural Language

```bash
curl -s -X POST http://localhost:8000/api/automations/boundaries/parse/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Never send more than 3 emails per week to any customer"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- AI parses the natural language into a structured boundary
- Extracted: type=frequency_limit, value=3, period=week, scope=per_customer
- Boundary can be saved from the parsed result

---

### Test 16.6: Industry Default Boundaries

```bash
curl -s http://localhost:8000/api/automations/boundaries/industry-defaults/?industry=construction \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns industry-specific default boundaries
- Construction: higher thresholds (large project invoices)
- Education: restrictions during academic breaks
- Wholesale: EDI-specific boundaries

---

### Test 16.7: Get Boundary Suggestions

```bash
curl -s http://localhost:8000/api/automations/boundaries/suggestions/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:2000])"
```

**Assertions:**
- Returns AI-suggested boundaries based on user's data
- Suggestions are relevant and reasonable
- Each suggestion explains the reasoning

---

### Test 16.8: Update Boundary

```bash
curl -s -X PUT http://localhost:8000/api/automations/boundaries/<BOUNDARY_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "threshold_value": 15000,
    "description": "Updated: Require approval for invoices over $15,000"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Boundary updated successfully
- New threshold takes effect immediately
- Audit log entry for the change

---

### Test 16.9: Delete Boundary

```bash
curl -s -X DELETE http://localhost:8000/api/automations/boundaries/<BOUNDARY_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json
try:
    data = json.load(sys.stdin)
    print(json.dumps(data, indent=2))
except:
    print('Boundary deleted (empty response)')
"
```

**Assertions:**
- Boundary deleted (204 or 200)
- Boundary no longer enforced
- Audit log entry for deletion

---

### Test 16.10: Cross-User Boundary Isolation

```bash
# Construction user's boundaries should not affect education user
curl -s http://localhost:8000/api/automations/boundaries/ \
  -H "Authorization: Bearer $EDUCATION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
print(f'Education user boundaries: {len(data) if isinstance(data, list) else data}')
# Should NOT contain construction user's custom boundaries
"
```

**Assertions:**
- Education user's boundary list doesn't include construction user's custom boundaries
- Each user has their own boundary space
- Default boundaries may be shared but customizations are isolated

**Phase 16 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 17: ADVANCED APPROVAL WORKFLOWS

This phase tests the full approval system — approval requests, token-based approvals (from email links), deferral, escalation, and bulk operations.

### Test 17.1: List Approval Requests

```bash
curl -s http://localhost:8000/api/automations/approvals/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Response 200
- Returns list of approval requests
- Each has: id, type, status, created_at, details

---

### Test 17.2: Create Approval Request (High-Value Action)

```bash
# Trigger an action that requires approval
curl -s -X POST http://localhost:8000/api/automations/ar/collection/campaign/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_ids": ["<DELINQUENT_CUSTOMER_ID>"],
    "action_type": "escalation",
    "tone": "firm"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- If action requires approval: approval request created with status "pending"
- If auto-approved: action proceeds with audit trail
- Response includes approval_request_id if approval needed

---

### Test 17.3: Approval Request Detail

```bash
curl -s http://localhost:8000/api/automations/approvals/<APPROVAL_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns full approval details
- Includes: action details, customer info, invoice info, risk assessment
- Shows approval token (if email-based approval is enabled)

---

### Test 17.4: Approve via API

```bash
curl -s -X POST http://localhost:8000/api/automations/approvals/<APPROVAL_ID>/approve/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Approved - customer has been unresponsive for 90+ days"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Approval status changes to "approved"
- Original action is executed
- Audit log entry with approver info and notes
- Notification sent confirming approval

---

### Test 17.5: Deny Approval

```bash
curl -s -X POST http://localhost:8000/api/automations/approvals/<APPROVAL_ID>/deny/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Customer called — payment plan agreed"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Approval status changes to "denied"
- Original action is NOT executed
- Denial reason stored
- Audit log entry

---

### Test 17.6: Defer Approval

```bash
curl -s -X POST http://localhost:8000/api/automations/approvals/<APPROVAL_ID>/defer/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"defer_until": "2026-03-08", "reason": "Waiting for payment to clear"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Approval status changes to "deferred"
- Deferred until date is stored
- System will re-present the approval on the defer date

---

### Test 17.7: Token-Based Approval (Email Link)

```bash
# Get the approval token
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import ApprovalRequest
req = ApprovalRequest.objects.filter(status='pending').first()
if req:
    print(f'Approval: {req.id}')
    print(f'Token: {req.token}')
    print(f'Action: {req.action_type}')
else:
    print('No pending approvals found')
"
```

Then use the token:
```bash
curl -s -X POST http://localhost:8000/api/automations/approvals/token/<TOKEN>/respond/ \
  -H "Content-Type: application/json" \
  -d '{"action": "approve"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Token-based approval works without JWT auth (email link scenario)
- Approval is processed correctly
- Token becomes invalid after use (one-time use)
- Expired tokens are rejected

---

### Test 17.8: Approval Stats

```bash
curl -s http://localhost:8000/api/automations/approvals/stats/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns: total_pending, total_approved, total_denied, total_deferred
- Average response time
- Approval rate percentage
- Stats are accurate based on actual data

---

### Test 17.9: Expired Approval Cleanup (Celery Task)

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from notifications.tasks import expire_pending_approvals
result = expire_pending_approvals()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Old pending approvals (>7 days) are marked as expired
- Expired approvals don't block workflow

---

### Test 17.10: Review Queue — Unified View

```bash
curl -s http://localhost:8000/api/automations/review-queue/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Returns unified view of ALL pending items (action queue + approvals + PO reviews)
- Items sorted by priority/urgency
- Each item has enough context to make a decision

---

### Test 17.11: Bulk Approve — Multiple Items

```bash
curl -s -X POST http://localhost:8000/api/automations/review-queue/bulk-approve/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "item_ids": ["<ID1>", "<ID2>", "<ID3>"],
    "notes": "Bulk approved — routine reminders"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- All specified items approved
- Each item has individual audit log entry
- Response shows success/failure per item
- Failed items (if any) don't block successful ones

---

### Test 17.12: Auto-Approve Eligible Items

```bash
curl -s -X POST http://localhost:8000/api/automations/review-queue/auto-approve/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Items meeting auto-approval criteria are approved
- Criteria based on: learned patterns, boundary compliance, low risk
- Items NOT meeting criteria remain pending
- Report of what was auto-approved and why

**Phase 17 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 18: NATURAL LANGUAGE RULES & EMAIL TEMPLATES

### Test 18.1: Parse Natural Language Rule

```bash
curl -s -X POST http://localhost:8000/api/automations/rules/parse/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "If a customer has not paid within 45 days and their invoice is over $5000, escalate to a firm tone and send reminders every 3 days"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- AI parses natural language into structured rule
- Extracted conditions: days_overdue > 45, amount > 5000
- Extracted actions: tone = firm, frequency = 3 days
- Parsed result is valid and can be saved

---

### Test 18.2: List Rule Templates

```bash
curl -s http://localhost:8000/api/automations/rules/templates/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Returns predefined rule templates
- Templates cover common scenarios: escalation, frequency, tone adjustment
- Industry-specific templates available

---

### Test 18.3: Create Rule from Template

```bash
curl -s -X POST http://localhost:8000/api/automations/rules/from-template/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "<TEMPLATE_ID>",
    "customizations": {
      "days_overdue": 30,
      "amount_threshold": 10000
    }
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Rule created from template with customizations applied
- Rule is in "active" or "draft" state
- Customized values override template defaults

---

### Test 18.4: Simulate Rule Behavior

```bash
curl -s -X POST http://localhost:8000/api/automations/rules/simulate/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_id": "<RULE_ID>",
    "test_data": {
      "customer_segment": "VIP",
      "days_overdue": 50,
      "amount": 15000,
      "previous_reminders": 3
    }
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Simulation runs without modifying real data
- Returns: would_trigger (true/false), matched_conditions, resulting_actions
- Explains which conditions matched and what would happen

---

### Test 18.5: Activate/Deactivate Rule

```bash
# Activate
curl -s -X POST http://localhost:8000/api/automations/rules/<RULE_ID>/activate/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"

# Deactivate
curl -s -X POST http://localhost:8000/api/automations/rules/<RULE_ID>/deactivate/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Rule status toggles correctly
- Active rules affect workflow decisions
- Deactivated rules don't trigger

---

### Test 18.6: Rule Chat — Create Rule via Conversation

```bash
curl -s -X POST http://localhost:8000/api/automations/rules/chat/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "I want to stop sending reminders to customers who have an active dispute"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:2000])"
```

**Assertions:**
- AI understands the intent
- Proposes a structured rule
- User can confirm to create the rule
- Rule is valid and enforceable

---

### Test 18.7: List Active NL Rules

```bash
curl -s http://localhost:8000/api/automations/rules/nl/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Returns all user-created NL rules
- Each rule shows: original text, parsed conditions, status, created_at
- Rules are properly associated with the user

---

### Test 18.8: Create Email Template

```bash
curl -s -X POST http://localhost:8000/api/automations/email-templates/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Construction Retainage Follow-Up",
    "template_type": "follow_up",
    "industry": "construction",
    "subject": "Retainage Release Request - {{project_name}}",
    "body": "Dear {{customer_name}},\n\nWe are writing regarding the retainage balance of {{amount}} for {{project_name}}. As the project milestone has been completed and approved, we kindly request the release of the retainage funds.\n\nPlease let us know if you need any additional documentation.\n\nBest regards"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Template created with all fields
- Template variables ({{customer_name}}, etc.) preserved
- Industry association correct

---

### Test 18.9: Update Email Template

```bash
curl -s -X PUT http://localhost:8000/api/automations/email-templates/<TEMPLATE_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Updated: Retainage Release - {{project_name}} ({{invoice_number}})"
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Template updated, only specified fields changed
- Other fields preserved
- Updated_at timestamp changed

---

### Test 18.10: Delete Email Template

```bash
curl -s -X DELETE http://localhost:8000/api/automations/email-templates/<TEMPLATE_ID>/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN"
echo "Status: $?"
```

**Assertions:**
- Template deleted (204)
- Template no longer appears in list
- Existing emails sent using this template are not affected

**Phase 18 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 19: PRIVACY, SECURITY & PERMISSIONS

### Test 19.1: PII Data Masking — Verify Before AI Call

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.services.data_masking_service import DataMaskingService

service = DataMaskingService()

# Test masking
test_data = {
    'customer_name': 'John Smith',
    'customer_email': 'john.smith@example.com',
    'phone': '+15551234567',
    'address': '123 Main St, Anytown, USA',
    'amount': 15000,
    'invoice_number': 'INV-001'
}

masked = service.mask(test_data)
print('MASKED DATA:')
for k, v in masked.items():
    print(f'  {k}: {v}')
print()

# Verify PII is not visible
assert 'John Smith' not in str(masked), 'Name not masked!'
assert 'john.smith@example.com' not in str(masked), 'Email not masked!'
assert '+15551234567' not in str(masked), 'Phone not masked!'
assert '123 Main St' not in str(masked), 'Address not masked!'
print('ALL PII PROPERLY MASKED')

# Unmask
unmasked = service.unmask(masked)
print()
print('UNMASKED DATA:')
for k, v in unmasked.items():
    print(f'  {k}: {v}')
assert unmasked['customer_name'] == 'John Smith', 'Unmask failed!'
print('UNMASK VERIFIED')
"
```

**Assertions:**
- PII fields (name, email, phone, address) are masked with tokens (CUST_XXXXX, EMAIL_XXXXX)
- Non-PII fields (amount, invoice_number) are NOT masked
- Unmasking restores original values
- Tokens are deterministic (same input → same token)

---

### Test 19.2: Privacy Stats Endpoint

```bash
curl -s http://localhost:8000/api/automations/privacy/stats/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns privacy statistics
- Includes: total_masking_operations, masking_failures, data_types_masked
- All masking operations succeeded (0 failures)

---

### Test 19.3: Privacy Audit Log

```bash
curl -s http://localhost:8000/api/automations/privacy/audit-log/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Audit log tracks all PII access events
- Each entry: who accessed, what data, when, why
- Log is immutable (entries can't be modified)

---

### Test 19.4: Cross-User Data Isolation — Invoices

```bash
# Education user tries to access construction user's invoice
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User

construction_user = User.objects.get(email='artest.construction@singoa.com')
education_user = User.objects.get(email='artest.education@singoa.com')

# Get a construction invoice ID
c_invoice = Invoice.objects.filter(user=construction_user).first()
print(f'Construction invoice: {c_invoice.id}')

# Try to access it as education user (via ORM, simulating API)
e_invoices = Invoice.objects.filter(user=education_user, id=c_invoice.id)
print(f'Education user can see it: {e_invoices.exists()}')
assert not e_invoices.exists(), 'SECURITY VIOLATION: Cross-user data access!'
print('PASS: Cross-user isolation verified')
"
```

**Assertions:**
- Education user cannot access construction user's invoices
- Query returns empty, not 404 or error (prevents enumeration)

---

### Test 19.5: Cross-User Data Isolation — API Level

```bash
# Get a construction user's invoice ID, then try with education token
CONSTRUCTION_INVOICE_ID=$(cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Invoice
from authentication.models import User
u = User.objects.get(email='artest.construction@singoa.com')
inv = Invoice.objects.filter(user=u).first()
print(inv.id)
")

curl -s http://localhost:8000/api/invoices/$CONSTRUCTION_INVOICE_ID/ \
  -H "Authorization: Bearer $EDUCATION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))
# Should be 404 or 403, NOT the invoice data
"
```

**Assertions:**
- Returns 404 or 403 (not the invoice data)
- No data leakage in error response
- Access attempt is logged

---

### Test 19.6: Cross-User Data Isolation — Customers

```bash
curl -s http://localhost:8000/api/invoices/customers/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
customers = data if isinstance(data, list) else data.get('results', [])
emails = set()
for c in customers:
    emails.add(c.get('customer_email',''))
print(f'Construction user sees {len(customers)} customers')
"

curl -s http://localhost:8000/api/invoices/customers/ \
  -H "Authorization: Bearer $EDUCATION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
customers = data if isinstance(data, list) else data.get('results', [])
print(f'Education user sees {len(customers)} customers')
"
```

**Assertions:**
- Each user sees ONLY their own customers
- No overlapping customer IDs between users
- Customer count matches seeded data

---

### Test 19.7: JWT Token Expiry & Refresh

```bash
# Get a fresh token pair
curl -s -X POST http://localhost:8000/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"email": "artest.construction@singoa.com", "password": "TestPass123!"}' | python3 -c "
import sys,json; data=json.load(sys.stdin)
print('Access token:', data.get('access','')[:50] + '...')
print('Refresh token:', data.get('refresh','')[:50] + '...')

# Decode the access token to check expiry
import base64
parts = data['access'].split('.')
payload = base64.urlsafe_b64decode(parts[1] + '==')
import json as j
p = j.loads(payload)
print(f'Token expires: {p.get(\"exp\",\"\")}')
print(f'User ID: {p.get(\"user_id\",\"\")}')
"
```

**Assertions:**
- Login returns both access and refresh tokens
- Access token has reasonable expiry (15-60 minutes)
- Token contains user_id claim

---

### Test 19.8: Expired Token Rejection

```bash
# Use a deliberately expired/invalid token
curl -s http://localhost:8000/api/invoices/ \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1MDAwMDAwMDB9.invalid" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))"
```

**Assertions:**
- Returns 401 Unauthorized
- Error message is generic (doesn't reveal internal details)
- No data returned

---

### Test 19.9: Token Refresh Flow

```bash
curl -s -X POST http://localhost:8000/api/auth/token/refresh/ \
  -H "Content-Type: application/json" \
  -d "{\"refresh\": \"$CONSTRUCTION_REFRESH_TOKEN\"}" | python3 -c "
import sys,json; data=json.load(sys.stdin)
print('New access token:', data.get('access','')[:50] + '...' if data.get('access') else 'FAILED')
"
```

**Assertions:**
- Refresh returns a new access token
- New access token is valid for API calls
- Old access token may still work until expiry

---

### Test 19.10: Unauthenticated Access Rejection

```bash
# Try all major endpoints without auth
for endpoint in "invoices/" "invoices/customers/" "automations/ar/workflow/status/" "automations/ar/action-queue/" "communications/hub/messages/" "notifications/" "dashboard/"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/$endpoint)
  echo "$endpoint -> $STATUS"
done
```

**Assertions:**
- ALL endpoints return 401 without authentication
- No endpoint leaks data to unauthenticated users
- Error responses are consistent

---

### Test 19.11: SQL Injection Prevention

```bash
curl -s "http://localhost:8000/api/invoices/customers/?search='; DROP TABLE invoices_customer; --" \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin)
print('Response received (DB not dropped):')
print(json.dumps(data, indent=2)[:500])
"

# Verify table still exists
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
print(f'Customers still exist: {Customer.objects.count()}')
"
```

**Assertions:**
- Injection attempt doesn't crash the server
- Database tables are intact
- Query is properly parameterized

---

### Test 19.12: XSS Prevention — Input Sanitization

```bash
curl -s -X POST http://localhost:8000/api/invoices/customers/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "<script>alert(\"XSS\")</script>",
    "customer_email": "xss@test.com"
  }' | python3 -c "
import sys,json; data=json.load(sys.stdin)
name = data.get('customer_name', '')
print(f'Stored name: {name}')
assert '<script>' not in name, 'XSS vulnerability: script tag stored!'
print('PASS: XSS prevented')
"
```

**Assertions:**
- Script tags are sanitized or escaped
- Stored data doesn't contain executable JavaScript
- Response properly escapes output

---

### Test 19.13: Audit Trail Integrity Verification

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/audit-log/verify/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Audit chain verification passes
- No gaps in the audit trail
- No tampered entries detected
- Chain integrity hash is valid

---

### Test 19.14: Active Token Sessions

```bash
curl -s http://localhost:8000/api/automations/privacy/active-sessions/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns list of active sessions for the user
- Includes: login time, last activity, IP address (if tracked)
- Only current user's sessions shown

---

### Test 19.15: Privacy Audit Report

```bash
curl -s http://localhost:8000/api/automations/privacy/audit-report/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Comprehensive privacy report generated
- Covers: data access patterns, masking effectiveness, compliance status
- Suitable for regulatory review

**Phase 19 is PASS when ALL 15 tests pass. Update state tracker.**

---

## PHASE 20: REPORTS, ANALYTICS & DEEP ANALYSIS

### Test 20.1: Automation Stats Endpoint

```bash
curl -s http://localhost:8000/api/automations/stats/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns automation statistics
- Includes: total_actions, actions_by_type, success_rate, average_time
- Stats reflect actual data

---

### Test 20.2: Dashboard KPI Endpoint

```bash
curl -s http://localhost:8000/api/dashboard/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Returns comprehensive dashboard data
- KPIs include: total_outstanding, overdue_amount, collection_rate, DSO
- Data is accurate (matches actual invoice totals)
- Includes trend data (vs previous period)

---

### Test 20.3: AR Aging Report

```bash
curl -s http://localhost:8000/api/reports/aging/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Returns aging buckets: current, 1-30, 31-60, 61-90, 90+
- Each bucket shows: count, total_amount, percentage
- Amounts are accurate
- Customers are in correct buckets based on due dates

---

### Test 20.4: Deep Analysis Report Generation

```bash
curl -s -X POST http://localhost:8000/api/automations/deep-analysis/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"analysis_type": "collection_effectiveness", "period": "last_30_days"}' | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Report generated (may be async/task)
- Contains AI-powered insights
- Insights reference actual data trends
- Recommendations are actionable

---

### Test 20.5: Export Automation Data

```bash
curl -s -X POST http://localhost:8000/api/automations/export/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"format": "csv", "date_range": "last_30_days"}' | head -20
```

**Assertions:**
- Export generates valid CSV (or returns download link)
- CSV contains: date, customer, invoice, action, status, tone
- Data matches what's in the database
- Only current user's data exported

---

### Test 20.6: Custom Report Builder — List Available Fields

```bash
curl -s http://localhost:8000/api/reports/builder/fields/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Returns list of available fields for report building
- Fields from: invoices, customers, automations, communications
- Each field has: name, type, description, filterable flag

---

### Test 20.7: Custom Report — Create & Execute

```bash
# Create
curl -s -X POST http://localhost:8000/api/reports/custom/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Overdue VIP Customers",
    "fields": ["customer_name", "total_outstanding", "days_overdue", "last_reminder_date"],
    "filters": {"segment": "VIP", "status": "overdue"},
    "sort_by": "days_overdue",
    "sort_order": "desc"
  }' | python3 -c "import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"

# Execute
curl -s http://localhost:8000/api/reports/custom/<REPORT_ID>/execute/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Report created successfully
- Execution returns data matching filters
- Only VIP customers with overdue invoices shown
- Sorted by days_overdue descending

---

### Test 20.8: AI Context Snapshot

```bash
curl -s http://localhost:8000/api/automations/ai/context-snapshot/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Returns comprehensive AI context for the user
- Includes: customer summary, outstanding totals, recent activity, risk indicators
- Data is current and accurate
- Suitable for feeding to AI for decision-making

---

### Test 20.9: Collection Effectiveness by Tone

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.models import CommunicationEffectivenessLog
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
from django.db.models import Count, Avg
results = CommunicationEffectivenessLog.objects.filter(user=user).values('tone').annotate(
    total=Count('id'),
    replied=Count('id', filter=models.Q(replied=True)),
    paid=Count('id', filter=models.Q(resulted_in_payment=True))
)
from django.db import models
for r in results:
    print(f'Tone: {r[\"tone\"]} | Total: {r[\"total\"]} | Replied: {r[\"replied\"]} | Paid: {r[\"paid\"]}')
"
```

**Assertions:**
- Effectiveness tracked per tone (friendly, professional, firm, urgent, final)
- Data shows which tones are most effective
- Used by learning system for future recommendations

---

### Test 20.10: ROI Calculation

```bash
curl -s http://localhost:8000/api/reports/roi/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Returns Singoa ROI metrics
- Includes: time_saved_hours, additional_collected_amount, cost_per_collection
- Comparison: before Singoa vs after Singoa
- ROI percentage calculation

**Phase 20 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 21: WEBSOCKET, REAL-TIME & CELERY RESILIENCE

### Test 21.1: WebSocket Connection Test

```bash
# Test WebSocket connectivity
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

channel_layer = get_channel_layer()
if channel_layer:
    print(f'Channel layer: {type(channel_layer).__name__}')
    # Send a test message
    async_to_sync(channel_layer.send)('test-channel', {'type': 'test', 'message': 'ping'})
    print('WebSocket channel layer is working')
else:
    print('No channel layer configured')
"
```

**Assertions:**
- Channel layer is configured (Redis-backed)
- Test message can be sent
- WebSocket infrastructure is functional

---

### Test 21.2: Real-Time Notification Delivery

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from notifications.services.notification_service import NotificationService
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
service = NotificationService()

# Create a test notification
result = service.create_notification(
    user=user,
    notification_type='action_required',
    title='Test Real-Time Notification',
    message='This notification should appear in real-time on the dashboard',
    data={'test': True}
)
print(f'Notification created: {result}')
"
```

**Assertions:**
- Notification created in database
- If WebSocket connected: delivered in real-time
- If no WebSocket: available via polling

---

### Test 21.3: Celery Worker Health Check

```bash
cd singoa-api && celery -A singoa_project inspect ping 2>/dev/null || echo "Celery worker not responding"
cd singoa-api && celery -A singoa_project inspect active 2>/dev/null | head -20
cd singoa-api && celery -A singoa_project inspect stats 2>/dev/null | python3 -c "
import sys; data=sys.stdin.read()
print(data[:2000] if data else 'No stats available')
"
```

**Assertions:**
- Worker responds to ping
- Active tasks list is retrievable
- Stats show: task count, uptime, pool info

---

### Test 21.4: Celery Beat Schedule Verification — All Tasks

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from django.conf import settings

beat = settings.CELERY_BEAT_SCHEDULE
print(f'Total scheduled tasks: {len(beat)}')
print()
for name, config in sorted(beat.items()):
    task = config.get('task', 'N/A')
    schedule = config.get('schedule', 'N/A')
    print(f'{name}:')
    print(f'  Task: {task}')
    print(f'  Schedule: {schedule}')
    print()
"
```

**Assertions:**
- All expected tasks are in the beat schedule
- Schedules are reasonable (not too frequent, not too rare)
- Critical tasks included: execute_due_planned_actions, process_inbound_emails, update_customer_segments

---

### Test 21.5: Stale Lock Recovery Task

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks import release_stale_execution_locks
result = release_stale_execution_locks()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Releases locks held longer than threshold (30 min)
- Reports how many locks released

---

### Test 21.6: Cleanup Tasks — Activity Feed

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks import cleanup_old_activity_feed_items
result = cleanup_old_activity_feed_items()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Removes items older than retention period
- Recent items preserved

---

### Test 21.7: Cleanup Tasks — Old Planned Actions

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks import cleanup_old_planned_actions
result = cleanup_old_planned_actions()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Completed/cancelled actions older than retention period archived
- Active/pending actions preserved

---

### Test 21.8: Automation Health Check Task

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks import run_automation_health_checks
result = run_automation_health_checks()
print(f'Result: {result}')
"
```

**Assertions:**
- Health check passes
- Reports: stuck tasks, orphaned locks, overdue actions
- No critical issues found (or issues are surfaced)

---

### Test 21.9: Process Pending Agent Actions

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from automations.tasks import process_pending_agent_actions
result = process_pending_agent_actions()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Pending agent actions processed
- Results logged

---

### Test 21.10: Notification Queue Processing

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from notifications.tasks import process_notification_queue
result = process_notification_queue()
print(f'Result: {result}')
"
```

**Assertions:**
- Task runs without error
- Queued notifications delivered
- Quiet hours respected

---

### Test 21.11: Daily Digest Generation

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from notifications.tasks import generate_all_digests_for_date
from datetime import date
result = generate_all_digests_for_date(date.today().isoformat())
print(f'Result: {result}')
"
```

**Assertions:**
- Digest generated for all active users
- Digest contains: recent activity, pending actions, upcoming reminders
- Digest is formatted as email-ready content

---

### Test 21.12: Celery Task Error Recovery

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
# Simulate a task that might fail and verify retry behavior
from celery import current_app

# Check retry configuration
for task_name, task in current_app.tasks.items():
    if 'automations' in task_name or 'notifications' in task_name:
        retry_policy = getattr(task, 'max_retries', 'default')
        print(f'{task_name}: max_retries={retry_policy}')
"
```

**Assertions:**
- Critical tasks have retry configuration
- Retry uses exponential backoff
- Max retries prevent infinite loops

**Phase 21 is PASS when ALL 12 tests pass. Update state tracker.**

---

## PHASE 22: IMPORT/EXPORT, CUSTOMER CREDIT & RECONCILIATION

### Test 22.1: Bulk Invoice Import via API

```bash
curl -s -X POST http://localhost:8000/api/invoices/import/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "invoices": [
      {
        "invoice_number": "IMPORT-TEST-001",
        "customer_name": "Meridian Holdings LLC",
        "amount": 8500.00,
        "issue_date": "2026-02-15",
        "due_date": "2026-03-15",
        "description": "Imported test invoice",
        "status": "sent"
      },
      {
        "invoice_number": "IMPORT-TEST-002",
        "customer_name": "Titan Infrastructure Group",
        "amount": 12300.00,
        "issue_date": "2026-02-01",
        "due_date": "2026-03-01",
        "description": "Imported test invoice 2",
        "status": "overdue"
      }
    ]
  }' | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Import succeeds
- Invoices created with correct data
- Customer matching works (existing customers linked)
- Duplicate detection (if invoice_number already exists)

---

### Test 22.2: CSV Invoice Import

```bash
# Create a test CSV
echo "invoice_number,customer_name,amount,due_date,status
CSV-001,Meridian Holdings LLC,5000,2026-03-10,sent
CSV-002,Cascade Supply Co,3200,2026-02-20,overdue" > /tmp/test_import.csv

curl -s -X POST http://localhost:8000/api/invoices/import/csv/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -F "file=@/tmp/test_import.csv" | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- CSV parsed correctly
- Invoices created
- Error handling for malformed rows
- Import job tracked

---

### Test 22.3: Customer Credit Score

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<CUSTOMER_ID>/credit-score/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Credit score calculated based on payment history
- Score components: payment_timeliness, dispute_frequency, communication_responsiveness
- VIP customers should have high scores
- Delinquent customers should have low scores
- Score is 0-100 range

---

### Test 22.4: Customer Credit Report

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<CUSTOMER_ID>/credit-report/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Comprehensive credit report
- Includes: score, history, trends, risk factors, recommendations
- AI-powered analysis of payment behavior

---

### Test 22.5: Customer Segmentation Run

```bash
curl -s -X POST http://localhost:8000/api/automations/ar/customers/segment/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" \
  -H "Content-Type: application/json" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2)[:3000])"
```

**Assertions:**
- Segmentation runs for all customers
- Customers assigned to segments: VIP, STANDARD, RISK, DELINQUENT
- Segment assignment matches payment behavior
- Segments updated based on latest data

---

### Test 22.6: Customer Segments List

```bash
curl -s http://localhost:8000/api/automations/ar/customers/segments/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:2000])"
```

**Assertions:**
- Returns segment breakdown
- Each segment: name, count, total_outstanding, average_days_overdue
- Segment counts match actual customer distribution

---

### Test 22.7: Customer Strategy per Customer

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<VIP_CUSTOMER_ID>/strategy/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"

curl -s http://localhost:8000/api/automations/ar/customers/<DELINQUENT_CUSTOMER_ID>/strategy/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- VIP: gentle approach, low frequency, high-touch personal
- Delinquent: aggressive approach, high frequency, escalation path
- Strategy includes: recommended_tone, reminder_frequency, escalation_triggers
- Strategy is data-driven (based on customer history)

---

### Test 22.8: Customer Automation Status

```bash
curl -s http://localhost:8000/api/automations/ar/customers/<CUSTOMER_ID>/automation-status/ \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"
```

**Assertions:**
- Shows automation status per customer
- Includes: is_paused, next_scheduled_action, total_actions_sent, last_action_date
- Accurate reflection of current state

---

### Test 22.9: Reconciliation — Detect Duplicates

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from invoices.models import Customer
from authentication.models import User

user = User.objects.get(email='artest.construction@singoa.com')
customers = Customer.objects.filter(user=user)

# Check for potential duplicates (similar names)
from difflib import SequenceMatcher
names = [(c.id, c.customer_name) for c in customers]
for i, (id1, name1) in enumerate(names):
    for id2, name2 in names[i+1:]:
        ratio = SequenceMatcher(None, name1.lower(), name2.lower()).ratio()
        if ratio > 0.8:
            print(f'Potential duplicate: \"{name1}\" vs \"{name2}\" (similarity: {ratio:.2f})')

print('Duplicate scan complete')
"
```

**Assertions:**
- Duplicate detection identifies similar customer names
- No actual duplicates in seeded data (each is unique)
- System can flag potential duplicates for review

---

### Test 22.10: Data Export — Full AR Report

```bash
curl -s http://localhost:8000/api/reports/export/?format=json&type=ar_summary \
  -H "Authorization: Bearer $CONSTRUCTION_TOKEN" | python3 -c "
import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2)[:3000])"
```

**Assertions:**
- Export contains comprehensive AR data
- Includes: customer list, invoice summary, automation history, aging
- Data is complete and accurate
- Suitable for external reporting/analysis

**Phase 22 is PASS when ALL 10 tests pass. Update state tracker.**

---

## PHASE 23: MEGA FINAL REGRESSION — COMPLETE RE-RUN

This is the absolute final phase. After ALL previous phases pass, run this mega regression to verify nothing broke across phases.

### Test 23.1: Critical Path Regression

Run these critical tests again (copy the exact commands from earlier phases):

1. **From Phase 1**: Re-run Test 1.1 (Strategy Planner) — verify planned actions still generate correctly
2. **From Phase 2**: Re-run Test 2.5 (Manual Execution) — verify task execution still works
3. **From Phase 3**: Re-run Test 3.1 (Paid Invoice Skip) — verify skip logic
4. **From Phase 4**: Re-run Test 4.3 (VIP Email Generation) — verify AI email quality
5. **From Phase 5**: Re-run Test 5.1 (Send Reminder) — verify email sending
6. **From Phase 6**: Re-run Test 6.1 (Automation Log) — verify logging
7. **From Phase 7**: Re-run Test 7.2 (Inbound Email) — verify reply handling
8. **From Phase 8**: Re-run Test 8.3 (Resolve Queue Item) — verify approval flow
9. **From Phase 13**: Re-run Test 13.5 (Real Email to shivambindal155@gmail.com) — verify real delivery
10. **From Phase 19**: Re-run Test 19.4 (Cross-User Isolation) — verify security

**Assertions:**
- ALL 10 critical path tests pass
- No regressions from earlier phases
- Data integrity maintained across all tests

---

### Test 23.2: Database Integrity Check

```bash
cd singoa-api && python -c "
import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','singoa_project.settings'); django.setup()
from authentication.models import User
from invoices.models import Customer, Invoice
from automations.models import PlannedAction, AutomationLog, ActionQueueItem, ActivityFeedItem, ImmutableAuditLog, BusinessRuleSet

test_emails = ['artest.construction@singoa.com', 'artest.education@singoa.com', 'artest.wholesale@singoa.com', 'artest.generic@singoa.com']
users = User.objects.filter(email__in=test_emails)

total_checks = 0
passed_checks = 0

for u in users:
    print(f'\\n=== {u.email} ===')

    # Business rules
    total_checks += 1
    rules = BusinessRuleSet.objects.filter(user=u).exists()
    if rules: passed_checks += 1
    print(f'  Business rules: {\"PASS\" if rules else \"FAIL\"} ')

    # Customers (15+ per user)
    total_checks += 1
    cust_count = Customer.objects.filter(user=u).count()
    ok = cust_count >= 12
    if ok: passed_checks += 1
    print(f'  Customers: {cust_count} ({\"PASS\" if ok else \"FAIL\"})')

    # Invoices (25+ per user)
    total_checks += 1
    inv_count = Invoice.objects.filter(user=u).count()
    ok = inv_count >= 20
    if ok: passed_checks += 1
    print(f'  Invoices: {inv_count} ({\"PASS\" if ok else \"FAIL\"})')

    # Planned actions exist
    total_checks += 1
    pa_count = PlannedAction.objects.filter(user=u).count()
    ok = pa_count >= 1
    if ok: passed_checks += 1
    print(f'  Planned actions: {pa_count} ({\"PASS\" if ok else \"FAIL\"})')

    # Automation logs exist (emails sent)
    total_checks += 1
    log_count = AutomationLog.objects.filter(user=u).count()
    ok = log_count >= 1
    if ok: passed_checks += 1
    print(f'  Automation logs: {log_count} ({\"PASS\" if ok else \"FAIL\"})')

    # Activity feed entries
    total_checks += 1
    act_count = ActivityFeedItem.objects.filter(user=u).count()
    ok = act_count >= 1
    if ok: passed_checks += 1
    print(f'  Activity feed: {act_count} ({\"PASS\" if ok else \"FAIL\"})')

    # Audit log entries
    total_checks += 1
    audit_count = ImmutableAuditLog.objects.filter(actor_id=u.id).count()
    ok = audit_count >= 1
    if ok: passed_checks += 1
    print(f'  Audit trail: {audit_count} ({\"PASS\" if ok else \"FAIL\"})')

print(f'\\n=== MEGA FINAL: {passed_checks}/{total_checks} checks passed ===')
if passed_checks == total_checks:
    print('ALL CHECKS PASSED — SYSTEM IS 1000% VERIFIED')
else:
    print(f'FAILURES: {total_checks - passed_checks} — FIX AND RE-RUN')
"
```

**Assertions:**
- ALL 28 checks pass (7 per user × 4 users)
- Data is consistent across all users
- No orphaned or corrupted records

---

### Test 23.3: Service Health Final Verification

```bash
# Verify all services are still running
echo "=== Service Health ==="
echo -n "Django: " && curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/core/health/intelligent-ar/
echo ""
echo -n "Redis: " && redis-cli ping
echo -n "Celery Worker: " && cd singoa-api && celery -A singoa_project inspect ping 2>/dev/null | head -1
echo ""
echo "=== Final Service Check Complete ==="
```

**Assertions:**
- Django: 200
- Redis: PONG
- Celery: responds to ping

**Phase 23 is PASS when ALL 3 mega tests pass. This is the absolute final gate.**

**THE ENTIRE WORKFLOW IS COMPLETE WHEN ALL 23 PHASES PASS WITH 1000% CERTAINTY.**

---

## IMPORTANT NOTES

### API Paths Reference

| Area | Method | Endpoint |
|------|--------|----------|
| Login | POST | `/api/auth/login/` |
| Register | POST | `/api/auth/register/` |
| Communication Settings | GET/PUT | `/api/auth/settings/communication/` |
| Invoices | GET/POST | `/api/invoices/` |
| Customers | GET/POST | `/api/invoices/customers/` |
| AR Workflow Run | POST | `/api/automations/ar/workflow/run/` |
| AR Workflow Status | GET | `/api/automations/ar/workflow/status/` |
| Scheduled Tasks | GET | `/api/automations/scheduled/` |
| Execute Tasks | POST | `/api/automations/scheduled/execute/` |
| Cancel Tasks | POST | `/api/automations/scheduled/invoice/<id>/cancel/` |
| Action Queue | GET/POST | `/api/automations/ar/action-queue/` |
| Resolve Queue Item | POST | `/api/automations/ar/action-queue/<id>/resolve/` |
| Dismiss Queue Item | POST | `/api/automations/ar/action-queue/<id>/dismiss/` |
| Quick Action | POST | `/api/automations/ar/action-queue/<id>/quick-action/` |
| Activity Feed | GET | `/api/automations/ar/activity-feed/` |
| Audit Log | GET | `/api/automations/ar/audit-log/` |
| Verify Audit Chain | POST | `/api/automations/ar/audit-log/verify/` |
| Business Rules | GET/PUT | `/api/automations/ar/business-rules/` |
| Industry Defaults | GET | `/api/automations/ar/business-rules/industry-defaults/` |
| Collection Campaign | POST | `/api/automations/ar/collection/campaign/` |
| Invoice Reminder | POST | `/api/automations/ar/invoice/<id>/reminder/` |
| Customer Strategy | GET | `/api/automations/ar/customers/<id>/strategy/` |
| Customer Segmentation | POST | `/api/automations/ar/customers/segment/` |
| Segments List | GET | `/api/automations/ar/customers/segments/` |
| Decision Engine | GET | `/api/automations/ar/decision/<invoice_id>/` |
| Pause Automation | POST | `/api/automations/pause/` |
| Send Reminder | POST | `/api/automations/send-reminder/` |
| Send Custom Email | POST | `/api/automations/send-customer-email/` |
| Automation Logs | GET | `/api/automations/logs/` |
| Customer History | GET | `/api/communications/customer/<id>/history/` |
| Hub Messages | GET | `/api/communications/hub/messages/` |
| Hub Message Detail | GET | `/api/communications/hub/messages/<uuid>/` |
| Reclassify Message | POST | `/api/communications/hub/messages/<uuid>/reclassify/` |
| Respond to Message | POST | `/api/communications/hub/messages/<uuid>/respond/` |
| Hub Stats | GET | `/api/communications/hub/stats/` |
| Email Inbound | POST | `/api/communications/email-inbound/` |
| Notifications | GET | `/api/notifications/` |
| Notification Prefs | GET/PUT | `/api/notifications/preferences/` |
| Unread Count | GET | `/api/notifications/unread_count/` |
| Mark All Read | POST | `/api/notifications/mark-all-read/` |
| Action Token | GET/POST | `/api/notifications/action/<token>/` |
| Digests | GET | `/api/notifications/digests/` |
| Review Queue | GET | `/api/automations/review-queue/` |
| Bulk Approve | POST | `/api/automations/review-queue/bulk-approve/` |
| NL Rules Parse | POST | `/api/automations/rules/parse/` |
| NL Rules List | GET | `/api/automations/rules/nl/` |
| Rule Templates | GET | `/api/automations/rules/templates/` |
| Privacy Stats | GET | `/api/automations/privacy/stats/` |
| Dashboard | GET | `/api/dashboard/` |
| Email Sync | POST | `/api/integrations/emails/sync/` |
| Integrations List | GET | `/api/integrations/` |
| Agent Conversations | GET | `/api/automations/agent/conversations/` |
| Agent Chat | POST | `/api/automations/agent/chat/` |
| Agent Confirm | POST | `/api/automations/agent/confirm/` |
| Agent Insights | GET | `/api/automations/agent/insights/` |
| Agent Performance | GET | `/api/automations/agent/performance/` |
| Agent Tools | GET | `/api/automations/agent/tools/` |
| AI Preferences | GET/PUT | `/api/automations/ai/preferences/` |
| AI Context | GET | `/api/automations/ai/context/` |
| AI Context Snapshot | GET | `/api/automations/ai/context-snapshot/` |
| Suggestions | GET | `/api/automations/suggestions/` |
| Generate Suggestions | POST | `/api/automations/suggestions/generate/` |
| Accept Suggestion | POST | `/api/automations/suggestions/<id>/accept/` |
| Dismiss Suggestion | POST | `/api/automations/suggestions/<id>/dismiss/` |
| Approval Patterns | GET | `/api/automations/approval-patterns/` |
| Communication Effectiveness | GET | `/api/automations/communication-effectiveness/` |
| Experiments | GET/POST | `/api/automations/experiments/` |
| Experiment Detail | GET | `/api/automations/experiments/<id>/` |
| Experiment Results | GET | `/api/automations/experiments/<id>/results/` |
| Experiment Pause | POST | `/api/automations/experiments/<id>/pause/` |
| Experiment Cancel | POST | `/api/automations/experiments/<id>/cancel/` |
| Boundaries | GET/POST | `/api/automations/boundaries/` |
| Boundary Detail | GET/PUT/DELETE | `/api/automations/boundaries/<id>/` |
| Boundary Check | POST | `/api/automations/boundaries/check/` |
| Boundary Parse | POST | `/api/automations/boundaries/parse/` |
| Boundary Defaults | GET | `/api/automations/boundaries/industry-defaults/` |
| Boundary Suggestions | GET | `/api/automations/boundaries/suggestions/` |
| Approvals | GET | `/api/automations/approvals/` |
| Approval Detail | GET | `/api/automations/approvals/<id>/` |
| Approval Approve | POST | `/api/automations/approvals/<id>/approve/` |
| Approval Deny | POST | `/api/automations/approvals/<id>/deny/` |
| Approval Defer | POST | `/api/automations/approvals/<id>/defer/` |
| Token Approval | POST | `/api/automations/approvals/token/<token>/respond/` |
| Approval Stats | GET | `/api/automations/approvals/stats/` |
| Bulk Approve | POST | `/api/automations/review-queue/bulk-approve/` |
| Auto Approve | POST | `/api/automations/review-queue/auto-approve/` |
| Rule Parse | POST | `/api/automations/rules/parse/` |
| Rule Simulate | POST | `/api/automations/rules/simulate/` |
| Rule Chat | POST | `/api/automations/rules/chat/` |
| Rule From Template | POST | `/api/automations/rules/from-template/` |
| Rule Activate | POST | `/api/automations/rules/<id>/activate/` |
| Email Templates | GET/POST | `/api/automations/email-templates/` |
| Email Template Detail | GET/PUT/DELETE | `/api/automations/email-templates/<id>/` |
| Template Preview | POST | `/api/automations/template-preview/` |
| Privacy Audit Log | GET | `/api/automations/privacy/audit-log/` |
| Privacy Report | GET | `/api/automations/privacy/audit-report/` |
| Active Sessions | GET | `/api/automations/privacy/active-sessions/` |
| Automation Stats | GET | `/api/automations/stats/` |
| Deep Analysis | POST | `/api/automations/deep-analysis/` |
| Export Data | POST | `/api/automations/export/` |
| Reports Aging | GET | `/api/reports/aging/` |
| Reports ROI | GET | `/api/reports/roi/` |
| Report Builder Fields | GET | `/api/reports/builder/fields/` |
| Custom Reports | GET/POST | `/api/reports/custom/` |
| Execute Report | GET | `/api/reports/custom/<id>/execute/` |
| Invoice Import | POST | `/api/invoices/import/` |
| CSV Import | POST | `/api/invoices/import/csv/` |
| Customer Credit Score | GET | `/api/automations/ar/customers/<id>/credit-score/` |
| Customer Credit Report | GET | `/api/automations/ar/customers/<id>/credit-report/` |
| Customer Auto Status | GET | `/api/automations/ar/customers/<id>/automation-status/` |
| Token Refresh | POST | `/api/auth/token/refresh/` |

### Frontend Paths Reference

| Page | Path |
|------|------|
| Dashboard | `/dashboard` |
| Invoices | `/invoices` |
| Customers | `/customers` |
| Customer Detail | `/customers/<id>` |
| Review Queue | `/automations/review-queue` |
| AI Automation Settings | `/settings/ai-automation` |
| Notification Settings | `/settings/notifications` |
| Communications Hub | `/hub` |

### File Organization
- All test-related files go in `_extras/`
- State tracker: `_extras/data/ar-workflow-e2e-state.json`
- Bug fix notes: `_extras/docs/notes/`
- Enhancement logs: `_extras/docs/references/`

### Never Do
- Never write automated test scripts
- Never skip a failing test
- Never delete code to make a test pass
- Never use mock data where real data should be
- Never claim completion without all phases passing
- Never modify the tracker file to fake results
- Never output the completion promise unless EVERY phase is genuinely PASS
