# Title Calculators — Build Requirements

Five public-facing estimate calculators for a Texas title company website.

**Audience:** the developers building them. No title-insurance background assumed — §2 explains
every term used, and every formula is given with worked examples.

**Framework:** your choice. Nothing here depends on a particular stack. The only hard constraints
are that results recalculate live as the user types, that anything stored in the database use the same database as the WordPress site, ~~that the rate tables and fee amounts are
editable by a site administrator without a code deploy,~~ and that each calculator can export its
current on-screen state to PDF.

- [Title Calculators — Build Requirements](#title-calculators--build-requirements)
	- [1. The five calculators](#1-the-five-calculators)
	- [2. Domain primer](#2-domain-primer)
		- [2.1 What is being sold](#21-what-is-being-sold)
		- [2.2 Basic Premium](#22-basic-premium)
		- [2.3 Endorsements](#23-endorsements)
		- [2.4 Statutory charges](#24-statutory-charges)
		- [2.5 Company service fees](#25-company-service-fees)
		- [2.6 Refinance credit (the "R-8 credit")](#26-refinance-credit-the-r-8-credit)
	- [3. The rate engine](#3-the-rate-engine)
		- [3.1 `basicPremium(coverageAmount) → dollars`](#31-basicpremiumcoverageamount--dollars)
		- [3.2 `refinanceCredit(existingLoanAgeInYears) → percentage`](#32-refinancecreditexistingloanageinyears--percentage)
		- [3.3 Rate schedule versioning](#33-rate-schedule-versioning)
	- [4. Endorsement pricing rules](#4-endorsement-pricing-rules)
		- [4.1 Percentage endorsements on the owner's policy](#41-percentage-endorsements-on-the-owners-policy)
		- [4.2 Percentage endorsements on the loan policy](#42-percentage-endorsements-on-the-loan-policy)
		- [4.3 Flat endorsements](#43-flat-endorsements)
	- [5. Admin-configurable values](#5-admin-configurable-values)
		- [5.1 State-promulgated rates](#51-state-promulgated-rates)
		- [5.2 Endorsement rates and minimums](#52-endorsement-rates-and-minimums)
		- [5.3 Company service fees](#53-company-service-fees)
		- [5.4 Editable display text](#54-editable-display-text)
		- [5.5 Admin UI requirements](#55-admin-ui-requirements)
	- [6. Calculator specifications](#6-calculator-specifications)
		- [6.1 Calculator 1 — Residential Buyer's Estimated Title Fees](#61-calculator-1--residential-buyers-estimated-title-fees)
			- [User inputs](#user-inputs)
			- [Table A — Residential Owner's Policy Premium](#table-a--residential-owners-policy-premium)
			- [Table B — Lender's Title Policy Premium](#table-b--lenders-title-policy-premium)
			- [Reactive totals](#reactive-totals)
		- [6.2 Calculator 2 — Residential Estimated Refinance Fees](#62-calculator-2--residential-estimated-refinance-fees)
			- [User inputs](#user-inputs-1)
			- [Table A — Estimated Premium](#table-a--estimated-premium)
			- [Table B — Lender's Title Policy Premium](#table-b--lenders-title-policy-premium-1)
			- [Reactive totals](#reactive-totals-1)
		- [6.3 Calculator 3 — Residential Estimated Seller's Net](#63-calculator-3--residential-estimated-sellers-net)
			- [Section 1 — sale details (user inputs)](#section-1--sale-details-user-inputs)
			- [Section 2 — closing cost lines](#section-2--closing-cost-lines)
			- [Totals](#totals)
			- [Reactive totals](#reactive-totals-2)
		- [6.4 Calculator 4 — Commercial Estimated Title Fees](#64-calculator-4--commercial-estimated-title-fees)
			- [User inputs](#user-inputs-2)
			- [Table A — Commercial Owner's Title Policy Premium](#table-a--commercial-owners-title-policy-premium)
			- [Table B — Lender's Title Policy Premium](#table-b--lenders-title-policy-premium-2)
			- [Reactive totals](#reactive-totals-3)
		- [6.5 Calculator 5 — Commercial Estimated Refinance Fees](#65-calculator-5--commercial-estimated-refinance-fees)
			- [User inputs](#user-inputs-3)
			- [Table A — Premium](#table-a--premium)
			- [Table B — Lender's Title Policy Premium](#table-b--lenders-title-policy-premium-3)
			- [Reactive totals](#reactive-totals-4)
	- [7. PDF export](#7-pdf-export)
		- [7.1 Requirements](#71-requirements)
		- [7.2 Approach](#72-approach)
	- [8. Shared behaviour across all five](#8-shared-behaviour-across-all-five)
		- [8.1 Live recalculation](#81-live-recalculation)
		- [8.2 Input handling](#82-input-handling)
		- [8.3 Layout](#83-layout)
		- [8.4 Accessibility](#84-accessibility)
		- [8.5 Rate engine as shared code](#85-rate-engine-as-shared-code)
		- [8.6 Disclaimer](#86-disclaimer)
	- [9. Before build starts](#9-before-build-starts)
	- [10. Acceptance criteria](#10-acceptance-criteria)

---

## 1. The five calculators

| # | Calculator | Audience | Answers the question |
|---|---|---|---|
| 1 | Residential Buyer's Estimated Title Fees | Homebuyer | "What will title cost me at closing?" |
| 2 | Residential Estimated Refinance Fees | Homeowner refinancing | "What will title cost to refinance?" |
| 3 | Residential Estimated Seller's Net | Home seller | "After everything comes out, what do I walk away with?" |
| 4 | Commercial Estimated Title Fees | Commercial buyer | Same as #1, commercial rates |
| 5 | Commercial Estimated Refinance Fees | Commercial borrower | Same as #2, commercial rates |

All five share the same rate engine (§3) and the same shared behaviours (§8). They differ in which
inputs they collect, which fee lines they display, and which endorsement percentages apply.

Every calculator produces an **estimate**. None of them are quotes, and the UI must say so (§8.6).
- The client will provide disclaimer messaging. As long as they can add the disclaimer to the page via the block editor it is not required to be part of the calculator component.

---

## 2. Domain primer

Read this once. Everything after it uses these terms precisely.

### 2.1 What is being sold

**Title insurance** protects against defects in the ownership history of a property — an unpaid
lien, a forged signature, an unknown heir. It is bought once, at closing, for a one-time premium.

There are two separate policies, and a transaction can involve one or both:

- **Owner's Policy** — protects the buyer. Its coverage amount is the **sales price**.
- **Loan Policy** (also called the Lender's Policy) — protects the mortgage lender. Its coverage
  amount is the **loan amount**.

A cash purchase has an owner's policy only. A refinance has a loan policy only — there is no sale,
so no new owner's policy.

### 2.2 Basic Premium

The **basic premium** is the base price of a policy. In Texas it is set by the state, not by the
title company, so every company charges the same amount for the same coverage. It is a pure
function of the coverage amount: `basicPremium(coverageAmount)`. The full table and formula are
in §3.1.

You will call this function on three different inputs depending on context:

- `basicPremium(salesPrice)` → the owner's policy basic premium
- `basicPremium(loanAmount)` → the loan policy basic premium
- `basicPremium(newLoanAmount)` → the refinance premium

**Important:** on a purchase where the buyer takes out a mortgage, both policies are issued at the
same time, so the lender's policy is discounted to a flat **simultaneous issue** fee (currently
$100) instead of its own basic premium. But several endorsements are still priced as a percentage
of the **full basic premium for the loan amount**, not of the $100. Compute
`basicPremium(loanAmount)` even though you display $100. See §4.2 for exactly where each is used.

### 2.3 Endorsements

An **endorsement** is an optional add-on to a policy covering a specific extra risk. Each has a
form number assigned by the state (T-17, T-19, T-42, and so on). Two pricing shapes:

- **Flat fee** — a fixed dollar amount (e.g. T-17 is $25). Admin-editable.
- **Percentage** — a percentage of the relevant basic premium, with a dollar minimum. Example:
  the residential T-19.1 is 10% of the owner's basic premium, but never less than $50. Both the
  percentage and the minimum are admin-editable.

Endorsements used in these calculators:

| Form | Name | Pricing |
|---|---|---|
| Survey Amendment (T-1R) | Removes the survey exception from the owner's policy | Percentage of owner's basic premium, min $20 |
| T-19.1 | Restrictions / Encroachments / Minerals — **owner's** policy | Percentage of owner's basic premium, min $50 |
| T-19 | Restrictions / Encroachments / Minerals — **loan** policy | Percentage of loan basic premium, min $50 |
| T-17 | Planned Unit Development | Flat |
| T-23 | Access | Flat |
| T-25 | Contiguity | Flat |
| T-28 | Condominium | Flat |
| T-33 | Adjustable Rate Mortgage | Flat |
| T-36 | Environmental Protection Lien | Flat |
| T-39 | Balloon Mortgage | Flat |
| T-42 | Home Equity | Percentage of new loan premium, no minimum |
| T-42.1 | Home Equity Supplemental | Percentage of new loan premium, no minimum |

The Survey Amendment and T-19.1 are **priced as a pair**: when a customer buys both, the T-19.1
percentage drops, because the two overlap in what they cover. So the T-19.1 rate depends on
whether the Survey Amendment was also selected. §4.1 gives the exact rule.

### 2.4 Statutory charges

Small fixed charges required on every policy, set by the state:

- **Policy Guaranty Fee** — funds the state's title insurance guaranty association. Charged **per
  policy**. A transaction with both an owner's policy and a loan policy pays it twice.
- **Tax Deletion** — removes the standard tax exception from the loan policy.
- **Not Yet Due and Payable** — a related tax-exception charge on the loan policy.

### 2.5 Company service fees

==Charges set by the title company itself, not the state==. These change more often than state rates
and must be admin-editable: settlement/closing fee, escrow fee, tax certificate fee, document
preparation, attorney fee, recording and e-recording fees.

- These are the only fees that need to be editable by the site admin without a code update.

### 2.6 Refinance credit (the "R-8 credit")

When a homeowner refinances, they already bought a loan policy on the original mortgage. The state
requires a discount on the new loan policy, scaled to how recently the previous policy was issued.
The discount is a percentage of the **basic premium on the existing loan balance**, and the
percentage steps down with the age of the existing loan. Table in §3.2.

---

## 3. The rate engine

One shared module. All five calculators call into it. Everything in this section is
state-promulgated and must be admin-editable as a versioned, effective-dated rate schedule (§5.1).

### 3.1 `basicPremium(coverageAmount) → dollars`

Two regimes: a stepped lookup table up to $100,000, a tiered formula above it.

**Source of truth.** Everything in this section is transcribed from the Texas Department of
Insurance schedule "Texas Title Insurance Premium Rates, Rates Effective March 1, 2026" (TDI
document 2025-9697), <https://tdi.texas.gov/title/documents/titlerates2026.pdf>. Do not
re-derive these values from any other source.

**Rule 0 — arithmetic precision.** Do not use binary floating point for the tiered calculation.
Every rate in Rule 3 is exactly *n*/100,000, so the whole computation can be done in integers, and
must be. See Rule 3 for the required expression. Floating point produces off-by-one-dollar errors
at face amounts that land on an exact half-dollar; there are 162 such amounts in the
$50,000,001-$100,000,000 tier alone.

**Rule 0.1 — input precision.** Round the coverage amount to a whole dollar before doing anything
else. The state schedule is denominated in whole dollars. A sales price of $25,000.50 is a
$25,001 policy, not a $25,000 one, and must not be allowed to select a different step by accident.

- **Note: there is no need to allow user inputs that aren't whole dollars. These caculators are for estimates, not precise accounting.**

**Rule 1 — zero or blank.** If the coverage amount is missing, blank, zero or negative, return
`0`. (A cash purchase has no loan amount; the loan policy section is simply absent.) This is a
product convention, not a state rule: the TDI schedule does not address amounts below $25,000.

**Rule 2 — coverage of $100,000 or less.** Walk the step table and return the premium on the
**first row whose "Coverage up to" is greater than or equal to the coverage amount.** This matches
the state table's own column header, "Policy Face Amount Up to and Including".

Informally: round the coverage amount up to the next $500 step, leaving amounts already sitting on
a step unchanged. The normative rule is the one in bold above; prefer it if the two ever seem to
disagree.

The table is **151 rows in $500 increments**. An earlier version of this document carried a
16-row table in $5,000 increments, which was a coarse sample of the real schedule and overstated
the premium at roughly 91% of coverage amounts in this range, by up to $29. Use the table below.

- **Note: this table can be hard coded. It would be more tedious to manually input this via fields than to deploy a change via theme update.** 

| Coverage up to | Premium | | Coverage up to | Premium | | Coverage up to | Premium | | Coverage up to | Premium |
|---:|---:|---|---:|---:|---|---:|---:|---|---:|---:|
| $25,000 | $308 | | $44,000 | $428 | | $63,000 | $547 | | $82,000 | $667 |
| $25,500 | $310 | | $44,500 | $431 | | $63,500 | $551 | | $82,500 | $672 |
| $26,000 | $314 | | $45,000 | $434 | | $64,000 | $554 | | $83,000 | $675 |
| $26,500 | $317 | | $45,500 | $437 | | $64,500 | $557 | | $83,500 | $677 |
| $27,000 | $319 | | $46,000 | $440 | | $65,000 | $560 | | $84,000 | $680 |
| $27,500 | $322 | | $46,500 | $444 | | $65,500 | $563 | | $84,500 | $684 |
| $28,000 | $325 | | $47,000 | $446 | | $66,000 | $567 | | $85,000 | $687 |
| $28,500 | $328 | | $47,500 | $448 | | $66,500 | $571 | | $85,500 | $689 |
| $29,000 | $333 | | $48,000 | $453 | | $67,000 | $574 | | $86,000 | $692 |
| $29,500 | $336 | | $48,500 | $457 | | $67,500 | $575 | | $86,500 | $697 |
| $30,000 | $339 | | $49,000 | $460 | | $68,000 | $579 | | $87,000 | $701 |
| $30,500 | $341 | | $49,500 | $462 | | $68,500 | $582 | | $87,500 | $703 |
| $31,000 | $345 | | $50,000 | $465 | | $69,000 | $585 | | $88,000 | $705 |
| $31,500 | $348 | | $50,500 | $468 | | $69,500 | $588 | | $88,500 | $709 |
| $32,000 | $351 | | $51,000 | $470 | | $70,000 | $592 | | $89,000 | $713 |
| $32,500 | $355 | | $51,500 | $474 | | $70,500 | $596 | | $89,500 | $715 |
| $33,000 | $357 | | $52,000 | $478 | | $71,000 | $599 | | $90,000 | $718 |
| $33,500 | $361 | | $52,500 | $482 | | $71,500 | $601 | | $90,500 | $721 |
| $34,000 | $364 | | $53,000 | $484 | | $72,000 | $604 | | $91,000 | $725 |
| $34,500 | $368 | | $53,500 | $488 | | $72,500 | $608 | | $91,500 | $729 |
| $35,000 | $371 | | $54,000 | $491 | | $73,000 | $611 | | $92,000 | $731 |
| $35,500 | $373 | | $54,500 | $493 | | $73,500 | $613 | | $92,500 | $734 |
| $36,000 | $376 | | $55,000 | $496 | | $74,000 | $617 | | $93,000 | $737 |
| $36,500 | $380 | | $55,500 | $499 | | $74,500 | $621 | | $93,500 | $741 |
| $37,000 | $383 | | $56,000 | $504 | | $75,000 | $625 | | $94,000 | $742 |
| $37,500 | $386 | | $56,500 | $507 | | $75,500 | $627 | | $94,500 | $747 |
| $38,000 | $390 | | $57,000 | $509 | | $76,000 | $629 | | $95,000 | $751 |
| $38,500 | $393 | | $57,500 | $513 | | $76,500 | $632 | | $95,500 | $754 |
| $39,000 | $395 | | $58,000 | $517 | | $77,000 | $636 | | $96,000 | $755 |
| $39,500 | $399 | | $58,500 | $519 | | $77,500 | $639 | | $96,500 | $759 |
| $40,000 | $401 | | $59,000 | $522 | | $78,000 | $643 | | $97,000 | $763 |
| $40,500 | $406 | | $59,500 | $525 | | $78,500 | $646 | | $97,500 | $766 |
| $41,000 | $408 | | $60,000 | $529 | | $79,000 | $650 | | $98,000 | $769 |
| $41,500 | $412 | | $60,500 | $533 | | $79,500 | $651 | | $98,500 | $773 |
| $42,000 | $415 | | $61,000 | $536 | | $80,000 | $655 | | $99,000 | $776 |
| $42,500 | $418 | | $61,500 | $537 | | $80,500 | $658 | | $99,500 | $779 |
| $43,000 | $420 | | $62,000 | $541 | | $81,000 | $662 | | $100,000 | $780 |
| $43,500 | $424 | | $62,500 | $545 | | $81,500 | $664 | |  |  |

$25,000 is the lowest row in the state schedule. There are no rows beneath it, which is normally
read as $25,000 being the minimum policy amount, so any positive amount below it takes the $308
rate. **Confirm this with the client before build** (§9).

Worked examples:

```
$18,000  -> first row >= 18,000  is  $25,000  -> $308
$25,000  -> first row >= 25,000  is  $25,000  -> $308
$25,001  -> first row >= 25,001  is  $25,500  -> $310
$26,000  -> first row >= 26,000  is  $26,000  -> $314
$27,300  -> first row >= 27,300  is  $27,500  -> $322
$92,000  -> first row >= 92,000  is  $92,000  -> $731
$99,600  -> first row >= 99,600  is  $100,000 -> $780
$100,000 -> first row >= 100,000 is  $100,000 -> $780
```

**Rule 3 — coverage above $100,000.** The state prescribes four steps. Implement them in this
order and in these words:

```
Step 1  In column (1), find the range that includes the policy's face value.
Step 2  Subtract the value in column (2) from the policy's face value.
Step 3  Multiply the result in Step 2 by the value in column (3), and round to the
        nearest dollar.
Step 4  Add the value in column (4) to the result of the value from Step 3.
```

- **Note: There is no guarantee the the state won't completely change the process for calculating this premium. Therefore it is safe to hard code this process and table since we can't assume future state policy changes won't require a theme update anyway.**  

| (1) Policy range | (2) Subtract | (3) Multiply by | (4) Add | Equivalent |
|---|---:|---:|---:|---|
| $100,001 – $1,000,000 | $100,000 | 0.00494 | $780 | $4.94 per $1,000 |
| $1,000,001 – $5,000,000 | $1,000,000 | 0.00406 | $5,226 | $4.06 per $1,000 |
| $5,000,001 – $15,000,000 | $5,000,000 | 0.00335 | $21,466 | $3.35 per $1,000 |
| $15,000,001 – $25,000,000 | $15,000,000 | 0.00238 | $54,966 | $2.38 per $1,000 |
| $25,000,001 – $50,000,000 | $25,000,000 | 0.00143 | $78,766 | $1.43 per $1,000 |
| $50,000,001 – $100,000,000 | $50,000,000 | 0.00129 | $114,516 | $1.29 per $1,000 |
| over $100,000,000 | $100,000,000 | 0.00116 | $179,016 | $1.16 per $1,000 |

**Rounding at Step 3 is half-up.** An exact half-dollar rounds away from zero, so $864.50 becomes
$865. A $275,000 policy lands on exactly this tie. Note that JavaScript's `Math.round` is half-up
and correct here, but Python's `round()`, .NET's `Math.Round`, and any banker's-rounding default
are half-to-even and will return a dollar less. Do not rely on a language default; make the tie
direction explicit.

**Required expression.** Let `d = faceAmount - subtractValue` and `n = multiplyBy x 100,000` (so
`n` is 494, 406, 335, 238, 143, 129 or 116). Then:

```
premium = addValue + (d * n + 50000) / 100000        // integer division, exact
```

This is the state's four steps carried out without floating point. Storing the rate as a scaled
integer `n` rather than as `0.00494` is the point of the exercise; if the admin UI accepts a
decimal rate, convert it to `n` once on load and keep it that way.

Worked examples. These seven are the state's own, reproduced verbatim from the schedule, and are
the conformance suite for this function (§8.5):

| Policy face amount | Step 2 | Step 3 | Step 4 | Premium |
|---:|---:|---:|---:|---:|
| $268,500 | $168,500 | x 0.00494 = $832 | + $780 | $1,612 |
| $4,826,600 | $3,826,600 | x 0.00406 = $15,536 | + $5,226 | $20,762 |
| $10,902,800 | $5,902,800 | x 0.00335 = $19,774 | + $21,466 | $41,240 |
| $17,295,100 | $2,295,100 | x 0.00238 = $5,462 | + $54,966 | $60,428 |
| $39,351,800 | $14,351,800 | x 0.00143 = $20,523 | + $78,766 | $99,289 |
| $75,300,200 | $25,300,200 | x 0.00129 = $32,637 | + $114,516 | $147,153 |
| $151,250,300 | $51,250,300 | x 0.00116 = $59,450 | + $179,016 | $238,466 |

Further examples at amounts used elsewhere in this document:

```
$250,000    -> 780    + round((250,000    -   100,000) x 0.00494) = $1,521
$275,000    -> 780    + round((275,000    -   100,000) x 0.00494) = $1,645   // exact .5 tie
$300,000    -> 780    + round((300,000    -   100,000) x 0.00494) = $1,768
$360,000    -> 780    + round((360,000    -   100,000) x 0.00494) = $2,064
$450,000    -> 780    + round((450,000    -   100,000) x 0.00494) = $2,509
$2,500,000  -> 5,226  + round((2,500,000  - 1,000,000) x 0.00406) = $11,316
$8,000,000  -> 21,466 + round((8,000,000  - 5,000,000) x 0.00335) = $31,516
$61,250,000 -> 114,516 + round((61,250,000 - 50,000,000) x 0.00129) = $129,029  // exact .5 tie
```

**Implementation requirement — store column (4), and validate it.** The state publishes column (4)
explicitly. Store it as given; do not compute it and discard the published figure. Each tier's
published base does happen to equal the running total of every tier below it:

```
base[0] = 780                                                   // the $100,000 step premium
base[n] = base[n-1] + (floor[n] - floor[n-1]) x rate[n-1]
```

Check: `780 + (1,000,000 - 100,000) x 0.00494 = 5,226`. ✓
`5,226 + (5,000,000 - 1,000,000) x 0.00406 = 21,466`. ✓

Run that derivation **as a validation on save** and warn the administrator when a stored base does
not match, since a mismatch usually means a rate or a base was mistyped and the premium curve now
jumps at a tier boundary. But warn only. Never silently substitute the derived value for the
published one: these figures are promulgated by the state, and if a future schedule ever publishes
a base that is not the exact running total, the promulgated number is the one that governs.

**Implementation requirement — data structure.** Hold the step table as an array sorted ascending
by "coverage up to" and select with a linear scan or binary search. Do not iterate object keys and
do not rely on key ordering. At 151 rows this matters more than it did at 16: the previous
implementation used object-key iteration and returned the $100,000 premium of $780 for every
amount between $90,001 and $95,000.

### 3.2 `refinanceCredit(existingLoanAgeInYears) → percentage`

| Age of the existing loan | Credit |
|---|---:|
| 4 years or less | 50% |
| over 4 years, up to 8 years | 25% |
| over 8 years | 0% |

Age is the elapsed time between the **Original Date of Existing Loan** entered by the user and
today, measured in years. Use a calendar-aware date difference (a proper date library), not a
fixed-length-year approximation.

### 3.3 Rate schedule versioning

The tables in §3.1 and §3.2 are the schedule **effective March 1, 2026**. The admin interface must
store them as a dated schedule so a future rate change can be entered and take effect on its date
without a deploy. Display the effective date somewhere on each calculator and include it in the
PDF export.

---

## 4. Endorsement pricing rules

### 4.1 Percentage endorsements on the owner's policy

Let `B = basicPremium(salesPrice)` — the owner's basic premium. **`B` is always computed from the
full sales price**, regardless of which party ends up paying for any given line. Who pays affects
only which calculator *displays* the charge (§6.1, §6.3), never the amount.

**Survey Amendment to T-1R**

```
if surveyAmendment == "yes":
    amount = max( B x surveyAmendmentRate , surveyAmendmentMinimum )
else:
    amount = 0
```

**T-19.1 (owner's)** — the rate depends on whether the Survey Amendment was also selected:

```
if t19_1 == "yes":
    rate   = (surveyAmendment == "yes") ? t19_1RateWithSurvey : t19_1RateAlone
    amount = max( B x rate , t19_1Minimum )
else:
    amount = 0
```

Current rates:

| | Residential | Commercial |
|---|---:|---:|
| Survey Amendment rate | 5% | 15% |
| Survey Amendment minimum | $20 | $20 |
| T-19.1 rate, Survey Amendment also selected | 5% | 10% |
| T-19.1 rate, T-19.1 alone | 10% | 15% |
| T-19.1 minimum | $50 | $50 |

Worked example — residential, sales price $450,000, both endorsements selected:

```
B = basicPremium(450,000) = $2,509
Survey Amendment = max(2,509 x 0.05, 20) = $125.45
T-19.1           = max(2,509 x 0.05, 50) = $125.45     // "with survey" rate, 5%
```

Same property, T-19.1 selected but Survey Amendment not:

```
Survey Amendment = $0
T-19.1           = max(2,509 x 0.10, 50) = $250.90     // "alone" rate, 10%
```

### 4.2 Percentage endorsements on the loan policy

**T-19 (loan)** is a percentage of the **basic premium on the loan amount** — not of the $100
simultaneous-issue fee, and not of the owner's premium.

```
L      = basicPremium(loanAmount)          // or basicPremium(newLoanAmount) on a refinance
amount = t19Selected ? max( L x t19Rate , t19Minimum ) : 0
```

| | Residential | Commercial |
|---|---:|---:|
| T-19 rate | 5% | 10% |
| T-19 minimum | $50 | $50 |

Worked example — residential purchase, loan amount $360,000, T-19 selected:

```
L      = basicPremium(360,000) = $2,064
T-19   = max(2,064 x 0.05, 50) = $103.20
```

Note the loan policy line itself still displays the flat $100 simultaneous-issue fee. The $2,064
is used only as the basis for this endorsement.

**T-42 and T-42.1 (residential refinance only)** are percentages of the new loan premium with **no
minimum**:

```
N       = basicPremium(newLoanAmount)
T-42    = homeEquity == "yes" ? N x t42Rate   : 0        // t42Rate    = 10%
T-42.1  = homeEquity == "yes" ? N x t42_1Rate : 0        // t42_1Rate  = 15%
```

### 4.3 Flat endorsements

Each is `selected ? adminValue : 0`. Current values:

| Endorsement | Amount |
|---|---:|
| T-17 (PUD) | $25.00 |
| T-23 (Access) | $100.00 |
| T-25 (Contiguity) | $100.00 |
| T-28 (Condo) | $0.00 |
| T-33 (ARM) | $20.00 |
| T-36 (EPA) | $25.00 |
| T-39 (Balloon) | $25.00 |

T-36 behaves differently by calculator: on the residential calculators it applies **automatically**
whenever a loan policy is present (no checkbox); ~~on the commercial calculators it is a
**user-selectable checkbox**. Both behaviours are intentional.~~
**correction 9-1-2026:** the T-36 does not apply to commercial real estate. Commercial real estate has a T-36.1
but we're waiting for the client to confirm if this should be added and if there is a fee. From what
we've seen in our research the T-36.1 is likely $0.00 and other calculators we've reviewed do not include it.

---

## 5. Admin-configurable values

~~Everything in this section must be editable by a site administrator through the CMS, with no code
change and no deploy. Nothing in §3 or §4 may be hardcoded.~~

Group the admin UI as below — the grouping matters, because the two groups change for different
reasons and are approved by different people.

### 5.1 State-promulgated rates

~~Changes when the state reissues its rate schedule. Store as an effective-dated version so a future
schedule can be staged in advance.~~

~~| Setting | Shape | Current |
|---|---|---|
| Basic premium step table | Repeating rows: coverage-up-to, premium | **151 rows** in $500 increments, $25,000/$308 → $100,000/$780 (§3.1) |
| Basic premium tiers | Repeating rows: tier floor, rate per dollar, **published base** | 7 rows, $100,000/0.00494/$780 → $100,000,000/0.00116/$179,016 (§3.1) |
| Refinance credit schedule | Repeating rows: max age in years, credit % | 3 rows: ≤4/50%, ≤8/25%, above/0% |
| Policy Guaranty Fee | Currency | $2.00 |
| Tax Deletion | Currency | $20.00 |
| Not Yet Due and Payable | Currency | $5.00 |
| Simultaneous issue — loan policy | Currency | $100.00 |
| Schedule effective date | Date | March 1, 2026 |~~

~~Rate tables must be **repeating/addable rows**, not a fixed number of fields. A future schedule may
have a different number of steps or tiers.~~

~~Because the step table is 151 rows, the admin UI needs a bulk path: CSV or spreadsheet paste-in,
not 151 hand-typed fields. A rate change replaces the whole table at once. Hand-editing individual
rows is the exception, not the workflow.~~

~~Store the tier rate as the scaled integer `n` described in §3.1 (494, 406, and so on), or convert
to it on load. The premium calculation must not run on a binary float.~~

### 5.2 Endorsement rates and minimums

~~| Setting | Residential | Commercial |
|---|---:|---:|
| Survey Amendment rate | 5% | 15% |
| Survey Amendment minimum | $20.00 | $20.00 |
| T-19.1 rate — with Survey Amendment | 5% | 10% |
| T-19.1 rate — alone | 10% | 15% |
| T-19.1 minimum | $50.00 | $50.00 |
| T-19 rate (loan policy) | 5% | 10% |
| T-19 minimum | $50.00 | $50.00 |
| T-42 rate (Home Equity) | 10% | n/a |
| T-42.1 rate (Home Equity Supplemental) | 15% | n/a |
| T-17 / T-23 / T-25 / T-28 / T-33 / T-36 / T-39 | $25 / $100 / $100 / $0 / $20 / $25 / $25 | same |~~

### 5.3 Company service fees

Set by the company, change more often than state rates.

| Setting | Current | Used by |
|---|---:|---|
| Settlement/Closing Fee | $600.00 | 1, 2, 3 |
| Escrow Fee | $600.00 | 4, 5 |
| Tax Certificate Fee | $72.15 | 2, 3 |
| Document Preparation of One Transfer of Lien | $75.00 | 2 |
| Attorney Fee (Deed/Release) | $225.00 | 3 |
| Recording Fees (Per Release) | $38.00 | 3 |
| eRecording Fee — transaction with a loan | $6.40 | 1, 2 |
| eRecording Fee — cash transaction | $3.20 | 1 |
| Estimated Recording Fees — transaction with a loan | $160.00 | 1, 2 |
| Estimated Recording Fees — cash transaction | $38.00 | 1 |
| Estimated Recording Fees — commercial | $0.00 | 4, 5 |

### 5.4 Editable display text

- If the disclaimers can be added to the page via the block editor, they don't need to be built as fields in the component. We can create a synced pattern for the text to keep it consistent and easy to update intuitively in place.

~~| Setting | Current |
|---|---|
| Recording fee footnote, residential | "Per document charge is $26 for the first page, $4 for each additional page" |
| Recording fee footnote, commercial | "The recording fee for the first page is $25.00 and Simplefile's fee is $5.06 per document." |
| Estimate disclaimer (§8.6) | see §8.6 |~~

### 5.5 Admin UI requirements

- **Typed fields with defaults.** Currency fields accept currency, percentage fields accept
  percentages. Every field has a default; a cleared field falls back to its default rather than
  being treated as zero or empty (zero can be set as the default if that's the logical choice).
- **Validation on save.** Reject negative values and non-numeric input. ~~Warn if a step table's
  premiums are not ascending, or if tier floors are not ascending.~~
- ~~**Preview before publish.** Show the resulting premium at a handful of sample coverage amounts
  so an administrator can sanity-check an edited rate table before it goes live. Include the seven
  state examples from §3.1 in the preview, and flag it loudly if any of them no longer produces the
  published figure.~~
- ~~**Change log.** Record who changed what and when. These values drive numbers shown to consumers.~~

---

## 6. Calculator specifications

Notation: every "Result" row is computed and read-only; every "Input" row is user-entered. `→` means
"is displayed as". All currency displays as `$1,234.56`.

### 6.1 Calculator 1 — Residential Buyer's Estimated Title Fees

#### User inputs

| Field | Type | Required | Notes |
|---|---|---|---|
| Property | Text | No | Label only, appears in the PDF |
| Sales Price | Currency | Yes | |
| Loan Amount | Currency | Yes | Enter 0 or leave blank for a cash purchase |
| Who Pays for the Owner's Policy? | Select: Buyer / Seller | Yes | |
| Survey Amendment to T-1R Policy | Select: Yes / No | Yes | |
| — Who Pays? | Select: Buyer / Seller | Conditional | **Shown only when Survey Amendment = Yes** |
| T-19.1 (Rest/Encro/Minerals) | Select: Yes / No | Yes | |
| — Who Pays? | Select: Buyer / Seller | Conditional | **Shown only when T-19.1 = Yes** |
| T-17 (PUD Endorsement) | Checkbox | No | In the lender's table |
| T-19 (Rest/Encro/Mineral) | Checkbox | No | In the lender's table |
| T-28 (Condo) | Checkbox | No | In the lender's table |
| T-33 (ARM) | Checkbox | No | In the lender's table |
| T-39 (Balloon) | Checkbox | No | In the lender's table |

The two conditional "Who Pays?" selects appear inline beneath their parent question. When the
parent flips back to No, clear the child's value.

#### Table A — Residential Owner's Policy Premium

This calculator is the **buyer's** sheet: it shows only what the buyer pays. A line assigned to the
seller displays as `$0.00`.

```
B = basicPremium(salesPrice)                    // always the full sales price

Basic Policy         = (whoPaysOwnersPolicy == "Buyer") ? B : 0
Policy Guaranty Fee  = (whoPaysOwnersPolicy == "Buyer") ? policyGuarantyFee : 0

Survey Amendment to T-1R
                     = (surveyAmendment == "Yes" AND whoPaysSurveyAmendment == "Buyer")
                       ? max(B x surveyAmendmentRate, surveyAmendmentMinimum)     // §4.1
                       : 0

T-19.1               = (t19_1 == "Yes" AND whoPaysT19_1 == "Buyer")
                       ? max(B x t19_1Rate, t19_1Minimum)                          // §4.1
                       : 0

TOTAL OWNER'S POLICY PREMIUM = Basic Policy + Policy Guaranty Fee
                             + Survey Amendment + T-19.1
```

Note that `B` is computed from the sales price whether the buyer or the seller pays for the policy.
Only the *display* is gated. This matters when the seller pays for the owner's policy but the buyer
pays for an endorsement — the endorsement is still a percentage of the full premium.

#### Table B — Lender's Title Policy Premium

Only relevant when there is a loan. Define:

```
hasLoan = loanAmount is present AND loanAmount > 0
L       = basicPremium(loanAmount)
```

When `hasLoan` is false, every policy line in this table is `$0.00`, and the two recording fees
switch to their cash-transaction values.

```
Simultaneous w/ Owner's Policy = hasLoan ? simultaneousIssueFee : 0     // flat $100
Policy Guaranty Fee            = hasLoan ? policyGuarantyFee     : 0     // second one, see §2.4
Tax Deletion                   = hasLoan ? taxDeletion           : 0
Not Yet Due and Payable        = hasLoan ? notYetDuePayable      : 0
T-36 (EPA)                     = hasLoan ? t36Fee                : 0     // automatic, no checkbox

T-17 (PUD Endorsement) = t17Checked ? t17Fee : 0
T-19 (Rest/Encro/Mineral)
                       = t19Checked ? max(L x t19Rate, t19Minimum) : 0    // §4.2
T-28 (Condo)           = t28Checked ? t28Fee : 0
T-33 (ARM)             = t33Checked ? t33Fee : 0
T-39 (Balloon)         = t39Checked ? t39Fee : 0

TOTAL LOAN POLICY PREMIUM = Simultaneous + Policy Guaranty Fee + Tax Deletion
                          + Not Yet Due and Payable
                          + T-17 + T-19 + T-28 + T-33 + T-36 + T-39

Settlement/Closing Fee = settlementClosingFee                  // always, loan or cash
eRecording Fee         = hasLoan ? eRecordingFeeLoan : eRecordingFeeCash

TOTAL LENDER'S TITLE INSURANCE & TITLE SERVICES FEE
                       = TOTAL LOAN POLICY PREMIUM
                       + Settlement/Closing Fee + eRecording Fee

Estimated Recording Fees = hasLoan ? estRecordingLoan : estRecordingCash
```

**Estimated Recording Fees is informational and is excluded from both totals.** Display it below
the final total with the residential footnote from §5.4.

The Policy Guaranty Fee appears on both tables when the buyer pays for the owner's policy. That is
correct — it is charged once per policy, and there are two policies.

#### Reactive totals

Recalculate on every input change: Table A's four line items and its total; Table B's every line
item and both totals; Estimated Recording Fees.

---

### 6.2 Calculator 2 — Residential Estimated Refinance Fees

#### User inputs

| Field | Type | Required | Notes |
|---|---|---|---|
| Borrower | Text | No | Label only, appears in the PDF |
| New Loan Amount | Currency | Yes | |
| Balance of Existing Loan | Currency | Yes | |
| Original Date of Existing Loan | Date | Yes | Defaults to today |
| Home Equity | Select: Yes / No | Yes | Drives T-42 and T-42.1 |
| T-17, T-19, T-28, T-33, T-39 | Checkboxes | No | In the lender's table |

#### Table A — Estimated Premium

```
New Loan Premium      = basicPremium(newLoanAmount)
Existing Loan Premium = basicPremium(balanceExistingLoan)

loanAge      = calendar years between originalDateOfExistingLoan and today
% Credit     = refinanceCredit(loanAge)              // §3.2 — displayed as a percentage
Resulting Credit = Existing Loan Premium x % Credit

ADJUSTED PREMIUM = max( New Loan Premium - Resulting Credit , 0 )
                   floored at minimumBasicPremium when New Loan Premium > 0
```

That is: subtract the credit from the new premium; never go below zero, and when there *is* a new
loan, never go below the minimum basic premium (the first row of the step table, currently $308).
If the new loan amount is zero or blank, the adjusted premium is `$0.00`.

Worked example — new loan $300,000, existing balance $250,000, existing loan 2 years old:

```
New Loan Premium      = basicPremium(300,000) = $1,768
Existing Loan Premium = basicPremium(250,000) = $1,521
% Credit              = 50%          (2 years is within the 4-year band)
Resulting Credit      = 1,521 x 0.50 = $760.50
ADJUSTED PREMIUM      = 1,768 - 760.50 = $1,007.50
```

#### Table B — Lender's Title Policy Premium

```
N = basicPremium(newLoanAmount)

Adjusted Premium        = (from Table A)
Policy Guaranty Fee     = policyGuarantyFee          // always
Tax Deletion            = taxDeletion                // always
Not Yet Due and Payable = notYetDuePayable           // always
T-36 (EPA)              = t36Fee                     // always, automatic

T-17   = t17Checked ? t17Fee : 0
T-19   = t19Checked ? max(N x t19Rate, t19Minimum) : 0
T-28   = t28Checked ? t28Fee : 0
T-33   = t33Checked ? t33Fee : 0
T-39   = t39Checked ? t39Fee : 0

T-42   (Home Equity)              = homeEquity == "Yes" ? N x t42Rate   : 0    // 10%, no minimum
T-42.1 (Home Equity Supplemental) = homeEquity == "Yes" ? N x t42_1Rate : 0    // 15%, no minimum

TOTAL LENDER POLICY PREMIUM = Adjusted Premium + Policy Guaranty Fee + Tax Deletion
                            + Not Yet Due and Payable
                            + T-17 + T-19 + T-28 + T-33 + T-36 + T-39 + T-42 + T-42.1

Tax Certificate Fee                          = taxCertificateFee
Settlement/Closing Fee                       = settlementClosingFee
Document Preparation of One Transfer of Lien = documentPreparationTransferLien
eRecording Fee                               = eRecordingFeeLoan

TOTAL LENDER'S TITLE INSURANCE & TITLE SERVICES FEE
    = TOTAL LENDER POLICY PREMIUM + Tax Certificate Fee + Settlement/Closing Fee
    + Document Preparation of One Transfer of Lien + eRecording Fee

Estimated Recording Fees = estRecordingLoan          // informational, excluded from totals
```

#### Reactive totals

Recalculate on every input change: all five rows of Table A; every line item of Table B and both
totals. Changing the date changes % Credit, which changes Resulting Credit → Adjusted Premium →
both Table B totals.

---

### 6.3 Calculator 3 — Residential Estimated Seller's Net

The largest of the five. The user enters the sale details at the top, then a long list of
deductions, and the calculator subtracts everything from the sales price.

#### Section 1 — sale details (user inputs)

| Field | Type | Required | Notes |
|---|---|---|---|
| Customer | Text | No | Label only, appears in the PDF |
| Property Address | Text | No | Label only, appears in the PDF |
| Date of Closing | Date | Yes | Defaults to today; drives tax proration |
| Sales Price | Currency | Yes | |
| Who Pays for the Owner's Policy? | Select: Buyer / Seller | Yes | |
| Survey Amendment to T-1R Policy | Select: Yes / No | Yes | |
| — Who Pays? | Select: Buyer / Seller | Conditional | Shown only when Survey Amendment = Yes |
| T-19.1 (Rest/Encro/Minerals) | Select: Yes / No | Yes | |
| — Who Pays? | Select: Buyer / Seller | Conditional | Shown only when T-19.1 = Yes |

#### Section 2 — closing cost lines

Mixed: some are entered by the user, some are computed.

**Payoffs — all user inputs, currency:** Payoff – First Mortgage, Payoff – Second Mortgage,
Payoff – Other.

**Commission**

| Row | Kind | Formula |
|---|---|---|
| % Commission | Input, percentage | user-entered |
| Commission | Result | `salesPrice x percentCommission / 100` |

**Title Company Fees** — this is the **seller's** sheet, so it is the mirror of Calculator 1: a
line assigned to the buyer displays as `$0.00`.

```
B = basicPremium(salesPrice)                    // always the full sales price

Owner's Policy Premium = (whoPaysOwnersPolicy == "Seller") ? B : 0
Policy Guaranty Fee    = (whoPaysOwnersPolicy == "Seller") ? policyGuarantyFee : 0

Survey Amendment to T-1R
    = (surveyAmendment == "Yes" AND whoPaysSurveyAmendment == "Seller")
      ? max(B x surveyAmendmentRate, surveyAmendmentMinimum) : 0

T-19.1
    = (t19_1 == "Yes" AND whoPaysT19_1 == "Seller")
      ? max(B x t19_1Rate, t19_1Minimum) : 0

Attorney Fee (Deed/Release)   = attorneyFee              // always
Recording Fees (Per Release)  = recordingFees            // always
Tax Certificate Fee           = taxCertificateFee        // always
Settlement/Closing Fee        = settlementClosingFee     // always
```

As in Calculator 1, `B` is the full basic premium regardless of who pays. Gate the display, not the
calculation — the endorsement percentages must not collapse to their minimums when the buyer pays
for the policy.

**Tax Prorations** — the seller owes property tax for the portion of the year they owned the home.

| Row | Kind | Formula |
|---|---|---|
| Annual Tax Amount | Input, currency | user-entered |
| Number of Days to Prorate | Result, integer | days from January 1 of the closing year through the closing date, inclusive. January 1 = 1; February 1 = 32. |
| Prorate Total | Result, currency | `(annualTaxAmount / 365) x numberOfDaysToProrate` |

Use 365 as the divisor in all years.

**Other — all user inputs, currency:** Unpaid HOA Dues, HOA Transfer Fee, Resale Certificate Fee,
Home Warranty Fee, Survey, Seller Paid Borrower Closing Cost, Option Money Credit, Other, Other.

The two "Other" rows are independent free-entry lines. Both "Seller Paid Borrower Closing Cost" and
"Option Money Credit" are amounts that come *out* of the seller's proceeds, so both are added to
the closing-cost total like any other line.

#### Totals

```
ESTIMATED CLOSING COST =
      Payoff First Mortgage + Payoff Second Mortgage + Payoff Other
    + Commission
    + Owner's Policy Premium + Policy Guaranty Fee
    + Survey Amendment + T-19.1
    + Attorney Fee + Recording Fees + Tax Certificate Fee + Settlement/Closing Fee
    + Prorate Total
    + Unpaid HOA Dues + HOA Transfer Fee + Resale Certificate Fee + Home Warranty Fee
    + Survey + Seller Paid Borrower Closing Cost + Option Money Credit
    + Other + Other

ESTIMATED NET TO SELLER = Sales Price - ESTIMATED CLOSING COST
```

Treat every blank currency input as `0`.

#### Reactive totals

Both totals, plus Commission, all four Title Company Fee result rows, Number of Days to Prorate and
Prorate Total. Every one of the ~17 currency inputs feeds Estimated Closing Cost and therefore
Estimated Net to Seller — both must update on every keystroke.

Estimated Net to Seller can legitimately go negative (payoffs exceeding the sales price). Display
negative results clearly rather than clamping to zero.

---

### 6.4 Calculator 4 — Commercial Estimated Title Fees

Same shape as Calculator 1, with commercial endorsement rates, no "Who Pays?" questions, an escrow
fee instead of a settlement fee, and two extra endorsements (T-23, T-25) available on both policies.

#### User inputs

| Field | Type | Required | Notes |
|---|---|---|---|
| Property | Text | No | Label only, appears in the PDF |
| Sales Price | Currency | Yes | |
| Loan Amount | Currency | Yes | |
| Survey Amendment | Select: Yes / No | Yes | |
| T-19.1 (Rest/Encro/Minerals) | Select: Yes / No | Yes | |
| T-23 Access Endorsement | Checkbox | No | Owner's table |
| T-25 Contiguity Endorsement | Checkbox | No | Owner's table |
| T-19, T-23, T-25, T-33, T-39 | Checkboxes | No | Lender's table |

There is no "Who Pays?" branching on the commercial calculators. Every applicable line is shown.

- **Pending question for client:** Texas has a T-36.1 EPA endorsement for commercial title insurance. The T-36 is only for residential. We're waiting confirm if the T-36.1 should be added as an option, and if there is a fee associated with it.

#### Table A — Commercial Owner's Title Policy Premium

```
B = basicPremium(salesPrice)

Basic Policy        = B
Policy Guaranty Fee = policyGuarantyFee

Survey Amendment = (surveyAmendment == "Yes")
                   ? max(B x comSurveyAmendmentRate, surveyAmendmentMinimum) : 0    // 15%, min $20

T-19.1           = (t19_1 == "Yes")
                   ? max(B x comT19_1Rate, t19_1Minimum) : 0                        // 10% or 15%, min $50
                   // 10% when Survey Amendment = Yes, 15% when T-19.1 alone

T-23 Access Endorsement      = t23Checked ? t23Fee : 0
T-25 Contiguity Endorsement  = t25Checked ? t25Fee : 0
Escrow Fee                   = escrowFee

TOTAL OWNER'S POLICY = Basic Policy + Policy Guaranty Fee + Survey Amendment
                     + T-19.1 + T-23 + T-25

TOTAL OWNER'S POLICY PREMIUM & ESCROW FEE = TOTAL OWNER'S POLICY + Escrow Fee
```

Display order: the line items, then Escrow Fee, then Total Owner's Policy, then Total Owner's
Policy Premium & Escrow Fee.

#### Table B — Lender's Title Policy Premium

```
L = basicPremium(loanAmount)

Simultaneous w/ Owner's Title Policy = simultaneousIssueFee     // flat $100
Policy Guaranty Fee                  = policyGuarantyFee
Tax Deletion                         = taxDeletion
Not Yet Due and Payable              = notYetDuePayable

T-19 (Rest/Encro/Mineral) = t19Checked ? max(L x comT19Rate, t19Minimum) : 0    // 10%, min $50
T-23 (Access)             = t23LenderChecked ? t23Fee : 0
T-25 (Contiguity)         = t25LenderChecked ? t25Fee : 0
T-33 (ARM)                = t33Checked ? t33Fee : 0
T-39 (Balloon)            = t39Checked ? t39Fee : 0

TOTAL LENDER POLICY PREMIUM = Simultaneous + Policy Guaranty Fee + Tax Deletion
                            + Not Yet Due and Payable
                            + T-19 + T-23 + T-25 + T-33 + T-39

Escrow Fee = escrowFee

TOTAL LENDER POLICY & ESCROW FEE = TOTAL LENDER POLICY PREMIUM + Escrow Fee

Estimated Recording Fees = estRecordingCommercial    // informational, excluded from totals
```

Display the commercial recording footnote from §5.4.

#### Reactive totals

Every line item and all five totals across both tables.

---

### 6.5 Calculator 5 — Commercial Estimated Refinance Fees

Calculator 2's structure with commercial rates. No Home Equity question, so no T-42 or T-42.1;
T-23, T-25 and T-28 are available; an escrow fee replaces the residential service fees.

#### User inputs

| Field | Type | Required | Notes |
|---|---|---|---|
| Borrower | Text | No | Label only, appears in the PDF |
| New Loan Amount | Currency | Yes | |
| Balance of Existing Loan | Currency | Yes | |
| Original Date of Existing Loan | Date | Yes | Defaults to today |
| T-19, T-23, T-25, T-28, T-33, T-39 | Checkboxes | No | Lender's table |

#### Table A — Premium

Identical to Calculator 2's Table A:

```
New Loan Premium      = basicPremium(newLoanAmount)
Existing Loan Premium = basicPremium(balanceExistingLoan)
% Credit              = refinanceCredit(loanAge)
Resulting Credit      = Existing Loan Premium x % Credit
ADJUSTED PREMIUM      = max( New Loan Premium - Resulting Credit , 0 )
                        floored at minimumBasicPremium when New Loan Premium > 0
```

#### Table B — Lender's Title Policy Premium

```
N = basicPremium(newLoanAmount)

Adjusted Premium        = (from Table A)
Policy Guaranty Fee     = policyGuarantyFee          // always
Tax Deletion            = taxDeletion                // always
Not Yet Due and Payable = notYetDuePayable           // always

T-19 = t19Checked ? max(N x comT19Rate, t19Minimum) : 0        // 10%, min $50
T-23 = t23Checked ? t23Fee : 0
T-25 = t25Checked ? t25Fee : 0
T-28 = t28Checked ? t28Fee : 0
T-33 = t33Checked ? t33Fee : 0
T-39 = t39Checked ? t39Fee : 0

TOTAL LENDER POLICY PREMIUM = Adjusted Premium + Policy Guaranty Fee + Tax Deletion
                            + Not Yet Due and Payable
                            + T-19 + T-23 + T-25 + T-28 + T-33 + T-39

Escrow Fee = escrowFee

TOTAL LENDER POLICY & ESCROW FEE = TOTAL LENDER POLICY PREMIUM + Escrow Fee

Estimated Recording Fees = estRecordingCommercial    // informational, excluded from totals
```

#### Reactive totals

All five rows of Table A; every line item of Table B and both totals.

---

## 7. PDF export

**Every calculator needs a clearly labelled button — "Save as PDF" — that exports the calculator in
its current state.**

### 7.1 Requirements

- **Captures live state.** Whatever the user has entered and whatever the calculator currently
  shows is what lands in the PDF: all entered values, all selected checkboxes and dropdowns, every
  computed line item and total.
- **Reads as a document, not a screenshot.** Selectable text, not a rasterised image. Laid out for
  US Letter portrait, sized to be legible printed.
- **Includes a header** with the company logo, the calculator's name, and the date generated.
- **Includes the identifying field** the user typed — Property, Borrower, or Customer / Property
  Address depending on the calculator — so a saved PDF is identifiable later.
- **Includes the rate schedule effective date** (§3.3).
- **Includes the disclaimer** from §8.6, verbatim, on every page.
- **Filename** follows a predictable pattern:
  `{calculator-name}-{property-or-borrower-or-customer}-{YYYY-MM-DD}.pdf`, with the identifying
  field slugified and omitted when blank.
- **Empty and zero rows are retained**, not suppressed. A `$0.00` line is meaningful — it tells the
  reader that item was considered and is not being charged.
- **Works on mobile browsers**, iOS Safari included.

### 7.2 Approach

Client-side generation is fine and avoids a server round-trip. A print stylesheet driving the
browser's own print-to-PDF is also acceptable if it meets the requirements above — but it must be
triggered by the in-page button, not left to the user to find in a browser menu, and the printed
output must not include site navigation, footer, or unrelated page chrome.

Do not submit calculator values to a third-party service to render the PDF. Users enter loan
balances and sales prices; that data should not leave the browser.

---

## 8. Shared behaviour across all five

### 8.1 Live recalculation

Results update **as the user types**, not on blur and not behind a Calculate button. Every result
row and every total in §6 is derived state: recompute from the current inputs on each change.

There is no submit step. None of these forms post anywhere.

### 8.2 Input handling

- **Currency inputs** display thousands separators and two decimals when not focused
  (`1,250,000.00`); switch to a plain editable value on focus. Accept typed commas and strip them.
  Treat blank as zero everywhere in the arithmetic.
- **Percentage inputs** (Calculator 3's % Commission) accept a plain number: `6` means 6%.
- **Date inputs** default to today and use a native date picker.
- **Required fields** are marked with a visible indicator. Because there is no submit step,
  required-ness is advisory: show the marker, and let results compute from whatever is filled in.
- **Rounding.** `basicPremium` returns whole dollars. Every other intermediate value keeps full
  precision through the arithmetic and is rounded to two decimals only for display. Never sum
  already-rounded display values.

### 8.3 Layout

- Calculators 1, 3 and 4: single column, form above results.
- Calculators 2 and 5: form and premium summary side by side on desktop, stacked on mobile, with
  the lender's table full-width beneath.
- Result tables use striped rows, with total rows visually distinct from line items.
- Fully responsive. Tables must remain readable on a phone — the seller's net sheet in particular
  is long and has a label/value/input structure that needs care at narrow widths.

### 8.4 Accessibility

- Every input has a programmatically associated label.
- Results update in a way screen readers announce — an appropriate live region on the totals, not
  on every keystroke of every line.
- Keyboard-operable throughout, including the Save as PDF button.
- Meets WCAG 2.1 AA for contrast and focus visibility.

### 8.5 Rate engine as shared code

Implement `basicPremium()` and `refinanceCredit()` once, in one module, consumed by all five
calculators. They must read from the admin-configured values at runtime.

Unit-test the rate engine against the worked examples in §3.1, §3.2 and §4, plus:

- **The seven state examples** in §3.1. These are TDI's own published figures and are the
  conformance suite. If any one fails, the engine is wrong, whatever else passes.
- **Regime boundaries:** $0, $1, $24,999, $25,000, $25,001, $100,000, $100,001.
- **Step boundaries:** every $500 row of the table, plus row-1 and row+1 for each, to prove the
  "up to and including" comparison is `>=` and not `>`. $25,001 must return $310, not $339;
  $92,000 must return $731, not $751; nothing between $90,001 and $95,000 may return $780.
- **Tier boundaries:** each tier floor, floor+1, and the top of each range.
- **Rounding ties:** $275,000 must return $1,645 and $61,250,000 must return $129,029. Both land on
  an exact half-dollar at Step 3. A half-to-even implementation returns a dollar less on each and
  will pass every other test in this list.
- **Precision:** $61,250,000 is the cheapest regression test for the floating-point defect described
  in §3.1 Rule 0.

### 8.6 Disclaimer

~~Display on every calculator and in every PDF:~~

> ~~These figures are estimates only, based on the information entered and on rates in effect as of
> the date shown. Actual charges may differ. This is not a quote, a commitment to insure, or a
> Closing Disclosure. Contact us for an exact figure for your transaction.~~

Exact wording is admin-editable (§5.4) and should be confirmed before launch.

---

## 9. Before build starts

Confirm with the client:

1. **The basic premium tables in §3.1** are transcribed from the TDI schedule effective March 1,
   2026 and check out against the state's own seven worked examples. Confirm no newer order has been
   issued.
2. **All other values in §5 are current** as of the build date — every endorsement rate and minimum,
   the R-8 credit bands, the statutory charges, and every company service fee. Unlike §3.1 these
   were reverse engineered from the previous calculator and have **not** been verified against the
   state rate manual. They are a starting point for the admin defaults, not a substitute for
   verification.
3. **What premium applies below $25,000**, where the state table has no row (§3.1 Rule 1). The
   previous calculator returned $0.00; this document assumes $308. This affects small second liens
   and small home equity refinances.
4. **The disclaimer wording** in §8.6.
5. **Whether administrators need per-calculator fee overrides**, or whether one shared set of
   values across all five (as specified here) is sufficient.

---

## 10. Acceptance criteria

- [ ] All five calculators recalculate live, with no Calculate button and no page reload.
- [ ] `basicPremium()` reproduces all seven state examples in §3.1 exactly.
- [ ] `basicPremium()` returns the correct value at every boundary listed in §8.5, including the
      two exact-half-dollar rounding ties and the $500 step boundaries.
- [ ] The step table holds all 151 rows and no coverage amount between $25,000 and $100,000 returns
      the premium of a higher step.
- [ ] The two conditional "Who Pays?" fields appear and hide correctly and clear when their parent
      changes (Calculators 1 and 3).
- [ ] Calculator 1 switches between loan and cash recording fees on the loan amount.
- [ ] Percentage endorsements are computed from the full basic premium and gated only on display
      (Calculators 1 and 3).
- [ ] Refinance % Credit changes at the 4-year and 8-year boundaries (Calculators 2 and 5).
- [ ] Estimated Recording Fees is excluded from all totals on all calculators.
- [ ] Every value in §5 is editable in the CMS and takes effect without a deploy.
- [ ] Editing a rate table in the CMS changes calculator output immediately, and a tier base that
      breaks continuity is flagged to the administrator on save rather than silently overwritten.
- [ ] Save as PDF produces a legible, correctly named, selectable-text document reflecting current
      state, on desktop and mobile.
- [ ] All five pass WCAG 2.1 AA and are usable on a phone.
