110 lines
4.2 KiB
Markdown
110 lines
4.2 KiB
Markdown
# Pet Insurance — Code Challenge
|
||
|
||
A Java coding challenge implementing a data driven calculation logic for a highly extensible pet health insurance product.
|
||
|
||
## Build & test
|
||
|
||
Requires **Java 25** and **Maven 3.x**.
|
||
|
||
```bash
|
||
# Compile
|
||
mvn compile
|
||
|
||
# Run all tests
|
||
mvn test
|
||
|
||
# Full build
|
||
mvn package
|
||
```
|
||
|
||
## Project structure
|
||
|
||
```
|
||
src/
|
||
├── main/
|
||
│ ├── java/com/pfefferminzia/petinsurance/
|
||
│ │ ├── animals/ # Animal sealed interface + Dog, Cat, SmallAnimal records
|
||
│ │ ├── calculations/ # InsuranceContract, Calculations, CalculationResult
|
||
│ │ ├── exception/ # InvalidInsuranceContractException
|
||
│ │ └── tariffs/ # TariffOption, TariffConfiguration, TariffLoader
|
||
│ └── resources/
|
||
│ └── tariff.properties # Runtime tariff configuration
|
||
└── test/
|
||
├── java/.../calculations/
|
||
│ └── CalculationsTest.java
|
||
└── resources/
|
||
└── tariff.properties # Test tariff configuration (mirrors main)
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Animals
|
||
|
||
`Animal` is a sealed interface (`permits Dog, Cat, SmallAnimal`). Each type is an immutable record:
|
||
|
||
- `Dog(LocalDate birthday, String breed)` — identifies with species key `"DOG"`
|
||
- `Cat(LocalDate birthday)` — species key `"CAT"`
|
||
- `SmallAnimal(LocalDate birthday)` — species key `"SMALLANIMAL"`
|
||
|
||
The interface exposes `birthday()` and `speciesKey()`. Base insurance sums are **not** hardcoded in the animal types; they live in configuration (see below).
|
||
|
||
### Tariff configuration (`tariff.properties`)
|
||
|
||
All tariff data is loaded from `tariff.properties` at startup via `TariffLoader.loadFromFile()`, which produces a `TariffConfiguration` record. This makes adding new tariffs, animals, age brackets, or breed adjustments a config-only change.
|
||
|
||
```properties
|
||
# Animal base insurance sums
|
||
baseSum.DOG=2500.00
|
||
baseSum.CAT=2000.00
|
||
baseSum.SMALLANIMAL=1000.00
|
||
|
||
# Tariffs: factor,availableFromDate
|
||
tariffs.KOMPAKT=1.0,1970-01-01
|
||
tariffs.OPTIMAL=1.2,1970-01-01
|
||
tariffs.PREMIUM=1.4,2026-07-01
|
||
|
||
# Age-based premium factors (≤ age → factor; elderlyFactor covers > 7)
|
||
age.2=0.20
|
||
age.5=0.25
|
||
age.7=0.28
|
||
elderlyFactor=0.30
|
||
|
||
# Dog breed surcharge (positive) / discount (negative)
|
||
breed.DACKEL=-0.1
|
||
breed.BERNHARDINER=0.1
|
||
```
|
||
|
||
`TariffConfiguration.getTariffFactor(tariffName, startDate)` throws `IllegalStateException` if the chosen tariff is not yet available on the given start date (e.g. PREMIUM before 2026-07-01).
|
||
|
||
### Calculation flow
|
||
|
||
1. Build an `InsuranceContract(animal, insuranceStartDate, tariffName)`. The record constructor validates all fields eagerly and throws `InvalidInsuranceContractException` collecting all violations.
|
||
2. Call `Calculations.calculateInsuranceSumAndYearlyPremium(contract)`, which returns a `CalculationResult(insuranceSum, yearlyPremium)`.
|
||
|
||
**Insurance sum** = `baseSum(animal) × tariffFactor`
|
||
|
||
**Yearly premium** = `insuranceSum × ageFactor × (1 + breedFactor)`
|
||
|
||
- `ageFactor` is looked up from the age bracket table based on exact years between `birthday` and `insuranceStartDate`.
|
||
- `breedFactor` only applies to `Dog`; all other animals use 0 (no adjustment).
|
||
|
||
### Worked examples
|
||
|
||
| Animal | Insurance sum | Yearly premium |
|
||
|-----------------------------|--------------|----------------|
|
||
| Husky, 7y, PREMIUM | 3500 | 980 |
|
||
| Dackel, 7y, PREMIUM | 3500 | 882 |
|
||
| Bernhardiner, 7y, PREMIUM | 3500 | 1078 |
|
||
| Cat, 3y, OPTIMAL | 2400 | 600 |
|
||
| SmallAnimal, 1y, KOMPAKT | 1000 | 200 |
|
||
|
||
All five examples are covered by `CalculationsTest`.
|
||
|
||
## Extensibility
|
||
|
||
- **New animal type** — add a record implementing `Animal`, register its base sum in `tariff.properties`.
|
||
- **New tariff** — add one line to `tariff.properties` with a factor and availability date.
|
||
- **New breed adjustment** — add one line to the `breed.*` section.
|
||
- **New age bracket** — add one `age.N` entry; `TariffConfiguration.getAgeFactor` uses a `NavigableMap` ceiling lookup so ordering is automatic.
|
||
- **New calculation** — extend `Calculations` or add a new method; `CalculationResult` can be widened to carry additional fields.
|