# Telemetry Receiver — TaseesAI Side

The Muassis gateway uploads anonymized daily aggregates to:
```
https://learn.taseesai.com/v1/ingest
```
(Configurable per-install via `MUASSIS_TELEMETRY_ENDPOINT` env var.)

**You (TaseesAI) own this endpoint.** It must be stood up before consented telemetry actually flows anywhere. Until then, the uploader fails gracefully (logs + journal) and the customer sees no impact.

## What the endpoint receives

POST body (JSON), one per tenant per day:

```jsonc
{
  "tenant_anon_id": "a3f2b9c1d8e74021",   // SHA-256 truncated, NOT reversible
  "date": "2026-05-09",
  "message_count": 47,
  "language_distribution": { "ar": 30, "en": 12, "mixed": 5 },
  "intent_distribution": { "greeting": 5, "booking": 12, "complaint": 2, ... },
  "tool_usage": { "web_search": 8, "create_goal": 1, "send_email": 3, ... },
  "length_distribution": { "short": 18, "medium": 22, "long": 7 },
  "avg_response_latency_ms": 1240,
  "saudi_context_signal_count": {
    "zatca": 3, "gosi": 0, "nitaqat": 0,
    "salla": 5, "zid": 0, "mada": 1,
    "prayer": 0, "tadawul": 2, "hijri": 1
  },
  "client_version": "1.1.0"
}
```

**No PII. No message content. No customer names/phones/financials.** Already scrubbed at the source by `src/telemetry/scrubber.ts` BEFORE the upload runs.

## Deployment options (cheapest → most flexible)

### Option A: Cloudflare Worker (recommended — free up to 100k req/day, 5 min to deploy)
```js
// learn-receiver.worker.js
export default {
  async fetch(request, env) {
    if (request.method !== 'POST') return new Response('POST only', { status: 405 });
    const url = new URL(request.url);
    if (url.pathname === '/v1/ingest') {
      const body = await request.json();
      // Persist: write to D1, R2, KV, or forward to your data warehouse
      await env.TELEMETRY_KV.put(
        `${body.tenant_anon_id}:${body.date}`,
        JSON.stringify({ ...body, received_at: new Date().toISOString() }),
        { expirationTtl: 60 * 60 * 24 * 365 }, // 1-year TTL
      );
      return new Response(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' } });
    }
    if (url.pathname === '/v1/ingest/purge' && request.method === 'POST') {
      const { tenant_anon_id } = await request.json();
      // Walk and delete all keys for this anon_id
      const list = await env.TELEMETRY_KV.list({ prefix: `${tenant_anon_id}:` });
      for (const k of list.keys) await env.TELEMETRY_KV.delete(k.name);
      return new Response(JSON.stringify({ ok: true, deleted: list.keys.length }));
    }
    return new Response('Not found', { status: 404 });
  },
};
```

Deploy: `wrangler publish` after binding `TELEMETRY_KV` namespace.

### Option B: Vercel Serverless Function (5 min, free tier covers MVP)
```ts
// api/v1/ingest.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { sql } from '@vercel/postgres';

export default async function handler(req: VercelRequest, res: VercelResponse) {
  if (req.method !== 'POST') return res.status(405).json({ error: 'POST only' });
  const body = req.body;
  await sql`
    INSERT INTO telemetry_aggregates (tenant_anon_id, date, payload, received_at)
    VALUES (${body.tenant_anon_id}, ${body.date}, ${JSON.stringify(body)}, NOW())
    ON CONFLICT (tenant_anon_id, date) DO UPDATE SET payload = EXCLUDED.payload
  `;
  return res.status(200).json({ ok: true });
}
```

### Option C: AWS Lambda + S3 (max scale, in-Kingdom data residency)
Lambda receives → writes JSON to `s3://taseesai-telemetry/<date>/<anon_id>.json` partitioned by date for Athena/Glue query later.

For Saudi-residency requirements (PIF, Aramco), use AWS me-south-1 region.

## Schema for downstream use

When you go to train, query the warehouse:

```sql
-- Top intents this month
SELECT
  jsonb_object_keys(intent_distribution) AS intent,
  SUM((intent_distribution ->> jsonb_object_keys(intent_distribution))::int) AS n
FROM telemetry_aggregates
WHERE date >= '2026-05-01'
GROUP BY intent
ORDER BY n DESC;

-- Saudi context signal density (for proving KSA-vertical fit)
SELECT
  date,
  SUM((saudi_context_signal_count ->> 'zatca')::int) AS zatca_mentions,
  SUM((saudi_context_signal_count ->> 'salla')::int) AS salla_mentions
FROM telemetry_aggregates
GROUP BY date
ORDER BY date DESC;
```

## Privacy + legal

- The uploader gates on `preference.telemetry_consent = on` per-tenant. Default is **off**.
- Customer can audit (`muassis privacy show`), pause (`muassis privacy off`), and purge (`muassis privacy purge --confirm`).
- `tenant_anon_id` is `SHA256(tenant_id + ':muassis-telemetry').slice(0,16)` — irreversible.
- TaseesAI must publish a **public retention policy** at `https://taseesai.com/privacy` and honor purges within 30 days (PDPL Article 18).
- **Recommended retention**: 1 year for individual aggregates, indefinite for fully-anonymized rolled-up cohorts (no individual attribution).

## What you do with the data

Training pipeline ideas:
1. **Intent classifier fine-tune** — build a Saudi-business intent labels dataset from `intent_distribution`.
2. **Tool-call routing** — observe which tools are used after which intents → improve the brain's tool-selection heuristics.
3. **Saudi context coverage** — identify gaps (e.g., few Salla mentions = our skill keywords aren't catching Salla queries).
4. **Cohort analysis** — segment by industry vertical (after manual labeling) and customize defaults.

Do NOT:
- Try to re-identify tenants. The hash is one-way; you'd be violating PDPL even if you could.
- Sell or share the data. Customers consented to TaseesAI training, not third-party use.
- Combine with other datasets in ways that could re-identify (k-anonymity ≥ 5 minimum).

## When to stand this up

Before publishing v1.1.0 to npm. Otherwise the uploader will silently log "telemetry upload failed" forever, and customers who opted in see no benefit.

The endpoint can be a 50-line Cloudflare Worker for the first 6 months. Scale up to a real ETL pipeline once volume justifies it.
