Skip to main content

Coherent & relational data

Ordinary fake-data generators fill fields independently, so you get impossible pairs: a Fiat with the model Altima (that's a Nissan), a city from one state with a ZIP code from another. TDC does it differently.

The mechanism is one rule: a template address can interpolate another field's value. The parent picks the file the child is drawn from:

value="common.vehicle.model.${{Brand}}"

If the brand comes out as Fiat, the address becomes common.vehicle.model.Fiat and the model is drawn from the Fiat file — never "Fiat Altima".

Outputs are illustrative

The values below come from a fixed seed, so they're reproducible, but exact strings and proportions can differ between core versions. Treat them as examples of shape, not guarantees.

How it looks

Two sequences: a brand and a model. The model declares parent="Brand" (so it sees the chosen brand) and interpolates it into the template address with ${{Brand}}:

<tdc>
<env count="5" seed="showroom" local="en">
<sequence name="Brand"><gen type="template" value="common.vehicle.brand"/></sequence>
<sequence name="Model" parent="Brand"><gen type="template" value="common.vehicle.model.${{Brand}}"/></sequence>
</env>
<block><line><data>${{Brand}} ${{Model}}</data></line></block>
</tdc>
./run showroom.tdc
Honda CR-V
Toyota Corolla
Ford Maverick
Chevrolet Bolt EV
Nissan Kicks

Every model belongs to its brand. And common.vehicle.brand is a weighted pack (Toyotas are common, Maybachs are rare), so the makes themselves show up in realistic proportions too — you get coherent pairs and a believable market mix from one config.

One child per parent — a cuisine and its dish

The same shape works for any parent/child pair. A cuisine pulls its own dish (food.cuisinefood.dishByCuisine.<cuisine>). Use it when the two fields would look absurd drawn independently — nobody believes in "Korean Falafel":

<sequence name="Cuisine"><gen type="template" value="food.cuisine"/></sequence>
<sequence name="Dish" parent="Cuisine"><gen type="template" value="food.dishByCuisine.${{Cuisine}}"/></sequence>
./run menu.tdc
Lebanese: Falafel
Korean: Bulgogi
Indian: Rogan Josh
Chinese: Peking Duck
Greek: Souvlaki

One parent, several linked children

A single parent can feed more than one child. Each child interpolates the same parent value into its own address, so every field on the row stays consistent with the others. Here a country (weighted by population) pulls both a capital and a currency:

<sequence name="Country"><gen type="template" value="geo.country"/></sequence>
<sequence name="Capital" parent="Country"><gen type="template" value="geo.capitalByCountry.${{Country}}"/></sequence>
<sequence name="Currency" parent="Country"><gen type="template" value="geo.currencyByCountry.${{Country}}"/></sequence>
./run atlas.tdc
China — Beijing — Renminbi
United States — Washington — US Dollar
India — New Delhi — Indian Rupee
Indonesia — Jakarta — Rupiah
China — Beijing — Renminbi

Use it when several fields all depend on the same key: address parts hanging off a state, product details hanging off a category, org data hanging off a department. Declare each child with the same parent and they all read the same chosen value.

How the data is laid out

The parent is an ordinary list, and each of its values gets its own child file, named after the value itself:

data/packs/common/vehicle/
brand.txt # the brands (parent)
model/
Toyota.txt # Toyota models
Fiat.txt # Fiat models
Mercedes-Benz.txt # names with a hyphen or space work too

The file address is the dotted path: model/Fiat.txtcommon.vehicle.model.Fiat. In the template, ${{Brand}} fills in the file name and TDC finds the right list. To add a brand, drop in model/NewBrand.txt and add a line to brand.txt. Ready-made coherent sets ship for car makes, food.cuisine, medical.specialtyCoherent, work.industryCoherent, common.dev.languageCoherent, sport.sportCoherent, and geo.country.

Things to remember

  • Declare the parent before the child — TDC materializes sequences top to bottom, so ${{Brand}} reads a value that has already been computed. A child that interpolates a field defined below it has nothing to read.
  • parent="Brand" links the child to the parent and fixes the order. For a plain lookup that's enough; stricter filtering on a specific value (parent="Brand.Fiat") is covered in Hierarchical dependencies.
  • Every parent value needs a matching child file, or the address won't resolve and you get an error. That's why the parent list usually holds exactly the values that have files (like common.vehicle.brand).
  • Engine. A config like this always runs on the in-memory engine — it's the only one that resolves an address per row, so memory grows with count. This feature is about realistic coherence, not streaming gigabytes; Which engine runs your config lists this and the four other shapes that route the same way.

The CSV cousin — row + weight

When your related fields live in one CSV rather than in per-value files, link them with row instead: several file generators that share the same row read the same line on each record, so the fields stay together on one row of real data. Add weight to one of them to draw that line by its real frequency:

<sequence name="Place">
<gen name="City" type="file" src="cities.csv" column="city" row="loc" weight="population"/>
<gen name="State" type="file" src="cities.csv" column="state" row="loc"/>
</sequence>
./run cities.tdc
Seattle, WA
Austin, TX
Chicago, IL
Seattle, WA
Denver, CO

Because both generators share row="loc", the city and its state always come from the same line; weight="population" on the city makes bigger places show up more often. Full details are on the File generator page.

See also