All articles

Tasuki Keifu V0.1: Technical Plan

Personal projectsRead in Chinese

Tasuki Keifu V0.1: Technical Plan

Source document date: September 8, 2026. Related: V0.1 PRD and the later GitHub deployment and data synchronization plan. The notes evolved between June 28 and June 29: they narrowed V0.1 to core athlete facts, added affiliation timelines and a four-level competition model, chose initial co-location with the personal site, moved real-data updates away from seed, and added name-normalization and duplicate checks. The proposed architecture and later observed deployment state are distinguished below.

1. Technical Goal and Release Boundary

V0.1 aimed to support a small, trustworthy athlete archive: athlete profiles as the central page; Japanese as the main interface with Chinese and English UI scaffolding; representative records from Aoyama Gakuin, Kokugakuin, and Chuo universities; and P0 facts covering identity, affiliations, current personal bests, Hakone appearances, sources, and verification status. Readers should be able to navigate to other athletes from the same high school, university, or current organization. The model should allow later expansion to more schools, corporate teams, competitions, and results.

The priority order was a clear model, maintainable data, readable pages, traceable sources, and room to evolve. V0.1 was deliberately core athlete profiles, not a complete results database.

P0 fields included Japanese name, reading, romanization, birth date, hometown, high school, university, current organization after graduation or transfer, 5,000 m/10,000 m/half-marathon bests, marathon best where present, Hakone appearances, source, and data status. Historical or second-best PBs, full race results, systematic Izumo and All-Japan coverage, general track results, fan-profile fields, and a complex relationship graph were deferred. V0.2 was the proposed stage for a broader race-result database.

2. Stack and Shape

The chosen application stack was Next.js, React, TypeScript, App Router, Tailwind CSS, shadcn/ui, and lucide-react. A single full-stack application could serve public pages, internal administration, and data queries. SSR/SSG supported an archive and search indexing, while TypeScript made the evolving fact model easier to maintain. The visual direction was “Archive, not Dashboard”: a modern athlete directory rather than a news site or analytics wall.

PostgreSQL with Prisma ORM was selected as the primary fact store. Early complexity lay in entities, relationships, provenance, state, and constraints, which a relational database could express. Prisma supplied schema, migrations, and generated types. Neo4j, ClickHouse, BigQuery, a separate warehouse, and a graph database were explicitly outside V0.1; ordinary relationships and navigation could be served by PostgreSQL queries.

The initial monolith was a Next.js application with home, athlete list, athlete detail, search, proposed admin pages, server actions or API routes, and PostgreSQL tables for people, organizations, memberships, competitions, editions, races, results, bests, sources, audits, and aliases. No independent backend service was required.

3. Modeling Rules

Collect Broadly, Display Selectively

The public V0.1 UI could stay restrained while the data model retained confirmed public facts from sources with different fields. Uncertain values could be collected only with an explicit status; fan-oriented fields were not default core content; important facts should link to sources. This limited repeated historical re-collection without forcing every field onto a profile page.

Model Relationships Before Pages

Person represents a person, initially mostly an athlete. Organization covers high schools, universities, corporate teams, clubs, companies, and federations. Membership connects a person to an organization over a dated interval. High school, university, and corporate team should not be fixed columns on Person. Current affiliation is derived from an open or current Membership.endDate.

The model needed to answer same university, same high school, same current organization, career timeline, school-to-university-to-corporate progression, competition appearances, and personal bests, even if V0.1 exposed only a few of those views.

Provenance and Status Are First-Class

Important facts should carry source URL, name, type, access or entry date, verification status, and notes. Suggested statuses were verified, pending, conflicting, and missing. Trust depends on provenance and checks, not the count of populated fields.

Original Japanese Names and Stable Identity

Japanese was the main fact language. Chinese and English names were translations or display aids; romanization, kana reading, old forms, abbreviations, and media spellings belonged in name variants. V0.1 did not require complete translation of all data.

