rastriq. Talk to us โ†’
Rastriq / Guides / Normalize car configurator pricing

Normalizing car configurator pricing: list, net and discount

Each manufacturer answers "how much does this car cost?" with different fields. This guide shows how to read real configurator fields and map them to a common schema without mixing tax bases.

The problem: one question, twenty-eight answers

If you extract prices from several configurators and put them in one column, you are most likely comparing different things. One manufacturer publishes the price with VAT, another without; one already applies the promotion and another shows the list price; some include registration tax and others leave it out. If you do not tell the base of each amount, any comparison across brands, countries or dates is contaminated.

Normalizing is not about "cleaning" the data but about declaring, for each amount, what it is: list or discounted, gross or net, with or without special taxes. That declaration is what lets you use the data later in a dashboard, a pricing model or a contract. Behind every brand page of the OEM configurators hub there is a field dictionary for exactly this purpose.

The three axes you cannot mix

Before looking at fields, it helps to fix the vocabulary. There are three independent axes, and every amount in a configurator sits at some position on each:

  • List vs final: the list price (MSRP) before any promotion, versus the price after the manufacturer or dealer discount.
  • Gross vs net: the amount with VAT (what a private buyer pays) versus the amount without VAT (what matters to a company that deducts it).
  • With or without special taxes: Spain has a registration tax that depends on emissions; some configurators fold it into the displayed price, others do not, and other countries have equivalent taxes with their own names and logic.

Real fields: BMW, Kia and Toyota

The best way to see the difference is to look at real rows. The table shows one row per brand, taken from the samples of our actors for the Spanish market, in euros. These are not aggregate figures or comparisons: they are the fields exactly as each configurator delivers them.

Brand / rowFieldValueWhat it represents
BMW 118d (ES)gross_list_price42768.1Gross list price
BMW 118d (ES)net_list_price34010.4Net list price
BMW 118d (ES)total_taxes8757.678Taxes between gross and net
BMW 118d (ES)emission_tax_pct0.0475Emissions-based percentage
Kia EV6 Air (ES)msrp_before_discount48995.01List price before discount
Kia EV6 Air (ES)discount_amount13465Discount in euros
Kia EV6 Air (ES)msrp_dc_price35530.01Price after discount
Kia EV6 Air (ES)is_net_priceFalseThe displayed amount is not net
Kia EV6 Air (ES)net_price29363.64Net of the discounted price
Toyota Aygo X Cross (ES)price_list27800List price
Toyota Aygo X Cross (ES)price_discount3300Discount
Toyota Aygo X Cross (ES)price_with_discount24500Price after discount
Toyota Aygo X Cross (ES)price_net22975.21Net of the list price
Toyota Aygo X Cross (ES)price_tax4824.79Tax on the list price

What those rows teach

In BMW, net and gross are sibling fields and the difference between them is published in total_taxes. In that row, 8757.678 divided by the net 34010.4 gives 0.2575, which is 21% VAT plus the 4.75% in emission_tax_pct. The practical conclusion: total_taxes is not just VAT; it groups VAT and the emissions tax, and if you subtract it assuming it is VAT you will get a wrong net.

Kia has a different structure: a list price, a discount and a final price, plus a boolean, is_net_price, that tells you whether the displayed amount is net. Here the check is arithmetic: 48995.01 minus 13465 is 35530.01, and 35530.01 divided by 1.21 is 29363.64. So net_price is the net of the discounted price, not of the list price.

Toyota is the subtlest trap. price_net is 22975.21, which is the net of the list price (27800 divided by 1.21), not of the discounted price. The post-discount net lives in another field, price_ex_vat, at 20247.93. Two brands with a field called "net" and two different bases: that is why you should never map by field name, only by what each field represents, verified against a row.

A common schema to homogenize

The goal is a schema where every amount carries its base explicitly. A minimal proposal, which we use as a starting point in data normalization projects, is the following:

