All articles

Tasuki Keifu: Athlete Relationship Labels and Scoring Plan

Personal projectsRead in Chinese

Athlete Relationship Labels and Scoring Plan

Document date: October 25, 2026. This plan fixes proposed label semantics, the next relationship-cache payload, and a first scoring formula before changing the front end. The distributions below were observations of the then-current production cache; thresholds, scores, and page rules were proposed for the next iteration, not reported as shipped behavior.

1. Label Vocabulary

The proposed informational labels were:

Label keyUser meaning
same_hometownSame hometown
same_junior_high_teamJunior-high teammates for X years
same_junior_high_originAttended the same junior high school, possibly at different times
same_high_school_teamHigh-school teammates for X years
same_high_school_originAttended the same high school, possibly at different times
same_university_teamUniversity teammates for X years
same_university_originAttended the same university, possibly at different times
same_corporate_team_originBoth belonged to the same corporate running team at some point

An “X years together” label applies only to junior high, high school, and university. A shared-school history does not require temporal overlap. Corporate-team history is shown without an overlap duration in this version.

The three proposed competition labels were frequent_matchup (“Repeated matchups”), long_term_matchup (“Long-running matchups”), and cross_stage_matchup (“Matchups across career stages”). User-facing labels should carry stable meaning; numeric signals remain in the payload for ranking and later tuning. A feature state such as “head-to-head details available” is not a relationship label.

2. Boundaries of Informational Signals

The existing Membership data had relatively complete start and end dates for junior-high, high-school, and university organizations. School years are also natural units for explaining an overlap. Since PlayerRelationCache is a derived JSON cache, adding teamOverlapYears to the payload is comparatively small in scope. These facts support “teammates for X years” at those three stages.

Corporate-team dates were less reliable: joining and leaving dates were often missing, and transfers or transition periods were more complicated. Reporting “X years” there would create false precision. The first version therefore allows “Shared corporate-team history,” but does not calculate corporate-team overlap years or include them in teamOverlapYears.

3. Thresholds From Observed Relationship Data

The aim was to avoid labels that applied to nearly everyone or almost no one. The source analysis examined 90,486 relationship edges from PlayerRelationCache.payload.topRelations. matchupYearCount counted distinct years in which both athletes appeared in the same Race.

Observed conditionEdges
matchupCount >= 311,667
matchupCount >= 46,132
matchupCount >= 53,221
matchupYearCount >= 220,893
matchupYearCount >= 36,791
matchupYearCount >= 42,163
stageCount >= 24,435
stageCount >= 383

The first proposed cutoffs were:

LabelConditionApproximate coverage
Repeated matchupsmatchupCount >= 46.8%
Long-running matchupsmatchupYearCount >= 37.5%
Cross-stage matchupsstageCount >= 24.9%

Two years would make the long-running label too broad; three career stages would make cross-stage too rare. The selected cutoffs leave useful overlap without making all three labels synonymous.

4. Proposed Cache Schema

No new primary business table was proposed. The next version would extend the derived PlayerRelationCache.payload:

type RelationStageKey =
  | "junior_high_school"
  | "high_school"
  | "university"
  | "corporate_team";

type PlayerRelationContextV2 = {
  sameHometown: boolean;
  sharedOrigins: {
    juniorHighSchool: boolean;
    highSchool: boolean;
    university: boolean;
    corporateTeam: boolean;
  };
  teamOverlapYears: {
    juniorHighSchool?: number;
    highSchool?: number;
    university?: number;
  };
};

type PlayerRelationMatchupSignals = {
  matchupCount: number;
  matchupYearCount: number;
  stageCount: number;
  ekidenCount: number;
  latestMatchAt: string | null;
};

type PlayerRelationLabels = {
  info: Array<
    | "same_hometown"
    | "same_junior_high_team"
    | "same_junior_high_origin"
    | "same_high_school_team"
    | "same_high_school_origin"
    | "same_university_team"
    | "same_university_origin"
    | "same_corporate_team_origin"
  >;
  matchup: Array<
    | "frequent_matchup"
    | "long_term_matchup"
    | "cross_stage_matchup"
  >;
};

type PlayerRelationEntryV2 = {
  relatedPersonId: string;
  context: PlayerRelationContextV2;
  matchupSignals: PlayerRelationMatchupSignals;
  labels: PlayerRelationLabels;
  hasHeadToHeadDetail: boolean;
  latestCompetitionEditionId: string | null;
  latestCompetitionName: string | null;
  score?: number;
  scoreBreakdown?: Record<string, number>;
};

type PlayerRelationCachePayloadV2 = {
  personId: string;
  generatedAt: string;
  topRelations: PlayerRelationEntryV2[];
};

Label generation uses the underlying facts: matching hometowns; a shared school with overlap for teammate labels; a shared school without requiring overlap for origin labels; and shared corporate-team history without requiring overlap. Competition labels use the three numeric cutoffs above.

5. Copy and Page Naming

