Methodology

Algorithm activity-v1 · revert rules revert-rules-v1 · diff pipeline diff-v6 · prompt revision-summary-v1

HistoryRewritten answers two narrow questions about a Wikipedia article and an event date: did the article’s editing pattern change after the event, and what actually changed in the text? The first is a deterministic, model-free score. The second is a source-grounded comparison of two exact revisions. Neither is a verdict.

Activity is not proof of bias or misconduct. An edit spike can mean new sources, cleanup, disputes, or plain attention.

What the activity score measures

The score compares an article’s editing after the event with the same article’s own past: the 36 full calendar months before the event month. There is no cross-article model and no notion of a “normal” Wikipedia article — a page that has always been busy is measured against its own busy baseline. Six components each produce a value from 0 to 1:

ComponentRaw featureNormalization to 0–1Weight
Edit rate
edit_rate_anomaly
Median edits per full post-event month, against the baseline months.robust z ÷ 8, capped at 1; scale floor 1 edit/month.40%
Text churn
churn_anomaly
Median bytes added + removed per post-event month, against the baseline months.robust z ÷ 8, capped; floor max(1, 10% of the baseline median).20%
Revert-like edits
revert_anomaly
Share of revert-like edits in the post-event revision sample minus the baseline sample.increase ÷ 0.25 (a rise of 25 percentage points saturates).15%
Talk-page activity
talk_page_anomaly
Median talk-page edits per post-event month, against the baseline months.robust z ÷ 8, capped; floor 1.10%
Editor concentration
editor_concentration_anomaly
Herfindahl–Hirschman index of edits per editor (aggregate only), post-event sample minus baseline.increase ÷ 0.5.10%
Attention-adjusted
attention_adjusted_anomaly
(post-event edits per million pageviews + 1) ÷ (pre-event + 1).log₂(ratio) ÷ 3 (a 8× ratio saturates); ratios below 1 count as 0.5%

The monthly components use a robust z-score: the post-event value minus the median of the baseline months, divided by max(1.4826 × MAD, floor), where MAD is the median absolute deviation of those baseline months. Medians and MADs are not thrown off by one wild month the way means and standard deviations are, and the floor keeps a perfectly constant baseline from turning any change into an infinite anomaly. Only increases count: a z below zero normalizes to 0.

The score is the weighted sum of the components, scaled to 0–100. When a component cannot be computed for a page — no pageview data for the range, no talk page, no revision sample — it is reported as unavailable and the remaining weights are renormalized to sum to 1, so missing data never silently reads as “typical”. Every response returns each component’s raw intermediates, normalized value, configured weight, effective weight, and point contribution; any score can be reconstructed by hand from the “Why this score” card on its report.

score = 100 × Σ (wᵢ / Σ w_available) × vᵢ      over available components
w = { edit_rate .40, churn .20, revert .15, talk_page .10, editor_concentration .10, attention_adjusted .05 }

The two gates

Two conditions withhold a score entirely, because a number would be less honest than none:

Below the gates, individual missing metrics are listed under Data quality on every report, together with flags such as a truncated revision sample or a very recent event with no full post-event month yet (the first-90-day daily rate stands in, flagged).

How to read a score

ScoreLabel
034Typical revision activity
3559Elevated revision activity
6079High revision activity
80100Very high revision activity

A worked example

Zionism (en.wikipedia.org) after October 7, 2023, as displayed on its report. Normalized values are shown on the 0–100 scale the report uses; points are weight × normalized value.

ComponentWeightNormalizedPoints
Edit ratemedian post-event month 53 edits vs. baseline median 6.50.4010040
Text churn0.2010020
Revert-like edits0.15192.85
Talk-page activity0.1010010
Editor concentration0.1040.4
Attention-adjusted0.05502.5
Sum, rounded once to a whole number75.7576

The report computes from unrounded values and rounds only the final sum, so a hand check from the displayed figures can differ from the headline by a tenth of a point. The result falls in the “high” band. Read with the components: edit rate, churn, and talk-page activity saturated; revert-like share rose modestly; editor concentration barely changed. The comparison on the same report shows the exact sentences that differ between the last pre-event revision and the current one.

The comparison pipeline

  1. Pinned revisions. Source is the latest revision at or before the event; destination is a pinned current revision (or an explicit one). Both are immutable, and both IDs are shown with links to the exact revisions and the official on-wiki diff.
  2. Structured extraction. Each revision’s rendered HTML becomes a list of blocks — lead and section paragraphs, list items, quotes, infobox fields — with citation URLs resolved from footnotes. Navboxes, page chrome, reference glyphs, and data tables are stripped.
  3. Deterministic alignment. Globally identical blocks are removed; sections are matched by heading path, with renamed sections detected by content similarity (Jaccard ≥ 0.5); remaining blocks within a section are paired greedily by token Jaccard similarity (≥ 0.25); leftovers become additions and removals. No model is involved in deciding what changed.
  4. Evidence packets with stable IDs. Every change gets an ID (chg_0001, chg_0002, …) in document order, sentence-trimmed context, and a lexical-change ratio. A 12,000-token input budget selects packets by priority (lead, infobox, headings, citation changes, attribution language, numeric changes, lexical delta); the number withheld is always reported, never silently dropped.
  5. A tool-less, schema-constrained model. Only when you click. openai/gpt-5.6-luna through the Vercel AI Gateway receives the packets as untrusted data, has no tools or web access, and must return JSON matching a fixed schema in which every finding cites one or more evidence IDs. Its instructions forbid inferring intent, ideology, identity, or misconduct.
  6. Grounding validator. Findings must cite at least one known ID and no unknown ones, with confidence in [0, 1]. A failing response gets one repair attempt; then a deterministic, schema-valid fallback summary takes over (also used when model budgets are exhausted). The response says which one you got.
  7. The excerpts are the evidence. The exact before/after text of every cited packet is rendered beside the summary, with the canonical Wikipedia diff link. The model’s sentence is never the only thing on the page.

Known limits

Versions

Activity algorithmactivity-v1
Revert-like rulesrevert-rules-v1
Diff pipelinediff-v6
Summary promptrevision-summary-v1
Modelopenai/gpt-5.6-luna

Every API response carries its algorithm version; every comparison carries its prompt version, model, and both revision IDs. A cached analysis is served without upstream checks for 24 hours, re-verified cheaply after that, and never served more than 7 days after it was computed; reports show their compute time.

Wikimedia etiquette

All data comes live from the public Wikimedia Analytics (AQS) and MediaWiki Action APIs. Requests carry an informative User-Agent identifying the project; a per-process governor keeps REST calls to at most 3 concurrent and Action API calls to one at a time, spaced at least 200 ms apart; Retry-After and maxlag responses are honored with exponential backoff; and results are cached so a popular article costs Wikimedia a handful of calls a week, not one per reader. HistoryRewritten is an independent project, not affiliated with or endorsed by the Wikimedia Foundation.

Read the full specification

The complete algorithm document, threat model, privacy notes, and evaluation plan are in the repository: docs/ALGORITHM.md, THREAT_MODEL.md, EVALUATION.md. The constants quoted on this page are read from the same configuration file the service runs on. Questions the page doesn’t answer may already be in the FAQ.