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:
| Component | Raw feature | Normalization to 0–1 | Weight |
|---|---|---|---|
Edit rateedit_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 churnchurn_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 editsrevert_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 activitytalk_page_anomaly | Median talk-page edits per post-event month, against the baseline months. | robust z ÷ 8, capped; floor 1. | 10% |
Editor concentrationeditor_concentration_anomaly | Herfindahl–Hirschman index of edits per editor (aggregate only), post-event sample minus baseline. | increase ÷ 0.5. | 10% |
Attention-adjustedattention_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:
- Insufficient baseline — fewer than 12 full baseline months or fewer than 20 pre-event edits. The normalized components are still shown, unscored, so you can see the signals without a headline number that thin history cannot support.
- Created after the event — the article’s first revision is later than the event. There is no pre-event baseline to measure against, so the report shows the article’s full history descriptively and nothing else. (Try Large language model against the ChatGPT release: the gate, working.)
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
| Score | Label |
|---|---|
| 0–34 | Typical revision activity |
| 35–59 | Elevated revision activity |
| 60–79 | High revision activity |
| 80–100 | Very high revision activity |
- It is a sustained median shift, not a burst. The monthly components compare the median post-event month with the baseline. A two-week surge that then settles back barely moves the median; a change that holds across many months moves it a lot. The first-90-day window is reported in the analysis-windows table but enters the score only when no full post-event month exists yet.
- “Typical” does not mean nothing happened. It means the editing since the event looks like the article’s own past. Ancient Rome after October 7, 2023 scores 12 — a control article, still edited at its usual pace. Theodor Herzl (2), Balfour Declaration (1), Israel (9), and Photosynthesis (4) also score typical for that date; contested is not the same as anomalous.
- “High” does not mean wrongdoing. Activity is not proof of bias or misconduct. OpenAI after the ChatGPT release scores 69 — high. Nothing in that number says whether the editing was good or bad; it says the article was edited far more than its own past. The component breakdown shows which signals drove a score, and the comparison shows what changed in the text; the conclusion is yours to draw from the evidence.
- A known blind spot, stated first: Ukraine after February 24, 2022 scores 1. A median-shift score cannot tell a burst that later settles back toward the article’s own baseline from no change at all, and a low score on a famously busy article is a statement about medians, not about the event. That is the design working as specified, and it is a limitation. An acute-window component is a candidate for a future algorithm version; until then, read the timeline and the acute window alongside the score — that is where such editing shows.
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.
| Component | Weight | Normalized | Points |
|---|---|---|---|
| Edit rate — median post-event month 53 edits vs. baseline median 6.5 | 0.40 | 100 | 40 |
| Text churn | 0.20 | 100 | 20 |
| Revert-like edits | 0.15 | 19 | 2.85 |
| Talk-page activity | 0.10 | 100 | 10 |
| Editor concentration | 0.10 | 4 | 0.4 |
| Attention-adjusted | 0.05 | 50 | 2.5 |
| Sum, rounded once to a whole number | 75.75 → 76 | ||
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
- 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.
- 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.
- 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.
- 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. - A tool-less, schema-constrained model. Only when you click.
openai/gpt-5.6-lunathrough 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. - 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.
- 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
- Median-shift design. Short, intense bursts are under-weighted by construction (the Ukraine example above).
- Weights are hypotheses. The weights and thresholds on this page have not yet been calibrated against a labeled benchmark. The evaluation plan optimizes for precision — a false alarm on a control article would damage the product more than a missed weak signal — and any change ships as a new algorithm version with a comparison report.
- Conservative revert heuristic. An edit counts as revert-like only if it carries one of the MediaWiki tags
mw-undo,mw-rollback,mw-manual-revertor its edit summary begins with a revert phrase such as “Reverted”, “rv”, or “Undid revision”. Manual rewrites that function as reverts are missed; the label is always “revert-like”, never certainty. - Revision sampling. Revert and editor aggregates use at most 3,000 revisions per window; when the cap binds, the report is flagged
revision_sample_truncated. - Tables are dropped. Data tables other than the infobox are outside the comparison in this version, so a change confined to a table is invisible to it.
- No usernames, no intent — by design. The product returns aggregate editor statistics only and never displays who edited or why. This is a hard boundary, not a tuning choice.
- Rendered-revision drift. MediaWiki renders old revisions with today’s templates. The pipeline neutralizes known artifacts and refuses to serve a comparison whose alignment looks degenerate, rather than presenting template noise as evidence.
Versions
| Activity algorithm | activity-v1 |
| Revert-like rules | revert-rules-v1 |
| Diff pipeline | diff-v6 |
| Summary prompt | revision-summary-v1 |
| Model | openai/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.