Insurance platform · .NET · React
Apólice
Insurance end to end, from quote to claim payment: auditable premium calculation, events that are never lost and a documented API, in .NET 10 and React.
- Client
- Personal open-source project
- Role
- Software Engineer — independent project
- Year
- 2026
- Technologies
- C#
- .NET 10
- ASP.NET Core
- EF Core
- React
- TypeScript
- PostgreSQL
- Aurora DSQL
- RabbitMQ
- Amazon SQS
- AWS Lambda
- Terraform
- xUnit
- Testcontainers
- Playwright
- GitHub Actions































Context
Open-source insurance platform, built in October 2026, that covers the life cycle of an Auto, Home and Life policy: quote, issuance, endorsement, cancellation and claim. Broker, claims analyst and admin get resource-based authorization, and a broker only sees their own book. All data is fictitious, with a demo sign-in by persona. The project takes the insurance domain I worked with on the front-end of i4Pro's ERP, where I consumed .NET APIs, to a C# and .NET back-end, and was run with Spec-Driven Development: a versioned specification as the source of truth, a design guide, 8 ADRs and an independent review of each layer before merging.
Challenge
Insurance is a domain where every cent has to add up: the premium depends on per-product factors, an endorsement charges or refunds pro rata, cancellation has three refund rules and a claim goes through review, approval and payment with a record of who did what. The challenge was to deliver this whole cycle as a real production system: business rules that are testable and isolated from infrastructure, events that are never lost and never duplicate effects, protection against access to another broker's book, observability and automated deployment on AWS at close to zero cost.
Results
- passing .NET tests: 548 domain, 398 application, 32 architecture and 165 integration tests with real PostgreSQL and RabbitMQ
- 1,143
- line coverage in the domain, and about 99.9% in the application layer
- ~99%
- front-end tests with Vitest, Testing Library and axe, plus a Playwright E2E test of the main flow against production
- 168
- production cost on AWS, within the free tier, with a budget and an alert in place
- ~US$ 0
Key deliverables
- 01Architected the API in C# and .NET 10 with Clean Architecture (Domain, Application, Infrastructure, Api and Worker), tactical DDD with aggregates, value objects and domain events, and the Strategy pattern for each product's pricing, with 32 architecture tests (ArchUnitNET) covering the dependencies between layers and the conventions for handlers and aggregates.
- 02Developed 32 versioned REST operations under /api/v1 with ASP.NET Core Minimal APIs, OpenAPI 3.1 with Scalar and ProblemDetails errors (RFC 9457), using EF Core 10 for writes, under optimistic concurrency, and Dapper for reads, with keyset pagination (CQRS-lite).
- 03Implemented a transactional outbox with idempotent consumers: RabbitMQ locally and Amazon SQS with a dead-letter queue in production, where a scheduled relay republishes whatever is still pending.
- 04Secured the API following the OWASP API Top 10: 15-minute JWTs with a rotating refresh token in an HttpOnly cookie and reuse detection, another broker's resource answering 404 (anti-BOLA), rate limiting, Idempotency-Key, security headers and an audit trail of sensitive actions.
- 05Built the front-end in React 19 and strict TypeScript, with TanStack Query, React Hook Form + Zod, Tailwind CSS 4 and Radix UI, mobile first, with light and dark themes and a WCAG 2.1 AA accessibility target checked with axe in the tests.
- 06Automated delivery on AWS with Terraform and GitHub Actions through OIDC (.NET 10 arm64 Lambda, Aurora DSQL with IAM authentication, SQS, EventBridge and secrets in SSM), with 1,143 .NET tests, 165 of them integration tests with Testcontainers, and a Playwright E2E test of the main flow passing against production.
Screens and components
Persona sign-in
In the demo sign-in, four personas get in without a password: two brokers, a claims analyst and an admin, each with its own scope. Fictitious data.

Portfolio overview
The dashboard shows the book within the signed-in user's scope: active policies, premium written over the last 12 months, conversion, loss ratio and the split by product. Fictitious data.

Quote with live pricing
At the coverage step of the 5-step wizard, the premium is recalculated on every change, and the calculation breakdown shows every coverage, every factor and the IOF tax. Fictitious data.

The policy as a certificate
The policy is shown as a certificate, with coverage period, premium, installments, coverages, a timeline and the PDF, claim, endorsement and cancellation actions. Fictitious data.

Simulated cancellation
Before confirming, the cancellation simulates the refund under the rule of the reason: the short-rate table when the insured asks, pro rata otherwise. Fictitious data.

Claim handling
From the analyst's view, a claim under review brings together the automatic triage, the history and the approve or deny actions, here in dark theme. Fictitious data.

On mobile
On mobile, the dashboard becomes a two-column grid, the calculation breakdown opens in a bottom sheet, the policy keeps its certificate face and the claim history fits on one screen. Fictitious data.
Design system foundations
Primitive colors become semantic tokens through light-dark(), and the same token serves both themes. The serif marks policy numbers and amounts, while spacing, radii, motion and buttons by variant and state round out the foundation.

Domain components
The domain components, in React and strict TypeScript, turn insurance into interface: the policy as a certificate, the premium calculation with every factor, the statuses, the claim history, the installments and the KPIs. Fictitious data.

States and accessibility
The screens handle empty, error with a correlation ID, loading and missing permission, and the same token changes value in dark theme. Contrast follows WCAG 2.1 AA, and axe runs in the tests. Fictitious data.

