# Use cases: the same steps, different tables

Krusgraph turns tables without declared keys into an explained graph, then into what people actually use: a GraphQL API for an app, a Fabric IQ ontology draft and, planned, a semantic model, SQL views and a data agent. The same steps work in any industry; what differs is the tables.

Plan from names (`plan_backend`, nothing is deployed), check against the rows (`validate_plan`), then create (`create_backend`). Ambiguous columns are reported, never guessed.

## What comes out

- **GraphQL API**: an API for GraphQL item in your workspace, for an app or for questions in Claude
- **Fabric IQ ontology draft**: entity types, keys and relationships (preview)
- **Semantic model**: for Power BI (planned)
- **SQL views**: for the joins a rule cannot express (planned)
- **Data agent**: that answers over the same model (planned)

## How to read the rules

- **K2**: a key found by name: `id`, `<table>_id`, `<table>_no`, …
- **K1**: a key you give, for example a composite one
- **K4**: a junction: the leading reference columns together, such as `customer_id + account_id`
- **K5**: a line: the reference to the header plus a line number, such as `order_id + line_no`
- **K3**: no key: the table can be queried, but nothing can point at it
- **R1**: a column named after another table: `customer_id` → `customers`
- **R2**: the same name as another table's single key column
- **R3**: all columns of another table's composite key
- **R4**: the weaker match: a role in front of a table's name, such as `billing_account_id` → `accounts`; check it
- **to confirm**: a column that names no table, such as `manager_id`: linked only when you confirm it

## Retail & e-commerce

Who bought what, on which order line, and what came back.

### Typical tables

- **customers**: `customer_id` (key, K2), `email`, `country`, `created_at`
- **orders**: `order_id` (key, K2), `customer_id` (→ customers, R1), `order_date`, `status`
- **order_lines**: `order_id` (key, K5), `line_no` (key, K5), `product_id` (→ products, R1), `quantity`, `unit_price`
- **products**: `product_id` (key, K2), `sku`, `category_id` (→ categories, R1), `list_price`
- **categories**: `category_id` (key, K2), `parent_category_id` (→ categories? to confirm), `name`
- **returns**: `return_id` (key, K2), `order_id` (→ orders, R1), `line_no`, `reason`; `order_id + line_no` → order_lines (R3)

### What Krusgraph finds

- **R1**: `orders.customer_id` → `customers`: the column is named after the table. The same rule links `order_lines.product_id` → `products` and `products.category_id` → `categories`.
- **K5**: `order_lines` has no key column of its own. The line rule (K5) takes the reference to its header plus the line number, `order_id + line_no`, and `returns.order_id + line_no` → `order_lines` follows by R3.
- **to confirm**: `categories.parent_category_id` points back at its own table, and no rule links a table to itself by name. Confirm it once and the API gets a `parent_category` field.

### What you build

- A returns desk in Lovable over the GraphQL API: an order, its lines and products, and what came back, in one nested query.
- Questions in Claude through `query_graphql`, such as which categories see the most returns.
- A Power BI semantic model from the same keys (planned).

### Where the value is

- A working read API over the order tables as soon as the plan is accepted, without writing a schema by hand.
- The line-level key is stated once, so returns are not joined to whole orders and counted once per line.
- Every link carries its rule. That is the documentation when the data moves to another team.

## Manufacturing

Work orders on machines, what they make, what goes into it, and whether it passed.

### Typical tables

- **items**: `item_no` (key, K2), `description`, `unit`, `item_type`
- **bill_of_materials**: `parent_item_no` (key, K4), `component_item_no` (key, K4), `quantity`
- **work_orders**: `work_order_id` (key, K2), `item_no` (→ items, R1), `machine_id` (→ machines, R1), `planned_start`, `status`
- **machines**: `machine_id` (key, K2), `line_name`, `model`
- **quality_checks**: `quality_check_id` (key, K2), `work_order_id` (→ work_orders, R1), `checked_at`, `result`

### What Krusgraph finds

- **R1**: `items` gets `item_no` as its key (K2 also reads `_no`), and `work_orders.item_no` → `items` follows. `work_orders.machine_id` → `machines` and `quality_checks.work_order_id` → `work_orders` the same way.
- **R4**: `bill_of_materials` points at `items` twice, through `parent_item_no` and `component_item_no`. Both are linked by R4, the weaker rule for a role in front of a table's name, as `parent_item` and `component_item` fields. Check them before you build.
- **K4**: `bill_of_materials` has no key column and starts with those two references, so the junction rule (K4) makes `parent_item_no + component_item_no` its key. `validate_plan` counts duplicates and NULLs in it before anything is built.

### What you build

- A shop-floor app over GraphQL: a work order with its item, machine and quality checks.
- One level of a bill of materials per query. A full explosion is recursive, and that stays a SQL job.
- Questions in Claude, such as which machines had failed checks last week.

### Where the value is

- The two-way bill of materials is a classic wrong join. Both roles carry the weaker rule, so they get reviewed instead of assumed.
- A first backend over exported production tables, with no keys declared in the lakehouse.
- The rules and confirmations are the handover note: the next person sees why each link exists.

## SaaS & subscriptions

Accounts on plans, invoices with lines, and a stream of usage events.

### Typical tables

- **accounts**: `account_id` (key, K2), `name`, `owner_email`, `created_at`
- **plans**: `plan_id` (key, K2), `name`, `monthly_price`
- **subscriptions**: `subscription_id` (key, K2), `account_id` (→ accounts, R1), `plan_id` (→ plans, R1), `billing_account_id` (→ accounts, R4), `started_at`
- **invoices**: `invoice_id` (key, K2), `account_id` (→ accounts, R1), `subscription_id` (→ subscriptions, R1), `issued_at`, `total`
- **invoice_lines**: `invoice_id` (key, K5), `line_no` (key, K5), `description`, `amount`
- **usage_events**: `event_id`, `subscription_id` (→ subscriptions, R1), `event_type`, `occurred_at`; no key (K3)

### What Krusgraph finds

- **R1**: `subscriptions.account_id` → `accounts` and `subscriptions.plan_id` → `plans`; `invoices.subscription_id` → `subscriptions` as well.
- **R4**: `subscriptions.billing_account_id` → `accounts` by R4, the weaker rule for a role in front of a table's name, as a `billing_account` field. Probably right, for example a parent company paying for a subsidiary, but only you know: check it, or confirm another target.
- **K5**: `invoice_lines` gets `invoice_id + line_no` by the line rule (K5). `usage_events` has `event_id`, which is not named after the table, so it gets no key (K3). It still points at `subscriptions`, but nothing can point at it until you give the key (K1).

### What you build

- A self-service billing page in Lovable: an account, its subscriptions, invoices and invoice lines.
- Usage per plan, asked in Claude and answered through the API without writing SQL.
- A semantic model for revenue reporting from the same keys (planned).

### Where the value is

- The using account and the paying account are kept apart from the start, not discovered in a wrong revenue figure later.
- Time to a first working API is the time it takes to read and accept a plan.
- The plan doubles as a data dictionary for new engineers.

## Professional services

Clients, projects, consultants, and the hours that end up on invoices.

### Typical tables

- **clients**: `client_id` (key, K2), `name`, `sector`
- **consultants**: `consultant_id` (key, K2), `name`, `grade`, `manager_id` (→ consultants? to confirm)
- **projects**: `project_id` (key, K2), `client_id` (→ clients, R1), `lead_consultant_id` (→ consultants, R4), `name`
- **time_entries**: `time_entry_id` (key, K2), `consultant_id` (→ consultants, R1), `project_id` (→ projects, R1), `work_date`, `hours`, `invoice_id` (→ invoices, R1)
- **invoices**: `invoice_id` (key, K2), `client_id` (→ clients, R1), `issued_at`, `amount`

### What Krusgraph finds

- **R1**: `time_entries.project_id` → `projects`, `time_entries.consultant_id` → `consultants` and `time_entries.invoice_id` → `invoices`. An empty `invoice_id` is an unbilled hour: `validate_plan` counts rows with a value and orphans separately.
- **R4**: `projects.lead_consultant_id` → `consultants` by R4, a role in front of the table's name, as a `lead_consultant` field. `consultants.manager_id` names no table at all, so it stays unlinked until you confirm it; then it is `manager`.
- **K2**: Every table here has a key by name (`client_id`, `project_id`, `time_entry_id`, …), so the API can fetch any single row by its key.

### What you build

- A utilisation app: hours per consultant and project, with the client one level up.
- Questions in Claude, such as which projects have hours that are not invoiced yet.
- A Fabric IQ ontology draft (preview) with Client, Project, Consultant and Time entry entity types.

### Where the value is

- Hours to invoices is the join that decides revenue. Its rule and its orphan count are visible before anything is built.
- A backend for an internal tool without a modelling project first.
- A new controller reads the plan instead of reverse-engineering old reports.

