# LATAM monitor fan-out worker

Handler: `worker_function.lambda_handler`

## Responsibility

The worker consumes one tenant per SQS record, updates the LATAM target database,
and sends a scoring request after the database transaction commits.

For an active candidate it:

1. loads or classifies email-domain intelligence, including zero or more ICP
   categories from Fintech, Retail, E-Commerce, AI, and iGaming;
2. queries DevOps/Clinic for cluster counts and 30-day average QPS;
3. queries Billing for credit-card and active-credit signals;
4. upserts `latam_active_tenants`;
5. sends the tenant ID to the scoring queue.

For a new signup it only performs domain intelligence, upserts the same
`latam_active_tenants` table with `source_type = 'new_signup'`, and sends a
scoring request. It does not call the cluster or Billing APIs.

ICP categories are stored as the canonical JSON values `fintech`, `retail`,
`e_commerce`, `ai`, and `igaming`. Unknown values are discarded, duplicates
are removed, and personal/unknown entities cannot retain an ICP match.

## Failure behavior

- Enrichment failures are recorded in `latam_active_tenants.error_message` and
  produce an `active_failed` or `new_signup_failed` scoring event.
- A scoring-queue send failure is not swallowed. The current SQS record is
  returned in `batchItemFailures` so Lambda retries it.
- Because the target-table writes are upserts, retrying after a scoring send
  failure is safe and does not create duplicate tenant rows.
- Partial cluster failures are retained in `error_message`; successfully
  resolved cluster counts are still written. QPS is written as `NULL` when
  any cluster QPS request fails, so unavailable or incomplete data is not
  mistaken for a real zero-QPS result.
- Domain Intelligence, cluster metrics, and Billing failures are isolated.
  A failure in one enrichment source preserves its previous value when
  available and does not prevent the other sources from being collected.
- When a cached Billing config is missing, inactive, or has no valid credit
  card, the Worker rediscovers all active Billing configs before concluding
  that the tenant has no valid card.
- A new-signup upsert preserves an existing tenant score and priority instead
  of replacing them with `NULL`.
- When `cluster_count >= 10`, the Worker keeps the DevOps cluster and service
  plan counts but skips all per-cluster Clinic QPS calls. It writes
  `tenant_avg_qps = NULL`; Scoring treats a non-individual tenant at this
  threshold as at least P1. At 50 or more clusters, Scoring makes every tenant
  P0 regardless of its domain-intelligence category.

## Configuration

Required for every record:

- `TARGET_DATABASE_URL` (preferred), or the `TARGET_DB_*` variables

Required for every successfully persisted record:

- `SCORING_QUEUE_URL`

Required only for active-candidate enrichment:

- `DEVOPS_API_TOKEN`

Required only when an active candidate has fewer than 10 clusters:

- `CLINIC_API_KEY`

Required for Qwen domain classification:

- `API_KEY`
- `WORKSPACE_ID`
- `DASHSCOPE_ENDPOINT`
- `REGION`

The Worker calls the Qwen OpenAI-compatible Responses API with built-in
`web_search`. It uses `reasoning.effort = none` because domain classification
is a short structured task. The prompt requires explicit public evidence for
AI classifications; automation, analytics, generic marketing language, and an
`.ai` domain are not sufficient on their own.

Qwen cache rows use the versioned source `qwen_web_search_v1`. Cache rows from
the previous OpenAI classifier are intentionally not reused, so the first run
after this migration reclassifies each non-personal LATAM domain with Qwen.

Optional:

- `OPERATOR_EMAIL`
- `DEVOPS_BASE_URL`
- `CLINIC_BASE_URL`
- `QWEN_WEB_SEARCH_MODEL` (default `qwen3.8-max`)
- `DOMAIN_INTELLIGENCE_TTL_DAYS`
- `LLM_TIMEOUT_SECONDS`
- `BILLING_REQUEST_INTERVAL_SECONDS`
- `BILLING_RETRY_COUNT`
- `TARGET_TIDB_SSL_CA_PATH` or `TIDB_SSL_CA_PATH`

The role needs CloudWatch Logs, permission to consume the monitor queue, and
`sqs:SendMessage` for the scoring queue. Keep batch size 1 and reserved
concurrency 2 initially because the external Billing and Clinic APIs dominate
latency and have rate limits.

## Removed legacy code

The Worker package no longer contains a Dispatcher handler, DevOps tenant-list
queries, raw SQL overrides, or the old all-tenants-in-one-invocation handler.
It also no longer logs full tenant, payment, or credit objects.
