Skip to content

Proposal: optional Longevity Tools screen (deep links to longevity-tools.com) - #1714

Open
marian001 wants to merge 1 commit into
elkimek:mainfrom
marian001:feat/longevity-tools-links
Open

marian001 wants to merge 1 commit into
elkimek:mainfrom
marian001:feat/longevity-tools-links

Conversation

@marian001

Copy link
Copy Markdown
Contributor

Summary

Adds an optional Longevity Tools screen under Analysis tools in the sidebar. It lists the interpreters, calculators, biological-age tools and biomarker pages published by longevity-tools.com. Clicking one opens that page in a new window with the profile's latest stored values pre-filled, so the site immediately shows its evaluation (e.g. Levine PhenoAge, Bortz Blood Age, CVD PREVENT, thyroid/liver/iron/kidney interpreters, single-biomarker pages).

A note on direction — please read first

I'm aware this may not be the direction you want for get-based. It sends the user to a third-party service for analyses instead of computing them inside get-based, which runs against the local-first, "everything in one place" spirit of the project. So please treat this PR as a proposal for discussion rather than a finished feature request.

Some options, if the idea is interesting at all:

  • Merge as-is, as a convenience link-out next to the native analyses.
  • Make it opt-in: hide the sidebar entry behind a setting (off by default). This could be the first case of a more general optional modules / plug-ins mechanism, where integrations with external services (this one, lab connectors, …) can be switched on or off per user, keeping the core lean and fully local.
  • Use it only as a reference: the catalog documents which calculators exist and which inputs they need. That could guide which of them are worth implementing natively in get-based.
  • Close it: no hard feelings, it is easy to keep locally.

I'm happy to rework it in whichever direction you prefer, for example by adding the on/off toggle or by reshaping it as a plug-in.

How it works

  • Catalog (js/longevity-tools-catalog.ts): 100 entries in 11 sections (11 interpreters, 26 calculators, 2 bio-age tools, 61 biomarker pages). It is taken from the site's public llms.txt and homepage, and each site parameter ID is mapped to an exact get-based marker key and its stored unit (63 mappings, exact keys only, no fuzzy matching).
  • Values (js/longevity-tools-values.ts): takes the latest stored value per marker from importedData.entries. It reads the stored SI values, not the display-converted ones, and keeps the draw date. Age, sex, height, weight, blood pressure and waist come from the profile and body data.
  • URL: the site's documented hash format, #ID=value_unit&… (/ → ~). The site does its own unit conversion, so values are sent in get-based's stored unit and never pre-converted. The only scale fix is lymphocytes, which are stored as a fraction and sent as %. The legacy Levine PhenoAge page takes bare numbers.
  • Screen (js/longevity-tools-view.ts, lazy-loaded via js/longevity-tools-loader.ts): has search, a Tools/Biomarkers filter and an "only with my data" toggle. Each tool shows which inputs are filled and which are missing, plus the questionnaire items that can only be answered on the site, so the user knows why a calculator may stay blank. Links open through openUtilsRuntimeWindow(…, 'noopener,noreferrer').
  • Privacy: values travel in the URL fragment. Browsers don't send the fragment in the HTTP request, but the site's own scripts read it, so opening a tool shares the listed values with longevity-tools.com. The screen states this explicitly. Nothing is sent until the user clicks a tool.

Wiring

  • New route longevity-tools with handlers in views-router.ts and navigation-views.ts, a sidebar row in nav.ts, sidebar-presentation.ts, SidebarNavigation.svelte and SidebarIcon.svelte, and service-worker assets.
  • Emitted js/longevity-tools-*.js are added to .gitignore. MODULE_MAP.md and scripts/dom-sink-policy.json are regenerated.
  • Sidebar receipt tests: the new row is qualified the same way Make Nutrition and Supplements standalone lenses #1693 qualified the Nutrition/Supplements lens entries (longevityToolsEntry() in tests/helpers/sidebar-lens-entries.ts, inserted after Correlations in expectedCurrentSidebarPwaReceipt). The grammar SHA-256 digests for SidebarNavigation.svelte and SidebarIcon.svelte are updated.

