Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ that pipeline:

---

## Screenshots

### Customer Portal — My Claims
> Policy holder view: submitted invoices with CHF reimbursement amounts and colour-coded status chips.

![My Claims](docs/claims_customer.png)

### Submit a New Claim
> PDF invoice upload form — the OCR + validation pipeline triggers automatically on submission.

![Submit Claim](docs/submit_new_claim_dialog.png)

### Backoffice — Review Queue (MANUAL\_REVIEW\_REQUIRED)
> Role-gated reviewer dashboard showing claims flagged by the rules engine for manual intervention.

![Review Queue](docs/review_queue_manual_review_required.png)

### Backoffice — Inline Approve / Reject
> Expanding a claim row reveals the review panel: flagged reason, reviewer notes, and decision buttons.

![Approve or Reject](docs/review_wueue_manual_review_aprove_or_reject.png)

---

## System Architecture

### Component Overview
Expand Down
1 change: 1 addition & 0 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ spring:
liquibase:
change-log: classpath:db/changelog/db.changelog-master.xml
enabled: true
contexts: ${spring.profiles.active:dev}

kafka:
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">

<!--
Dev-only seed data. Only runs when Liquibase context includes "dev".
policy_holder_id values are bound to the local Keycloak instance UUIDs
imported from keycloak/insurtech-realm.json.
customer → d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b
backoffice → 6a35eebc-9170-4d43-b468-37a7207ebab1
-->
<changeSet id="V005-1" author="insurtech-platform" context="dev">
<insert tableName="claims">
<column name="id" value="a0000001-0000-0000-0000-000000000001"/>
<column name="policy_holder_id" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="document_storage_key" value="a0000001-0000-0000-0000-000000000001/seed_tarmed-invoice-jan.pdf"/>
<column name="status" value="APPROVED"/>
<column name="total_amount" valueNumeric="148.60"/>
<column name="deductible" valueNumeric="0.00"/>
<column name="reimbursable_amount" valueNumeric="148.60"/>
<column name="submitted_at" valueComputed="'2026-05-10 09:15:00+00'::timestamptz"/>
<column name="created_at" valueComputed="'2026-05-10 09:15:00+00'::timestamptz"/>
<column name="updated_at" valueComputed="'2026-05-10 09:15:04+00'::timestamptz"/>
<column name="created_by" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="updated_by" value="system"/>
</insert>

<insert tableName="claims">
<column name="id" value="a0000001-0000-0000-0000-000000000002"/>
<column name="policy_holder_id" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="document_storage_key" value="a0000001-0000-0000-0000-000000000002/seed_tarmed-invoice-feb.pdf"/>
<column name="status" value="APPROVED"/>
<column name="total_amount" valueNumeric="320.50"/>
<column name="deductible" valueNumeric="0.00"/>
<column name="reimbursable_amount" valueNumeric="320.50"/>
<column name="submitted_at" valueComputed="'2026-05-22 14:30:00+00'::timestamptz"/>
<column name="created_at" valueComputed="'2026-05-22 14:30:00+00'::timestamptz"/>
<column name="updated_at" valueComputed="'2026-05-22 14:30:06+00'::timestamptz"/>
<column name="created_by" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="updated_by" value="system"/>
</insert>

<insert tableName="claims">
<column name="id" value="a0000001-0000-0000-0000-000000000003"/>
<column name="policy_holder_id" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="document_storage_key" value="a0000001-0000-0000-0000-000000000003/seed_tarmed-invoice-specialist.pdf"/>
<column name="status" value="MANUAL_REVIEW_REQUIRED"/>
<column name="reviewer_note" value="Invoice total CHF 475.80 exceeds remaining deductible of CHF 100.00. Manual review required."/>
<column name="total_amount" valueNumeric="475.80"/>
<column name="deductible" valueNumeric="100.00"/>
<column name="reimbursable_amount" valueNumeric="375.80"/>
<column name="submitted_at" valueComputed="'2026-05-28 11:00:00+00'::timestamptz"/>
<column name="created_at" valueComputed="'2026-05-28 11:00:00+00'::timestamptz"/>
<column name="updated_at" valueComputed="'2026-05-28 11:00:05+00'::timestamptz"/>
<column name="created_by" value="d22c39e7-d4ac-45ca-b7b7-e6fc5d23c81b"/>
<column name="updated_by" value="system"/>
</insert>

