March 31, 2026 · 21 min read

Building Companion Text for Scores

How to build dynamic, factor-based insight messages beside Sahha health scores. Covers the approach, selection logic with reference code, tone principles, and includes ready-to-use JSON message libraries for all five scores.

A user opens your app and sees Activity: 63. What does that mean? Without context, they glance at it, feel nothing, and move on.

Now imagine the same score with a message beneath it:

All at once — Most of today’s movement happened in a short burst. Spreading it across more hours keeps energy more even and gives your metabolism a steadier rhythm.

Same number. Entirely different experience. The user understands why the score is what it is and what specifically is behind it. That’s companion text — a short, data-driven message beside the score that turns a number into something worth reading every day.

This guide walks you through building this: the approach, the selection logic with reference code, and the tone. It also includes default message libraries for all five scores that you can ship as-is or adapt to your brand.

Prerequisites: You’re integrating Sahha’s Score API and receiving score responses that include factor data. You have a UI surface — card, detail screen, or notification — where text can appear beside the score.


The Approach

The simplest approach — and where most teams start — is mapping each score state to a static label. “Optimal,” “Good,” “Fair,” “Pay Attention.” The problem: these tell the user nothing they couldn’t already guess from the number. “Fair” beside 63 adds zero information.

The most engaging health products do something different. Instead of labeling the state, they surface the most notable factor — the specific thing driving the score up or down today. Sahha’s score response already gives you this. Every score returns an array of factors, each with its own sub-score. The factor with the lowest sub-score is the weak link. The one with the highest is the bright spot. That’s your message.

Each message has two parts:

  • Headline (2–5 words) — what catches the eye. Specific, warm, sometimes a question. “Good ground covered.” “Timing has drifted.” Never generic motivation like “Keep it up!” or “Great job!”
  • Body (1–2 sentences) — what the factor data means in plain language. Normalizes the situation when things are off. Readable in under five seconds. If users have to stop and parse it, it’s too long.

If you want something simpler to start, the message libraries below also include fallback text — one message per score state, no factor data needed. You can ship with fallback only and upgrade to factor-based messages later.


Selecting the Right Message

Every Sahha score response includes a factors array. Each factor has a name, score (sub-score from 0 to 1), state, and value (the raw metric, e.g. step count or hours slept). The selection rules:

  1. Score state is high → find the factor with the highest sub-score → show its strong message (celebrate the bright spot)
  2. Score state is medium, low, or minimal → find the factor with the lowest sub-score → show its weak message (explain the weak link)
  3. Factor score is null → skip it when searching; if all factors are null, use fallback state text

Only high celebrates. At medium, the user is doing ok but something is holding them back — the weakest factor IS the story and the most actionable insight. The score number already tells them they’re in decent shape; the text explains the gap.

Reference implementation:

function getCompanionText(score, messageLibrary) {
  const validFactors = score.factors.filter(f => f.score !== null);

  if (!validFactors.length) {
    return messageLibrary.fallback[score.state];
  }

  const celebrate = score.state === "high";
  const notable = validFactors.reduce((a, b) =>
    celebrate ? (a.score > b.score ? a : b) : (a.score < b.score ? a : b)
  );

  const template = messageLibrary.factors[notable.name]?.[celebrate ? "strong" : "weak"]
    ?? messageLibrary.fallback[score.state];

  const value = String(notable.value ?? "");
  return {
    headline: template.headline.replace("{value}", value),
    body: template.body.replace("{value}", value)
  };
}

The {value} placeholder in message templates is replaced with the factor’s raw value at render time. Format the value before passing it if you need locale-specific formatting (e.g. "8,400" instead of 8400) or unit conversion (e.g. "7h 45m" instead of 465).

Seeing It in Action

Given this response:

{
  "type": "activity",
  "state": "medium",
  "score": 0.63,
  "factors": [
    { "name": "steps", "score": 0.71, "state": "medium", "value": 8400 },
    { "name": "active_hours", "score": 0.42, "state": "low", "value": 4 },
    { "name": "extended_inactivity", "score": 0.65, "state": "medium", "value": 2.1 },
    { "name": "active_calories", "score": 0.58, "state": "low", "value": 180 }
  ]
}

