How Healper Works
I've gotten questions about how the numbers are calculated, so here's the full breakdown. If something looks off, hit the Feedback button below and I'll look in to it.
Supported Specs
Healper supports all 7 healer specializations:
| Spec | Class |
|---|---|
| Restoration Druid | Druid |
| Mistweaver Monk | Monk |
| Holy Paladin | Paladin |
| Discipline Priest | Priest |
| Holy Priest | Priest |
| Restoration Shaman | Shaman |
| Preservation Evoker | Evoker |
Each spec has its own benchmark data, tracked spells, cooldowns, and talent detection. The methodology below is the same across all specs — the specific spells and numbers are just tailored per spec.
HoT / Buff Uptime
HoT (or buff) uptime is the percentage of active combat time where at least one person in your group has that spell running. It answers "how much of the fight was I providing this to someone?" — not per-target.
This applies to any persistent heal-over-time or absorb buff your spec relies on. For Restoration Druid that's Rejuvenation, Lifebloom, and Wild Growth. For Restoration Shaman it's Riptide. For Mistweaver it's Renewing Mist. And so on.
Active Combat Time
We don't count the full dungeon timer. If there's a 3+ second gap between combat events (heals, damage, casts), we treat that as downtime. A 5-minute dungeon might only have 4 minutes of actual combat — we calculate against those 4 minutes.
def _detect_combat_segments(self, gap_threshold_ms: int = 3000):
combat_events = []
for event in self.events:
event_type = event.get("type", "")
if event_type in ["heal", "damage", "cast", "applybuff", "refreshbuff"]:
combat_events.append(event.get("timestamp"))
segments = []
segment_start = combat_events[0]
segment_end = combat_events[0]
for timestamp in combat_events[1:]:
if timestamp - segment_end <= gap_threshold_ms:
segment_end = timestamp
else:
segments.append((segment_start, segment_end))
segment_start = timestamp
segment_end = timestamp
return segments
Per-Pull Breakdown
A Mythic+ key is not one fight — it is twenty-odd pulls with gaps between them, and a whole-key average hides most of what a healer is actually asking about. The report splits the key into its pulls using those same combat segments, so the pull boundaries and every uptime figure on the page can never disagree about where a pull was.
Pulls are ranked only where the field is genuinely separable. A key with one long pull, or one whose top two sit inside a rounding margin, ranks nothing rather than naming a winner the measurement cannot support.
Note what the rows measure: healing done, which records what a pull demanded of you. A pull can be brutal for a tank and quiet for a healer, so this is not a difficulty ranking.
Pandemic Refreshes
When you refresh a HoT early, the remaining time carries over up to 30% of the base duration. Example for Restoration Druid:
| Spell | Base Duration | Pandemic Cap |
|---|---|---|
| Rejuvenation | 15s | 4.5s |
| Lifebloom | 15s | 4.5s |
| Wild Growth | 7s | 2.1s |
| Germination | 15s | 4.5s |
| Cultivation | 6s | 1.8s |
Each spec's tracked spells have their own durations configured accordingly.
Interval Merging
We track when each HoT is active on each target, then merge overlapping windows. If target A has Rejuv from 0:00–0:15 and target B has it from 0:10–0:25, we count that as 25 seconds of "Rejuv active on someone."
def _merge_intervals(self, intervals):
if not intervals:
return []
sorted_intervals = sorted(intervals)
merged = [sorted_intervals[0]]
for current in sorted_intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
merged[-1] = (last[0], max(last[1], current[1]))
else:
merged.append(current)
return merged
Mana Tracking
Mana Spent
We estimate mana cost per spell cast using a per-spec spell cost table. Example for Restoration Druid:
MANA_COSTS = {
774: 1000, # Rejuvenation
48438: 3000, # Wild Growth
33763: 800, # Lifebloom
8936: 1800, # Regrowth
18562: 1400, # Swiftmend
740: 0, # Tranquility
}
Every supported spec has its own equivalent cost table.
OOM Detection
Pretty simple — if your mana hits exactly 0 at any point, you went OOM:
if event["resourceAmount"] == 0 and not went_oom:
went_oom = True
oom_timestamp = event.get("timestamp")
Mana Efficiency
Just effective healing divided by mana spent. Higher = more healing per mana.
Fight Timeline
Your mana percent and the group's damage intake are plotted against one shared time axis, as two stacked panels rather than one chart with two y-axes. With two unrelated units on a single plot, the points where the lines cross are an artefact of whichever scales were picked, not a fact about the fight.
Behind the mana line sits a cohort band: p25 to p75 of where healers of your spec, on this encounter, in this bracket, averaged over the whole fight. It is drawn flat and the label says "average" on purpose. Each sampled log contributes exactly one number — its own fight-long average — so the band describes where healers ended up, not where you should be at any given second. A real pace curve would need the collector to store mana at fight-progress buckets, which it does not do.
The band is only ever built from a sample collected for that exact spec, encounter and bracket. A neighbouring bracket's mana habits are a different claim, and we do not borrow one for the other.
Overhealing
WCL gives us two values per heal: amount (what actually landed) and overheal (what was wasted on a full health target).
def calculate_overhealing(self):
total_healing = 0
effective_healing = 0
for event in self.events:
if event.get("type") == "heal":
amount = event.get("amount", 0)
overheal = event.get("overheal", 0)
total_healing += amount + overheal
effective_healing += amount
return ((total_healing - effective_healing) / total_healing * 100)
So if you cast a 10k heal on someone missing 3k health: 3k effective, 7k overheal, 70% overhealing on that cast.
Per-Spell Breakdown
"You overheal 38%" is a diagnosis with no prescription. Split by spell it becomes an instruction — Regrowth wasted 61% of its healing and accounts for 18% of everything you overhealed names the button to stop pressing.
The rows always add back up to the headline figure above them. It is the same heal events, split by ability, with nothing dropped; rows folded into "Other" stay in the totals. If you add up the table and get a different number than the stat card, that is a bug and worth reporting.
One trap this had to solve, because it is the sort of thing that fails silently. Our spell tables store the id a spell is cast under, but heal events carry the id it heals under — Dream Breath is cast as 355936 and heals as 355941, Atonement is cast as 194384 and heals as 81751. Naming rows from the cast table alone left a large share of a spec's healing sitting in anonymous Spell #<id> rows — above 60% for five of the seven specs — and split single buttons into two entries that could not merge, which sorted the row that should have been first below rows it beat. Names now come from the spell table first and fall back to the report's own ability list, which by construction contains every ability that appears in it.
Performance Score
The 0–100 score compares you to top performers at your key level.
The three categories are weighted differently for Mythic+ and for raid, because the two content types reward different things. Raid de-emphasises HoT uptime and moves that weight onto overheal efficiency and mana, which together stand in for spending cooldowns on scripted damage rather than spamming filler heals.
| Category | Mythic+ | Raid |
|---|---|---|
| HoT / Buff Uptimes | 50 | 30 |
| Mana Management | 20 | 25 |
| Overhealing | 30 | 45 |
Uptime Scoring
Each spec has 2–3 core spells weighted by importance, and those weights divide the HoT block rather than adding to it. Scores scale based on where you fall in the percentile distribution of top players for your spec. For example, a Restoration Druid splits the block 50/40/10 across Lifebloom, Rejuvenation and Wild Growth, so in Mythic+, where the block is 50 points, Lifebloom is worth 25, Rejuvenation 20 and Wild Growth 5. The same split over the 30-point raid block is worth 15, 12 and 3. A Restoration Shaman weights Riptide and Earth Shield differently.
if uptime >= p90:
score += spell_points
elif uptime >= p75:
score += spell_points * (0.76 + 0.24 * ((uptime - p75) / (p90 - p75)))
elif uptime >= p50:
score += spell_points * (0.5 + 0.26 * ((uptime - p50) / (p75 - p50)))
elif uptime > 0:
score += spell_points * 0.5 * (uptime / p50)
Mana Scoring
Based on average mana % compared to benchmarks for your spec. Going out of mana costs you half the mana block, which is 10 points in Mythic+ and 12.5 in raid.
Overhealing Scoring
Lower is better. We compare against what top players of your spec achieve at your key level.
Score Tiers
The score carries a quality label, not a percentile. A label describes the score itself; it does not claim a rank against a population, because the score is not a ranking. The ranked comparison lives on each benchmarked metric as the band described below.
| Score | Label |
|---|---|
| 95+ | Exceptional |
| 85–94 | Excellent |
| 75–84 | Good |
| 60–74 | Developing |
| 40–59 | Learning |
| <40 | Getting started |
Where the Score Came From
The score is never shown as a bare number. It carries its own composition — each category as a segment sized by the points it actually contributed, with the points available beside it. A 78 built from full uptime marks and one bad mana result is a different report from a 78 built from three near-misses, and the bar is what makes those two look different.
Each benchmarked metric also carries the band it landed in — "Top 10%", "Top 25%" — rather than a percentile number, and that is a deliberate limit rather than a rounding. The integer behind a band is a bucket midpoint chosen from five, so printing it would claim a rank the sample has never had. The band is what the data can carry.
Benchmarks
We pull data from top-performing healers for the exact thing you ran: your spec and key level bracket in that dungeon for Mythic+, or your spec and difficulty on that boss for raid. Both are refreshed periodically from Warcraft Logs rankings.
Which brackets actually have data is a question about collection, not about method, so the answer lives in Data coverage by season at the bottom of this page rather than in a list here. A list goes stale the week a bracket fills up, and a stale list is a claim we did not mean to make. Where your bracket has not been collected yet, the analysis falls back to Healper's general baseline and says so in your results.
For each bracket we calculate p25, p50, p75, p90, p95 for the core metrics of your spec:
- Primary HoT / buff uptimes
- Overhealing %
- Average mana %
When we say "75th percentile" — 75% of top players of your spec have a lower value than you, 25% have higher.
Which Slice of the Ladder
The target is achievable play, not the top of the leaderboard. Warcraft Logs serves rankings 100 to a page, and page 1 is roughly the top 0.1%: padding runs, unusually coordinated premades, and strategies a typical group cannot reproduce. So the collector is built to start at page 2: pages 2 through 4 for Mythic+, pages 2 through 3 for raid. Still excellent play, but the standard a good player can actually be coached toward.
The rule is the same for both, and so is the flag that records when it could not be met. What differs is only the addressing, because the two are ranked differently: a Mythic+ target is a spec in a dungeon at a key level bracket, and a raid target is a spec on a boss at a difficulty.
That is the aim, and it is worth being exact about when it holds, because it does not hold everywhere.
Provisional Benchmarks
Page 2 does not exist on the first day of a season. A brand-new dungeon or raid boss has a few hundred logged runs rather than a few thousand, so at a season launch the collector falls back to page 1 — which means those benchmarks are built from exactly the outlier band the rule exists to exclude, at the point in a season when unrepresentative play is most common.
That fallback is recorded rather than assumed away. Every benchmark file carries a sampling block, and one built from page 1 is flagged:
"sampling": {
"start_page": 1,
"provisional": True,
"reason": "page 2 returned only 12/100 rankings in bracket; "
"fell back to page 1, which includes the top-0.1% outlier band",
"rule": "page 2 returns >= 100 rankings inside the bracket",
}
A target graduates to page-2 sampling the moment page 2 returns a full 100 rankings inside its own bracket, and not before. The check runs per collected target, which means per spec and dungeon and key level bracket for Mythic+, and per spec and boss and difficulty for raid. Pools deepen at wildly different rates, and a single season-wide switch would be wrong at one end of the ladder whichever way it was set. Two specs on the same raid boss at the same difficulty can land on opposite sides of the rule. Graduated targets are re-collected from page 2; the ones still below the line are reported and skipped rather than rebuilt at full cost from the same page 1 they already have.
So the honest version of the claim: skipping the outlier band is the rule, and early in a season a good many benchmarks are still provisional — collected from page 1, including the top 0.1%, until their pool is deep enough to graduate. They tighten as the season fills in. Treat an early-season percentile as directional rather than as a settled standard.
Raids
Raid logs run through the same scoring pipeline. Benchmarks are collected per boss and per difficulty instead of per dungeon and key bracket — a Heroic kill is compared against Heroic kills of that boss by your spec, and each difficulty is a separate file. Which difficulties exist for a given tier depends on how far collection has got, in the same way brackets do for Mythic+.
Two things are worth knowing.
Raid sampling follows the same page-2 rule, resolved per cell. The graduation check described under Benchmarks runs per spec, boss and difficulty, exactly as it runs per spec, dungeon and key level bracket for Mythic+. Raid pools are thinner, which makes that check matter more here rather than less, and two specs on the same boss at the same difficulty can land on opposite sides of it. A cell whose page 2 is still too thin is collected from page 1 and marked provisional: those numbers still grade the run, but they describe the outlier band rather than the players you are being coached toward, so the report withholds the percentile claim instead of quoting a rank it cannot support.
Not every tab has a raid equivalent. Boss Mechanic Analysis grades casts against a curated dangerous-ability list, Interrupt Analysis assumes kickable trash casts, and the dispel catalogs were written for the Mythic+ pool. None of those exist for a raid boss, so those tabs are hidden on a raid log rather than filled in with a guess. Cooldowns and Deaths need none of that and run on any fight — the cooldown comparison drops its mechanic-keyed half and grades your cooldowns against measured damage intake instead.
Talent Detection
We grab your talents from WCL's CombatantInfo data. If that's not available, we infer from what spells you cast. Each spec has its own indicator spells. Example for Restoration Druid:
TALENT_INDICATOR_SPELLS = {
155777: "germination",
200389: "cultivation",
207386: "spring_blossoms",
439530: "symbiotic_blooms",
33891: "tree_of_life",
391528: "convoke",
197721: "flourish",
102693: "grove_guardians",
}
For hero tree detection, we check for tree-specific effects — for example, Symbiotic Blooms identifies Wildstalker, Grove Guardian enhancements identify Keeper of the Grove. Every spec with hero tree variants has equivalent detection logic.
Boss Mechanic Analysis
For dungeons with boss mechanic data, we analyze how you respond to specific abilities. When a boss casts something dangerous, we track:
- Your healing output in the 5-second window after the mechanic
- What spells you cast in response
- Comparison to top performers — what they typically cast and how much they heal
This helps identify specific mechanics where you might be under-responding or using the wrong tools.
Response Window
We use a 5-second window after each mechanic cast:
RESPONSE_WINDOW_MS = 5000
for event in healing_events:
if mechanic_timestamp <= event["timestamp"] <= mechanic_timestamp + RESPONSE_WINDOW_MS:
response_healing += event.get("amount", 0)
response_spells.append(event["abilityGameID"])
Benchmarked Mechanics
Not every mechanic needs a healing response. We only track abilities that top players consistently respond to with significant healing. These are determined by analyzing what mechanics correlate with healing spikes in top logs.
Interrupt Analysis
For dungeons with interrupt data, we track dangerous casts that your group failed to stop. This focuses on:
- High-priority interrupts — Casts that cause significant damage or debuffs
- Interruptible abilities — Only casts that can actually be kicked
We show which casts went through and on which pulls, helping identify interrupt coordination issues.
Response Spells, Ranked by Lift
When a cast does get through, we also show what top healers of your spec press as it lands — ranked by lift rather than by raw frequency.
Raw frequency is useless here: it puts the spec's most-pressed button on top of every ability, regardless of what the ability does. Lift divides a spell's rate on this cast by what the same spell does near dangerous casts generally, at the same bracket, pooled across the spec's other encounters. 1.0x means "pressed no more here than anywhere else", and only spells well clear of that are served.
Most dangerous casts carry an empty list, and that is the honest result rather than a gap — most of them have no spec-specific response signature at all.
Dispel Analysis
Season 2 is a dispel season. Its eight-dungeon pool carries 35 curated dispellable debuffs — 16 Magic, 11 Poison, 6 Curse, 2 Disease — and seven of the eight dungeons put at least one poison on the party. Two of the seven healer specs, Discipline and Holy Priest, cannot clear poison at all.
For each curated debuff we report:
- Applications — how many times it landed on a party member
- Cleared — how many were dispelled, by you or by anyone else
- Expired — how many ran their full duration instead
- Time to clear — from application to your dispel, as a median and a slowest
- Party share — what portion of the clears were yours, and who took the rest
Coverage, Not Blame
A "0 of 6" on a school your spec cannot touch is a fact about the party's composition, not a mark against you, and the panel says it that way. Nothing in this tab is a rank or a percentile either: there are no collected dispel benchmarks yet, so every number here is a record of your run rather than a comparison to anyone else's.
Where the panel does coach, it is gated twice — on the debuff's curated priority, and on how many of the sampled top logs cleared it at all. Not every dispellable debuff should be dispelled, so a raw clear-percentage would praise wrong play and scold right play. Below a floor of five sampled logs, the clear-rate is not evidence and is not offered as a reason.
Two Things That Make This Harder Than It Looks
The dispeller is not always the player. A Restoration Shaman's poison dispels come from Poison Cleansing Totem, and the dispel event names the totem as its source, not the Shaman. Attribution runs through the report's pet-owner map for that reason. Without it, a Shaman shows zero poison dispels while the party shows plenty.
A debuff that expired and a debuff that was dispelled end with the same event. removedebuff fires for both. The only thing separating them is whether a dispel event names that target and that debuff inside the application's window, which is why applications are tracked as intervals rather than simply counted. A debuff still up when the fight ended was neither cleared nor left to expire, so it is reported on its own rather than filed as either.
Cooldown Comparison
We compare your major cooldown usage against what top performers of your spec do in the same dungeon.
Tracked Cooldowns
Each spec has its own set of tracked cooldowns. Example for Restoration Druid:
TRACKED_COOLDOWNS = [
"Tranquility",
"Flourish",
"Tree of Life",
"Convoke the Spirits",
"Nature's Swiftness",
]
Other specs track their equivalent major cooldowns — Revival for Mistweaver, Avenging Crusader for Holy Paladin, Spirit Link Totem and Healing Tide for Restoration Shaman, and so on.
What We Measure
- Total casts — How many times you used each cooldown
- Expected casts — What top players of your spec typically use based on dungeon length
- Usage rate — Your casts compared to the benchmark
A "Good" rating means you're using your cooldowns at a similar rate to top players. "Could use more" suggests you might be holding cooldowns too long.
Reference Lanes
Where top logs agree on when a cooldown goes out, that agreement is drawn on the fight timeline as a lane — a stretch of fight progress rather than a single timestamp, because the agreement is a spread and drawing it as a point would overstate it. A lane carries the window the benchmark logs behind it were recorded in, so you can see how current it is.
Defensive Usage
Personal defensives and externals are counted separately and reported, not scored. Median personal usage is zero for several specs in heroic raid content, so grading it would penalize normal play.
Ramp Analysis (Restoration Druid)
Restoration Druid gets one spec-specific extra: Flourish ramps, graded on how many Rejuvenations were actually out when the Flourish landed, and summarised as excellent / good / partial / poor with the per-ramp detail underneath.
Death Autopsy
For every death in the fight we scrub back through the seconds before it and answer the question a healer actually opened the log for: what killed them, and was there anything you could have done?
The verdict is deliberately hard to say yes to. A healer reading this is usually already convinced the deaths were on them, so the failure mode that matters is agreeing with them when the log does not. The autopsy therefore never asserts that a death was your fault. Every death gets one of five verdicts:
| Verdict | What it rests on |
|---|---|
| Not on you | The fatal sequence was too short for anyone to answer, or your cooldowns were genuinely still recovering. |
| Avoidable damage | A curated avoidable boss ability landed the killing blow, or accounted for at least 35% of the damage in the window. They stood in something. |
| Personal unused | The victim's own survivability cooldown was up and unpressed. |
| Possible save | A healing cooldown or an external was available and there was time to press it. |
| Unclear | Said out loud whenever the data cannot carry a verdict. |
Only "Possible save" points back at the reader, and it says possible rather than responsible — it is reached only after every exculpating reading has been ruled out. Anything that cannot be resolved is labelled unclear rather than guessed at.
Cooldown availability comes from the same tracker the cooldown comparison and the damage-intake analysis read, so no two parts of the report can disagree about whether Tranquility was up. That tracker works from static cooldown durations, which can only ever make a cooldown look less available than it really was — so "available and unused" is the sound claim and "still recovering" is the one that gets hedged.
Timestamp Recommendations
When we have mechanic data, we can give you specific timing advice. If top players consistently pre-cast a healing cooldown 2–3 seconds before a specific boss ability, we'll tell you:
"Pre-cast Wild Growth 3s before Alerting Shrill"
These recommendations come from analyzing what top performers actually do, not theoretical advice.
Where Advice Comes From
Every recommendation on the report is one of two things: a reading of your own log, or a claim about what other healers do. The second kind needs a source, and it carries one.
Provenance
A benchmark-derived tip carries the sample it was drawn from, rendered underneath it:
Based on 214 logged Altar of Fangs runs in +10-14 keys by Mistweaver Monks.
That is the same sample size the confidence gate below already uses — provenance just makes it visible instead of discarding it. A tip with no benchmark sample behind it gets no such caption at all: rule-based advice about your own talents and casts is evidenced by your log, not by an external claim that needs sourcing, and inventing a citation for it would be worse than showing none.
Confidence Gating
Some advice is softened or dropped before you ever see it:
- Cross-mode benchmarks. A raid log graded against Mythic+ numbers, or the reverse, is not a comparison we will make.
- Talents you do not have. A tip about a talent that never appeared in your log is suppressed rather than shown.
- Thin samples. A recommendation backed by fewer than 20 benchmark logs is softened rather than asserted.
A percentile tick on an uptime bar uses a lower floor, 10 logs, and the difference is deliberate rather than a compromise. The two guard different things: the recommendation gate stands behind a sentence the product asserts, while the tick is a mark you compare your own number against, and uptime is far more stable across talent builds than a spell's share of healing is. Below either floor the established silence applies — the row renders with no tick rather than a hedged one.
Every suppression is logged with its reason, so the gate itself can be reviewed rather than trusted.
Telling Us the Advice Was Wrong
Every recommendation, mechanic note, interrupt call, cooldown verdict and death verdict carries a flag control. It needs no account and spends no Warcraft Logs quota. What comes back is recorded against the analyzer version that produced it, which is the only way to tell "this tip was always wrong" apart from "this tip stopped being right".
Saved Reports and Sharing
A finished analysis can be saved to your account and re-opened later, or turned into a share link that anyone can read without signing in.
A shared report renders from the payload stored when the link was created, not from a fresh analysis. That keeps a link showing what it showed the day it was made, even after the analyzer changes underneath it — which is the right behaviour for a link you sent someone, but does mean an old share link is a snapshot rather than a live view.
Questions?
Use the Feedback button below if something seems wrong or you have questions. I'm aiming for the numbers to be accurate.