> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getcanonic.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide: Vehicle Rental (SQLite)

> A SQLite-only project with cross-fact fanout and a NULL-handling guardrail.

An end-to-end canonic project on a vehicle rental service: one SQLite connection, five dimensions, three fact tables, nine metric contracts (three canonical), and one enforced guardrail. No external database required. The entire dataset lives in a single `rental.db` file.

<Info>Full source: [`examples/rental/`](https://github.com/mischuh/canonic/tree/main/examples/rental)</Info>

## Schema

```
DIMENSIONS
  vehicle_categories   category_id · name · daily_rate
  locations            location_id · code · city · country · airport_code
  employees             employee_id · name · title · location_id
  customers             customer_id · name · email · country · membership_tier
  vehicles              vehicle_id · make · model · year · category_id

FACTS
  rentals    rental_id · customer_id · vehicle_id · pickup/dropoff_location
             employee_id · dates · planned_days · actual_days
             rate_per_day · total_amount · status
  payments   payment_id · rental_id · payment_date · amount · method · status
  damages    damage_id · rental_id · vehicle_id · severity · repair_cost
```

Seed data: 40 rentals (32 completed, 3 active, 2 confirmed, 2 cancelled, 1 no-show), 32 settled payments, 8 damage claims, across 20 customers, 15 vehicles, 5 locations.

## Setup

```bash theme={null}
sqlite3 rental.db < setup.sql   # create the database (one-time)
cd examples/rental              # canonic commands must run from here
canonic status
# Canonic project: rental-demo (version 1)
```

## Quickstart

```bash theme={null}
canonic ingest --bootstrap                 # bootstrap the semantic layer from the live schema
canonic query --metrics rental_count --dimensions status
canonic mcp start                          # start the MCP server
canonic eval baseline \                    # (optional) grain-inference accuracy
  --candidates candidates.yaml \
  --dataset eval/grain_cases.jsonl
```

`canonic status`, `canonic ingest --bootstrap`, `canonic query`, and `canonic mcp start` never call the LLM: every table here has a declared primary key, so grain is inferred deterministically. `CANONIC_LLM_API_KEY` only matters for `canonic eval baseline`, which explicitly benchmarks the configured model against `eval/grain_cases.jsonl` (a labeled set with primary keys withheld, to force drafting).

## Metrics

`contracts/metrics/` ships **9** metric contracts. Three are the canonical showcase bindings. The rest support the guardrail's caveats and other reporting cuts.

| Metric                                                    | Source · measure                                   | Canonical for                              |
| --------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------ |
| `rental_revenue`                                          | `payments.total_paid`                              | Settled revenue across all payment methods |
| `rental_count`                                            | `rentals.completed_rental_count`                   | Number of completed agreements             |
| `avg_rental_duration`                                     | `rentals.avg_rental_days`                          | Mean actual days held per completed rental |
| `active_customers`                                        | distinct count on `rentals.customer_id`            | Customers with a non-cancelled rental      |
| `median_rental_amount`                                    | percentile: `rentals.total_amount`                 | Median order value                         |
| `ending_inventory`                                        | semi-additive: `vehicle_inventory.inventory_level` | Fleet size as of the latest snapshot date  |
| `damage_count` / `total_repair_cost` / `avg_repair_costs` | `damages.*`                                        | Damage-claim reporting                     |

## Guardrail

`contracts/guardrails/` ships **one** enforced guardrail, **`completed-rentals-only`**: `rentals.total_base_revenue` must never be summed without a `status = 'completed'` filter: active and cancelled rows have `NULL` in `total_amount`. Use `payments.total_paid` for financial reporting instead.

<Note>
  **Cross-fact fanout** (documented in a knowledge caveat, not a guardrail: none of the four guardrail kinds cover "reject a join between two fanning fact tables", see [Contracts & guardrails](/concepts/contracts-and-guardrails#guardrails)): joining `damages` to `payments` in a single query fans out both facts. Always bridge each independently through `rentals`.
</Note>

## Files

```
canonic.yaml                    ← SQLite connection, LLM, reconcile settings
setup.sql                       ← DDL + seed data
semantics/rental_db/            ← 5 dimensions + 4 facts (incl. vehicle_inventory)
contracts/metrics/              ← 9 metric contracts (see Metrics above)
contracts/guardrails/           ← completed-rentals-only.yaml
knowledge/global/               ← 5 pages: rental-revenue, rental-duration, active-customers,
                                   avg-repair-costs, ending-inventory definitions/notes
eval/grain_cases.jsonl          ← 8 labeled grain-inference cases (PK omitted)
```