The state is medium, so celebrate is false and the logic finds the weakest factor: active_hours at 0.42. Looking up active_hours.weak:

All at once — Most of today’s movement happened in a short burst. Spreading it across more hours keeps energy more even and gives your metabolism a steadier rhythm.

The user’s score is 63 — decent, but something’s off — and the text explains exactly what. If the score rises to high tomorrow with steps at 0.92 (value: 12,400), the logic flips to celebrate the strongest factor:

On your feet — 12,400 steps today — the kind of consistency that lifts mood, keeps energy steady, and adds up more than almost any other daily habit.

The message changes daily based on the data. No manual curation, no stale copy.


Message Libraries

Each library below contains a factors object (one strong and one weak message per factor) and a fallback object (one message per score state). These defaults are written in a warm, observational tone. Ship them directly or replace any message with your own copy — the selection logic works unchanged either way.

Activity Score

{
  "type": "activity",
  "factors": {
    "steps": {
      "strong": {
        "headline": "On your feet",
        "body": "{value} steps today — the kind of consistency that lifts mood, keeps energy steady, and adds up more than almost any other daily habit."
      },
      "weak": {
        "headline": "Slower day",
        "body": "Fewer steps than usual — and that's fine. If you feel like it later, even a short walk goes further than you'd think."
      }
    },
    "active_hours": {
      "strong": {
        "headline": "All day?",
        "body": "Active in {value} waking hours across your day, not crammed into one window. This kind of day keeps your energy even and your metabolism humming."
      },
      "weak": {
        "headline": "All at once",
        "body": "Most of today's movement happened in a short burst. Spreading it across more hours keeps energy more even and gives your metabolism a steadier rhythm."
      }
    },
    "extended_inactivity": {
      "strong": {
        "headline": "Barely sat still",
        "body": "You've been breaking up your sitting time well. Frequent breaks from sitting do more for your body than a single workout sandwiched between hours on a chair."
      },
      "weak": {
        "headline": "A lot of sitting",
        "body": "Some long stretches without moving today. Even standing for a minute between long sits helps keep your body from slipping into idle mode."
      }
    },
    "active_calories": {
      "strong": {
        "headline": "You put the work in",
        "body": "{value} active calories — real effort, not just going through the motions. This kind of output builds endurance and keeps your body composition in check."
      },
      "weak": {
        "headline": "Gentle pace",
        "body": "Lower energy burn than usual. A slightly brisker pace — even on everyday walks — is the simplest way to shift this."
      }
    },
    "intense_activity_duration": {
      "strong": {
        "headline": "That effort shows",
        "body": "{value} minutes at higher intensity. This is what pushes your fitness forward — the kind of effort that strengthens your heart and lungs in ways easy movement can't."
      },
      "weak": {
        "headline": "Easy does it",
        "body": "Mostly low-key movement today. If you're up for it, even 10 minutes of something that gets your heart rate up would stand out."
      }
    },
    "floors_climbed": {
      "strong": {
        "headline": "On another level",
        "body": "{value} floors. Stairs and incline are sneaky — they build leg strength, get your heart pumping, and burn more energy per minute than flat ground."
      },
      "weak": {
        "headline": "All flat today",
        "body": "Not much vertical movement. If you pass stairs at any point, they're one of the easiest ways to add a bit of challenge."
      }
    }
  },
  "fallback": {
    "high": {
      "headline": "Your day had a good rhythm",
      "body": "Volume, distribution, and effort all came together. This is the kind of day your body actually thrives on."
    },
    "medium": {
      "headline": "Some gaps to fill",
      "body": "Parts of the day were strong, parts were quiet. The mix is what's holding the score back."
    },
    "low": {
      "headline": "Not much to work with today",
      "body": "Less movement than usual across the board. Some days are like that — tomorrow's a fresh one."
    },
    "minimal": {
      "headline": "Almost nothing today",
      "body": "Very little movement today. Rest days are fine — just don't let a few stack up."
    }
  }
}

Sleep Score