<insert tableName="claims">
<column name="id" value="a0000001-0000-0000-0000-000000000004"/>
<column name="policy_holder_id" value="6a35eebc-9170-4d43-b468-37a7207ebab1"/>
<column name="document_storage_key" value="a0000001-0000-0000-0000-000000000004/seed_tarmed-invoice-hospital.pdf"/>
<column name="status" value="MANUAL_REVIEW_REQUIRED"/>
<column name="reviewer_note" value="Invoice total CHF 892.00 exceeds remaining deductible of CHF 100.00. Manual review required."/>
<column name="total_amount" valueNumeric="892.00"/>
<column name="deductible" valueNumeric="100.00"/>
<column name="reimbursable_amount" valueNumeric="792.00"/>
<column name="submitted_at" valueComputed="'2026-05-30 16:45:00+00'::timestamptz"/>
<column name="created_at" valueComputed="'2026-05-30 16:45:00+00'::timestamptz"/>
<column name="updated_at" valueComputed="'2026-05-30 16:45:07+00'::timestamptz"/>
<column name="created_by" value="6a35eebc-9170-4d43-b468-37a7207ebab1"/>
<column name="updated_by" value="system"/>
</insert>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
<include file="db/changelog/changes/V002__create_tarmed_positions_table.xml"/>
<include file="db/changelog/changes/V003__create_event_publication_table.xml"/>
<include file="db/changelog/changes/V004__add_financials_to_claims.xml"/>
<include file="db/changelog/changes/V005__seed_demo_data.xml"/>

</databaseChangeLog>
3 changes: 3 additions & 0 deletions backend/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ spring:
jpa:
show-sql: true

liquibase:
contexts: test

kafka:
consumer:
auto-offset-reset: earliest
Expand Down
Binary file added docs/claims_backoffice_manual_review.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/claims_customer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review_queue_aproved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review_queue_manual_review_required.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review_queue_ocr_processing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review_queue_pending_ocr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/submit_new_claim_dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div class="page-header">
<h1>Review Queue</h1>
<mat-form-field appearance="outline" class="status-filter">
<mat-label>Filter by Status</mat-label>
<mat-select [(ngModel)]="selectedStatus" (ngModelChange)="loadClaims()">
@for (s of statuses; track s) {
<mat-option [value]="s">{{ formatStatus(s) }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-button-toggle-group
[ngModel]="selectedStatus"
(ngModelChange)="selectedStatus = $event; loadClaims()"
aria-label="Filter by status">
@for (s of statuses; track s) {
<mat-button-toggle [value]="s">{{ formatStatus(s) }}</mat-button-toggle>
}
</mat-button-toggle-group>
</div>

@if (loading()) {
Expand All @@ -19,7 +19,7 @@ <h1>Review Queue</h1>
} @else if (claims().length === 0) {
<p class="empty">No claims found for this status.</p>
} @else {
<mat-table [dataSource]="claims()">
<mat-table [dataSource]="claims()" multiTemplateDataRows>

<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef>Claim ID</mat-header-cell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
}
}

.status-filter {
min-width: 220px;
mat-button-toggle-group {
flex-shrink: 0;
}

.center {
Expand Down Expand Up @@ -46,10 +46,11 @@ mat-table {
}
}

/* Detail row — hidden by default, expands via height */
/* Detail row — hidden by default, expands via height.
min-height must be overridden because mat-row sets it to 48px. */
.detail-row {
height: 0;
overflow: hidden;
min-height: 0 !important;

&.detail-row--expanded {
height: auto;
Expand All @@ -59,6 +60,7 @@ mat-table {
.detail-cell {
padding: 0 !important;
border-bottom: none !important;
overflow: hidden;
}

/* Review panel inside the expanded row */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { DatePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatChipsModule } from '@angular/material/chips';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { ClaimsApiService } from '../../../core/api/claims-api.service';
import { StatusChip } from '../../../shared/status-chip/status-chip';
import { Claim, ClaimStatus, ReviewDecision } from '../../../core/api/models/claim.model';
Expand All @@ -19,12 +19,12 @@ import { Claim, ClaimStatus, ReviewDecision } from '../../../core/api/models/cla
StatusChip,
MatTableModule,
MatProgressSpinnerModule,
MatSelectModule,
MatFormFieldModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatChipsModule,
MatButtonToggleModule,
FormsModule,
DatePipe,
],
Expand Down
29 changes: 24 additions & 5 deletions keycloak/insurtech-realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,33 @@
},
"clientScopes": [
{
"id": "f4bc76ca-377a-4519-bf25-6797f07a8596",
"name": "backoffice",
"description": "Grants access to backoffice-restricted API endpoints",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "false"
}
},
"protocolMappers": [
{
"id": "b4cac0e4-62c9-43ca-b1d6-fd7ffeccdaea",
"name": "realm-roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"multivalued": "true",
"userinfo.token.claim": "false",
"id.token.claim": "true",
"lightweight.claim": "false",
"access.token.claim": "false",
"claim.name": "realm_access.roles",
"jsonType.label": "String"
}
}
]
}
],
"clients": [
Expand Down Expand Up @@ -58,11 +78,10 @@
"openid",
"profile",
"email",
"roles"
],
"optionalClientScopes": [
"roles",
"backoffice"
]
],
"optionalClientScopes": []
}
],
"users": [
Expand Down
Loading