Documented API
The API documents its 32 operations in OpenAPI 3.1 with Scalar, including the endorsement and cancellation simulations and the Idempotency-Key header on issuance.

Architecture
The board follows the request from 1 to 10, from the SPA on Vercel to the .NET Lambda and Aurora DSQL, and the event through the outbox to SQS, the worker and, on failure, the DLQ with an email alarm. Delivery runs from A to G through OIDC and Terraform, and the notes carry the decisions from the ADRs.

Features and the pains they solve
Every feature was born from a real operational problem.
01
Quote with live pricing
- Pain
- The broker builds the quote without knowing what each choice costs, and a premium nobody can explain makes the insured suspicious.
- Solution
- The 5-step wizard recalculates the premium on every change, with a simulation that saves nothing, and shows the calculation breakdown: each coverage's premium, the factors applied (driver age, region by postal code, commercial use, bonus class, property type, smoker), IOF tax and the maximum number of installments. Each product has its own pricing strategy.
Impact: Auto, Home and Life; a saved quote is valid for 30 days
02
Issuance without duplicates
- Pain
- Issuing from an expired quote, or twice because of a repeated click, creates a duplicate policy and a wrong charge.
- Solution
- A policy can only come from an accepted, valid quote, with coverage starting within 30 days, a number in the AUTO-2026-000042 format and up to 10 installments. Issuance accepts an Idempotency-Key: repeating the key returns the original response, and the policy PDF is generated with QuestPDF.
03
Pro rata endorsement
- Pain
- Changing the limit or adding a coverage mid-term means charging or refunding only for the remaining days, math that usually ends up in a spreadsheet.
- Solution
- The system simulates before confirming and charges or refunds the difference pro rata for the remaining days. Every aggregate has a version, and a concurrent change becomes a 409 instead of overwriting the other one.
04
Cancellation with simulation
- Pain
- Each cancellation reason has its own refund rule, and applying the wrong one refunds too much or too little.
- Solution
- Before confirming, the dialog shows the method, days elapsed, percentage retained and the amount to refund: in full before coverage starts, by the short-rate table when the insured asks and pro rata per day for the other reasons.
Impact: The IOF tax is not refunded, and the screen says so before confirmation
05
Claims with triage and handling
- Pain
- Without triage, a late notice with an amount close to the limit gets the same treatment as a simple case; without a history, nobody knows who approved what.
- Solution
- Every new claim gets an automatic, asynchronous triage, with a 0–100 score and alerts (recent coverage start, amount near or above the limit, late notice). The analyst starts the review, approves up to the lower of the claimed amount and the limit, denies with a reason or records the payment, and every transition goes into the history with author and date.
06
Book isolated by role
- Pain
- In a system with several brokers, changing the id in the URL must never open a policy from another book (BOLA, the top risk in the OWASP API Top 10).
- Solution
- Authorization is resource-based: a broker only sees their own book, and someone else's resource answers 404, as if it did not exist. The analyst sees everything but cannot operate quotes or policies; the admin changes catalog rates and checks the audit trail.
07
Secure session
- Pain
- A long-lived token stored in the browser can be stolen through XSS and cannot be revoked.
- Solution
- A 15-minute JWT access token kept only in memory and a 7-day rotating refresh token in an HttpOnly, SameSite=Strict cookie, stored in the database only as a hash. Reusing an already rotated token revokes the whole family, and sign-in locks after 5 wrong passwords, even with parallel attempts.
08
Events that are never lost
- Pain
- Writing to the database and publishing to the queue in separate steps loses the event when either one fails, and redelivery duplicates emails or triage.
- Solution
- The event becomes an outbox row in the same SaveChanges as the aggregate; the API publishes right after commit and a scheduled relay covers failures. The consumer records the message in the same transaction as its effect, so a redelivery repeats nothing.
Impact: RabbitMQ locally; SQS with a DLQ and an alarm in production
09
Portfolio dashboard
- Pain
- To know how much they wrote, how well they convert and where the loss ratio stands, brokers have to stitch reports together.
- Solution
- The dashboard shows active policies, premium written this month and over the last 12 months, quote conversion, open claims, loss ratio and the book by product, within the signed-in user's scope: their own book or the whole portfolio. Reads use Dapper with parameterized SQL, separate from the EF Core writes.
10
API with a drift-free contract
- Pain
- Integrating with an API without an up-to-date contract is trial and error, and the front-end breaks silently when the back-end changes.
- Solution
- The 32 operations under /api/v1 are documented in OpenAPI 3.1 with Scalar, with ProblemDetails errors (RFC 9457). CI regenerates the contract and fails if the committed one is out of date, and the front-end's TypeScript types are generated from the same OpenAPI.
11
Serverless, observable production
- Pain
- An always-on server for a demo is expensive, and without correlated logs and traces there is no way to follow a request into the worker.
- Solution
- The API runs on AWS Lambda (.NET 10, arm64) behind a Vercel rewrite, on the same origin as the front-end, with Aurora DSQL authenticated through IAM, SQS and EventBridge, all in Terraform with OIDC deploys. Serilog writes JSON logs with a correlation id and no personal data, and OpenTelemetry carries the request's trace into the worker.
Impact: Production cost close to US$ 0 (free tier)