The later name-normalization addition addressed duplicate people created from reversed romanized name order. displayNameJa keeps conventional Japanese family-then-given order with one ASCII space. displayNameRoman uses GivenName FamilyName. A created slug remains stable even when a new source reverses the romanization; source wording is evidence, not an overwrite command. Romanization helps search and reconciliation but is not the identity key. A numbered slug suffix such as _2 is a last resort for genuinely unresolved same-name cases.

Before using a new source type for bulk names, sample two or three known athletes, establish whether it uses Family Given or Given Family, and record that conclusion. Do not batch-import names before that check.

Audit Changes, Not Real-World Events

V0.1 called for AuditLog, not a separate domain_event table. Memberships, bests, and race results already express real-world facts; another event table would duplicate entry without a defined feed, news, or timeline feature. AuditLog records maintenance actions. A later career timeline should first be derived from business facts.

4. Core Data Domains

DomainV0.1 meaning and important fields
PersonAthlete, with room for coach and staff later. Japanese name, kana, romanization and translations; birth, hometown, nationality, registration association, height/weight, type, notes, and status. Age is derived or a source-specific value rather than a durable identity key.
OrganizationSchool, university, corporate team, company, club, or organizer. Official and short names, readings/translations, type, location/prefecture, website, notes, status. A university and distance-running team could initially be one entity, with later separation if necessary.
MembershipDated affiliation: person, organization, relationship type, start/end dates and years, faculty/department/cohort, role, source, status. Grade is a source snapshot, not the career relationship.
PersonalBestCurrent best per athlete and discipline, with mark, achieved date, event, venue, organization/stage at achievement, school or university PB flags, source, and status. No historical PB or second-best record in V0.1.
CompetitionEvent series, initially Hakone as the core example; name, type, organizer, region, site, and notes. Izumo, All-Japan, road, and track expansion belonged to V0.2.
CompetitionEditionOne yearly or numbered occurrence, with year/number, dates, official and short names, source, and notes.
RaceA unit within an edition: ekiden leg, track event/round/heat, or other group; carries discipline, leg, distance, start time, status, and source.
RaceResultOne person's entry, actual start, race-day change, mark, placing, team placing context, source, status, and notes for a Race. Hakone leg prizes/records could be notes in V0.1 rather than special columns.
SourceName, URL, type, publication/access date, reliability, and notes for university/team pages, Hakone, NTV, JAAF, media, data/fan sites, PDF, World Athletics, or manual work.
AuditLogData create/update/delete/verify/merge/split; entity/field, old and new values, reason, source, actor, batch, and timestamp. The model was planned before every operation was automatically logged.
NameVariantAlternate names for people, organizations, or competitions, with language, type, primary flag, and source.

Membership was the career-timeline core. An example athlete could move from high school (2019-04-01 to 2022-03-31) to university (2022-04-01 to 2026-03-31) to a corporate team (2026-04-01 onward). High-school and university relations use Organization.type plus Membership.type=enrolled; a corporate team uses affiliated. origin remained available but was not the main high-school expression.

V0.1 used startDate/endDate, without a separate precision field. Common Japanese academic boundaries were April 1 and March 31; a corporate-team start could default to April 1 when evidence supported that inference, with uncertainty recorded in notes. startYear/endYear helped queries. Grade remained a snapshot from a source, not a long-term field.

Personal bests could cover 1,500 m, 3,000 m, steeplechase, 5,000 m, 10,000 m, ten miles, half marathon, and marathon, while the first page emphasized 5,000 m, 10,000 m, half marathon, and any marathon best. The planned four-level event model was:

Competition -> CompetitionEdition -> Race -> RaceResult

A Hakone fifth leg and a men's 10,000 m heat at another meeting are both Race units, avoiding a Hakone-only schema. RaceResult captures entry versus appearance, a race-day change, mark, and rank. V0.1 would put a leg prize or record in notes; a later structured result tag or achievement model could replace that.

5. Public Pages and Administration

The home page was an archive entry with site name, subtitle, search, three university links, recently updated athletes, and locale switch, rather than a marketing page. The athlete list would offer name search and filters for current affiliation, university history, and high school where data allowed. Its rows could show name/reading, current affiliation, university and school, major bests, and verification state.