Sleep factors split into two categories: behavioral (regularity, timing, duration) and physiological (deep sleep, REM, continuity). Behavioral factors apply to everyone. Physiological factors require a wearable — if the user doesn’t have one, those sub-scores come back as null and the selection logic skips them automatically.

{
  "type": "sleep",
  "factors": {
    "sleep_duration": {
      "strong": {
        "headline": "A full night",
        "body": "{value} hours last night — enough for your body to cycle through deep sleep, REM, and full repair. Energy, focus, and mood all benefit."
      },
      "weak": {
        "headline": "Cutting it short",
        "body": "Less sleep than your body wanted. It happens — even adding 15 or 20 minutes tomorrow night helps more than you'd expect."
      }
    },
    "sleep_regularity": {
      "strong": {
        "headline": "Like clockwork",
        "body": "Your sleep schedule has been impressively consistent. A regular rhythm makes it easier to fall asleep, improves sleep efficiency, and helps your body get more out of every hour in bed."
      },
      "weak": {
        "headline": "Timing's been variable",
        "body": "Bed and wake times have jumped around lately. Even small improvements in consistency help you fall asleep faster and wake up more refreshed."
      }
    },
    "sleep_debt": {
      "strong": {
        "headline": "All caught up",
        "body": "Barely any sleep debt hanging over you — sharper focus, more stable mood, and a stronger immune system. That's the payoff of consistently getting enough rest."
      },
      "weak": {
        "headline": "Running a tab",
        "body": "You've built up some sleep debt recently. Your body tracks this quietly — the good news is it resolves gradually with a few consistent nights."
      }
    },
    "circadian_alignment": {
      "strong": {
        "headline": "Right on time",
        "body": "Your sleep timing lined up with your body's natural rhythm. That alignment improves the quality of your sleep even if the duration doesn't change."
      },
      "weak": {
        "headline": "Burning the midnight oil?",
        "body": "Sleep timing shifted later than your body prefers. Morning light — even a few minutes outside — is the simplest way to nudge your clock back."
      }
    },
    "sleep_continuity": {
      "strong": {
        "headline": "Slept right through",
        "body": "Solid, uninterrupted sleep. Continuous rest is genuinely more restorative than the same hours broken into fragments."
      },
      "weak": {
        "headline": "A restless one",
        "body": "More tossing and waking than usual. Temperature, light, and noise are the usual suspects — small changes to your environment often fix this."
      }
    },
    "physical_recovery": {
      "strong": {
        "headline": "Deep sleep came through",
        "body": "{value} minutes of deep sleep last night — that's when your body does its heavy lifting. Muscle repair, immune support, tissue recovery. You can't get this from any other sleep stage."
      },
      "weak": {
        "headline": "Deep sleep fell short",
        "body": "Deep sleep was shorter than ideal. Exercise during the day and a cooler bedroom at night are the two things that help most."
      }
    },
    "mental_recovery": {
      "strong": {
        "headline": "Brain fully recharged",
        "body": "{value} minutes of REM sleep. That's when memory consolidation happens, emotional processing kicks in, and the brain runs maintenance it can't do while you're awake."
      },
      "weak": {
        "headline": "REM fell short",
        "body": "REM was lower than usual. It concentrates in the final sleep cycles, so cutting sleep even slightly short has an outsized effect."
      }
    }
  },
  "fallback": {
    "high": {
      "headline": "Sleep that pays off",
      "body": "Timing, duration, and recovery all came together. Nights like this are what good days are made of — better energy, sharper thinking, stronger recovery."
    },
    "medium": {
      "headline": "Something was off",
      "body": "Rest was ok, but not everything lined up — timing, duration, or recovery quality was a bit out."
    },
    "low": {
      "headline": "Sleep didn't come together",
      "body": "Multiple things were off last night — whether it's consistency, duration, or the quality of rest itself."
    },
    "minimal": {
      "headline": "Rough night",
      "body": "Sleep took a real hit across the board. Tomorrow's a chance to reset."
    }
  }
}

Readiness Score

Readiness companion text reads like guidance for today, not a judgment of yesterday.

