@rljson/edge 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/README.md CHANGED
@@ -8,4 +8,168 @@ found in the LICENSE file in the root of this package.
8
8
 
9
9
  # @rljson/edge - The Rljson Example Data Generator
10
10
 
11
- ## Users
11
+ Edge generates example data for Rljson applications: a car world with
12
+ manufacturers, their catalogs of cars, and for every car a price, a brand,
13
+ a workshop, a bill of materials and a CAD scene. One generated world uses
14
+ every Rljson data type, from a hundred rows for a unit test to 25 million
15
+ rows for a load test.
16
+
17
+ ## Goals
18
+
19
+ - Give the Uikit, `db`, the `io` backends and the validators realistic test
20
+ data in every Rljson data type
21
+ - Generate the same rows and hashes on every run and every machine, without
22
+ random numbers
23
+ - Scale from a unit test fixture to a load test with 20 million objects
24
+ - Run in Node.js, in the browser and in Web Workers, without file system or
25
+ database dependencies
26
+ - Keep the data vivid: real brands and models, plausible parts, workshops
27
+ with addresses and owners
28
+
29
+ ## State
30
+
31
+ [![Tests](https://github.com/rljson/edge/actions/workflows/quick_check.yaml/badge.svg)](https://github.com/rljson/edge/actions/workflows/quick_check.yaml)
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pnpm add @rljson/edge @rljson/rljson
37
+ ```
38
+
39
+ ## Documentation
40
+
41
+ - [Example data generator](https://github.com/rljson/rljson-pm/blob/main/doc/2026-Q4/concepts/topics/example-data-generator.md):
42
+ design, sizes and ideas, in the project management repo
43
+ - [Decisions edge-001 to edge-004](https://github.com/rljson/rljson-pm/blob/main/doc/2026-Q4/concepts/decisions/000-index.md):
44
+ no buffets, no randomness, callback sinks, derived model years
45
+ - The section »Generate Rljson« on [rljson.github.io](https://rljson.github.io):
46
+ step-by-step tutorials
47
+ - [Guides](doc/guides): how to develop, test and review in this repo
48
+
49
+ ## Code Examples
50
+
51
+ [src/example.ts](src/example.ts) runs these examples in the tests.
52
+
53
+ Generate a world in memory:
54
+
55
+ ```ts
56
+ import { Edge } from '@rljson/edge';
57
+
58
+ // The tiny preset: one manufacturer with one catalog of five cars
59
+ const { world, stats } = await Edge.preset('tiny').generate();
60
+
61
+ console.log(`${stats.cars} cars, ${stats.rowsTotal} rows`);
62
+ console.log(world.manufacturers._data[0].name);
63
+ ```
64
+
65
+ Configure a world and estimate its size first:
66
+
67
+ ```ts
68
+ import { Edge } from '@rljson/edge';
69
+
70
+ const edge = new Edge({
71
+ manufacturers: { count: 2, catalogsPerManufacturer: 2 },
72
+ catalogs: { carsPerCatalog: 20, revisions: { count: 1 } },
73
+ layers: {
74
+ prices: { currencies: ['EUR', 'CHF'], range: { min: 20000, max: 80000 } },
75
+ brands: { modelsPerManufacturer: 3, popularity: 'zipf' },
76
+ workshops: { perCatalog: 4 },
77
+ parts: { depth: 3, fanOut: 2 },
78
+ cad: { depth: 2, fanOut: 3 },
79
+ },
80
+ });
81
+
82
+ console.log(`estimated rows: ${edge.estimate().rowsTotal}`);
83
+ const { stats } = await edge.generate();
84
+ console.log(`generated rows: ${stats.rowsTotal}`);
85
+ ```
86
+
87
+ Stream the rows of a large world through a callback instead of keeping
88
+ them:
89
+
90
+ ```ts
91
+ import { Edge } from '@rljson/edge';
92
+
93
+ const rowsPerTable: Record<string, number> = {};
94
+ await Edge.preset('small').run({
95
+ onRow: (table) => {
96
+ rowsPerTable[table] = (rowsPerTable[table] ?? 0) + 1;
97
+ },
98
+ });
99
+ console.log(`prices: ${rowsPerTable.prices}, parts: ${rowsPerTable.parts}`);
100
+ ```
101
+
102
+ ## How It Works
103
+
104
+ ### The world
105
+
106
+ ```text
107
+ Manufacturers components manufacturers (the entry point)
108
+ └─ Manufacturer
109
+ ├─ Headquarters components addresses
110
+ └─ Catalogs jsonArray manufacturers.catalogsRef
111
+ └─ Catalog cakes catalogs (slices are cars)
112
+ ├─ Slice ids sliceIds carIds
113
+ ├─ Prices layers carPrices → components prices
114
+ ├─ Brands layers carBrands → components brands
115
+ ├─ Workshops layers carWorkshops → components workshops
116
+ │ ├─ addressRef → addresses
117
+ │ └─ ownerRef → persons
118
+ ├─ Parts layers carParts → components parts
119
+ │ └─ subPartRefs → parts
120
+ ├─ CAD layers carCad → trees cadScenes
121
+ │ └─ meta.partRef → parts
122
+ └─ Revisions revisions revisions (link the model years)
123
+ ```
124
+
125
+ - Every model year of a catalog is a cake of its own. Its slice ids and
126
+ layers build on the year before with `base`, `add` and `remove`, and a
127
+ revision links the two cakes.
128
+ - A bill of materials reaches up to four levels below the car. Levels 1 to
129
+ 3 belong to a variant, level 4 is a pool of standard parts every bill
130
+ shares. A CAD scene is a tree of groups and meshes, and a mesh refers to
131
+ a part.
132
+ - Lists are arrays of hashes in a `jsonArray` column that the table
133
+ configuration declares as a reference. Every table has a `TableCfg`, so
134
+ `BaseValidator` of `@rljson/rljson` checks types and references.
135
+
136
+ ### No randomness
137
+
138
+ Every value derives from an index into the dictionaries: 166
139
+ manufacturers, 917 models, nine assemblies with 36 parts each, 40
140
+ standard parts, 126 cities, 100 streets, 185 first names and 180 last
141
+ names. A share like `discountShare` selects every k-th car. The same
142
+ configuration yields the same hashes.
143
+
144
+ ### Row by row
145
+
146
+ Rows refer to rows by hash, so Edge emits a row after the rows it refers
147
+ to. It hashes every row, drops a row whose hash it emitted before, and
148
+ hands the rest to a sink, one at a time and awaited. `generate()` collects
149
+ them in an `EMemorySink`; `run(sink)` hands them to `onRow` and keeps
150
+ nothing, after `onTable` announced each table with its configuration.
151
+
152
+ Bills of materials and scenes are shared per car, per model or per
153
+ catalog. `estimate()` counts the rows of every table from the
154
+ configuration alone, and `scale.targetRows` uses it to pick the cars per
155
+ catalog.
156
+
157
+ ### Public API
158
+
159
+ | Export | Purpose |
160
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
161
+ | `Edge` | `new Edge(config)`, `preset()`, `estimate()`, `generate()`, `run()` |
162
+ | `mergeConfigs` | Merges two configurations section by section |
163
+ | `edgeDefaults`, `resolveEdgeConfig` | The defaults, and a configuration with defaults and checks |
164
+ | `edgePresets`, `edgePresetNames` | The presets `tiny`, `small`, `medium`, `large` and `xl` |
165
+ | `ESink`, `EMemorySink` | The callback interface of `run()` and the sink of `generate()` |
166
+ | `carWorldTableKeys`, `carWorldTableCfgs` | The table keys and their table configurations |
167
+ | `ECarWorld` and its row types | `EManufacturer`, `EAddress`, `EPerson`, `EWorkshop`, `EPrice`, `EBrand`, `EPart`, `ECadMeta` |
168
+ | `EConfig` and its sections | `ELayersConfig`, `EPricesConfig`, `EBrandsConfig`, `EWorkshopsConfig`, `EPartsConfig`, `ECadConfig`, `ERevisionsConfig`, `EResolvedConfig` |
169
+ | `EStats`, `EEstimate`, `EProgress` | The results and the progress of a run |
170
+ | Dictionaries | `manufacturers`, `vehicles`, `modelsOf`, `assemblies`, `standardParts`, `materialsOf`, `cadSystems`, `cadDetails`, `cadMaterials`, `cities`, `citiesIn`, `cityNamed`, `streets`, `firstNames`, `lastNames` |
171
+
172
+ ## Contributing
173
+
174
+ Work ticket by ticket with `gg`, as the [Develop Guide](doc/guides/develop-guide.md)
175
+ describes. Reviews follow the [Review Guide](doc/guides/for-ai/ai-review-guide.md).
@@ -0,0 +1,57 @@
1
+ import { EPartsConfig } from '../config/config.ts';
2
+ import { EEmitter } from '../core/emitter.ts';
3
+ import { EVariant } from './plan.ts';
4
+ /** A generated bill of materials */
5
+ export interface EBom {
6
+ /** The hash of the bill of materials: the part of level 0 */
7
+ rootRef: string;
8
+ /**
9
+ * The hashes of the parts in the order they were emitted, the root first.
10
+ * A standard part appears once for every part that contains it.
11
+ */
12
+ partRefs: string[];
13
+ }
14
+ /** What a bill of materials builder needs */
15
+ export interface EBomBuilderOptions {
16
+ /** Receives the rows */
17
+ emitter: EEmitter;
18
+ /** The parts layer configuration */
19
+ config: Required<EPartsConfig>;
20
+ /** The part number prefix of the manufacturer, e.g. "AUD" */
21
+ prefix: string;
22
+ /** Who the bill of materials belongs to */
23
+ variant: EVariant;
24
+ }
25
+ /** The level of the standard parts, a pool every bill of materials shares */
26
+ export declare const standardPartsLevel = 4;
27
+ /**
28
+ * Emits a bill of materials from the leaves up.
29
+ *
30
+ * Part numbers are handed out from the top down, so a part's number is
31
+ * lower than the numbers of its sub parts. Levels 1 to 3 carry the variant
32
+ * in their part numbers, so two variants never share them. Level 4, the
33
+ * standard parts, comes from a pool every bill shares. Build once per
34
+ * instance.
35
+ */
36
+ export declare class EBomBuilder {
37
+ private readonly options;
38
+ /**
39
+ * Constructor
40
+ * @param options - The emitter, the parts layer and the variant
41
+ */
42
+ constructor(options: EBomBuilderOptions);
43
+ /** Emits the bill of materials and returns its hashes */
44
+ build(): Promise<EBom>;
45
+ private sequence;
46
+ private readonly partRefs;
47
+ private get code();
48
+ private nextPartNumber;
49
+ private emit;
50
+ private root;
51
+ private assembly;
52
+ private subAssemblies;
53
+ private subAssembly;
54
+ private parts;
55
+ private part;
56
+ private standardParts;
57
+ }
@@ -0,0 +1,17 @@
1
+ import { EBodyType, EVehicleModel } from '../dictionaries/vehicles.ts';
2
+ import { EBrand } from './car-world.ts';
3
+ /** The body types from the cheapest to the most expensive */
4
+ export declare const bodyTypeRanks: readonly EBodyType[];
5
+ /**
6
+ * Returns the power of a model in kW: the typical power of its body type,
7
+ * plus a bit for every model the manufacturer lists before it.
8
+ * @param model - The model
9
+ * @param modelIndex - The index of the model within the manufacturer
10
+ */
11
+ export declare const powerKwOf: (model: EVehicleModel, modelIndex: number) => number;
12
+ /**
13
+ * Returns the brand row of a model.
14
+ * @param model - The model
15
+ * @param modelIndex - The index of the model within the manufacturer
16
+ */
17
+ export declare const brandRow: (model: EVehicleModel, modelIndex: number) => EBrand;
@@ -0,0 +1,135 @@
1
+ import { Json } from '@rljson/json';
2
+ import { CakesTable, ComponentsTable, LayersTable, Ref, RevisionsTable, Rljson, Row, SliceIdsTable, TablesCfgTable, TreesTable } from '@rljson/rljson';
3
+ import { EBodyType, EFuel } from '../dictionaries/vehicles.ts';
4
+ /** A manufacturer: the entry point of the world */
5
+ export interface EManufacturer extends Row {
6
+ /** The brand in kebab case, e.g. "mercedes-benz" */
7
+ id: string;
8
+ /** The name of the company */
9
+ name: string;
10
+ /** The brand the cars carry */
11
+ brand: string;
12
+ /** The ISO 3166 country code of the headquarters */
13
+ country: string;
14
+ /** The year the brand was founded */
15
+ founded: number;
16
+ /** The website, a made up address */
17
+ website: string;
18
+ /** The address of the headquarters */
19
+ headquartersRef: Ref;
20
+ /** The catalogs of the manufacturer, every model year, oldest first */
21
+ catalogsRef: Ref[];
22
+ }
23
+ /** A postal address with coordinates */
24
+ export interface EAddress extends Row {
25
+ street: string;
26
+ houseNumber: string;
27
+ zip: string;
28
+ city: string;
29
+ country: string;
30
+ lat: number;
31
+ lng: number;
32
+ }
33
+ /** A person, e.g. the owner of a workshop */
34
+ export interface EPerson extends Row {
35
+ firstName: string;
36
+ lastName: string;
37
+ email: string;
38
+ phone: string;
39
+ birthYear: number;
40
+ }
41
+ /** A workshop that services cars */
42
+ export interface EWorkshop extends Row {
43
+ name: string;
44
+ /** The address of the workshop */
45
+ addressRef: Ref;
46
+ /** The person who owns the workshop */
47
+ ownerRef: Ref;
48
+ phone: string;
49
+ email: string;
50
+ /** The services the workshop offers */
51
+ services: string[];
52
+ /** The rating from 3 to 5 */
53
+ rating: number;
54
+ }
55
+ /** The price of a car */
56
+ export interface EPrice extends Row {
57
+ amount: number;
58
+ currency: string;
59
+ /** The first day of the model year, as ISO date */
60
+ validFrom: string;
61
+ /** The discount in percent, 0 for none */
62
+ discountPercent: number;
63
+ taxIncluded: boolean;
64
+ }
65
+ /** The brand and model of a car */
66
+ export interface EBrand extends Row {
67
+ brand: string;
68
+ model: string;
69
+ bodyType: EBodyType;
70
+ fuel: EFuel;
71
+ powerKw: number;
72
+ }
73
+ /** A part of a bill of materials. Level 0 is the bill itself. */
74
+ export interface EPart extends Row {
75
+ name: string;
76
+ /** A part number, unique per manufacturer */
77
+ partNumber: string;
78
+ /** The category, e.g. "engine", or "bom" for the bill of materials */
79
+ category: string;
80
+ /** 0 for the bill of materials, up to 4 for a standard part */
81
+ level: number;
82
+ /** How many of the part the parent contains */
83
+ quantity: number;
84
+ /** The weight of one part in kilograms */
85
+ weightKg: number;
86
+ /** The material, or null for an assembly */
87
+ material: string | null;
88
+ /** The sub parts, empty for a leaf */
89
+ subPartRefs: Ref[];
90
+ }
91
+ /** The meta data of a node of a CAD scene */
92
+ export interface ECadMeta extends Json {
93
+ /** A group has children, a mesh has geometry */
94
+ type: 'group' | 'mesh';
95
+ /** What the scene shows, e.g. the model or the car it belongs to */
96
+ variant: string;
97
+ /** The position, rotation and scale relative to the parent */
98
+ transform: {
99
+ position: number[];
100
+ rotation: number[];
101
+ scale: number[];
102
+ };
103
+ /** The material of a mesh, null for a group */
104
+ material: string | null;
105
+ /** The number of vertices of a mesh, null for a group */
106
+ vertices: number | null;
107
+ /** Width, height and depth in meters */
108
+ boundsM: number[];
109
+ /** The part the mesh shows, or null */
110
+ partRef: Ref | null;
111
+ }
112
+ /** The generated world: an Rljson object with typed tables */
113
+ export interface ECarWorld extends Rljson {
114
+ manufacturers: ComponentsTable<EManufacturer>;
115
+ addresses: ComponentsTable<EAddress>;
116
+ persons: ComponentsTable<EPerson>;
117
+ workshops: ComponentsTable<EWorkshop>;
118
+ prices: ComponentsTable<EPrice>;
119
+ brands: ComponentsTable<EBrand>;
120
+ parts: ComponentsTable<EPart>;
121
+ cadScenes: TreesTable;
122
+ carIds: SliceIdsTable;
123
+ carPrices: LayersTable;
124
+ carBrands: LayersTable;
125
+ carWorkshops: LayersTable;
126
+ carParts: LayersTable;
127
+ carCad: LayersTable;
128
+ catalogs: CakesTable;
129
+ revisions: RevisionsTable;
130
+ tableCfgs: TablesCfgTable;
131
+ }
132
+ /** The keys of the tables of a car world, in the order they are announced */
133
+ export declare const carWorldTableKeys: readonly ["tableCfgs", "addresses", "persons", "workshops", "prices", "brands", "parts", "cadScenes", "carIds", "carPrices", "carBrands", "carWorkshops", "carParts", "carCad", "catalogs", "revisions", "manufacturers"];
134
+ /** The key of a table of a car world */
135
+ export type ECarWorldTableKey = (typeof carWorldTableKeys)[number];
@@ -0,0 +1,99 @@
1
+ import { EResolvedConfig } from '../config/config.ts';
2
+ import { EEmitter } from '../core/emitter.ts';
3
+ import { EVehicleModel } from '../dictionaries/vehicles.ts';
4
+ import { EBom } from './bom-builder.ts';
5
+ /** What a catalog needs to know about its manufacturer */
6
+ export interface ECatalogManufacturer {
7
+ /** The index of the manufacturer within the world */
8
+ index: number;
9
+ /** The id of the manufacturer, e.g. "audi" */
10
+ id: string;
11
+ /** The part number prefix, e.g. "AUD" */
12
+ prefix: string;
13
+ /** The ISO 3166 country code */
14
+ country: string;
15
+ /** The models the manufacturer offers */
16
+ models: readonly EVehicleModel[];
17
+ }
18
+ /** Bills of materials and scenes shared by the catalogs of a manufacturer */
19
+ export interface ECatalogCaches {
20
+ boms: Map<string, EBom>;
21
+ scenes: Map<string, string>;
22
+ }
23
+ /** What generating a catalog yields */
24
+ export interface ECatalogResult {
25
+ /** The hashes of the cakes, one per model year, oldest first */
26
+ cakeRefs: string[];
27
+ /** How many different cars the catalog lists over all years */
28
+ cars: number;
29
+ }
30
+ /** What a catalog generator needs */
31
+ export interface ECatalogGeneratorOptions {
32
+ /** Receives the rows */
33
+ emitter: EEmitter;
34
+ /** The resolved configuration */
35
+ config: EResolvedConfig;
36
+ /** The manufacturer of the catalog */
37
+ manufacturer: ECatalogManufacturer;
38
+ /** The index of the catalog within the manufacturer */
39
+ catalogIndex: number;
40
+ /** Bills of materials and scenes shared within the manufacturer */
41
+ caches: ECatalogCaches;
42
+ }
43
+ /**
44
+ * Emits one catalog with all its model years: components, slice ids, layers,
45
+ * cakes and revisions.
46
+ *
47
+ * Every model year is a cake of its own. Its slice ids and layers build on
48
+ * the year before, and a revision links the two cakes. Generate once per
49
+ * instance.
50
+ */
51
+ export declare class ECatalogGenerator {
52
+ private readonly options;
53
+ /**
54
+ * Constructor
55
+ * @param options - The emitter, the configuration and the catalog
56
+ */
57
+ constructor(options: ECatalogGeneratorOptions);
58
+ /** Emits the catalog and returns the hashes of its cakes */
59
+ generate(): Promise<ECatalogResult>;
60
+ private readonly catalogGlobal;
61
+ private readonly baseId;
62
+ private readonly segment;
63
+ private readonly plan;
64
+ private readonly modelCount;
65
+ private readonly currency;
66
+ private readonly aspects;
67
+ private workshopRefs;
68
+ private previousSliceRef;
69
+ private readonly cakeRefs;
70
+ private readonly previousLayer;
71
+ private readonly raises;
72
+ private readonly versionBoms;
73
+ private readonly current;
74
+ private get emitter();
75
+ private get layers();
76
+ private isFirst;
77
+ private newCarsOf;
78
+ private generateWorkshops;
79
+ private generateVersion;
80
+ private modelIndexOf;
81
+ private assignBrands;
82
+ private assignPrices;
83
+ private priceInputOf;
84
+ private assignWorkshops;
85
+ private assignParts;
86
+ private assignScenes;
87
+ private variantOf;
88
+ private shared;
89
+ private bomOf;
90
+ private sceneOf;
91
+ private partRefsOf;
92
+ private emitSliceIds;
93
+ private emitLayers;
94
+ private layerOf;
95
+ private addedCars;
96
+ private forget;
97
+ private emitCake;
98
+ private emitRevision;
99
+ }
@@ -0,0 +1,16 @@
1
+ import { EResolvedConfig } from '../config/config.ts';
2
+ import { EEmitter } from '../core/emitter.ts';
3
+ /**
4
+ * Returns the part number prefix of a brand: its first three letters.
5
+ * @param brand - The brand, e.g. "Mercedes-Benz"
6
+ */
7
+ export declare const prefixOf: (brand: string) => string;
8
+ /**
9
+ * Emits a whole car world: the table configurations, then manufacturer by
10
+ * manufacturer their catalogs, and returns how many cars it holds.
11
+ * @param config - The resolved configuration
12
+ * @param emitter - Receives the rows
13
+ */
14
+ export declare const generateCarWorld: (config: EResolvedConfig, emitter: EEmitter) => Promise<{
15
+ cars: number;
16
+ }>;
@@ -0,0 +1,130 @@
1
+ import { EResolvedConfig, ERevisionsConfig, ESharing } from '../config/config.ts';
2
+ import { EVehicleModel } from '../dictionaries/vehicles.ts';
3
+ /** The segments a manufacturer's catalogs are named after */
4
+ export declare const segments: readonly ["cars", "vans", "suvs", "fleet", "classics", "sports", "trucks", "compact", "premium", "electric"];
5
+ /** The digits of the car number in a slice id */
6
+ export declare const carNoDigits = 6;
7
+ /** The models a manufacturer uses when the brands layer is off */
8
+ export declare const modelsWithoutBrands = 5;
9
+ /**
10
+ * Returns the segment of a catalog, unique within a manufacturer.
11
+ * @param catalogIndex - The index of the catalog within the manufacturer
12
+ */
13
+ export declare const segmentOf: (catalogIndex: number) => string;
14
+ /**
15
+ * Returns the id of a catalog without its year, e.g. "audi-cars".
16
+ * @param manufacturerId - The id of the manufacturer
17
+ * @param catalogIndex - The index of the catalog within the manufacturer
18
+ */
19
+ export declare const catalogBaseId: (manufacturerId: string, catalogIndex: number) => string;
20
+ /**
21
+ * Returns the slice id of a car, e.g. "audi-cars-000017".
22
+ * @param baseId - The id of the catalog without its year
23
+ * @param carNo - The number of the car, starting at 1
24
+ */
25
+ export declare const carSliceId: (baseId: string, carNo: number) => string;
26
+ /**
27
+ * Returns the number of a car from its slice id.
28
+ * @param sliceId - The slice id of the car
29
+ */
30
+ export declare const carNoOf: (sliceId: string) => number;
31
+ /** One model year of a catalog */
32
+ export interface EVersionPlan {
33
+ /** The model year */
34
+ year: number;
35
+ /** The slice ids of all cars of the year */
36
+ carIds: string[];
37
+ /** The cars the year adds; all cars in the first year */
38
+ added: string[];
39
+ /** The cars the year removes */
40
+ removed: string[];
41
+ /** The cars whose price the year changes */
42
+ changed: string[];
43
+ }
44
+ /** What the model years of a catalog depend on */
45
+ export interface EPlanOptions {
46
+ /** The id of the catalog without its year */
47
+ baseId: string;
48
+ /** The number of cars in the first year */
49
+ cars: number;
50
+ /** The first model year */
51
+ startYear: number;
52
+ /** How many years follow, and their shares */
53
+ revisions: Required<ERevisionsConfig>;
54
+ }
55
+ /**
56
+ * Plans the model years of a catalog: which cars each year adds, removes and
57
+ * changes. Everything derives from the shares, nothing is random.
58
+ * @param options - The catalog, its first year and its revisions
59
+ */
60
+ export declare const planVersions: (options: EPlanOptions) => EVersionPlan[];
61
+ /** The counts of a catalog across its model years, for the estimate */
62
+ export interface EVersionCounts {
63
+ /** How many different cars the catalog lists over all years */
64
+ distinctCars: number;
65
+ /** How many price rows the years need at most, all years together */
66
+ priceRows: number;
67
+ /** How many price rows each year needs at most: new cars and changed prices */
68
+ priceRowsPerVersion: number[];
69
+ /** How many assignments the layers of all years hold together */
70
+ assignments: number;
71
+ }
72
+ /**
73
+ * Counts what planVersions would plan, without building a single id.
74
+ * @param cars - The number of cars in the first year
75
+ * @param revisions - How many years follow, and their shares
76
+ */
77
+ export declare const countVersions: (cars: number, revisions: Required<ERevisionsConfig>) => EVersionCounts;
78
+ /**
79
+ * Returns how many models the cars of a manufacturer use: the configured
80
+ * number, capped by the models the dictionary knows.
81
+ * @param config - The resolved configuration
82
+ * @param models - The models of the manufacturer
83
+ */
84
+ export declare const modelCountOf: (config: EResolvedConfig, models: readonly EVehicleModel[]) => number;
85
+ /** What the model of a car depends on */
86
+ export interface EModelOptions {
87
+ /** The number of the car, starting at 1 */
88
+ carNo: number;
89
+ /** The number of cars in the first year */
90
+ initialCars: number;
91
+ /** The number of models the manufacturer offers */
92
+ models: number;
93
+ /** uniform spreads the cars evenly, zipf favours the first models */
94
+ popularity: 'uniform' | 'zipf';
95
+ }
96
+ /**
97
+ * Returns the index of the model of a car. A car keeps its model over the
98
+ * years, so the index derives from the car number alone.
99
+ * @param options - The car, the catalog size and the models
100
+ */
101
+ export declare const modelIndexOf: (options: EModelOptions) => number;
102
+ /** Who shares a bill of materials or a scene: a code for ids and a name */
103
+ export interface EVariant {
104
+ /** A short code for part numbers and cache keys, e.g. "a4" */
105
+ code: string;
106
+ /** A readable name, e.g. "Audi A4" */
107
+ name: string;
108
+ /** A number that varies the assemblies and systems the variant uses */
109
+ offset: number;
110
+ }
111
+ /** What the variant of a car depends on */
112
+ export interface EVariantOptions {
113
+ /** perCar, perModel or perCatalog */
114
+ sharing: ESharing;
115
+ /** The slice id of the car */
116
+ sliceId: string;
117
+ /** The model of the car */
118
+ model: EVehicleModel;
119
+ /** The index of the model within the manufacturer */
120
+ modelIndex: number;
121
+ /** The segment of the catalog */
122
+ segment: string;
123
+ /** The index of the catalog within the manufacturer */
124
+ catalogIndex: number;
125
+ }
126
+ /**
127
+ * Returns the variant a car's bill of materials or scene belongs to.
128
+ * @param options - The sharing, the car, its model and its catalog
129
+ */
130
+ export declare const variantOf: (options: EVariantOptions) => EVariant;
@@ -0,0 +1,32 @@
1
+ import { EPricesConfig } from '../config/config.ts';
2
+ import { EVehicleModel } from '../dictionaries/vehicles.ts';
3
+ import { EPrice } from './car-world.ts';
4
+ /** A model has at most this many different prices per model year */
5
+ export declare const pricesPerModel = 100;
6
+ /** The discounts are this many steps of discountStepPercent: 5, 10 and 15 */
7
+ export declare const discountSteps = 3;
8
+ /** What a price depends on */
9
+ export interface EPriceInput {
10
+ /** The model of the car: its body type decides the price class */
11
+ model: EVehicleModel;
12
+ /** The index of the model within the manufacturer */
13
+ modelIndex: number;
14
+ /** The number of the car, starting at 1 */
15
+ carNo: number;
16
+ /** The model year the price is valid from */
17
+ year: number;
18
+ /** The currency of the catalog */
19
+ currency: string;
20
+ /** How many model years raised the price: 0 for the first price of a car */
21
+ raises: number;
22
+ }
23
+ /**
24
+ * Returns the price row of a car.
25
+ *
26
+ * Expensive body types land high in the range, cheap ones low. The model and
27
+ * the car number spread the prices a little: a model has at most a hundred
28
+ * different prices per year. Every raise adds three percent.
29
+ * @param config - The prices layer configuration
30
+ * @param input - What the price depends on
31
+ */
32
+ export declare const priceRow: (config: Required<EPricesConfig>, input: EPriceInput) => EPrice;