Examples of informational display copy include “Same hometown,” “Junior-high teammates · 1 year,” “High-school teammates · 3 years,” “University teammates · 4 years,” “Same university history,” and “Shared corporate-team history.” Competition copy is “Repeated matchups,” “Long-running matchups,” and “Matchups across career stages.”

The standalone page name was Relationship Network. The proposed locale labels were ja: 関係ネットワーク, zh: 关系网络, zh-Hant: 關係網絡, en: Relationship Network, and ko: 관계 네트워크.

6. First Scoring Formula

The first score was intended to be explainable, tunable, and conservative. Labels carry semantics; numeric fields carry strength. Shared team or school background and competition intensity both affect rank. The proposal used a human-readable heuristic, without dynamic rarity weights:

finalScore = infoScore + matchupScore + recencyBonus

Informational Score

sameHometown = +3

sameJuniorHighOrigin = +5
sameHighSchoolOrigin = +6
sameUniversityOrigin = +5
sameCorporateTeamOrigin = +4

sameJuniorHighTeam = 8 + 2 * juniorHighOverlapYears
sameHighSchoolTeam = 10 + 2 * highSchoolOverlapYears
sameUniversityTeam = 9 + 1.5 * universityOverlapYears

Overlapping teammates should rank noticeably above merely attending the same institution. Shared high-school and university teams are the strongest informational signals; hometown is a light supplement. Overlap duration affects score, but should not expand without bound. Examples: four university years yield 9 + 1.5 * 4 = 15; three high-school years yield 10 + 2 * 3 = 16; attending the same university without overlap yields 5.

Competition Score

matchupScore =
  6 * ln(1 + matchupCount)
  + 5 * ln(1 + matchupYearCount)
  + 4 * max(stageCount - 1, 0)

matchupCount represents frequency, matchupYearCount continuity, and stageCount breadth across career stages. Logarithmic compression prevents ten meetings from overwhelming three meetings simply through a linear count.

Recency Adjustment

latestMatchAt within 2 years => +2
latestMatchAt within 4 years => +1
older or null => +0

Recency is intentionally minor: a recent meeting should not outweigh a durable strong relationship. Missing latestMatchAt gives neither a bonus nor a penalty.

The observed cache was dominated by ordinary co-appearances, with many edges at matchupCount = 1. Shared team and school signals were rarer but meaningful, and many edges lacked a latest-match date. The proposed ranking should put four-year university teammates who also met often near the top; avoid letting a shared university alone outrank a sustained competition relationship; rank ten matchups above three; give cross-stage meetings a distinct effect; and prevent hometown alone from dominating.

7. Relationship Pages and Eligibility

The proposed first page design had two views using one relationship result, one display cutoff, and one sort order: a section on the athlete profile showing the top six, and a standalone Relationship Network page showing all qualifying pairs with pagination.

The standalone page would open for athletes with at least eight race results. The source counted 1,780 athletes at five or more results, 1,060 at eight or more, 838 at ten or more, and 484 at fifteen or more. Eight was selected to limit the full page without excluding too many moderately documented athletes. These were observed counts used to select a proposed gate.

The gate applies only to the standalone page. A less documented athlete can still show a relationship section if qualifying pairs exist. A qualifying athlete with no qualifying pairs should not show an empty profile section.

The proposed pair-level display cutoff was rawScore >= 15. At 10, weaker relationships survived too often for athletes with few races; at 20, too many athletes dropped to zero to two visible relationships. The profile would take six highest pairs from the >= 15 set; the standalone page would show all of them.

The first sort order was fixed:

  1. rawScore DESC
  2. matchupCount DESC
  3. latestMatchAt DESC
  4. relatedPersonId ASC

rawScore is internal for eligibility and ranking. displayScore maps to 0–100 with one decimal place for the standalone page. The profile section shows no score. It shows up to three labels per relationship. The standalone page shows all labels, ordered by contribution to that relationship, with displayScore on the right and no extra explanatory sentence.

8. Cache and Pagination Strategy

The profile section should keep reading PlayerRelationCache: it is visited more often, needs a stable top six, and should avoid request-time recomputation. The standalone page should use the same cache result. The proposal was to expand the cache from top 12 to the full set of qualifying relationships, then paginate that set at 24 items per page. No small hard cap was proposed initially.

The document rejected a split where the profile reads a cache but the standalone page recalculates everything online, or where SQL sorts raw edges before the page adds labels. Labels, signals, and scores should form a complete relationship object before rendering, keeping both pages consistent and making later filtering and debugging easier.

9. Proposed Implementation Order

  1. Extend RelationStageKey with junior_high_school.
  2. Extend PlayerRelationCache.payload.
  3. Cache the full qualifying set instead of only top 12.
  4. Generate informational and competition labels.
  5. Add rawScore, displayScore, and scoreBreakdown.
  6. Add the Relationship Network page with pagination.
  7. Adjust the athlete-profile section and entry copy.

The durable decisions in this plan were the meaning of each label, the retained numeric signals, the rationale for competition thresholds, and a shared object for both views. The exact scoring weights could still be tuned later without changing those semantics.

Tasuki Keifu: Athlete Relationship Labels and Scoring Plan | Xuhuan / Gelatoni