# Pet Insurance — Code Challenge A Java coding challenge implementing the calculation logic for a 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.