Designing archetypes¶
A segment's archetype is the trajectory shape its entities follow over the time window. plotsim ships six built-in shapes plus a small DSL for chaining and shifting them. This notebook walks through each shape, then composes them, then layers in baselines.
Under the hood every shape is a curve over [0, 1] that the engine evaluates per period. Every metric value an entity emits is derived from that single trajectory position — so two metrics on the same entity move together, by construction.
The six built-in shapes¶
| Shape | What it does |
|---|---|
growth |
smooth S-curve rise |
decline |
exponential fade |
seasonal |
two oscillation cycles |
flat |
low and constant |
spike_then_crash |
rapid rise, drop, low plateau |
accelerating |
compound growth |
Each is built from one or more curve primitives — from plotsim.builder import SHAPE_RECIPES exposes the parameter values.
import numpy as np
import matplotlib.pyplot as plt
from plotsim import create, generate_tables
# One segment per built-in shape, three entities each (the minimum count
# the builder accepts), 24 monthly periods, one score metric.
config = create(
about="Archetype showcase",
unit="entity",
window=("2023-01", "2024-12", "monthly"),
metrics=[{"name": "value", "type": "score", "polarity": "positive"}],
segments=[
{"name": "growth_seg", "count": 3, "archetype": "growth",
"attributes": {"shape": "growth"}},
{"name": "decline_seg", "count": 3, "archetype": "decline",
"attributes": {"shape": "decline"}},
{"name": "seasonal_seg", "count": 3, "archetype": "seasonal",
"attributes": {"shape": "seasonal"}},
{"name": "flat_seg", "count": 3, "archetype": "flat",
"attributes": {"shape": "flat"}},
{"name": "spike_seg", "count": 3, "archetype": "spike_then_crash",
"attributes": {"shape": "spike_then_crash"}},
{"name": "accelerating_seg", "count": 3, "archetype": "accelerating",
"attributes": {"shape": "accelerating"}},
],
)
tables = generate_tables(config, np.random.default_rng(config.seed))
sorted(tables)
# One representative trajectory from each segment, joined to the date dim.
fct = (
tables["fct_entity"]
.merge(tables["dim_date"][["date_key", "period_label"]], on="date_key")
.merge(tables["dim_entity"][["entity_id", "shape"]], on="entity_id")
)
fig, ax = plt.subplots(figsize=(10, 4.5))
for shape, group in fct.groupby("shape"):
one = group[group["entity_id"] == group["entity_id"].iloc[0]].sort_values("date_key")
ax.plot(one["period_label"], one["value"], marker="o", label=shape)
ax.set_title("One representative trajectory per built-in shape")
ax.set_xlabel("Period"); ax.set_ylabel("value (0–1)")
ax.tick_params(axis="x", rotation=45)
ax.legend(fontsize=8, ncol=2, loc="best")
plt.tight_layout(); plt.show()
Chaining shapes¶
The DSL lets you chain shapes in sequence. > separates phases and @ N marks the transition period:
growth > decline @ 12 # 12 periods of growth, then exponential decline
decline > flat > growth @ 6 @ 14 # fade, hit bottom at month 6, recover at 14
flat > accelerating @ 8 # quiet for 8 months, then explosive growth
The number of @ transitions is always one less than the number of chained shapes. The transition periods must be strictly ascending and strictly inside [1, n_periods - 1].
config = create(
about="Composite archetypes",
unit="entity",
window=("2023-01", "2024-12", "monthly"),
metrics=[{"name": "value", "type": "score", "polarity": "positive"}],
segments=[
{"name": "rise_then_fall", "count": 5, "archetype": "growth > decline @ 12",
"attributes": {"shape": "rise_then_fall"}},
{"name": "u_curve", "count": 5, "archetype": "decline > flat > growth @ 6 @ 14",
"attributes": {"shape": "u_curve"}},
{"name": "explosive", "count": 5, "archetype": "flat > accelerating @ 8",
"attributes": {"shape": "explosive"}},
],
)
tables = generate_tables(config, np.random.default_rng(config.seed))
fct = (
tables["fct_entity"]
.merge(tables["dim_date"][["date_key", "period_label"]], on="date_key")
.merge(tables["dim_entity"][["entity_id", "shape"]], on="entity_id")
)
fig, ax = plt.subplots(figsize=(10, 4))
for shape, group in fct.groupby("shape"):
one = group[group["entity_id"] == group["entity_id"].iloc[0]].sort_values("date_key")
ax.plot(one["period_label"], one["value"], marker="o", label=shape)
ax.set_title("Composite archetypes — sequential phases")
ax.tick_params(axis="x", rotation=45)
ax.legend(); plt.tight_layout(); plt.show()
Baselines¶
A baseline shifts a segment's metric value range without changing the trajectory shape. high restricts to the upper third of the metric range, low to the lower third, mid to the middle.
This is how you say "these segments share a shape but live in different value bands" — for example, three customer tiers all growing, but with very different revenue.
config = create(
about="Baseline shift demo",
unit="customer",
window=("2023-01", "2024-12", "monthly"),
metrics=[
{"name": "revenue", "type": "amount", "polarity": "positive", "range": [100, 10000]},
],
segments=[
{"name": "enterprise", "count": 5, "archetype": "growth",
"attributes": {"tier": "enterprise"}, "baseline": {"revenue": "high"}},
{"name": "midmarket", "count": 5, "archetype": "growth",
"attributes": {"tier": "midmarket"}, "baseline": {"revenue": "mid"}},
{"name": "starter", "count": 5, "archetype": "growth",
"attributes": {"tier": "starter"}, "baseline": {"revenue": "low"}},
],
)
tables = generate_tables(config, np.random.default_rng(config.seed))
fct = (
tables["fct_customer"]
.merge(tables["dim_date"][["date_key", "period_label"]], on="date_key")
.merge(tables["dim_customer"][["customer_id", "tier"]], on="customer_id")
)
fig, ax = plt.subplots(figsize=(10, 4))
for tier, group in fct.groupby("tier"):
one = group[group["customer_id"] == group["customer_id"].iloc[0]].sort_values("date_key")
ax.plot(one["period_label"], one["revenue"], marker="o", label=f"{tier} (baseline)")
ax.set_title("Same growth shape, three value bands")
ax.set_xlabel("Period"); ax.set_ylabel("revenue")
ax.tick_params(axis="x", rotation=45)
ax.legend(); plt.tight_layout(); plt.show()
Where to next¶
- Schema and dimensions —
schema_and_dimensions.ipynbshows how to swap the auto-generated schema for explicit dim/fact/event tables with FK references, SCD columns, and faker-generated text. - Connections and seasonality —
seasonality_and_correlations.ipynbcovers how archetype shapes interact with global seasonal modulation and per-metric correlations. - Archetypes —
docs/site/user-guide/archetypes.mdcatalogs every shape word and the DSL grammar.