From feedec0588542ce8cce70786b2947ea7f020ccd9 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Mon, 23 Mar 2026 09:47:15 +0530 Subject: [PATCH] docs: add team onboarding README with architecture and troubleshooting guide Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 264 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 188 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 8f0f65f..bd6be83 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,210 @@ -

- Nest Logo -

+# Helix Engage Server — Sidecar Backend -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest +NestJS sidecar that bridges Ozonetel telephony APIs with the FortyTwo platform. Handles agent auth, call control, disposition, missed call queue, worklist aggregation, AI enrichment, and live call assist. -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- +**Owner: Karthik** -## Description +## Architecture -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Project setup - -```bash -$ npm install +``` +┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ +│ helix-engage │ │ helix-engage-server │ │ FortyTwo Platform │ +│ React frontend │────▶│ (this repo) │────▶│ GraphQL API │ +│ │ │ Port 4100 │ │ Port 4000 │ +└─────────────────────┘ └──────────────────────┘ └─────────────────────┘ + │ + │ Ozonetel CloudAgent APIs + ▼ + ┌──────────────┐ + │ Ozonetel │ + │ in1-ccaas-api│ + └──────────────┘ ``` -## Compile and run the project +This server has **no database**. All persistent data flows to/from the FortyTwo platform via GraphQL. Ozonetel is the telephony provider (CloudAgent APIs). + +**Three repos:** +| Repo | Purpose | Owner | +|------|---------|-------| +| `helix-engage` | React frontend | Mouli | +| `helix-engage-server` (this) | NestJS sidecar | Karthik | +| `helix-engage-app` | FortyTwo SDK app — entity schemas | Shared | + +## Getting Started ```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod +npm install +npm run start:dev # http://localhost:4100 (watch mode) +npm run build # Production build +npm run start:prod # Run production build ``` -## Run tests +### Environment Variables -```bash -# unit tests -$ npm run test +| Variable | Purpose | Default | +|----------|---------|---------| +| `PORT` | Server port | `4100` | +| `CORS_ORIGIN` | Allowed frontend origin | `http://localhost:5173` | +| `PLATFORM_GRAPHQL_URL` | FortyTwo GraphQL endpoint | `http://localhost:4000/graphql` | +| `PLATFORM_API_KEY` | FortyTwo API key (server-to-server) | — | +| `EXOTEL_API_KEY` | Ozonetel API key | — | +| `EXOTEL_API_TOKEN` | Ozonetel API token | — | +| `EXOTEL_ACCOUNT_SID` | Ozonetel account SID | — | +| `OZONETEL_AGENT_ID` | Default agent ID | `agent3` | +| `OZONETEL_AGENT_PASSWORD` | Default agent password | — | +| `OZONETEL_SIP_ID` | Default SIP extension | `521814` | +| `OZONETEL_DID` | Inbound DID number | `918041763265` | +| `OZONETEL_CAMPAIGN_NAME` | Default campaign | `Inbound_918041763265` | +| `MISSED_QUEUE_POLL_INTERVAL_MS` | Missed call ingestion interval | `30000` | +| `OPENAI_API_KEY` | For AI enrichment / call assist | — | +| `ANTHROPIC_API_KEY` | Alternative AI provider | — | +| `DEEPGRAM_API_KEY` | Live transcription (STT) | — | -# e2e tests -$ npm run test:e2e +## Module Structure -# test coverage -$ npm run test:cov ``` +src/ +├── ozonetel/ # ⚡ Ozonetel telephony — WHERE MOST WORK HAPPENS +│ ├── ozonetel-agent.controller.ts # REST endpoints for agent operations +│ ├── ozonetel-agent.service.ts # Ozonetel API wrapper (token, CDR, abandon calls) +│ ├── ozonetel-agent.module.ts # Module wiring +│ └── kookoo-ivr.controller.ts # IVR callback handler (XML responses) +│ +├── worklist/ # Agent task queue + missed call queue +│ ├── worklist.controller.ts # GET /api/worklist, missed queue endpoints +│ ├── worklist.service.ts # Aggregates leads + missed calls + follow-ups +│ ├── missed-queue.service.ts # Ingestion, dedup, auto-assignment +│ ├── missed-call-webhook.controller.ts # Webhook receiver +│ └── kookoo-callback.controller.ts # Kookoo webhook +│ +├── call-events/ # Real-time call event processing +│ ├── call-events.service.ts # Incoming call handling, AI enrichment, disposition logging +│ ├── call-events.gateway.ts # WebSocket push to frontend (Socket.IO) +│ └── call-lookup.controller.ts # Reverse phone lookup + AI enrichment +│ +├── platform/ # FortyTwo platform GraphQL client +│ ├── platform-graphql.service.ts # query() for server-to-server, queryWithAuth() for user JWT +│ ├── platform.types.ts # Lead, Call, Activity types +│ └── platform.module.ts +│ +├── search/ # Cross-entity search +│ └── search.controller.ts # GET /api/search — leads + patients + appointments +│ +├── call-assist/ # Live call assistance +│ └── (Socket.IO namespace /call-assist, Deepgram STT, AI suggestions) +│ +├── ai/ # AI enrichment (lead summaries, suggested actions) +├── auth/ # User auth proxy +├── graphql-proxy/ # GraphQL passthrough to platform +├── health/ # Health check endpoint +├── config/ +│ └── configuration.ts # All env var loading +├── app.module.ts # Root module — imports all feature modules +└── main.ts # NestJS bootstrap (port 4100, CORS) +``` + +## API Endpoints + +### Ozonetel Agent (`/api/ozonetel/`) + +| Method | Path | Purpose | +|--------|------|---------| +| POST | `/agent-login` | Agent login to Ozonetel | +| POST | `/agent-logout` | Agent logout | +| POST | `/agent-state` | Change state (Ready/Pause) + auto-assign missed call on Ready | +| POST | `/agent-ready` | Force ready (logout + login) | +| POST | `/dispose` | Submit call disposition + update missed call status + auto-assign next | +| POST | `/dial` | Manual outbound dial | +| POST | `/call-control` | CONFERENCE, HOLD, UNHOLD, MUTE, UNMUTE, KICK_CALL | +| POST | `/recording` | Pause/unpause recording | +| GET | `/missed-calls` | Raw Ozonetel abandon calls | +| GET | `/call-history?date=` | CDR for a date | +| GET | `/performance?date=` | Aggregated agent metrics | + +### Worklist (`/api/worklist/`) + +| Method | Path | Purpose | +|--------|------|---------| +| GET | `/` | Agent's worklist (missed calls + follow-ups + leads) | +| GET | `/missed-queue` | Missed calls grouped by callback status | +| PATCH | `/missed-queue/:id/status` | Update callback status on a missed call | + +### Other + +| Method | Path | Purpose | +|--------|------|---------| +| GET | `/api/search?q=` | Cross-entity search (leads, patients, appointments) | +| POST | `/api/call/lookup` | Reverse phone lookup + AI enrichment | +| GET | `/api/health` | Health check | +| POST | `/graphql` | GraphQL proxy to platform | + +## Troubleshooting Guide — Where to Look + +### "Agent can't log in to Ozonetel" +**File:** `src/ozonetel/ozonetel-agent.controller.ts` → `agentLogin()` +**Service:** `src/ozonetel/ozonetel-agent.service.ts` → `loginAgent()` +Uses HTTP Basic auth to Ozonetel's `AgentAuthenticationV2` endpoint. "Already logged in" responses have `status: "error"` but are not real errors. Check `OZONETEL_AGENT_ID` and `OZONETEL_AGENT_PASSWORD` env vars. + +### "Disposition failing / ACW not releasing" +**File:** `src/ozonetel/ozonetel-agent.controller.ts` → `dispose()` +**Service:** `src/ozonetel/ozonetel-agent.service.ts` → `setDisposition()` +All dispositions currently map to `'General Enquiry'` (campaign limitation). Uses `autoRelease: 'true'` to end ACW. If agent stays in ACW, the Ozonetel campaign's wrapup time (8s) may not have elapsed. + +### "Missed calls not being ingested" +**File:** `src/worklist/missed-queue.service.ts` → `ingest()` +Runs on a 30s interval (`onModuleInit`). Polls Ozonetel `abandonCalls` API for the last 5 minutes. Look for log lines with `[MissedQueueService]`. Common issues: Ozonetel token expired (55-min cache), platform API key missing, phone number format mismatch. + +### "Auto-assignment not working" +**File:** `src/worklist/missed-queue.service.ts` → `assignNext()` +Triggered from two places: `dispose()` and `agent-state()` in `ozonetel-agent.controller.ts`. Queries platform for oldest `PENDING_CALLBACK` call with empty `agentName`. Uses a mutex to prevent race conditions. If no calls are assigned, check that `callbackstatus` field exists on the Call entity (custom field, all-lowercase in GraphQL). + +### "Worklist returning empty" +**File:** `src/worklist/worklist.service.ts` +Three parallel queries: `getMissedCalls()`, `getPendingFollowUps()`, `getAssignedLeads()`. All filter by `agentName`. If the agent name from the JWT doesn't match what's stored in lead/call records, results will be empty. Check `resolveAgentName()` in `worklist.controller.ts`. + +### "Call events / webhooks not arriving" +**File:** `src/call-events/call-events.service.ts` +Ozonetel sends webhooks to the sidecar. Check that the webhook URL is configured in the Ozonetel dashboard and that the sidecar is reachable from the internet (Caddy reverse proxy on the VPS). + +### "AI enrichment / call assist broken" +**Files:** `src/ai/`, `src/call-assist/` +Live transcription uses Deepgram Nova STT via raw WebSocket. AI suggestions use OpenAI gpt-4o-mini. Check `DEEPGRAM_API_KEY` and `OPENAI_API_KEY` env vars. The call-assist gateway uses Socket.IO namespace `/call-assist`. + +### "Search not finding records" +**File:** `src/search/search.controller.ts` +Runs three parallel GraphQL queries (leads, patients, appointments), filters client-side. Requires minimum 2 characters. Uses the user's JWT (passed from frontend auth header). + +## Key Technical Patterns + +### Two Auth Models +1. **User JWT passthrough** — `platform.queryWithAuth(query, vars, authHeader)` — for user-facing endpoints (worklist, search). The frontend sends its JWT and the sidecar forwards it. +2. **Server API key** — `platform.query(query, vars)` — for server-to-server operations (missed call ingestion, auto-assignment). Uses `PLATFORM_API_KEY`. + +### Ozonetel Token Caching +`ozonetel-agent.service.ts` → `getToken()` caches the bearer token for 55 minutes (tokens expire at 60 min). All CloudAgent API calls use this cached token. + +### Custom Field Naming +Fields added via the FortyTwo admin portal use **all-lowercase** GraphQL names: +- `callbackstatus` (not `callbackStatus`) +- `callsourcenumber` (not `callSourceNumber`) +- `missedcallcount` (not `missedCallCount`) +- `callbackattemptedat` (not `callbackAttemptedAt`) + +App-defined (managed) fields keep camelCase: `callStatus`, `agentName`, etc. + +### Error Handling Pattern +Ozonetel endpoints return `{ status: 'error', message }` instead of throwing — this prevents UI from blocking on telephony failures. The frontend catches errors silently on disposition and recording. ## Deployment -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. - -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: - ```bash -$ npm install -g @nestjs/mau -$ mau deploy +npm run build +# Then tar + scp + docker cp + restart (see deploy script in project docs) ``` -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. +The sidecar runs inside a Docker container (`fortytwo-staging-sidecar-1`) on the staging VPS. -## Resources +## Git Workflow -Check out a few resources that may come in handy when working with NestJS: - -- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. -- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). -- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). -- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. -- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). -- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). -- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). -- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). +- `dev` — active development +- `master` — stable baseline