The athlete detail would present basics, current and historical affiliations, main PBs, Hakone record, related athletes, and sources. V0.1's related links were same current organization, university, and high school, not a graph.

A lightweight internal admin interface was proposed because data maintenance mattered more than ornamental public UI. It would manage people, organizations, memberships, bests, Hakone records, sources, and status, with audit viewing possibly later. Full CSV import was postponed until a deep single-athlete sample tested the model. If admin or any write API reached the public internet, login, authorization, CSRF or equivalent protection, server validation, secrets, and audit writes were prerequisites.

6. Real-Data Collection and Import

The suggested first sample was Asahi Kuroda: identity, high-school-to-university-to-corporate timeline, current PBs, Hakone appearances, source priority/conflicts, and audit history. Once that sample tested the model and pages, the scope could widen to representative athletes at the three universities and only later to CSV or bulk import.

Initial sources included the universities' distance-team pages; official Hakone entry PDFs and historical records; NTV pages; athletics media profiles; current corporate-team sites; World Athletics for international marks; and JAAF or official event results to confirm bests. These could cover rosters, readings, romanization, faculty, hometown, school, bests, Hakone appearances, and team roles.

The proposed manual priority was current team site for current affiliation; university/team and official Hakone/KGRR entry data for school history; official Hakone records before KGRR or NTV for Hakone results; event/JAAF/World Athletics/current-team cross-checks for PBs; media as supplementation; and fan/data sites as leads requiring confirmation for critical facts. The hierarchy was a maintenance policy, not a hard-coded V0.1 rule engine.

The later June 29 design changed real-data maintenance to local scripts writing a single online PostgreSQL business database directly. Here “local” described where fetch/parse scripts ran, not a second offline database to synchronize. Early scale and few maintainers favored one write target and immediate page/query verification. This increased the need for unified write entry, traceable raw sources, explainable matching, duplicate blocking, audit, and backup discipline.

seed remained only for minimal development initialization, demo samples, and rebuilding a local skeleton. It was no longer the routine path for real-world records or production updates. Mixing source facts into seed.ts would make incremental provenance, deduplication, audit, and failure recovery difficult.

The recommended data path was:

  1. Fetch: save source HTML, JSON, PDF, URL, and fetch time in a local evidence folder.
  2. Normalize: extract people, organizations, events, results, and PBs into a common input shape.
  3. Reconcile: match existing IDs, check same-name and reversed-romanization candidates, and flag conflicts.
  4. Publish: write through a controlled import script to production with Source, source reference, and AuditLog; verify by database query and public page.

The proposed command responsibilities were import:person, import:race, import:organization, reconcile:person, and verify:import, even if final script names changed. Hand-editing seed.ts, untracked ad hoc SQL, or throwaway scripts without audit should not remain the main path. Raw source files are local evidence, not an offline master database.

Identity and Quality Gates

A slug should never be regenerated from a newly encountered romanized order. displayNameRoman uses GivenName FamilyName and remains an auxiliary matching field. Prefer a stable external-source mapping or existing entity over string guessing. Before a write, check normalized Japanese-name duplicates, reversed romanization, highly similar people in the same organization and cohort, and one source key mapping to several internal people. A match should block or enter review, not create a new person automatically.

The proposed source-reference mechanism would map an external page's stable key and URL to an internal person, organization, or race ID so later visits reuse the mapping. Basic validation should also flag overlapping affiliation conflicts, duplicate PBs per discipline, malformed marks, invalid discipline enums, missing sources, unknown high schools, absent current affiliation, and source conflicts.

Every formal import should answer what changed, why, and on which source evidence, and distinguish create, correction, and overwrite. It should write an audit entry, import-batch identifier, and source reference. This was initially for maintainers, not public display.

7. Search, Locales, and Admin Security

V0.1 could search Japanese names and romanization, and filter by university or high school, through PostgreSQL. Meilisearch, Typesense, and Elasticsearch were deferred.

The proposed routes were /ja, /zh, and /en, with /ja as the main site locale. V0.1 localization covered navigation, headings, field labels, site explanation, data status, and source explanation, not every athlete biography, school name, or note. UI copy could use JSON dictionaries; entity translations or variants remained in database fields. Locale switching should work on home, list, and detail while preserving the current path where possible.