{
  "type": "readiness",
  "factors": {
    "sleep_duration": {
      "strong": {
        "headline": "Fully charged",
        "body": "{value} hours behind you — plenty for full recovery. Today should feel easier — more energy, clearer head, better mood."
      },
      "weak": {
        "headline": "Running on less",
        "body": "Shorter sleep than your body wanted. Getting to bed a little earlier tonight is the simplest thing that would help."
      }
    },
    "physical_recovery": {
      "strong": {
        "headline": "Deeply restored",
        "body": "Plenty of deep sleep last night. Muscle recovery, immune function, and tissue repair are all taken care of — physically, you're starting fresh."
      },
      "weak": {
        "headline": "Body didn't fully reset",
        "body": "Deep sleep was below target — physical recovery wasn't complete. You might feel it in how your body responds to effort today."
      }
    },
    "mental_recovery": {
      "strong": {
        "headline": "Brain well recovered",
        "body": "Solid REM last night. Expect sharper focus, more patience, and better stress tolerance today."
      },
      "weak": {
        "headline": "REM was short",
        "body": "REM sleep fell short last night, which means the brain's recovery cycle wasn't fully complete. That can show up in focus and patience."
      }
    },
    "sleep_debt": {
      "strong": {
        "headline": "Clean slate",
        "body": "No sleep debt weighing you down — more cognitive reserve, steadier energy, and a higher ceiling for what today can be."
      },
      "weak": {
        "headline": "The deficit shows",
        "body": "Sleep debt has been building and it's weighing on readiness. This resolves gradually — a few consistent full nights will bring it back."
      }
    },
    "walking_strain_capacity": {
      "strong": {
        "headline": "Room to roam",
        "body": "Recent daily activity has been well-absorbed. There's room in the tank for a full, active day."
      },
      "weak": {
        "headline": "Body's catching up",
        "body": "Daily activity stepped up recently and your body is still adjusting. Normal — it adapts with a little time and rest."
      }
    },
    "exercise_strain_capacity": {
      "strong": {
        "headline": "Ready to push",
        "body": "You've recovered well from recent training. There's capacity for intensity today if you want it."
      },
      "weak": {
        "headline": "Still absorbing the effort",
        "body": "Recent workouts were demanding and your body hasn't fully caught up. A lighter session today lets the adaptation happen."
      }
    },
    "resting_heart_rate": {
      "strong": {
        "headline": "Ticking along nicely",
        "body": "{value} bpm — right in your normal range. A reliable sign you're not carrying unusual stress into today."
      },
      "weak": {
        "headline": "Heart rate's a bit elevated",
        "body": "Resting heart rate is higher than usual. Could be recovery needs, stress, dehydration, or the early stages of something. Worth paying attention to."
      }
    },
    "heart_rate_variability": {
      "strong": {
        "headline": "Balanced and ready",
        "body": "HRV is strong — your nervous system has capacity today. You're well-positioned to handle both physical and mental demands."
      },
      "weak": {
        "headline": "Carrying more load",
        "body": "HRV is below baseline — the nervous system is managing more than usual. A calmer, easier day would support recovery."
      }
    }
  },
  "fallback": {
    "high": {
      "headline": "Green light",
      "body": "Recovery looks solid across the board. If there's something demanding on the schedule, today can handle it."
    },
    "medium": {
      "headline": "Steady as she goes",
      "body": "Recovery is in a normal range. Routine is fine — but probably not the day to go all-out."
    },
    "low": {
      "headline": "Take it easy today",
      "body": "Recovery isn't where it needs to be. Lighter activity is the smarter call."
    },
    "minimal": {
      "headline": "Listen to your body",
      "body": "Recovery is well behind. Rest isn't lazy today — it's the right move."
    }
  }
}

Mental Wellbeing Score

Mental Wellbeing requires the most care. Every message should describe behavioral patterns — never mental states. The tone should feel like a thoughtful friend noticing something, not a clinician delivering results.

Important: Never write “your mental health is declining” or “you may be at risk.” This score measures behavioral patterns, not mental states. When the score is persistently low, shift from behavioral observations to supportive resource surfacing — breathing exercises, mindfulness content, or gentle prompts toward professional guidance.

