Children’s clothing sizing is messier than adult sizing for a structural reason: children grow. An adult can wear the same size for years; a child may need a new size every three to six months in infancy, tapering to once or twice a year by age eight. A sizing system for children has to account not just for current measurements but for growth rate and the practical need to buy ahead.
The two systems: age-based and height-based
Most children’s clothing uses one of two labeling conventions — or a hybrid:
Age-based labels — “0–3M”, “3–6M”, “6–9M”, “12M”, “18M”, then “2T”, “3T”, “4T” (Toddler), then numeric “4”, “5”, “6” through “14” or “16”. These are intuitive for parents but deeply imprecise: a large 6-month-old and a small 9-month-old both fit in “6–9M” clothes.
Height-based labels — Common in European sizing (particularly German, French, and Scandinavian brands). A label of “80” means the garment fits a child approximately 80cm tall. More accurate than age, but parents in the US rarely know their child’s height in cm.
# Age-to-height mapping
# Source: WHO Child Growth Standards median heights
# Columns: age_months, median_height_cm (boys), median_height_cm (girls)
MEDIAN_HEIGHTS_BY_AGE_MONTHS = {
0: (49.9, 49.1),
1: (54.7, 53.7),
2: (58.4, 57.1),
3: (61.4, 59.8),
4: (63.9, 62.1),
6: (67.6, 65.7),
9: (72.0, 70.1),
12: (75.7, 74.0),
18: (82.3, 80.7),
24: (87.8, 86.4),
30: (92.1, 91.1),
36: (96.1, 95.1),
48: (102.9, 101.6),
60: (109.2, 108.4),
72: (115.1, 114.6),
84: (121.2, 120.0),
96: (126.6, 126.5),
108: (132.0, 132.2),
120: (137.6, 138.6),
132: (142.9, 144.8),
144: (148.4, 150.7),
156: (154.5, 155.6)
}
def age_months_to_median_height_cm(age_months: int, gender: str) -> float:
"""Interpolate median height for an age in months."""
ages = sorted(MEDIAN_HEIGHTS_BY_AGE_MONTHS.keys())
if age_months <= ages[0]:
heights = MEDIAN_HEIGHTS_BY_AGE_MONTHS[ages[0]]
return heights[0] if gender == "male" else heights[1]
if age_months >= ages[-1]:
heights = MEDIAN_HEIGHTS_BY_AGE_MONTHS[ages[-1]]
return heights[0] if gender == "male" else heights[1]
# Linear interpolation between surrounding ages
for i, age in enumerate(ages[:-1]):
if age <= age_months <= ages[i + 1]:
lower_h = MEDIAN_HEIGHTS_BY_AGE_MONTHS[age]
upper_h = MEDIAN_HEIGHTS_BY_AGE_MONTHS[ages[i + 1]]
frac = (age_months - age) / (ages[i + 1] - age)
idx = 0 if gender == "male" else 1
return lower_h[idx] + frac * (upper_h[idx] - lower_h[idx])
return 100.0 # Fallback
Predicting dimensions with the pediatric API model
The DimensionsPot API has dedicated pediatric calculation models accessed via age_category. This handles the pediatric model internally — you don’t need to pass a separate calculation_model flag when age_category is set:
import requests
def predict_child_dimensions(
gender: str,
height_cm: float,
weight_kg: float,
age_months: int,
region: str = "GLOBAL"
) -> dict:
"""
Predict body dimensions for a child using the pediatric API model.
age_months: child's age in months (determines which model tier is used)
height_cm: recumbent length for age <24 months, standing height for age >=24 months
"""
# Map age_months to age_category
if age_months < 3:
age_category = "INFANT"
elif age_months < 24:
age_category = "TODDLER"
elif age_months < 72:
age_category = "CHILD"
elif age_months < 120:
age_category = "PRE_TEEN"
else:
age_category = "TEEN"
response = requests.post(
"https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict",
json={
"input_data": {
"input_unit_system": "metric",
"subject": {
"gender": gender,
"age_category": age_category,
"input_origin_region": region
},
"anchors": {
"body_height": int(height_cm * 10), # cm → mm
"body_mass": weight_kg
}
},
"output_settings": {
"calculation": {
"target_region": region,
"body_build_type": "CIVILIAN"
},
"requested_dimensions": {
"bundle": "FULL_BODY"
},
"output_format": {
"include_range_95": True,
"confidence_score_threshold": 55
}
}
},
headers={
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "dimensionspot-bodysize-engine.p.rapidapi.com"
}
)
data = response.json()
return {
dim_id: {
"value": d.get("value"),
"range_95": d.get("range_95"),
"confidence_score": d.get("confidence_score")
}
for dim_id, d in data.get("body_dimensions", {}).items()
if d.get("value") is not None
}
Size chart mapping: infant through pre-teen
Children’s size systems differ by region and brand, but a reasonable universal approach is to map heights to size labels:
from dataclasses import dataclass
@dataclass
class ChildSizeEntry:
label: str # e.g., "6-9M", "3T", "6"
age_range_months: tuple # (min_months, max_months) — reference only
height_range_cm: tuple # (min_cm, max_cm) — primary matching criterion
chest_range_mm: tuple # Secondary matching criterion
waist_range_mm: tuple
inseam_range_mm: tuple # For bottoms/pants
region: str # "US", "EU", "UK"
# US children's size chart (height-based, source: major US brands average)
US_CHILDREN_SIZES = [
ChildSizeEntry("Preemie", (0, 0), (0, 47), (0, 0), (0, 0), (0, 0), "US"),
ChildSizeEntry("Newborn", (0, 1), (47, 55), (0, 0), (0, 0), (0, 0), "US"),
ChildSizeEntry("0-3M", (0, 3), (55, 60), (380, 420), (360, 400), (150, 200), "US"),
ChildSizeEntry("3-6M", (3, 6), (60, 65), (420, 445), (400, 430), (200, 250), "US"),
ChildSizeEntry("6-9M", (6, 9), (65, 70), (445, 470), (430, 455), (250, 290), "US"),
ChildSizeEntry("9-12M", (9, 12), (70, 75), (470, 490), (455, 475), (290, 330), "US"),
ChildSizeEntry("12M", (12, 15), (75, 78), (490, 510), (475, 490), (330, 370), "US"),
ChildSizeEntry("18M", (15, 21), (78, 83), (510, 530), (490, 510), (370, 410), "US"),
ChildSizeEntry("24M", (21, 27), (83, 89), (530, 555), (510, 530), (410, 450), "US"),
ChildSizeEntry("2T", (24, 33), (89, 94), (555, 570), (530, 545), (450, 490), "US"),
ChildSizeEntry("3T", (30, 42), (94, 100), (570, 590), (545, 560), (490, 530), "US"),
ChildSizeEntry("4T", (42, 54), (100, 107), (590, 610), (560, 575), (530, 570), "US"),
ChildSizeEntry("5", (54, 66), (107, 114), (610, 635), (575, 590), (570, 610), "US"),
ChildSizeEntry("6", (66, 78), (114, 121), (635, 660), (590, 605), (610, 655), "US"),
ChildSizeEntry("7", (78, 90), (121, 128), (660, 685), (605, 620), (655, 700), "US"),
ChildSizeEntry("8", (87, 102), (128, 135), (685, 710), (620, 640), (700, 745), "US"),
ChildSizeEntry("10", (99, 114), (135, 142), (710, 745), (640, 660), (745, 790), "US"),
ChildSizeEntry("12", (111, 126),(142, 150), (745, 785), (660, 690), (790, 840), "US"),
ChildSizeEntry("14", (123, 138),(150, 160), (785, 830), (690, 720), (840, 890), "US"),
ChildSizeEntry("16", (135, 150),(160, 170), (830, 870), (720, 755), (890, 940), "US"),
]
# European/metric size chart — label = height_cm approximation
EU_CHILDREN_SIZES = [
ChildSizeEntry("50", (0, 1), (48, 53), (0, 0), (0, 0), (0, 0), "EU"),
ChildSizeEntry("56", (0, 3), (53, 59), (360, 400), (340, 380), (140, 190), "EU"),
ChildSizeEntry("62", (2, 5), (59, 65), (400, 430), (380, 410), (190, 240), "EU"),
ChildSizeEntry("68", (4, 8), (65, 71), (430, 455), (410, 435), (240, 285), "EU"),
ChildSizeEntry("74", (7, 12), (71, 77), (455, 475), (435, 460), (285, 330), "EU"),
ChildSizeEntry("80", (11, 17), (77, 83), (475, 500), (460, 485), (330, 375), "EU"),
ChildSizeEntry("86", (15, 22), (83, 89), (500, 525), (485, 510), (375, 415), "EU"),
ChildSizeEntry("92", (21, 30), (89, 95), (525, 545), (510, 530), (415, 455), "EU"),
ChildSizeEntry("98", (28, 40), (95, 101), (545, 570), (530, 550), (455, 500), "EU"),
ChildSizeEntry("104", (36, 52), (101, 107), (570, 595), (550, 570), (500, 545), "EU"),
ChildSizeEntry("110", (48, 66), (107, 113), (595, 620), (570, 590), (545, 590), "EU"),
ChildSizeEntry("116", (60, 78), (113, 119), (620, 645), (590, 610), (590, 635), "EU"),
ChildSizeEntry("122", (72, 90), (119, 125), (645, 670), (610, 630), (635, 680), "EU"),
ChildSizeEntry("128", (84, 102), (125, 131), (670, 700), (630, 655), (680, 730), "EU"),
ChildSizeEntry("134", (96, 114), (131, 137), (700, 730), (655, 680), (730, 780), "EU"),
ChildSizeEntry("140", (108,126), (137, 143), (730, 760), (680, 705), (780, 830), "EU"),
ChildSizeEntry("146", (120,138), (143, 149), (760, 795), (705, 735), (830, 880), "EU"),
ChildSizeEntry("152", (132,150), (149, 156), (795, 830), (735, 765), (880, 935), "EU"),
ChildSizeEntry("158", (144,162), (156, 163), (830, 865), (765, 795), (935, 990), "EU"),
ChildSizeEntry("164", (156,175), (163, 170), (865, 905), (795, 830), (990, 1045), "EU"),
]
The size recommendation function
def recommend_children_size(
height_cm: float,
chest_mm: float | None,
waist_mm: float | None,
inseam_mm: float | None = None,
garment_type: str = "tops", # "tops", "bottoms", "outerwear"
region: str = "US",
growth_accommodation: str = "current" # "current", "size_up", "size_up_2"
) -> dict:
"""
Recommend a children's size.
growth_accommodation:
"current" — fits now
"size_up" — buy one size larger (3–6 months growth room)
"size_up_2" — buy two sizes larger (6–12 months growth room)
"""
size_chart = US_CHILDREN_SIZES if region == "US" else EU_CHILDREN_SIZES
candidates = []
for entry in size_chart:
min_h, max_h = entry.height_range_cm
if min_h == 0 and max_h == 0:
continue
# Primary filter: height fit
if not (min_h <= height_cm <= max_h):
continue
score = 0.0
# Height score: how centered in the range?
mid_h = (min_h + max_h) / 2
range_h = max_h - min_h
height_score = 1.0 - abs(height_cm - mid_h) / (range_h / 2)
score += height_score * 0.5
# Chest score (for tops/outerwear)
if chest_mm and entry.chest_range_mm[0] > 0 and garment_type in ("tops", "outerwear"):
min_c, max_c = entry.chest_range_mm
if min_c <= chest_mm <= max_c:
mid_c = (min_c + max_c) / 2
chest_score = 1.0 - abs(chest_mm - mid_c) / ((max_c - min_c) / 2)
score += chest_score * 0.3
else:
score -= 0.3 # Chest doesn't fit
# Waist score (for bottoms/outerwear)
if waist_mm and entry.waist_range_mm[0] > 0 and garment_type in ("bottoms", "outerwear"):
min_w, max_w = entry.waist_range_mm
if min_w <= waist_mm <= max_w:
mid_w = (min_w + max_w) / 2
waist_score = 1.0 - abs(waist_mm - mid_w) / ((max_w - min_w) / 2)
score += waist_score * 0.3
else:
score -= 0.3
# Inseam score (for bottoms)
if inseam_mm and entry.inseam_range_mm[0] > 0 and garment_type == "bottoms":
min_i, max_i = entry.inseam_range_mm
if min_i <= inseam_mm <= max_i:
mid_i = (min_i + max_i) / 2
inseam_score = 1.0 - abs(inseam_mm - mid_i) / ((max_i - min_i) / 2)
score += inseam_score * 0.2
candidates.append((entry, score))
if not candidates:
# Fall back to nearest height
nearest = min(size_chart, key=lambda e: abs(
(e.height_range_cm[0] + e.height_range_cm[1]) / 2 - height_cm
) if e.height_range_cm[1] > 0 else float("inf"))
candidates = [(nearest, 0.0)]
# Sort by score
candidates.sort(key=lambda x: x[1], reverse=True)
best_entry, best_score = candidates[0]
# Apply growth accommodation
best_idx = size_chart.index(best_entry)
size_up_steps = {"current": 0, "size_up": 1, "size_up_2": 2}
steps = size_up_steps.get(growth_accommodation, 0)
recommended_idx = min(best_idx + steps, len(size_chart) - 1)
recommended_entry = size_chart[recommended_idx]
return {
"recommended_size": recommended_entry.label,
"current_fit_size": best_entry.label,
"growth_accommodation": growth_accommodation,
"height_range_cm": f"{recommended_entry.height_range_cm[0]}–{recommended_entry.height_range_cm[1]} cm",
"region": region,
"fit_confidence": round(best_score, 2),
"note": _growth_note(growth_accommodation, best_entry.label, recommended_entry.label)
}
def _growth_note(accommodation: str, current_label: str, recommended_label: str) -> str | None:
if accommodation == "current":
return None
if accommodation == "size_up":
return f"Sized up from {current_label} to {recommended_label} for approximately 3–6 months of growth room."
return f"Sized up from {current_label} to {recommended_label} for approximately 6–12 months of growth room."
Complete pipeline: from age to size
def children_size_from_age(
age_months: int,
gender: str,
garment_type: str = "tops",
region: str = "US",
growth_accommodation: str = "size_up",
known_height_cm: float | None = None,
known_weight_kg: float | None = None
) -> dict:
"""
Recommend children's clothing size from age (and optionally height/weight).
If height and weight are provided, uses the API for precise dimension prediction.
If only age is provided, uses WHO median heights as height estimate.
"""
# Use known height, or estimate from age median
if known_height_cm:
height_cm = known_height_cm
else:
height_cm = age_months_to_median_height_cm(age_months, gender)
# Estimate weight from WHO median if not provided (rough — better to provide actual)
if known_weight_kg:
weight_kg = known_weight_kg
else:
# Very approximate weight from height (Livi formula, pediatric)
weight_kg = (height_cm - 100) * 0.5 + 50 * 0.5 # Rough only
weight_kg = max(2.0, min(80.0, weight_kg))
# Get API predictions if we have reasonable inputs
chest_mm = waist_mm = inseam_mm = None
if age_months >= 1 and height_cm > 40:
try:
dims = predict_child_dimensions(gender, height_cm, weight_kg, age_months)
chest_mm = dims.get("chest_circumference", {}).get("value")
waist_mm = dims.get("waist_circumference_natural", {}).get("value")
inseam_mm = dims.get("inseam_length", {}).get("value")
except Exception:
pass # Fall back to height-only sizing
result = recommend_children_size(
height_cm=height_cm,
chest_mm=chest_mm,
waist_mm=waist_mm,
inseam_mm=inseam_mm,
garment_type=garment_type,
region=region,
growth_accommodation=growth_accommodation
)
result["inputs"] = {
"age_months": age_months,
"age_display": _format_age(age_months),
"gender": gender,
"height_cm": round(height_cm, 1),
"height_source": "measured" if known_height_cm else "WHO median estimate",
"weight_kg": round(weight_kg, 1),
"weight_source": "measured" if known_weight_kg else "estimated from height"
}
return result
def _format_age(months: int) -> str:
if months < 24:
return f"{months} months"
years = months // 12
rem = months % 12
if rem == 0:
return f"{years} years"
return f"{years} years {rem} months"
Growth accommodation strategy
def suggest_growth_accommodation(
age_months: int,
budget_sensitivity: str = "normal" # "gift" (buy ahead), "normal", "economy"
) -> str:
"""
Recommend a growth accommodation strategy based on child's age and context.
Children grow fastest in infancy — buy further ahead for younger children.
"""
if budget_sensitivity == "gift":
# Gifts should have extra growth room — recipient may not use immediately
if age_months < 6:
return "size_up_2"
return "size_up"
if age_months < 3:
# Newborns grow rapidly, current fit is often wrong by the time it arrives
return "size_up"
elif age_months < 12:
# Infants: one size up is sensible for most purchases
return "size_up"
elif age_months < 36:
# Toddlers: one size up for seasonal items, current for basics
return "size_up" if budget_sensitivity == "normal" else "current"
else:
# Children 3+: growth slows, current fit more appropriate
return "current"
API response example
result = children_size_from_age(
age_months=18,
gender="female",
garment_type="tops",
region="US",
growth_accommodation="size_up",
known_height_cm=81.0,
known_weight_kg=10.5
)
# Result:
# {
# "recommended_size": "24M",
# "current_fit_size": "18M",
# "growth_accommodation": "size_up",
# "height_range_cm": "83–89 cm",
# "region": "US",
# "fit_confidence": 0.82,
# "note": "Sized up from 18M to 24M for approximately 3–6 months of growth room.",
# "inputs": {
# "age_months": 18,
# "age_display": "18 months",
# "gender": "female",
# "height_cm": 81.0,
# "height_source": "measured",
# "weight_kg": 10.5,
# "weight_source": "measured"
# }
# }
Practical considerations
Always ask for height, not just age. Age-based sizing in the code above is a fallback. The most accurate recommendation requires actual height. A child at the 95th height percentile and one at the 5th percentile are the same age but need very different sizes. If your UX can collect height (even approximately — “about 85cm”), use it.
Infant weight is a better proxy than infant age. For 0–12 months, body weight is more strongly correlated with clothing fit than age. If the parent knows their baby’s weight from a recent checkup, use it as the primary input.
Mark sizes clearly as region-specific. A “size 6” in the US is not the same as a “6” in Europe (which is EU “118” approximately). If your catalog includes international brands, convert all sizes to a measurement basis (height + chest + waist) and then back to each market’s label system.
For infants, note the position of measurement. The API expects recumbent length (lying down) for infants under 24 months — the same measurement taken standing is approximately 0.7–1.0cm shorter. The body_height anchor you send should reflect how the measurement was taken; the API’s pediatric model uses length norms, not standing height norms, for INFANT and TODDLER categories.