Skip to content

Commit 7e78eb8

Browse files
committed
bazi: Implement root weighting system.
1 parent 2792aa6 commit 7e78eb8

2 files changed

Lines changed: 34 additions & 17 deletions

File tree

src/stellium/chinese/bazi/strength.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,28 +129,45 @@ def _get_seasonal_score(element: Element, month_branch: EarthlyBranch) -> int:
129129
return 0
130130

131131

132-
def _count_roots(chart: "BaZiChart", day_master_element: Element) -> int:
133-
"""Count how many branch hidden stems match the Day Master's element.
132+
def _count_roots(chart: "BaZiChart", day_master_element: Element) -> float:
133+
"""Calculate weighted root strength from branch hidden stems.
134134
135135
A "root" (根) is when the Day Master's element appears as a hidden
136-
stem in any of the four branches. More roots = more grounded and stable.
136+
stem in any of the four branches. Root position matters:
137137
138-
The Day Pillar's own branch is especially significant (called 禄/lu
139-
or 根/gen depending on the position).
138+
- Day branch (坐下, "sitting beneath"): weight 1.5 — YOUR branch,
139+
the strongest possible root
140+
- Month branch (月支, "command"): weight 1.2 — seasonal authority
141+
- Hour branch (时支): weight 0.8 — children/future pillar
142+
- Year branch (年支): weight 0.6 — ancestors/distant pillar
143+
144+
Within each branch, the hidden stem position also matters:
145+
- Main qi (本气): full weight — primary energy
146+
- Middle qi (中气): 60% weight — secondary
147+
- Residual qi (余气): 30% weight — trace energy
140148
141149
Args:
142150
chart: The BaZi chart
143151
day_master_element: The Day Master's element
144152
145153
Returns:
146-
Number of matching hidden stems (0-12 theoretically, usually 0-4)
154+
Weighted root score (typically 0-6)
147155
"""
148-
roots = 0
149-
for pillar in chart.pillars:
150-
for hidden_stem in pillar.branch.get_hidden_stem_objects():
156+
# Pillar weights by position (Day is most significant)
157+
pillar_weights = [0.6, 1.2, 1.5, 0.8] # year, month, day, hour
158+
159+
# Hidden stem position weights (main > middle > residual)
160+
position_weights = [1.0, 0.6, 0.3]
161+
162+
total = 0.0
163+
for pillar, p_weight in zip(chart.pillars, pillar_weights, strict=True):
164+
hidden_stems = pillar.branch.get_hidden_stem_objects()
165+
for i, hidden_stem in enumerate(hidden_stems):
151166
if hidden_stem.element == day_master_element:
152-
roots += 1
153-
return roots
167+
h_weight = position_weights[i] if i < len(position_weights) else 0.2
168+
total += p_weight * h_weight
169+
170+
return total
154171

155172

156173
def _count_support_drain(
@@ -201,7 +218,7 @@ class StrengthAnalysis:
201218

202219
# Component scores
203220
seasonal_score: int
204-
root_count: int
221+
root_count: float
205222
support_count: int
206223
drain_count: int
207224
month_branch: EarthlyBranch
@@ -261,7 +278,7 @@ def to_dict(self) -> dict:
261278
},
262279
"factors": {
263280
"seasonal_score": self.seasonal_score,
264-
"root_count": self.root_count,
281+
"root_count": round(self.root_count, 2),
265282
"support_count": self.support_count,
266283
"drain_count": self.drain_count,
267284
},
@@ -282,8 +299,8 @@ def display(self) -> str:
282299
f" Seasonal: {self.seasonal_score:+d} "
283300
f"({self.day_master_element.english} in "
284301
f"{self.month_branch.pinyin} month)",
285-
f" Roots: {self.root_count} "
286-
f"(hidden stems matching {self.day_master_element.english})",
302+
f" Roots: {self.root_count:.1f} "
303+
f"(weighted hidden stems matching {self.day_master_element.english})",
287304
f" Support: {self.support_count} (Self + Companion + Resource)",
288305
f" Drain: {self.drain_count} (Output + Wealth + Power)",
289306
"",

tests/test_bazi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,7 @@ def test_roots_found(self):
11761176
chart = engine.calculate(datetime(2000, 12, 21, 0, 0))
11771177
dm_element = chart.day_master.element
11781178
roots = _count_roots(chart, dm_element)
1179-
assert isinstance(roots, int)
1179+
assert isinstance(roots, float)
11801180
assert roots >= 0
11811181

11821182
def test_roots_range(self):
@@ -1233,7 +1233,7 @@ def test_analysis_has_all_fields(self):
12331233
assert result.strength is not None
12341234
assert isinstance(result.score, float)
12351235
assert isinstance(result.seasonal_score, int)
1236-
assert isinstance(result.root_count, int)
1236+
assert isinstance(result.root_count, float)
12371237
assert isinstance(result.support_count, int)
12381238
assert isinstance(result.drain_count, int)
12391239

0 commit comments

Comments
 (0)