Testing

  • tests/longevity-tools.test.ts (fictional values) covers: latest value per marker plus draw date, the documented hash format, the fraction→% conversion, profile params, the bare-number Levine page, CVD PREVENT body inputs, and that every biomarker page has a mapped source.
  • tsc --noEmit, svelte-check (0 errors) and quality-guardrails (17/17) all pass. The architecture check passes with 0 cycles. Sidebar suites pass: sidebar-owned, sidebar-presentation, sidebar-pwa-expected-annotations.
  • Full vitest run compared against a clean origin/main worktree: the PR introduces no new failures. The failures that remain are the same environment-dependent ones as on main in my setup.
  • Manually verified in a browser against the live site: Levine PhenoAge and Bortz Blood Age compute a result from the pre-filled values. Bortz needs vitamin D present. The interpreters accept the filled values and units without errors.

Lists longevity-tools.com interpreters, calculators, bio-age tools and biomarker pages under Analysis tools and opens them with the profile's latest stored values in the URL hash. Exact marker-key mapping, SI values, per-tool input coverage, explicit sharing notice.
@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 3/5

[High impact] The new screen can send selected health and profile values to an external site.

Fix the height conversion and blood-pressure selection before merging.

Findings

  1. P1 Height gets converted twice ▶
  2. P1 Old blood pressure wins ▶

Summary

Adds a Longevity Tools screen that opens selected third-party calculators with saved health values.

  • Selected Longevity Tools pages open with matching profile values.
  • The Analysis tools sidebar opens a searchable Longevity Tools catalog.

Acknowledged direction: marian001 explicitly describes third-party sharing and the always-visible sidebar entry as proposal choices. The description offers an opt-in setting as a possible follow-up; its absence is not reported as a defect.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Sidebar selection] --> B[Load screen and stylesheet]
  B --> C[Read active profile values]
  C --> D[Show available and missing inputs]
  D --> E[User selects a tool]
  E --> F[Build URL with selected inputs]
  F --> G[Open longevity-tools.com]
Loading

Reviews (1) · Last reviewed commit: "Add optional Longevity Tools screen (dee..." · Reviewed by Greptile

Comment on lines +46 to +49
const { height, unit } = getProfileHeight(state.currentProfile);
const n = Number(height);
if (!Number.isFinite(n) || n <= 0) return null;
return /^in/i.test(String(unit)) ? n * 2.54 : n;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Height gets converted twice

For profiles that display inches, getProfileHeight() still returns the saved height in centimetres; unit is only a display preference. Someone who entered 70 inches already has 177.8 cm saved, but profileHeightCm() turns that into 451.6 cm. Height-based calculators then get wrong inputs or reject them. Return the saved centimetre value without multiplying it.

Suggested change
const { height, unit } = getProfileHeight(state.currentProfile);
const n = Number(height);
if (!Number.isFinite(n) || n <= 0) return null;
return /^in/i.test(String(unit)) ? n * 2.54 : n;
const { height } = getProfileHeight(state.currentProfile);
const n = Number(height);
if (!Number.isFinite(n) || n <= 0) return null;
return n;

Knowledge Base Used: Body measurement tracking

Comment on lines +41 to +42
if (Number.isFinite(sys) && Number.isFinite(dia) && sys > 0 && dia > 0) candidates.push({ sys, dia, date: String(m?.bp_systolic?.latestDate || m?.bp_diastolic?.latestDate || '') });
return candidates.sort((a, b) => b.date.localeCompare(a.date))[0] || null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Old blood pressure wins

After a user corrects a migrated blood-pressure reading, latestBloodPressure() can still export the old numbers. Migration retains biometrics.bp, while later edits update the wearable row and summary. Both candidates have the same date, but the legacy copy was inserted first and wins the date-only sort. The risk calculator then receives the uncorrected reading. Prefer the current reading on equal dates and use legacy data only as a fallback.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant