Personal identifiers
- Names - customer, employee or otherwise
- Street addresses
- Email addresses and phone numbers
- Full birth dates - age is stored as a band
- Government identifiers
- Bank and card information
Technical
Synthetic-only data, no personal data by construction, declared grains, documented lineage, reconciliation that proves rather than asserts, a read-only reporting role, and scope gates that block work rather than describe it.
Every warehouse record in this project is synthetic. Granite Auto Group and its three stores are fictional. No real dealership, customer, employee or lending data exists anywhere in the project.
Every figure comes from documented rules and a fixed random seed. Nothing here may be read as, compared against, or cited as the performance of any real automotive retailer.
The inventory shown on the dealership pages and in the inventory explorer is not machine-generated, and this project does not claim it is.
No row of it was ever observed anywhere, so it describes no real dealership and supports no comparison with one.
Calling this lane synthetic would claim more sanitization than was performed, so it is not called synthetic anywhere on this site.
The inventory shown on this site is sanitized public reference data, not a dealer management system export and not machine-generated. Real VINs, source URLs, listing keys, street addresses and real dealership identity were removed before the workbooks entered this repository; the vehicle attributes that remain are a de-identified snapshot of what a public listing source exposed. Every row describes a listing that was visible at capture time. None of them describes a sale, a delivery, a gross figure or a dealership result.
An inventory summary is descriptive evidence about a reference dataset. It is not an analytical finding about dealership performance, and publishing it does not open Gate 2 or complete the case study.
The trust framework
Select a layer to see the controls it holds and the file that implements each one. A control with no enforcement mechanism is a preference, and this page distinguishes the two.
The strongest privacy control available is not collecting the attribute. Every prohibited field in this project was never designed, so there is nothing to mask, redact or leak.
No name, address, email, phone, full birth date, government or bank identifier exists in any table
A dedicated privacy validation check runs against every generated dataset before load, and a unit test asserts it fails when a prohibited attribute is introduced.
Age is a band, not a date. Geography stops at county or market area.
The dimension DDL has no birth-date column and no street-address column, so the value cannot be stored even by a misbehaving loader.
No real VIN is linked to a synthetic customer
A written policy fixes the VIN format and its synthetic derivation, and the vehicle generator is tested against it.
The committed sample dataset is synthetic and small
Generated output is gitignored. Only a small extract is committed, and the secret check runs over the git index on every push.
Prohibited by construction
Data minimisation here means the column was never designed, not that it is masked. A masked column can be unmasked; a column that does not exist cannot leak.
Fairness and interpretation
A limitation recorded only in a separate document is a limitation nobody reads at the moment it matters. In this project the caution travels with the KPI: it is a required field in the catalogue, it is rendered on the KPI catalogue page, and it is part of the measure's description in the semantic model.
KPI_CATALOG.mdinterpretation caution per KPIView KPI_CATALOG.md (interpretation caution per KPI) on GitHub (opens in a new tab)Volume alone never ranks a person. A high-volume salesperson may show weak gross retention, poor follow-up, heavy discounting, or simply favourable lead routing. Any view that displays employee results must state how it shows lead quality, store traffic, tenure and vehicle mix alongside the number.
ARCHITECTURE.md section 23A lead is credited to the source that produced it, once. Multi-touch journeys are not modelled, so a campaign that assisted a sale without originating the lead receives no credit. This is a deliberate simplification of a genuinely hard problem, and marketing return figures must be read with it in mind.
KPI-MKT-003 interpretation cautionThe synthetic generators contain correlations because a plausible dataset needs them. Those correlations were written by the generator, so no finding drawn from this data may claim that one thing causes another - in the data or in the industry. This is why the project draws no conclusion about automotive retail at all.
LIMITATIONS.mdA versioned YAML rule file evaluated against the published datasets. No language model, no learned model, no scoring heuristic: every word comes from a rule template and every number from an exported column, so any action can be recomputed by hand. Evaluated once at export time, so the queue is identical for every visitor of a dataset version, and stateless: nothing is assigned, acknowledged or completed.
config/dashboard/action_rules.yamlEditing a review threshold changes the queue even though no business fact moved. The manifest therefore records the ruleset's hash, and the export check re-derives the queue and fails if the committed one differs. Every threshold the rule file owns is a project default for a fictional dealer group, never an industry benchmark, an OEM standard or a compliance requirement.
scripts/export_dashboard_dataset.py --checkF&I penetration, customer retention, service-to-sales opportunity and target attainment all depend on facts that have not been built. No result requiring a deferred fact may be published, which rules those four subjects out of any finding until the facts exist.
KPI_CATALOG.md section 35The gate system
A gate is opened by a written review that evaluates each of its conditions against a query or a test, not by a decision made on the way to starting the next thing. Two of the four gates are relevant now.
Met: Fact grains are approved
Every MVP fact declares one grain, enforced by a UNIQUE constraint and covered by tests/integration/test_schema_objects.py.
Met: Dimensions are documented
All eight MVP dimensions are specified in DATA_DICTIONARY.md with a source-to-target mapping each under docs/source-to-target/.
Met: KPI formulas are documented
All 29 governed KPIs carry a formula, an explicit numerator and denominator, a grain and a null rule in KPI_CATALOG.md.
Not met: Core Power BI report pages are complete
powerbi/ARPI_Performance_Intelligence/ARPI_Performance_Intelligence.Report/ is a PBIR shell: a .platform file and a definition.pbir pointing at the semantic model. It contains no page, no visual and no bookmark. Delivered by P2.2.
Not met: SQL and Power BI totals reconcile
The SQL side exists as powerbi/validation/sql_baseline.json. The Power BI side requires a refreshed model, and no engine has refreshed it. Delivered by P2.2-10.
Not met: Executive findings are drafted
docs/findings/ is empty. Delivered by P2.3.
No readiness review has been written for this gate yet, which is itself why it is closed. Absence of evidence closes a gate and never opens one.
Secrets and access
The database password is never read from a configuration file - only from an environment variable. Continuous integration runs to completion on a fork with zero repository secrets configured, and the only credential anywhere in the workflow is the throwaway password of a container that lives and dies inside one job.
arpi_admin owns the objects, arpi_loader writes the pipeline layers, and arpi_reporter reads the reporting schema. The reporter's inability to read raw, staging, warehouse or audit is asserted end to end by a test that attempts each read and requires it to fail - not described in a grants script and hoped for.
tests/integration/test_reporter_role_end_to_end.pyA safety net rather than a full scanner: it fails the build if a tracked file looks like it holds a credential - a committed .env, a live connection string with an embedded password, or a private key. The manifest generator that feeds this website applies the same patterns to its own output and refuses to write a file that matches one.
scripts/check_secrets.pyA check that has never failed is a check nobody has tested. Every critical reconciliation rule in this project has been run against a deliberately corrupted fixture and observed to fail, which is the only way to know it would catch the thing it exists to catch.
tests/integration/test_reconciliations.py