SignalDesk is a Spring Boot backend for triaging messy inbound support and operations requests into structured, reviewable case analysis.
It accepts raw text such as:
Customer says payment failed twice, they are threatening to cancel, engineering says no outage, finance says retry later.
SignalDesk turns that into:
- A concise summary
- A category
- A priority and risk score
- A suggested owner or team
- Recommended next actions
- A draft customer-safe reply
- Audit warnings
- Live progress events over Server-Sent Events (SSE)
The current implementation uses deterministic heuristics rather than an external LLM, but the analysis layer is already abstracted so a real provider can be added later.
- Async case intake and analysis pipeline
- Deterministic AI-style triage using maintainable heuristics
- Structured analysis results persisted in PostgreSQL
- Audit logging across pipeline stages
- Reviewer approval and edit workflow
- Live analysis progress streaming with Spring MVC
SseEmitter - Swagger UI / OpenAPI documentation
- Lightweight Thymeleaf admin UI for manual testing and demos
- Development data seeding for realistic sample cases
SignalDesk follows a layered Spring Boot architecture:
controller: REST APIs and server-rendered MVC controllersservice: business workflows and orchestrationservice.impl: concrete implementationsservice.ai: pluggable AI analysis abstractionrepository: Spring Data JPA repositoriesentity: JPA persistence modelsdto: request and response modelsmapper: DTO mapping logicconfig: async, OpenAPI, and dev seed configurationexception: consistent API error handlingenums: domain enums for status, category, stage, and severity
High-level flow:
- A client submits case text.
IntakeServicestores aCaseRecordwith statusNEW.AnalysisOrchestratorServiceruns asynchronously.- The case moves to
IN_PROGRESS. - Stage services classify, score priority, recommend an owner, generate a draft reply, and create audit logs.
- Progress events are published over SSE.
AnalysisResultis persisted and the case moves toANALYZED.- A reviewer can approve or edit the final output.
- Java 21
- Spring Boot 4
- Maven
- PostgreSQL
- Spring Data JPA
- Spring Web MVC
- Spring Validation
- Lombok
- Thymeleaf
- springdoc-openapi / Swagger UI
- JUnit + Spring Boot Test + MockMvc
src/main/java/com/example/signaldesk
├── config
├── controller
├── dto
│ ├── request
│ └── response
├── entity
├── enums
├── exception
├── mapper
├── repository
├── service
│ ├── ai
│ │ └── model
│ ├── impl
│ └── model
└── SignaldeskApplication.java
| Method | Path | Purpose |
|---|---|---|
POST |
/api/cases |
Create a case and trigger async analysis |
GET |
/api/cases/{id} |
Fetch full case details, including partial data while analysis is running |
GET |
/api/cases/{id}/stream |
Subscribe to live SSE progress events for a case |
POST |
/api/cases/{id}/review |
Save reviewer approval or edits |
GET |
/api/dashboard/cases |
List dashboard cases, optionally filtered by status |
| Path | Purpose |
|---|---|
/ui/cases/new |
Create a new case from a simple form |
/ui/cases/{id} |
View case details, progress, analysis, audit logs, and review form |
/ui/dashboard |
Browse the queue in a simple admin dashboard |
- Swagger UI:
http://localhost:8080/swagger-ui.html
- IntelliJ IDEA
- Java 21 SDK configured in IntelliJ
- PostgreSQL running locally
Open the project root in IntelliJ:
C:\Users\dhruv\Desktop\signaldesk
Create a local PostgreSQL database and set Spring datasource properties in IntelliJ using one of these approaches:
- Run configuration environment variables
- VM options with
-D... - A local
application-local.propertiesfile
Example properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/signaldesk
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
spring.jpa.show-sql=falseTo load sample cases automatically, run with the dev profile:
SPRING_PROFILES_ACTIVE=dev
The dev profile enables the seeded sample cases through:
signaldesk.seed.enabled=trueRun SignaldeskApplication from IntelliJ, or use Maven:
./mvnw spring-boot:runOn Windows PowerShell:
.\mvnw.cmd spring-boot:run- REST base:
http://localhost:8080/api - Admin UI:
http://localhost:8080/ui/dashboard - Swagger UI:
http://localhost:8080/swagger-ui.html
Example SQL:
CREATE DATABASE signaldesk;
CREATE USER signaldesk_user WITH PASSWORD 'signaldesk_password';
GRANT ALL PRIVILEGES ON DATABASE signaldesk TO signaldesk_user;Then use:
spring.datasource.url=jdbc:postgresql://localhost:5432/signaldesk
spring.datasource.username=signaldesk_user
spring.datasource.password=signaldesk_passwordFor local development, spring.jpa.hibernate.ddl-auto=update is the simplest option. For production, a migration tool such as Flyway should be added.
POST /api/cases
Content-Type: application/json
{
"source": "EMAIL",
"rawText": "Customer says payment failed twice, they are threatening to cancel, engineering says no outage, finance says retry later."
}Response:
{
"caseId": "8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1",
"status": "NEW",
"createdAt": "2026-03-22T18:15:42.194395Z"
}Notes:
- The endpoint returns
202 Accepted. - Analysis starts asynchronously after the case is created.
- The case status will move from
NEWtoIN_PROGRESStoANALYZED.
GET /api/cases/8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1Example response after analysis:
{
"caseId": "8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1",
"source": "EMAIL",
"rawText": "Customer says payment failed twice, they are threatening to cancel, engineering says no outage, finance says retry later.",
"status": "ANALYZED",
"createdAt": "2026-03-22T18:15:42.194395Z",
"updatedAt": "2026-03-22T18:15:43.508211Z",
"analysisResult": {
"id": "6e5b7be4-7170-45fa-bc17-5ab1ab0d8d2b",
"summary": "Customer reports repeated payment failures and churn risk with no confirmed outage.",
"category": "BILLING",
"priority": "HIGH",
"riskScore": 0.84,
"suggestedOwner": "Billing Ops",
"suggestedActions": "1. Confirm payment gateway behavior and invoice state\n2. Contact Billing Ops for immediate review\n3. Respond to the customer with a status update",
"draftReply": "Thanks for flagging this. We are reviewing the payment failure details with our billing team and will follow up as soon as we have a concrete update.",
"confidenceScore": 88,
"createdAt": "2026-03-22T18:15:43.204933Z",
"updatedAt": "2026-03-22T18:15:43.204933Z"
},
"auditLogs": [
{
"id": "5a2c7bd6-4827-4258-b0b1-b952b79f109a",
"stage": "CLASSIFICATION",
"severity": "INFO",
"message": "Category inferred as BILLING.",
"createdAt": "2026-03-22T18:15:42.702139Z"
},
{
"id": "e23705fc-345c-4cb8-973f-22d31c04d0d9",
"stage": "AUDIT",
"severity": "WARNING",
"message": "Critical cases should include an explicit urgent action phrase.",
"createdAt": "2026-03-22T18:15:43.410150Z"
}
],
"reviewDecision": null
}While analysis is still running, analysisResult and reviewDecision may be null, and auditLogs may be partially populated. The endpoint is designed to return partial case details safely during processing.
POST /api/cases/8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1/review
Content-Type: application/json
{
"approved": true,
"finalPriority": "HIGH",
"finalOwner": "Billing Ops",
"editedReply": "Thanks for your patience. We have escalated this to Billing Ops for immediate review and will update you as soon as we have confirmed next steps.",
"reviewerNotes": "Approved with slightly clearer customer wording."
}SignalDesk exposes live analysis progress over Server-Sent Events:
GET /api/cases/{id}/stream
Accept: text/event-streamThe in-memory progress stream service supports:
- Subscribe by case ID
- Publish stage updates
- Publish error events
- Complete and clean up streams safely
The orchestrator publishes these stages:
INTAKECLASSIFICATIONPRIORITY_SCORINGOWNER_RECOMMENDATIONDRAFT_GENERATIONAUDITCOMPLETED
Example SSE event payload:
{
"caseId": "8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1",
"stage": "PRIORITY_SCORING",
"message": "Priority set to HIGH with risk score 0.84.",
"timestamp": "2026-03-22T18:15:42.918Z"
}Example with curl:
curl -N http://localhost:8080/api/cases/8d0c12f0-9f27-4dd9-9478-5d5c1b8aa4e1/streamThis is intentionally simple for local and interview/demo use. It avoids external infrastructure while keeping a clean seam for future background job or event-bus integration.
Current analysis is deterministic and keyword-driven:
- Billing:
payment,invoice,billing,refund - Account access:
login,password,access,sign in - Incident:
outage,down,unavailable,incident - Retention:
cancel,churn,leaving,disappointed - Compliance:
exposed,leaked,wrong client,compliance,legal - Fallback:
GENERAL
Priority and risk score are based on signals such as:
- Threatening to cancel
- Payroll urgency
- Security or leaked-data language
- Repeated failures
- Constructor injection is used throughout the app.
- Entities are not returned directly from controllers; responses are mapped through DTOs.
- The analysis pipeline is synchronous at the service level and asynchronous at the request boundary.
AiAnalysisProviderkeeps the current heuristic implementation replaceable with a future LLM-backed provider.
Run all tests:
.\mvnw.cmd testCurrent test coverage includes:
- Heuristic service tests
- MockMvc controller integration tests for core case and review endpoints
- Add Flyway or Liquibase migrations
- Replace heuristic AI provider with a real LLM-backed provider
- Add authentication and reviewer roles
- Add pagination and richer filtering on the dashboard
- Split suggested actions into a first-class structured collection
- Persist SSE/job progress for reconnect support
- Add observability with metrics and tracing
- Add Docker and
docker-composesupport for local startup - Harden production config and deployment profiles
SignalDesk is intentionally built to feel like a production-style backend rather than a toy CRUD app. It demonstrates:
- Layered Spring architecture
- Async workflow orchestration
- Safe DTO boundaries
- Practical SSE streaming
- Deterministic AI-style business logic
- A clean path toward future LLM integration