Audience: an analyst reviewing the method and the evidence. No code knowledge assumed. Date: 2026-09-22. Every figure below was measured, not estimated; where something is an estimate or a judgement it says so.
A scoring engine that answers one question: do two astrological factors express the same underlying archetypal pattern?
It decomposes any chart factor into "genes" — the archetypal functions, qualities and processes it is made of — and scores two factors by how much of that material they share.
The point is that it sees past the form to the content:
Moon Opposition Pluto | an aspect between two planets |
Moon in 8th House | a placement naming neither Pluto nor an aspect |
These score 100% affinity, because the 8th house is ruled by Pluto, so both say the Moon meeting Pluto — once by angle, once by rulership. No text matching could find that.
It is an archetypal match, not interchangeable meaning. Venus square Saturn and Venus trine Saturn have the same underlying Venus–Saturn ingredients, and they do not mean the same thing. The aspect angle describes how those ingredients relate; affinity measures only which ingredients are present. The two score 100% against each other for that reason. The angle is a separate dimension that belongs alongside this number, never folded into it.
This distinction was the single most-repeated correction from external review, and the wording was changed at the source because of it.
Outward — "who else has this?" Scored against the full 22,693-component corpus at an 85% cut.
What is validated here is narrower than "finding similar people". The evidence supports "we can identify shared archetypal patterns between two charts". It does not yet support "we can identify astrologically similar people" — that additionally requires knowing how COMMON a shared pattern is, and no population frequency has been measured. One natal aspect in six is generational (§4). Until rarity weighting exists, two strangers can share a factor and it can mean nothing. Treat person-matching as designed and costed, not validated.
Inward — "what else in my own chart is like this?" Scored against the ~740 factors of one person's chart at a 70% cut.
The cut differs because the cut is a property of the candidate pool, not of the engine. 85 was calibrated against 22,000 corpus components where a loose threshold admits thousands of strangers. A chart is 740 factors that are already all about this one person, so the same number means something different. Using 85 inside a chart leaves one query in eight returning nothing at all.
134,061 scored pairs from 20 queries stratified across aspects, houses, signs, figures and all six chart layers. Each candidate was labelled by whether it genuinely carries the query's signature (function-set containment either way).
| cut | pool | genuinely shares the signature | shares nothing |
|---|---|---|---|
| 85% | 1,087 | 96% | 4% |
| 80% | 2,161 | 86% | 14% |
| 75% | 4,048 | 71% | 22% |
| 70% | 7,376 | 50% | 41% |
The decisive column is the last one. At 70, two in five matches shared no archetypal function with the query at all. Per-route precision at 85 is 94–99% for aspects, houses and signs.
403 real query factors swept across four real charts.
| cut | avg pool | empty pools | structural match rate |
|---|---|---|---|
| 85 | 1.9 | 50 / 403 | 97% |
| 70 | 10.1 | 18 / 403 | 94% |
The structural match rate barely moves while the pool grows five-fold.
"Structural match rate" is not quality. It means the returned factor shared a body or the whole signature with the query — a structural test the engine can run on itself, not a human judgement. Nobody should quote 94% as "94% of recommendations were judged good". See §6.4.
21 probes: pairs that ARE the same pattern reached by different routes, pairs that are NOT, and cross-layer pairs. Tracked through every change.
| at the start | final | |
|---|---|---|
| same-signature pairs | 56–100 (spread 44) | 93–100 (spread 7) |
| separation margin | 21 | 37 |
Layer ordering also behaves: same-layer 92–93% > same-family 84–86% > cross-family 77–81%.
Natal aspects with planets, angles, nodes and asteroids; bare sign and bare house placements; angle-in-sign; a dignified placement; transit aspect, house and sign; progressed aspect and sign; draconic aspect and house; synastry aspect and house; composite aspect; chart figure. 20 of 20 returned the astrologically correct relatives.
Four real charts pulled from the production chart API (Buenos Aires 1974, Tokyo 1991, Cairo 1966, Vancouver 2001) across four layers each — 16 API calls, 718–750 factors per person. A random factor from each was scored against that person's own chart. Every returned row was astrologically defensible; the failures found were about pool size, not correctness, and led to the 70 cut above.
Three external audits, two of them on stratified random samples the engine's author did not choose — specifically to remove selection bias. The third audit found no systemic scoring flaw and recommended stopping.
Substantive changes the audits caused:
North Node opposition South Node (true of every human) and Neptune sextile Pluto (a generational sextile). Any people-matching feature must weight a shared factor by its rarity in the population or it will connect everyone to everyone on nothing.Sun in Taurus in 9th House; the corpus stores two-part names. Only 49 of 144 natal rows exist as stored components. This does not block anything — the engine reads the name, not a database row — but it is why the app logs ~4,100 content lookup misses a day.Pluto Square Mars and Moon Trine Sun resolved to nothing because the corpus stores those pairs in the other order. They did not error — they silently vanished from the result.| Engine build (all 22,693 components decomposed) | 1.1 s, 73 MB, once at boot |
| Admit one person's 740-factor chart | 23 ms |
| Answer one query against that chart | 18 ms |
| Full pairwise matrix of a chart, two-stage | 68 ms |
| Person-match index | ~119 keys/person, ~2 KB; ~917 MB across 467,188 users |
Nothing is stored for the intra-chart read. The person-match index is the only storage, and it does not exist yet.
§6.4 names the missing validation: no astrologer has judged a sample blind. The instrument now exists so that can be done.
docs/product/VALIDATION-blind-rating-sheet.md — 200 pairs, one question each: do these two chart factors substantially express the same underlying archetypal pattern? Y / N / ?. Scores hidden, order randomised.Sampling. Drawn from real engine output across four score bands — 95–100, 85–94, 75–84, 60–74 — 50 per band. Within each band half the pairs are cross-route: one side an aspect and the other a placement. That was a deliberate correction to the first draft, where the sample mirrored the corpus and 76% of pairs were aspect-to-aspect. Two aspects sharing a planet pair look alike to anybody; the claim that actually needs a human is the cross-route one, where the engine says a placement and an aspect are the same pattern. No query contributes more than two pairs. Reproducible from seed 8812.
Report eight cells, never one number. Four bands × two route types. Collapsing them hides the only two failures worth distinguishing.
| score band | same-route "yes" | cross-route "yes" |
|---|---|---|
| 95–100 | ||
| 85–94 | ||
| 75–84 | ||
| 60–74 |
The reading is committed in advance, before any judgement is seen:
With more than one judge, inter-rater agreement bounds the whole exercise: a construct astrologers disagree about cannot be measured more finely than they agree, so compute that first and treat it as the ceiling on any precision claim.
node scripts/symbolism/_blind-score.js <completed-sheet.md> produces exactly that table from a filled-in sheet. It was written before any ratings existed, so the analysis cannot be shaped by the result.
node scripts/symbolism/relate.js --affinity "Moon in 8th House" --cat "Planets in Houses"
node scripts/symbolism/relate.js --affinity "Venus Square Saturn" --cat "Planet Aspects" --cut 70
node scripts/symbolism/relate.js --match "Moon Opposition Pluto" --cat "Planet Aspects"
The engine is scripts/symbolism/affinity.js and scripts/symbolism/relevance-core.js; the reasoning behind every threshold is in the code comments and the commit messages, with the measurement that produced it. Design and API notes: FEATURE-52 in app_issues, with the HD/I Ching audit in FEATURE-82.
Added 2026-10-05. Asked because nothing HD-flavoured appears as a facet or a connection, and because an external plan proposed building a three-level HD layer. Every figure below was measured against the live database on 2026-10-04.
| substrate | components | status |
|---|---|---|
| I Ching Hexagrams | 64 | present, linked from astro |
| I Ching Lines | 384 | present, nothing links to them from astro |
| Centers | 9 | present, linked from astro |
| Circuitry | 3 | present, no incoming links at all |
| Gene Keys | 0 | does not exist — no gene key, manifestor, projector, reflector or incarnation-cross content anywhere |
1. There is no five-category Matrix layer. The plan keyed everything to Purpose / Harmony / Love / Abundance / Evolution. The product uses 27 facets — Path & Career, Ambition & Drive, Vision & Awareness, Creativity & Imagination, Intimacy & Romance, Nurturing & Fulfillment, Growth & Wealth and twenty more — held in 22,159 authored rows and in the Flutter report model. Re-cutting to five would orphan all of them. The plan's worked example also named Creative Expression and Love & Romance; neither is a real facet.
2. "Virtues" does not exist, and its proposed replacements are not linkable. Principles (320 components), Characteristics (211) and Keywords (160) exist as component categories but have zero rows targeting them. Nothing in the corpus points at them, so they cannot be rendered under a facet without an authoring project first.
3. Three of the named link sources are empty from astro.
I Ching Lines 126 target rows — all 126 from I Ching Hexagrams, 0 from astro
Decans 681 target rows — Sabian 440, Tarot Minor 104, hexagrams 101, signs 36, 0 from astro
Circuitry 0 target rows
A UI section for any of the three renders blank for every user.
4. Gates are near-absent on aspects. Hexagram links reach placements but not aspects: Planets in Signs 418 and Planets in Houses 283, against Transit Aspects 210 (of 5,399 components) and Synastry Aspects 213 (of 3,143). Aspects are the bulk of a chart, so a gate appears on roughly one aspect in twenty. Authoring 64 gate signatures fixes gate identity; it does not fix this. Aspect-to-gate links are a separate generation job, and the plan collapsed the two.
Facet attribution is effectively complete — every astro category sits at 99–100%:
Planets in Signs 359/359 100% Transit Aspects 5333/5399 99%
Planets in Houses 384/387 99% Progressed Aspects 1114/1130 99%
Planet Aspects 2684/2703 99% Draconic Aspects 2853/2860 100%
Synastry Aspects 3125/3143 99% Composite Aspects 1807/1808 100%
with all eight in-house and in-sign categories at 100%. "Which of my factors drove Path & Career" is therefore a query against rows that already exist, not an authoring project.
And the transit path already computes it. The report builder derives a score per facet, a harmonious-versus-challenging tone count, and a capped list of contributing events, from a facet field the chart engine returns on every transit and forecast row. That explanation layer exists today and is consumed only by the AI report payload — the user never sees it.
Correspondence links are real, counted as rows whose source is an astro category:
Tarot Major Arcana 13,780 / 17,372 Chakras 14,183 / 17,621
Tarot Minor Arcana 9,998 / 12,839 Centers 10,642 / 13,235
Tarot Court Cards 436 / 1,233 I Ching Hexagrams 1,397 / 3,478
Tarot Minor Arcana is well covered, which happens to support the plan's own worked example. Court Cards are thin.
But affinity is structurally blind to all of it. The layer-family gate drops Hexagrams, Lines, Centers and Circuitry because none of them belongs to a chart layer. In the affinity scope Hexagram 1 decomposes to Individual, Power, Will, Fixed, Water — genes borrowed from the astro components that link to it, not an authored identity — and Hexagram 1 Line 5 decomposes to nothing, so all 384 lines are zeroed by the no-signature guard.
Use HD and I Ching as a surfaced correspondence layer where it already exists. Do not make it a scoring or affinity layer until gates have authored signatures and aspect coverage.
Build now: the 27-facet explanation layer, with a Symbolic Links block — never "Gate Themes", since gates are present on about 5% of aspects and must not name the section — rendering only the sections that have rows. Label honestly: I Ching Gate, Energy Center, Archetypal Gate, Symbolic Center, never "Human Design Analysis", "Your Gene Keys" or "Bodygraph", because the corpus does not support the claim.
Do not build: Gene Keys, Lines, Circuitry, Decans, HD affinity, HD relationship mechanics.
Filed as FEATURE-82, with S-a (explanation layer) and S-b (labelling) as the shippable half, and S-c (64 gate signatures) / S-d (aspect-to-gate links) as the later, separate unlocks.
Asked 2026-10-05: could we do something similar to Human Design's Projector / Generator labels? Nothing similar is needed. The real type is computable from what we already hold.
Measured 2026-10-05; nothing was authored for this.
| what | where | state |
|---|---|---|
| 64 gates with a centre and channel partners | I Ching Hexagrams | complete |
| gates per centre | Throat 11, Sacral 9, Root 9, G 8, Spleen 7, Solar Plexus 7, Ajna 6, Heart 4, Head 3 | exactly the real HD distribution |
| channels | 36 distinct, all reciprocal | 0 integrity problems; real HD has 36 |
| gate to sign | agrees with the canonical rave mandala | 64/64 |
| the 9 centres | Centers | complete, with descriptions |
Only the arithmetic was missing, and that is now scripts/symbolism/human-design.js.
The wheel offset. The stored sign is sign-resolution, so it pins the wheel's ORDER but not its OFFSET. Used the published constant — gate 41 opens at 02°00′00″ Aquarius, so a gate boundary falls at −1.75° and gate 25 spans 28°15′ Pisces to 03°52′30″ Aries. The wheel mode re-derives all 64 signs from that constant and checks them against the database, so a wrong offset cannot pass silently. Independent spot check: a Sun at 19°00′ Scorpio resolves to gate 43 line 1, and published gate tables open gate 43 at 18°52′30″ Scorpio.
The Design set. A bodygraph has two activation sets — Personality (birth) and Design (the moment the Sun was 88° of arc earlier, about 89 days before birth). Design is half the activations, so it is not optional. Newton iteration on the Sun's 0.9856°/day converges in two engine calls; observed deltas 0.30° then 0.008°. Total cost: 3 chart calls per person, once, static for life.
Nothing in the implementation was tuned toward this: the wheel, the offset and the channel table were all fixed before any type was computed. 120 real births, Personality + Design, 0 failures.
| type | ours | published | delta |
|---|---|---|---|
| Generator | 36% | 37% | −1 |
| Manifesting Generator | 35% | 33% | +2 |
| Projector | 18% | 20% | −2 |
| Manifestor | 11% | 9% | +2 |
| Reflector | 0% | 1% | −1 |
Average 4.7 defined centres of 9 and 19.5 activated gates of 26. Every type lands within two points.
This table was run three times, and each run found a fault invisible in a finished type label.
13% Reflectors against a published 1%. The chart engine returns the north node as True Node, so reading it by its Human Design name dropped one activation per set; and Chiron was included, though it is not one of the 13 activating bodies.
A −9 on Manifesting Generators that the second run could not explain. It had a cause: the chart engine returns Earth at 0.00° on every chart — 214 of 214, one distinct value across the whole sample. Earth is always exactly opposite the Sun, so the correct value is Sun + 180; trusting the engine put every person's Earth in gate 25 and corrupted 2 of the 26 activations. Earth is now derived and never read, and the maximum delta fell from 9 points to 2. An earlier draft of this section attributed that −9 to sample composition and to the published split being soft. That was wrong and is withdrawn.
The second fault is not confined to Human Design. 23 components are named Earth … — 12 Planets in Houses, 6 Synastry Aspects, 5 Transit Aspects — and they took 172 views in the 9-day window. Every one of those readings is selected from a longitude that is zero for everybody, so the house shown is the house of 0° Aries. Filed separately as ISSUE-571; the fix belongs in the engine, because the app reads it directly.
Computed and defensible: type, inner authority, profile lines, defined and open centres, defined channels, activated gates — plus, for the first time, a per-person route into the 384 I Ching Lines components, which §9 showed have zero incoming links from astro.
Not supported: a commercial Human Design product. No gate has authored content of its own (a gate's affinity-scope genes are borrowed from the astro components that link to it), and Gene Keys is not in the corpus at all. Ship the computed structure under honest labels, not a "Human Design Analysis".
Filed as FEATURE-83.
The individual type is one of four loops a Human Design product normally has. The other three were checked rather than assumed, and none of them needs new data.
Relationships. The four standard HD connection types — electromagnetic, companionship, dominance, compromise — compute from the same 36-channel table, with nothing added. 20 bodygraphs, all 190 pairs scored:
pairs with ZERO connections 0
connections per pair min 4 median 11 max 18 mean 10.8
mean per pair electromagnetic 3.28 dominance 3.59
compromise 3.45 companionship 0.43
No pair comes back empty, which is the opposite of the intra-chart affinity problem in §3.2. Companionship is the rare one, so it carries the most signal. 126,454 of 420,093 accounts (30.1%) already hold a second person with full birth data, and there are 1,005,915 HD-computable people in the database in total.
Timing. gateOf() takes any longitude, so the transit positions the app already computes are gates for free — no extra call, no extra data.
Depth. Covered in §9 and §10.5: the 384 line documents exist, are QA-passed, and have never been viewed.
So of the four loops, three are arithmetic over data already held. The costed one is the individual bodygraph, at 3 engine calls per person, once.
The cost in §10.2 is 3 engine calls per bodygraph. What that multiplies by is a design decision, and it was measured rather than assumed.
Key it to the birth identity, not the account. Across all HD-computable person rows:
person rows 1,026,089
unique birth identities 902,913 (year/month/day/time/timezone/lat/lng)
repeats 12.0%
engine calls, per row 3,078,267
engine calls, per identity 2,708,739
avoided 369,528
The repeats are shared between accounts, not within them. An early reading of this — that the duplicates were one account re-saving the same person — was wrong. 1/2/1980 12:00 PM appears in 1,414 separate accounts, 14/6/1946 10:54 AM in 307, the 4/7/1776 USA chart in 205; only 11,153 duplicate rows sit inside a single account. So a shared cache earns its keep through genuine cross-account reuse of public figures and defaults. (1/2/1980 12:00 PM at a noon time across 1,414 accounts looks like a placeholder rather than a person, and is worth a separate look.)
And it must be invalidated when birth data is repaired. users.data.big3 is the existing precedent for a per-person cached chart derivative, and the stale-cache classifier in services/chartMath.js exists precisely because timezone and location get corrected after the fact. A bodygraph cached without the same invalidation will outlive the chart it was computed from.
§10.3 is a distribution test. It has caught three real faults, but it is blind to any error that moves everybody the same way — and the room for one of those was measured rather than assumed.
The corpus agreeing 64/64 does not pin the wheel. The stored gate→sign table scores 64/64 for every offset between 0.940° and 2.810° — a window 1.870° wide, a third of a gate and almost exactly two line-widths. The published value in use, 1.75°, sits inside it with 0.81° below and 1.06° above. Across 2,354 real activations, moving the offset from one edge of that window to the other:
changes the GATE for 776 of 2,354 activations 32.97%
changes the LINE for 1,578 of 2,354 67.03%
So a third of everyone's gates could be wrong while every structural check in this repo still passes and the population split stays exactly where it is, because a uniform error does not move a distribution. Only a chart whose answer is published outside this codebase can catch it.
The gate. The test file holds 29 passing checks: gate and line boundaries rather than midpoints (midpoints survive a wrong offset), the full 384-cell grid, the type and authority truth table including motor-to-throat reachability through an intermediate centre, the four connection types, and regressions locking the three faults already found. The published-chart section is deliberately empty and fails under --ship. The default run stays green, because the repo's runner auto-discovers every test and a permanently red suite would destroy the signal for everyone else; the gate is on shipping, not on the suite.
Nothing Human Design may ship until
--shippasses.