{
  "type": "mental_wellbeing",
  "factors": {
    "steps": {
      "strong": {
        "headline": "On the move",
        "body": "{value} steps. Daily walking anchors your day better than almost anything — it's the simplest habit that keeps the rest on track."
      },
      "weak": {
        "headline": "Moving less than usual",
        "body": "Step count has dipped lately. That's alright — even a short walk, whenever it feels right, tends to help more than people expect."
      }
    },
    "active_hours": {
      "strong": {
        "headline": "Present all day",
        "body": "Active across your waking hours — not just one window. Spreading activity through the day builds a steadier rhythm than front-loading it into one session."
      },
      "weak": {
        "headline": "Fewer active hours",
        "body": "Less active than usual. It doesn't have to be exercise — standing, stretching, or a short walk are all worth something."
      }
    },
    "extended_inactivity": {
      "strong": {
        "headline": "Breaking it up",
        "body": "Good at interrupting long sitting stretches. Those small breaks keep your daily rhythm varied — and a varied day tends to be a more engaged one."
      },
      "weak": {
        "headline": "Long time sitting",
        "body": "More extended sedentary stretches than usual. Even standing briefly between long sits helps break the pattern."
      }
    },
    "activity_regularity": {
      "strong": {
        "headline": "Steady rhythm",
        "body": "Activity patterns have been predictable day to day. That kind of structure creates a foundation — when your days have shape, the rest tends to fall into place."
      },
      "weak": {
        "headline": "Rhythm's off",
        "body": "Activity has been less predictable lately. When things feel unstructured, one consistent anchor — same time, same small activity — can help steady things."
      }
    },
    "sleep_regularity": {
      "strong": {
        "headline": "Steady nights",
        "body": "Bed and wake times have been consistent. Sleep regularity is one of the strongest behavioral predictors of mental wellness — stronger than duration in many cases."
      },
      "weak": {
        "headline": "Sleep timing jumping around",
        "body": "Your sleep schedule has been less predictable. A consistent rhythm is one of the first things to stabilize — your body and mind both settle with it."
      }
    },
    "circadian_alignment": {
      "strong": {
        "headline": "In sync",
        "body": "Sleep timing aligns well with your body's natural clock. When your internal rhythm and your schedule agree, energy and daily patterns both stay more consistent."
      },
      "weak": {
        "headline": "Out of sync",
        "body": "Sleep timing has drifted from your body's natural window. A few minutes of morning light after waking is the simplest way to bring it back."
      }
    }
  },
  "fallback": {
    "high": {
      "headline": "Structure is holding",
      "body": "Daily patterns are consistent — stable activity and regular sleep timing. Routines like this tend to compound — one steady pattern reinforces the next."
    },
    "medium": {
      "headline": "A few things drifting",
      "body": "Most daily patterns are steady, but some variability is creeping in."
    },
    "low": {
      "headline": "Things have shifted",
      "body": "Daily routines have been less consistent recently. Activity or sleep patterns — or both — have changed."
    },
    "minimal": {
      "headline": "A lot has changed",
      "body": "The usual patterns look quite different lately. Be gentle with yourself."
    }
  }
}

Wellbeing Score

Wellbeing combines all 13 factors from Activity and Sleep into one holistic measure. Instead of highlighting a single factor, companion text focuses on the balance between the two dimensions. For individual factor-level messages, use the Activity and Sleep libraries above.