The proposed admin had one owner account, no public registration, posts, or comments. Auth.js or a simpler administrator-password/session design could be considered, but public deployment of /admin or any write API required authentication, API authorization, CSRF protection, server-side validation, secret management, and audit logging. Public pages could launch before the admin; controlled local import scripts could maintain real data meanwhile.

8. Deployment and Release

The deployment choice was to co-locate initially with the personal site on ubuntu-3 while isolating code, process/port, database/user, environment, Nginx site, logs, and backups. A separate ubuntu-4 host could be added later if traffic, import work, or operations warranted it. An early target diagram suggested a /srv/tasuki-keifu releases/shared/backups layout with a systemd service. The source later recorded an actual Docker Compose deployment under /opt/apps/tasuki-keifu; the systemd layout was a proposal, not an assertion about the running service.

The recorded deployed state was an application reachable internally at 127.0.0.1:3020 behind Nginx, a Compose file under /opt/apps/tasuki-keifu, Cloudflare DNS aimed at the origin, an HTTPS certificate issued through certbot, and a successful HTTP 200 check for https://tasuki-keifu.gelatoni.uk/ja. The origin IP is intentionally omitted from the public article.

The first deployment had been manual: local code copied to Ubuntu-3, Compose image build, prisma migrate deploy, and application restart. The proposed next regular path was GitHub Actions: push main, install/check/build, build and archive the Docker image, upload over SSH/SCP, docker load, run migrations, docker compose up -d app, then health and core-page checks. Routine deployment must not run seed.

The minimum release smoke checks were /ja, /zh, /en, /ja/players, one athlete detail, database connection, and Nginx HTTPS. PostgreSQL should use a dedicated database/user. The backup plan called for daily pg_dump, 7–14 day retention, a separate directory, and manual backups around important imports; off-host storage and restore drills were later improvements, not claimed as completed here.

Migration to another host would require provisioning Node/pnpm, PostgreSQL, and Nginx; creating a dedicated database/user; dumping and restoring data; copying code and environment; configuring process and HTTPS; testing through a temporary domain or hosts entry; switching DNS; and retiring the old process after stability checks. Because the app and data were isolated from the personal site, this would not require splitting the two applications' state. Vercel with Neon or Supabase was an optional alternative, not the chosen V0.1 path.

9. Later Versions and Exclusions

The roadmap placed a broader race-result database, Izumo/All-Japan/track/road results, structured achievements, stronger source audit, and CSV import in V0.2. V0.3 envisioned relationship visualization and career flows, organization pages, comparison, and filters. V0.4 considered search indexing, better Chinese/English experience, explanatory content, and SEO. V1.0 or later could explore automated imports, change monitoring, community corrections, review, data warehouse/analytics, and an API or downloads.

V0.1 explicitly excluded an independent backend, microservices, warehouse, graph database, complex crawler, live race updates, user system, community features, large BI, complete results database, PB history, fan Q&A, and domain_event feed.

10. Risks and Proposed Mitigations

  • Fragmented sources: retain provenance and status, organize a human-reviewed sample, and delay automation.
  • Same-name and variant risk: use name variants and stable internal IDs; never use translated names as keys.
  • Overloaded admin forms: edit basics, PBs, memberships, and sources in separate modules; show only core fields publicly.
  • Affiliations without time: use startDate/endDate across schools and teams, derive current affiliation, and keep grade as source snapshot.
  • Hakone-specific schema: use Competition -> CompetitionEdition -> Race -> RaceResult; keep leg awards in notes initially and structure them later.
  • Copyright and attribution: collect public facts, avoid copying media article text or photos, and link to sources.

The proposed build order was project and PostgreSQL/Prisma setup; core schema and timelines; four-level event model; minimal seed; home, list, and detail; first UI localization; one deep athlete sample and model/page checks; basic search; lightweight admin; visual polish; then internal V0.1 preview. Bulk CSV import was intentionally later, after the single-athlete sample had tested the model.

Tasuki Keifu V0.1: Technical Plan | Xuhuan / Gelatoni