## Financial services: AML

Anti-money laundering: customers, the accounts they hold, transactions, counterparties, alerts and KYC reviews.

### Typical tables

- **customers**: `customer_id` (key, K2), `name`, `country`, `risk_rating`
- **accounts**: `account_id` (key, K2), `product_type`, `opened_at`, `status`
- **account_holders**: `customer_id` (key, K4), `account_id` (key, K4), `holder_role`, `since`
- **transactions**: `transaction_id` (key, K2), `account_id` (→ accounts, R1), `counterparty_id` (→ counterparties, R1), `beneficiary_account_id` (→ accounts, R4), `amount`, `booked_at`
- **counterparties**: `counterparty_id` (key, K2), `name`, `country`, `bank_code`
- **alerts**: `alert_id` (key, K2), `transaction_id` (→ transactions, R1), `customer_id` (→ customers, R1), `scenario`, `raised_at`
- **kyc_reviews**: `kyc_review_id` (key, K2), `customer_id` (→ customers, R1), `reviewed_at`, `outcome`

### What Krusgraph finds

- **R1**: `transactions.account_id` → `accounts`, `transactions.counterparty_id` → `counterparties` and `alerts.transaction_id` → `transactions`.
- **K4**: `account_holders` is a bridge with two outgoing references: `account_holders.customer_id` → `customers` and `account_holders.account_id` → `accounts`, both R1. The junction rule (K4) makes `customer_id + account_id` its key: one row per holding.
- **R4**: `transactions.beneficiary_account_id` → `accounts` by R4, the weaker rule for a role in front of a table's name, as a `beneficiary_account` field. The plan says which rule made it, so it is checked rather than trusted.

### What you build

- One explained model that investigators and an agent in Claude query the same way.
- Questions such as all transactions between accounts held by the same customer in the last 90 days: one nested GraphQL query from customer through `account_holders`, accounts and transactions to the beneficiary account, with the rule or confirmation behind each step.
- A read-only investigation app in Lovable over the same API.

### Where the value is

- Fewer wrong joins in investigations: the bridge and the beneficiary are each stated once, with their rules.
- A documented model for model-risk and audit review: every link has a rule or a recorded confirmation, and `validate_plan` counts orphans before anyone relies on it.

### The next layer

Krusgraph works on how the tables connect. Finding rings among individual customers and accounts (communities, shared addresses or phones, money moving in cycles) is row-level graph analysis, built on top of that model, for example with graph tools in Fabric. The explained model makes it possible; it is not a Krusgraph feature today.

## Financial crime prevention

Fraud, sanctions screening and KYC: alerts and cases from teams that usually keep separate tables.

### Typical tables

- **customers**: `customer_id` (key, K2), `name`, `onboarded_at`
- **cases**: `case_id` (key, K2), `customer_id` (→ customers, R1), `linked_case_id` (→ cases? to confirm), `case_type`, `status`
- **alerts**: `alert_id` (key, K2), `case_id` (→ cases, R1), `customer_id` (→ customers, R1), `source_system`, `raised_at`
- **screening_hits**: `screening_hit_id` (key, K2), `customer_id` (→ customers, R1), `list_entry_id`, `match_score`
- **sanctions_lists**: `list_entry_id`, `list_name`, `entry_name`, `published_at`; no key (K3)
- **devices**: `device_id` (key, K2), `fingerprint`, `first_seen`
- **logins**: `login_id` (key, K2), `customer_id` (→ customers, R1), `device_id` (→ devices, R1), `ip_address`, `logged_in_at`

### What Krusgraph finds

- **R1**: `logins.device_id` → `devices`, `logins.customer_id` → `customers` and `alerts.case_id` → `cases`: fraud signals, alerts and cases meet at the customer.
- **R2**: `sanctions_lists` has one row per list entry, keyed by `list_entry_id`, which no key rule reads (K3). Give it as the key (K1) and `screening_hits.list_entry_id` → `sanctions_lists` follows by R2.
- **to confirm**: `cases.linked_case_id` ties a case to an earlier one. It points back at its own table, which no rule links by name, so you confirm it; then each case has a `linked_case` field.

### What you build

- One connected view for fraud, sanctions and AML analysts: a customer with cases, alerts, screening hits and logins.
- Questions in Claude, such as which customers with an open screening hit also have a fraud alert this month.
- A Fabric IQ ontology draft (preview) as a shared vocabulary across the teams.