{
  "type": "wellbeing",
  "dimensions": {
    "activity_strong_sleep_strong": {
      "headline": "Everything's clicking",
      "body": "Movement and sleep are both doing their job. This combination drives faster recovery, steadier energy, and better daily function than either one alone."
    },
    "activity_strong_sleep_weak": {
      "headline": "Sleep's the missing piece",
      "body": "You're putting in the effort during the day, but sleep isn't keeping up. Without adequate rest, your body can't fully recover from all that activity — the gains don't stick as well."
    },
    "activity_weak_sleep_strong": {
      "headline": "Sleep's strong, movement's light",
      "body": "Rest is solid — that's real. Adding more daily movement would round this out: better circulation, sharper daily function, and a body that's both well-rested and well-used."
    },
    "activity_weak_sleep_weak": {
      "headline": "Both sides need love",
      "body": "Activity and sleep are both below par. Starting with sleep is usually the smarter move — once rest improves, you'll have more energy to move during the day, and the cycle builds from there."
    },
    "activity_moderate_sleep_weak": {
      "headline": "Rest is the bottleneck",
      "body": "You're moving ok, but sleep is undermining the returns. Better rest means better recovery from that activity, and more capacity to keep it up."
    },
    "activity_weak_sleep_moderate": {
      "headline": "Movement would change this",
      "body": "Sleep is decent, but low activity limits how much that rest can do for you. Even adding a short daily walk or a few more active hours would shift the balance."
    }
  },
  "fallback": {
    "high": {
      "headline": "Moving and resting well",
      "body": "Both sides of the equation are working. This is where the compounding happens — movement fuels better sleep, better sleep fuels more movement."
    },
    "medium": {
      "headline": "One side's pulling the other down",
      "body": "Something in the balance is off — one dimension is likely undermining the other."
    },
    "low": {
      "headline": "Something's out of sync",
      "body": "Activity and sleep aren't supporting each other right now. The factor breakdown shows which side needs attention."
    },
    "minimal": {
      "headline": "Time to reset",
      "body": "Both movement and rest are well below where they should be. Start with sleep — the rest tends to follow."
    }
  }
}

Wellbeing uses a different selection pattern than the other four scores. Instead of picking the single weakest factor, compare the average sub-score of the activity factors against the sleep factors to determine which dimension message to show:

function getWellbeingText(score, messageLibrary) {
  const activityNames = [
    'steps', 'active_hours', 'extended_inactivity',
    'active_calories', 'intense_activity_duration', 'floors_climbed'
  ];
  const sleepNames = [
    'sleep_duration', 'sleep_regularity', 'sleep_debt',
    'circadian_alignment', 'sleep_continuity', 'physical_recovery', 'mental_recovery'
  ];

  const avg = (names) => {
    const scores = names
      .map(n => score.factors.find(f => f.name === n)?.score)
      .filter(s => s != null);
    return scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : null;
  };

  const activityAvg = avg(activityNames);
  const sleepAvg = avg(sleepNames);

  if (activityAvg == null || sleepAvg == null) {
    return messageLibrary.fallback[score.state];
  }

  const tier = (v) => v >= 0.70 ? 'strong' : v >= 0.40 ? 'moderate' : 'weak';
  const key = `activity_${tier(activityAvg)}_sleep_${tier(sleepAvg)}`;

  return messageLibrary.dimensions[key] ?? messageLibrary.fallback[score.state];
}

The dimension library covers the most important contrasts — when both sides are strong, when one clearly outweighs the other, and when both are weak. Combinations that don’t have a specific message (like both moderate) fall through to the fallback, which handles them naturally.


Writing Your Own

If you’re adapting the defaults or writing messages from scratch, these principles are what the defaults are built on:

Celebrate specifically. “Sleep schedule has been remarkably steady” — not “Great job!” Naming what’s actually good feels earned. Generic praise feels hollow.

Observe, don’t judge. “Step count is lower than usual” — not “You didn’t move enough.” Describe what the data shows. Let the user draw conclusions.

Normalize the bad days. “That’s ok” and “it happens” prevent the score from becoming a source of anxiety. The apps people trust are the ones they don’t dread opening.

Be warm, not clinical. “Still absorbing the effort” — not “Exercise strain capacity is reduced.” This isn’t a medical report.

Never diagnose. Especially for Mental Wellbeing and Readiness. Describe behaviors and patterns. Never name conditions or infer states of mind.


Setup Checklist

  • Add message libraries — use the defaults above or write your own
  • Wire up selection logic — implement getCompanionText (and getWellbeingText for Wellbeing)
  • Handle missing data — when all factors are null or no score exists yet, fall back gracefully
  • Display beside the score — headline and body on your main score screen or card
  • (Optional) Adapt to your brand — ship the defaults as-is, or rewrite the copy in your own voice

Further Reading