Common fieldMeaningHow it is obtained
price_list_grossList price with VATBMW: gross_list_price; Kia: msrp_before_discount; Toyota: price_list
discount_amountDiscount in local currencyKia: discount_amount; Toyota: price_discount; null if absent
price_final_grossFinal price with VATKia: msrp_dc_price; Toyota: price_with_discount
price_final_netFinal price without VATKia: net_price; Toyota: price_ex_vat; BMW: net_list_price
vat_rateVAT rate appliedSource field or derived and flagged as derived
registration_tax_amountRegistration tax, if knownOnly if the source separates it; otherwise null with a flag
price_basis_flagQuality of the tax basisok, derived or unknown

Normalize without losing the original

A rule that saves trouble: always keep the original field next to the normalized one and add a quality flag instead of dropping rows. If the net was derived by dividing by VAT, mark it as derived; if the source does not let you separate the registration tax, leave the field null and flag it. Whoever consumes the data decides whether to accept derived rows. Some of our actors already do this natively: Toyota delivers tax_calculation_status and price_quality, and Volkswagen delivers price_net_status and registration_tax_status.

This philosophy, flag and let through, avoids the opposite mistake, which is silently filtering so the analyst never learns that data was missing. With the flag, a dashboard can show verified-net coverage versus derived, and a model can decide whether to exclude the derived rows.

A classification lesson: the "EV" false positive in "Clio evolution"

Normalization is not only about prices. To compare electric cars with hybrids and combustion models you need to classify the powertrain, and the temptation is to detect it from the model or trim name. It is a classic mistake. If you search the text for the substring "ev", a trim called "Clio evolution" is classified as electric, as is any name containing "ev" anywhere, from "Level" to "Revolution".

The fix has three layers. First: use the fuel or powertrain fields the configurator delivers, such as fuel_type in BMW, fuel_code and is_hybrid in Toyota or engine_category_name, before the commercial name. Second: if you must use text, tokenize and compare whole words with word boundaries, never substrings. Third: store the signal you used, for example propulsion_source = fuel_field | token | unknown, so uncertain classifications can be audited later. And a unit test with the "Clio evolution" case keeps the bug from returning with a code change.

Steps to build the pipeline

Summarized as a working order you can follow:

  • Download a sample per brand and list the price and tax fields with their values in a real row.
  • For each field, check the base arithmetically: subtract, divide by VAT and verify it matches another field in the same row.
  • Define the common schema and the per-brand mapping table, and document ambiguous cases such as price_net in Toyota.
  • Derive what is missing, always with a derived flag, and leave null what cannot be computed.
  • Classify the powertrain by fuel fields and tokens, never by substrings.
  • Add tests with real rows, including the conflicting cases, and periodically review any new fields that appear.

Where to see each brand fields

Each brand has its own page with coverage, real rows and a field dictionary. For the examples in this guide: BMW, Kia, Toyota and Dacia and Renault, the latter with price_retail, price_type and price_without_tax. The commercial hub is new car pricing data, and if you would rather receive the data already homogenized, you can request a normalized dataset.

Frequently asked questions

Why can I not directly compare the price of two brands?

Because each configurator publishes the amount on a different basis: with or without VAT, list or discounted, with or without registration tax. Until you declare the basis of each field and map it to a common schema, the comparison mixes concepts.

What is the difference between list price, net price and discounted price?

The list price (MSRP) is the tariff amount with VAT; net is that amount without VAT, and the discounted price is the one after applying the manufacturer promotion. They are three different axes and one field can combine two of them, such as the net of an already discounted price.

Is registration tax always included in the price?

No. It depends on the configurator and the country. Some fold it into the displayed price and others leave it out or publish only a percentage, such as `emission_tax_pct` in BMW. That is why the common schema treats it as an optional field with a quality flag.

How do I avoid misclassifying an electric car?

Do not detect the powertrain from substrings of the name: "Clio evolution" contains "ev" and is not an electric car. Use the source fuel fields, and if you must parse text, compare whole words and record which signal you used.

Do you offer the data already normalized?

Yes. Besides the samples and the actor for each brand, there is a normalization service that delivers a common schema with a quality flag. You can request brands, markets and period from the datasets page.

Want to see the data before you decide?

Download a real sample of up to 100 rows, already sanitized, or tell us which sources you need.

Keep reading and take action