### Where the value is

- Teams that work in separate tables share one model and see the same joins.
- Explained links are defensible to auditors: each one says which rule made it, or that someone confirmed it.
- `validate_plan` shows orphans, such as screening hits for customers missing from `customers`, first.

### The next layer

Several customers sharing one device is a row-level question this model can answer (device, its logins, their customers). Scoring, network analysis and screening logic on top are separate work; Krusgraph supplies the explained joins.

## Pharmaceutical research

Clinical studies and lab work: subjects at sites, visits, adverse events, compounds and assay results.

### Typical tables

- **studies**: `study_id` (key, K2), `compound_id` (→ compounds, R1), `phase`, `start_date`
- **sites**: `site_id` (key, K2), `country`, `investigator`
- **subjects**: `subject_id` (key, K2), `study_id` (→ studies, R1), `site_id` (→ sites, R1), `arm_code`, `enrolled_at`
- **visits**: `subject_id` (→ subjects, R1), `visit_no` (key, K2), `visit_date`, `visit_type`
- **adverse_events**: `adverse_event_id` (key, K2), `subject_id` (→ subjects, R1), `visit_no` (→ visits, R1), `severity_code`, `meddra_code`
- **compounds**: `compound_id` (key, K2), `code_name`, `modality`
- **assays**: `assay_id` (key, K2), `assay_type`, `unit`
- **results**: `result_id` (key, K2), `assay_id` (→ assays, R1), `subject_id` (→ subjects, R1), `visit_no` (→ visits, R1), `value`

### What Krusgraph finds

- **R1**: `visits.subject_id` → `subjects`, `results.assay_id` → `assays`, `subjects.site_id` → `sites` and `studies.compound_id` → `compounds`.
- **K1**: From names alone, `visits` gets `visit_no` as its key (K2) and `results.visit_no` → `visits` (R1). Both are wrong: visit numbers repeat per subject, and `validate_plan` shows the duplicates. The engine does not suggest the fix here; you give `subject_id + visit_no` (K1). The single-column links go, and `results` and `adverse_events` reach `visits` by R3 on both columns.
- **to confirm**: Coded columns such as `adverse_events.severity_code`, `adverse_events.meddra_code` and `subjects.arm_code` point at code lists. No rule links them and nothing is guessed: add the code-list tables, or confirm each link.

### What you build

- An agent in Claude that answers research questions through the explained plan, such as which compounds had adverse events at more than one site.
- A study overview app: a subject with visits, results and adverse events.
- A semantic model for study reporting (planned).

### Where the value is

- Fewer confident-but-wrong joins: the visit key above is that kind of mistake, caught before an answer depends on it.
- Orphans are counted first: results without an assay, adverse events without a visit.
- Regulated use, such as GxP work, needs the organisation's own validation; Krusgraph does not provide it.

### Better answers

Asked which compounds had adverse events at more than one site, Claude goes from adverse events to subjects, then to sites and to studies and compounds, through joins that were planned, explained and checked. The answer can show its path and the rule behind every step, and `validate_plan` has already shown whether rows go missing on the way.

## Environment & sustainability

Sites, energy meters and readings, activities with emission factors, and purchases from suppliers (Scope 3).

### Typical tables

- **sites**: `site_id` (key, K2), `name`, `region`
- **meters**: `meter_id` (key, K2), `site_id` (→ sites, R1), `energy_type`
- **meter_readings**: `meter_id` (→ meters, R1), `read_at`, `value_kwh`; no key (K3)
- **activities**: `activity_id` (key, K2), `site_id` (→ sites, R1), `emission_factor_id` (→ emission_factors, R1), `activity_type`, `quantity`, `period_start`
- **emission_factors**: `emission_factor_id` (key, K2), `activity_type`, `valid_from`, `kg_co2e_per_unit`
- **suppliers**: `supplier_id` (key, K2), `name`, `country`
- **purchases**: `purchase_id` (key, K2), `supplier_id` (→ suppliers, R1), `site_id` (→ sites, R1), `spend_category`, `amount`, `purchased_at`

### What Krusgraph finds

- **R1**: `activities.emission_factor_id` → `emission_factors`: the factor behind each activity is an explained join. `meters.site_id` → `sites` and `meter_readings.meter_id` → `meters` as well.
- **R1**: `purchases.supplier_id` → `suppliers` and `purchases.site_id` → `sites`, for spend-based Scope 3 per site and supplier.
- **K3**: `meter_readings` has no key, which is fine for rows that are read, not pointed at. Where a factor is picked by activity type and date range instead of an id, that is not a key relationship: Krusgraph does not infer it, and it belongs in a SQL view.

