A full-stack insurance claims management system built with .NET 10, PostgreSQL 16 + pgvector, RabbitMQ, and React. Features a state machine-driven claims workflow, AI-powered fraud detection, advanced SQL reporting, and event-driven architecture.
Transparency note: The backend (.NET, database design, architecture) was written by me as a learning project. The React frontend was generated with AI tooling and is not representative of my frontend skills.
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐
│ React UI │────▸│ .NET 10 API │────▸│ PostgreSQL │
│ (port 3000)│ │ (port 5000) │ │ + pgvector │
└─────────────┘ └────────┬────────┘ └──────────────┘
│
┌────────▼────────┐
│ RabbitMQ │
│ (MassTransit) │
└─────────────────┘
| Layer | Responsibility |
|---|---|
ClaimFlow.Domain |
Entities, enums, domain events, state machine. Zero framework dependencies. |
ClaimFlow.Application |
CQRS commands/queries (MediatR), DTOs, validation (FluentValidation), interfaces |
ClaimFlow.Infrastructure |
EF Core, Dapper queries, MassTransit consumers, Hangfire jobs, outbox processor |
ClaimFlow.Api |
Minimal API endpoints, middleware, DI composition root |
ClaimFlow.Tests |
Integration tests with Testcontainers (real PostgreSQL) |
Claims follow a strict lifecycle enforced by the Stateless library:
Submitted → UnderReview → DocumentsRequested → UnderReview
→ UnderInvestigation → Approved → PaymentScheduled → Paid → Closed
→ Rejected → Appeal → UnderReview (re-enters cycle)
- Invalid transitions throw exceptions (e.g., can't Approve a Submitted claim directly)
- Every transition is recorded in
ClaimStatusHistoryfor full audit trail - Domain events (
ClaimSubmittedEvent,ClaimApprovedEvent, etc.) are published on each transition
Events are saved to an outbox_messages table in the same database transaction as the claim change. A background worker (OutboxProcessor) polls every 5 seconds and publishes to RabbitMQ via MassTransit. This guarantees no events are lost if the app crashes between save and publish.
When a claim is submitted, its description is embedded as a 1536-dimension vector. The fraud scoring system combines 4 factors (0-100 score):
| Factor | Max Points | Logic |
|---|---|---|
| Vector similarity to known fraud | 40 | Cosine distance via <=> operator against flagged claims |
| Amount anomaly | 20 | Claim amount vs. historical average for the policy |
| Timing | 20 | Claims filed within 30/90 days of policy start date |
| Frequency | 20 | Number of claims in the last 6 months |
The similarity query uses Dapper with raw SQL:
SELECT c."Id", c."Description",
1 - (c."Embedding" <=> @embedding::vector) AS similarity_score
FROM claims c
WHERE c."IsFraud" = true
ORDER BY c."Embedding" <=> @embedding::vector
LIMIT 5;All reporting queries use Dapper instead of EF Core to demonstrate knowledge of when each tool is appropriate:
- Materialized views for monthly loss ratios (claims paid vs. premiums collected)
- Window functions for rolling 3-month average claim amounts per product type
- CTEs for hierarchical agent performance reports
- Full-text search with
tsvectorandts_rankfor searching claim descriptions
Every entity carries a TenantId. Policies inherit their tenant from the customer (business rule: a policy always belongs to the same branch as its customer). PostgreSQL Row-Level Security can be layered on top.
| Job | Schedule | Description |
|---|---|---|
PremiumRenewalReminderJob |
Daily 09:00 | Flags policies expiring within 30 days |
StaleClaimAlertJob |
Daily 08:00 | Alerts on claims stuck in review > 7 days |
PolicyExpirationJob |
Daily 00:00 | Auto-expires policies past their end date |
MonthlyStatementJob |
1st of month 06:00 | Generates monthly policyholder statements |
- Serilog structured logging with correlation IDs, console sink
- OpenTelemetry tracing across HTTP, ASP.NET Core, and MassTransit
- Health checks at
/health/ready(database) and/health/live(app running)
| Category | Technology |
|---|---|
| Framework | .NET 10, ASP.NET Core Minimal APIs |
| Database | PostgreSQL 16 + pgvector extension |
| ORM | EF Core 10 (Npgsql) + Dapper for reporting |
| Messaging | RabbitMQ + MassTransit 8 |
| CQRS | MediatR with pipeline behaviors |
| Validation | FluentValidation |
| State Machine | Stateless |
| Background Jobs | Hangfire with PostgreSQL storage |
| Logging | Serilog |
| Tracing | OpenTelemetry with OTLP exporter |
| Testing | xUnit + Testcontainers |
| Connection Pooling | PgBouncer |
| Frontend | React 19 (AI-generated) |
| Containerization | Docker + Docker Compose |
| CI/CD | GitHub Actions |
- .NET 10 SDK
- Docker Desktop
- Node.js 20+ (only if running frontend locally)
git clone https://github.com/yourusername/ClaimFlow.git
cd ClaimFlow
docker compose up --build -d| Service | URL |
|---|---|
| API (Swagger) | http://localhost:5000/swagger |
| Frontend | http://localhost:3000 |
| RabbitMQ Dashboard | http://localhost:15672 (claimflow / claimflow_dev_2026) |
| Hangfire Dashboard | http://localhost:5000/hangfire |
# Start infrastructure only
docker compose up postgres rabbitmq pgbouncer -d
# Run the API
cd src/ClaimFlow.Api
dotnet run
# (Optional) Run the frontend
cd client
npm install
npm startAPI will be at https://localhost:5001/swagger, frontend at http://localhost:3000.
Run these requests in order via Swagger (/swagger) or Postman to test the full claims lifecycle:
POST /api/tenants{
"name": "Istanbul Regional Office",
"code": "IST-01"
}Save the returned id.
POST /api/customers{
"fullName": "Ahmet Yilmaz",
"email": "ahmet.yilmaz@email.com",
"tenantId": "<tenant-id>"
}Save the returned id.
POST /api/policies{
"policyNumber": "POL-2026-00001",
"customerId": "<customer-id>",
"productType": 1,
"startDate": "2026-01-01",
"endDate": "2027-01-01"
}Save the returned id. (productType: 0=Health, 1=Auto, 2=Home, 3=Travel)
POST /api/claims{
"policyId": "<policy-id>",
"description": "Rear-ended at traffic light on E5 highway. Bumper and tail lights damaged.",
"claimedAmount": 15000.00
}Save the returned id.
Each step is a PATCH /api/claims/<claim-id>/transition:
5a. Start Review
{ "trigger": 0, "changedBy": "system", "notes": "Auto-assigned to review queue" }5b. Request Documents
{ "trigger": 1, "changedBy": "adjuster-ali", "notes": "Need police report and damage photos" }5c. Documents Received (back to review)
{ "trigger": 0, "changedBy": "system", "notes": "Documents uploaded by policyholder" }5d. Approve
{ "trigger": 3, "changedBy": "adjuster-ali", "notes": "Damage confirmed. Approved 12000 TL." }5e. Schedule Payment
{ "trigger": 5, "changedBy": "finance-dept", "notes": "Payment scheduled for next week" }5f. Confirm Payment
{ "trigger": 6, "changedBy": "finance-dept", "notes": "Wire transfer completed" }5g. Close
{ "trigger": 7, "changedBy": "adjuster-ali", "notes": "Claim resolved successfully" }GET /api/claims/<claim-id>Response includes statusHistory array with 7 entries showing the complete lifecycle.
GET /api/claims/<claim-id>/fraud-checkReturns a risk score (0-100) with breakdown of risk factors.
Submit a new claim, then try to approve it directly (trigger 3) without starting review first. The API should return an error because the state machine blocks it.
ClaimFlow/
├── src/
│ ├── ClaimFlow.Api/ # Composition root, endpoints, middleware
│ │ ├── Extensions/ # Minimal API endpoint groups
│ │ ├── Middlewares/ # Exception handling
│ │ └── Program.cs # DI configuration
│ │
│ ├── ClaimFlow.Application/ # Use cases, no framework dependencies
│ │ ├── Behaviors/ # MediatR pipeline (validation)
│ │ ├── DTOs/ # Data transfer objects
│ │ ├── Features/ # CQRS commands & queries
│ │ │ ├── Claims/
│ │ │ ├── Customers/
│ │ │ ├── Policies/
│ │ │ └── Tenants/
│ │ ├── Interfaces/ # Abstractions (IAppDbContext, IFraudDetectionService)
│ │ └── Messages/ # MassTransit message contracts
│ │
│ ├── ClaimFlow.Domain/ # Pure domain logic
│ │ ├── Entities/ # Claim, Policy, Customer, Tenant, etc.
│ │ ├── Enums/ # ClaimStatus, ClaimTrigger, ProductType, etc.
│ │ ├── Events/ # Domain events (INotification)
│ │ └── StateMachines/ # ClaimStateMachine (Stateless)
│ │
│ └── ClaimFlow.Infrastructure/ # External concerns
│ ├── BackgroundServices/ # OutboxProcessor
│ ├── Consumers/ # MassTransit RabbitMQ consumers
│ ├── Data/
│ │ ├── Configurations/ # EF Core Fluent API configurations
│ │ └── Migrations/
│ ├── Jobs/ # Hangfire recurring jobs
│ └── Services/ # FraudDetection, Embedding, Reporting
│
├── tests/
│ └── ClaimFlow.Tests/ # Integration tests with Testcontainers
│
├── client/ # React frontend (AI-generated)
│ ├── src/
│ │ ├── components/
│ │ └── pages/
│ ├── Dockerfile
│ └── nginx.conf
│
├── docker-compose.yml
├── pgbouncer.ini
└── .github/workflows/ci.yml
# Docker must be running (Testcontainers spins up a real PostgreSQL)
dotnet testTests include:
- Tenant creation and unique constraint validation
- Full claim lifecycle through the state machine
- Invalid transition rejection
- Multi-tenancy data isolation
- Claim persistence with status history
This project was built for learning purposes.