### What you build

- A site app over GraphQL: a site with its meters, activities and purchases.
- Questions in Claude, such as emissions per site and quarter with the factor behind each figure.
- SQL views for date-range factor matching (planned); until then, those are written by hand.

### Where the value is

- Traceable numbers for sustainability reporting: every figure has a path back to its source tables, and every step has a rule.
- Each activity points at the factor row it used, so a changed factor shows up as a changed link.
- It documents joins. It does not make a report meet any reporting standard.

## Property & real estate

Properties split into units, leased to tenants, with maintenance requests.

### Typical tables

- **properties**: `property_id` (key, K2), `address`, `city`
- **units**: `unit_id` (key, K2), `property_id` (→ properties, R1), `floor`, `area_m2`
- **tenants**: `tenant_id` (key, K2), `name`, `contact_email`
- **leases**: `lease_id` (key, K2), `unit_id` (→ units, R1), `tenant_id` (→ tenants, R1), `start_date`, `monthly_rent`
- **maintenance_requests**: `maintenance_request_id` (key, K2), `unit_id` (→ units, R1), `reported_by_tenant_id` (→ tenants, R4), `status`, `opened_at`

### What Krusgraph finds

- **R1**: `units.property_id` → `properties` (the plural is understood), `leases.unit_id` → `units` and `leases.tenant_id` → `tenants`.
- **R4**: `maintenance_requests.reported_by_tenant_id` → `tenants` by R4, a role in front of the table's name, as a `reported_by_tenant` field. Check it before you build.
- **K2**: All five tables have a key by name, so the API can fetch any unit, lease or request by its key.

### What you build

- A caretaker app in Lovable: a unit with its lease, tenant and open requests. Creating requests from the app needs a warehouse or SQL database source; lakehouse sources are read-only through the API.
- Questions in Claude, such as which properties have the most open requests per unit.
- A Fabric IQ ontology draft (preview) with Property, Unit, Lease and Tenant entity types.

### Where the value is

- A first working backend for a property app without a modelling phase.
- Tenant, lease and unit are joined once, so reports stop guessing which tenant belongs to which unit.
- When the app changes hands, the plan explains every link.

## Education

Students enrolled in courses each term, taught by teachers, with grades.

### Typical tables

- **students**: `student_id` (key, K2), `name`, `cohort`
- **teachers**: `teacher_id` (key, K2), `name`, `department`
- **courses**: `course_id` (key, K2), `teacher_id` (→ teachers, R1), `title`, `credits`
- **enrolments**: `student_id` (key, K4), `course_id` (key, K4), `term`, `enrolled_at`
- **grades**: `grade_id` (key, K2), `student_id` (→ students, R1), `course_id` (→ courses, R1), `term`, `grade`; `student_id + course_id` → enrolments (R3)

### What Krusgraph finds

- **R1**: `courses.teacher_id` → `teachers`, `enrolments.student_id` → `students` and `enrolments.course_id` → `courses`.
- **K4**: `enrolments` has no key column and starts with two references, so the junction rule (K4) makes `student_id + course_id` its key, and `grades` reaches it by R3. If a student can retake a course in another term, that key is wrong, and `validate_plan` shows the duplicates.
- **K1**: Give `student_id + course_id + term` (K1). Then `grades.student_id + course_id + term` → `enrolments` follows by R3 on all three columns.

### What you build

- A course overview app: a course, its teacher and the students enrolled this term.
- Questions in Claude, such as grades per course and term.
- A semantic model for term reports from the same keys (planned).

### Where the value is

- A three-column key is easy to half-write in a join and then double-count. Stating it once avoids that.
- A working API for a course tool without declaring keys in the lakehouse first.
- Documentation for next term's administrator, generated with the API.

## For Fabric consultancies

The same engine runs on every client's tables, so the first conversation can start from their own schema instead of a slide. The rules and the plan are open source, and every key and link says why it is there, which makes a review with the client's data owners short and concrete. Start at [https://krusgraph.com/start](https://krusgraph.com/start), read [https://krusgraph.com/docs](https://krusgraph.com/docs), and write to hello@krusgraph.com to discuss a pilot.

*Scenarios are illustrative; they describe how the tool works on typical table designs, not customer projects.*
