v0.3.0: scoring assembly, API serving, Docker, ownership data, factuality feasibility

- orient: party-mean speech embeddings -> axis sign + reliability flag (ADR-0003)
- score/run: deterministic ratings.json + stories artifact; report: evidence pages
- serve: stdlib read-only API (/health /outlets /ratings /stories /manifests
  /evidence), CORS, path-traversal-safe
- Docker: CPU-torch image (1.23GB) w/ baked model for offline recompute; compose;
  CI builds every push, publishes GHCR from main
- outlets.yaml: sourced ownership data per outlet (verified flags); outlets doc
  regenerated w/ ownership columns
- docs/feasibility-factuality.md: anchor analysis, F1/F2/F3 architecture, gate
- fix: torch as direct dep pinned to CPU index (image was pulling CUDA, 6.7GB)
This commit is contained in:
flan
2026-07-10 07:00:52 +00:00
parent a7bd9b8c90
commit 4d9200a785
44 changed files with 3845 additions and 296 deletions
+12
View File
@@ -0,0 +1,12 @@
.git
.venv
data
releases
__pycache__
*.pyc
.pytest_cache
.ruff_cache
.github
docs
tests
scripts
+26
View File
@@ -16,6 +16,32 @@ jobs:
- name: tests (includes docs gates - readability, glossary, decision-block lint)
run: uv run pytest -q
docker:
# image must always build; publish to GHCR only from main
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: log in to GHCR
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build (and push from main)
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
tags: |
ghcr.io/sudolulo/tiltmeter:latest
cache-from: type=gha
cache-to: type=gha,mode=max
docs-sync:
# Code and config may not change without a docs change in the same range.
runs-on: ubuntu-latest
+39
View File
@@ -8,6 +8,45 @@ requires a version bump and, if it changes methodology, a decision record in
## [Unreleased]
## [0.3.0] - 2026-07-10
### Added
- Axis orientation (`orient.py`): party-mean speech embeddings decide the
axis sign (negative = left, positive = right); agreement strength is
published and weak orientation is flagged `reliable: false` (ADR-0003).
- Scoring assembly (`tiltmeter run`): manifest → deterministic ratings.json
(schema_version 1, no timestamps — reruns are byte-identical) + per-outlet
evidence pages with the axis-distinguishing stories, real headlines,
covered/skipped marks, neighbors, and CIs.
- Read-only HTTP API (`tiltmeter serve`, stdlib, CORS enabled): /health,
/ratings, /ratings/latest, /ratings/{id}, /manifests/{id},
/evidence/{id}/… — tiltmeter is a data layer; visualization is a separate
consumer's job (ADR-0003).
- Docker: reproducible image with the pinned embedding model baked in
(offline recomputation), compose file serving the API on :8477; CI builds
the image on every push and publishes ghcr.io/sudolulo/tiltmeter from main.
- First dry run on a one-day snapshot: pipeline exercised end to end; output
correctly self-flagged orientation as unreliable (ρ = +0.18, 63 stories).
- Outlet ownership data in `config/outlets.yaml`: owner, structure type, and
control notes per outlet, each entry carrying source URL, retrieval date,
and verified flag (same integrity rules as reference ratings). Served at
`/outlets`; shown in the generated outlets table. Context only — never a
scoring input.
- Story-cluster artifact (`stories-{snapshot}.json`) and `/stories/{id}`
endpoint: the side-by-side coverage primitive for consumer apps — who
covered each story and how each outlet headlined it.
- Factuality feasibility study (`docs/feasibility-factuality.md`): the anchor
problem, five candidate anchors evaluated, proposed F1/F2/F3 architecture
(process reliability / corroboration / resolvable claims), naming rule, and
validation gate. Planning only; no factuality signal is implemented.
### Fixed
- Docker image pulled CUDA torch via the transitive dependency (6.7GB image);
torch is now a direct dependency pinned to the CPU wheel index — image is
1.23GB and runs anywhere.
## [0.2.0] - 2026-07-10
### Added
+34
View File
@@ -0,0 +1,34 @@
# tiltmeter: the whole pipeline in one reproducible image.
# The pinned embedding model is baked in at build time, so a container can
# recompute any release fully offline — part of the reproducibility story,
# not just packaging convenience.
#
# Default command serves the read-only API over /app/releases; every other
# pipeline stage is available as a one-shot command, e.g.:
# docker compose run --rm tiltmeter ingest
# docker compose run --rm tiltmeter run --manifest releases/manifest-<id>.json
FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:0.7 /uv /uvx /bin/
WORKDIR /app
ENV UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1
# dependency layer first: rebuilds only when the lockfile changes
COPY pyproject.toml uv.lock README.md ./
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev --no-install-project
COPY src ./src
COPY config ./config
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-dev
# bake the pinned model so runs need no network and no HF account
RUN /app/.venv/bin/python -c "from tiltmeter import embed; embed._load_model()"
ENV PATH="/app/.venv/bin:$PATH"
VOLUME ["/app/data", "/app/releases"]
EXPOSE 8477
ENTRYPOINT ["tiltmeter"]
CMD ["serve", "--releases", "releases"]
+18
View File
@@ -40,8 +40,26 @@ uv run tiltmeter snapshot --start 2026-07-10 --end 2026-07-24
# fetch the orientation anchor: congressional floor speeches + party records
uv run tiltmeter reference --end 2026-07-09 --days 10
# compute ratings + evidence pages from a manifest
uv run tiltmeter run --manifest releases/manifest-2026-07-10_2026-07-24.json
# serve the computed releases as a read-only JSON API on :8477
uv run tiltmeter serve
```
Or with Docker (the pinned embedding model is baked into the image, so
recomputation works offline):
```sh
docker compose up -d # API on :8477
docker compose run --rm tiltmeter ingest # any pipeline command as one-shot
```
tiltmeter is a **data layer**: it computes and serves ratings JSON plus evidence
pages. Visualization is deliberately someone else's job — point your news reader
or dashboard at the API.
## The auditability contract
- **Reproducible**: same snapshot + same version ⇒ byte-identical ratings. Anyone
+15
View File
@@ -0,0 +1,15 @@
# API serving + one-shot pipeline commands.
# docker compose up -d # serve ratings API :8477
# docker compose run --rm tiltmeter ingest # poll feeds once
# docker compose run --rm tiltmeter snapshot --start ... --end ...
# docker compose run --rm tiltmeter run --manifest releases/manifest-<id>.json
services:
tiltmeter:
build: .
image: tiltmeter:latest
ports:
- "8477:8477"
volumes:
- ./data:/app/data
- ./releases:/app/releases
restart: unless-stopped
+144 -1
View File
@@ -8,69 +8,212 @@
# - feed must actually work; broken outlets get swapped for a
# same-region-of-spectrum alternative, recorded in CHANGELOG.md
#
# Fields: name (canonical id), feed (RSS/Atom URL), homepage.
# Ownership is factual context served alongside ratings (never a scoring
# input). Integrity rules match reference_ratings.yaml: every entry carries
# source + retrieval date; verified: false = transcribed from secondary
# knowledge, confirm at source before relying on it downstream.
#
# Fields: name (canonical id), feed (RSS/Atom URL), homepage, ownership.
outlets:
- name: cs-monitor
homepage: https://www.csmonitor.com
feed: https://rss.csmonitor.com/feeds/politics
ownership:
owner: The Christian Science Publishing Society (First Church of Christ, Scientist)
type: nonprofit
source: https://en.wikipedia.org/wiki/The_Christian_Science_Monitor
retrieved: 2026-07-10
verified: false
- name: pbs-newshour
homepage: https://www.pbs.org/newshour
feed: https://www.pbs.org/newshour/feeds/rss/politics
ownership:
owner: NewsHour Productions LLC (WETA, PBS member station)
type: nonprofit
source: https://en.wikipedia.org/wiki/PBS_News_Hour
retrieved: 2026-07-10
verified: false
- name: npr
homepage: https://www.npr.org
feed: https://feeds.npr.org/1014/rss.xml
ownership:
owner: National Public Radio, Inc.
type: nonprofit
source: https://en.wikipedia.org/wiki/NPR
retrieved: 2026-07-10
verified: false
- name: axios
homepage: https://www.axios.com
feed: https://api.axios.com/feed/
ownership:
owner: Cox Enterprises
type: private-company
note: family-held conglomerate; acquired Axios Media 2022
source: https://en.wikipedia.org/wiki/Axios_(website)
retrieved: 2026-07-10
verified: false
- name: the-hill
homepage: https://thehill.com
feed: https://thehill.com/homenews/feed/
ownership:
owner: Nexstar Media Group
type: public-company
note: acquired 2021; NASDAQ NXST
source: https://en.wikipedia.org/wiki/The_Hill_(newspaper)
retrieved: 2026-07-10
verified: false
- name: nytimes
homepage: https://www.nytimes.com
feed: https://rss.nytimes.com/services/xml/rss/nyt/Politics.xml
ownership:
owner: The New York Times Company
type: public-company
note: NYSE NYT; Ochs-Sulzberger family voting control via Class B shares
source: https://en.wikipedia.org/wiki/The_New_York_Times_Company
retrieved: 2026-07-10
verified: false
- name: washington-post
homepage: https://www.washingtonpost.com
feed: https://feeds.washingtonpost.com/rss/politics
ownership:
owner: Nash Holdings (Jeff Bezos)
type: private-company
source: https://en.wikipedia.org/wiki/The_Washington_Post
retrieved: 2026-07-10
verified: false
- name: cnn
homepage: https://www.cnn.com
feed: http://rss.cnn.com/rss/cnn_allpolitics.rss
ownership:
owner: Warner Bros. Discovery
type: public-company
note: in flux — WBD sale to Paramount Skydance DOJ-approved 2026-06-12;
CNN slated for the Discovery Global linear-networks entity; re-verify
on completion
source: https://www.pbs.org/newshour/nation/cnn-and-fellow-cable-networks-are-in-limbo-amid-takeover-bids-of-warner-bros-discovery
retrieved: 2026-07-10
verified: true
- name: nbc-news
homepage: https://www.nbcnews.com
feed: https://feeds.nbcnews.com/nbcnews/public/politics
ownership:
owner: NBCUniversal (Comcast)
type: public-company
note: NBC News remained with Comcast after the 2025 Versant cable spinoff
(which took MSNBC/CNBC); confirm current structure
source: https://en.wikipedia.org/wiki/NBC_News
retrieved: 2026-07-10
verified: false
- name: huffpost
homepage: https://www.huffpost.com
feed: https://www.huffpost.com/section/politics/feed
ownership:
owner: BuzzFeed, Inc.
type: public-company
note: acquired 2021
source: https://en.wikipedia.org/wiki/HuffPost
retrieved: 2026-07-10
verified: false
- name: mother-jones
homepage: https://www.motherjones.com
feed: https://www.motherjones.com/politics/feed/
ownership:
owner: Center for Investigative Reporting
type: nonprofit
note: Foundation for National Progress merged into CIR 2024-02-01
source: https://pressgazette.co.uk/the-wire/media-mergers-news-tracker/mother-jones-merges-with-center-for-investigative-reporting/
retrieved: 2026-07-10
verified: true
- name: the-nation
homepage: https://www.thenation.com
feed: https://www.thenation.com/feed/?post_type=article
ownership:
owner: The Nation Company, L.P.
type: private-company
source: https://en.wikipedia.org/wiki/The_Nation
retrieved: 2026-07-10
verified: false
# WSJ's politics feed is dead; the working feed is general news (not the
# Opinion feed — news and opinion are rated separately by every incumbent
# rater, and we collect news). Topic-mix tradeoff noted in METHODOLOGY D9.
- name: wsj
homepage: https://www.wsj.com
feed: https://feeds.a.dj.com/rss/RSSWorldNews.xml
ownership:
owner: Dow Jones & Company (News Corp)
type: public-company
note: NASDAQ NWSA; Murdoch family trust voting control
source: https://en.wikipedia.org/wiki/The_Wall_Street_Journal
retrieved: 2026-07-10
verified: false
- name: national-review
homepage: https://www.nationalreview.com
feed: https://www.nationalreview.com/feed/
ownership:
owner: National Review Institute
type: nonprofit
note: wholly-owned subsidiary of the 501(c)(3) NRI since 2015
source: https://ballotpedia.org/National_Review
retrieved: 2026-07-10
verified: true
- name: washington-examiner
homepage: https://www.washingtonexaminer.com
feed: https://www.washingtonexaminer.com/feed
ownership:
owner: Clarity Media Group (Anschutz Corporation)
type: private-company
source: https://en.wikipedia.org/wiki/Washington_Examiner
retrieved: 2026-07-10
verified: false
- name: ny-post
homepage: https://nypost.com
feed: https://nypost.com/politics/feed/
ownership:
owner: NYP Holdings (News Corp)
type: public-company
note: Murdoch family trust voting control
source: https://en.wikipedia.org/wiki/New_York_Post
retrieved: 2026-07-10
verified: false
- name: fox-news
homepage: https://www.foxnews.com
feed: https://moxie.foxnews.com/google-publisher/politics.xml
ownership:
owner: Fox News Media (Fox Corporation)
type: public-company
note: NASDAQ FOXA; Murdoch family trust voting control
source: https://en.wikipedia.org/wiki/Fox_News
retrieved: 2026-07-10
verified: false
- name: daily-wire
homepage: https://www.dailywire.com
feed: https://www.dailywire.com/feeds/rss.xml
ownership:
owner: The Daily Wire, LLC
type: private-company
note: co-founders Ben Shapiro and Jeremy Boreing; principal early funder
Farris Wilks
source: https://en.wikipedia.org/wiki/The_Daily_Wire
retrieved: 2026-07-10
verified: false
- name: breitbart
homepage: https://www.breitbart.com
feed: https://feeds.feedburner.com/breitbart
ownership:
owner: Breitbart News Network, LLC
type: private-company
note: Mercer family reported major stakeholders; CEO Larry Solov
source: https://en.wikipedia.org/wiki/Breitbart_News
retrieved: 2026-07-10
verified: false
- name: newsmax
homepage: https://www.newsmax.com
feed: https://www.newsmax.com/rss/Politics/1/
ownership:
owner: Newsmax, Inc.
type: public-company
note: NYSE NMAX since 2025; founder Christopher Ruddy controlling
source: https://finance.yahoo.com/quote/NMAX/
retrieved: 2026-07-10
verified: true
@@ -0,0 +1,54 @@
# ADR-0003: Orientation estimator; tiltmeter stays a data layer
- **Status**: accepted
- **Date**: 2026-07-10
- **Supersedes**: none (implements METHODOLOGY D5; scopes D10's delivery)
## Decision
1. **Orientation estimator**: each party's floor speeches (first 200 words each)
are embedded with the same pinned model as articles and averaged into a D-mean
and an R-mean vector. Each outlet's mean article embedding yields a proxy:
cosine-to-R minus cosine-to-D. The Spearman correlation between the unoriented
selection axis and this proxy decides the sign (negative correlation ⇒ flip),
with the convention **negative = left, positive = right**. If |ρ| < 0.3 the
orientation is marked `reliable: false` in ratings.json and the CLI prints
"UNRELIABLE — do not interpret".
2. **Product shape**: tiltmeter is a data layer — a pipeline plus a read-only HTTP
API (`tiltmeter serve`, stdlib only, CORS `*`) over the releases directory.
Visualization belongs to separate consumer software. tiltmeter never grows a UI;
outputs stay machine-consumable with versioned JSON schemas
(`schema_version` in ratings.json).
## Rationale (sources)
- Embedding-similarity to party language is the modern, cheap analog of
Gentzkow & Shapiro (2010) phrase-frequency slant; used here only for a **single
sign bit + a diagnostic**, not for scoring, which keeps the heavy lifting in the
transparent coverage signal (ADR-0001).
- Publishing the axis↔party-language correlation makes orientation failure visible
instead of silent — on day-one dry-run data it correctly reported ρ = +0.18,
UNRELIABLE (D6's honesty requirement working as intended).
- Serving files the pipeline already wrote means the API adds zero audit surface:
what you GET is byte-identical to what the pipeline produced and hash-pinned.
## Alternatives considered
- Full GentzkowShapiro phrase-based slant as the orientation source — planned as
signal S2 (v0.4+); requires the Gentzkow, Shapiro & Taddy (2019) estimator.
- Orienting via declared anchor outlets — rejected in ADR-0001.
- A web framework (FastAPI/Flask) for serving — more ergonomic, but a dependency
and an attack surface for what is, by design, static-file delivery. Revisit only
if the API grows beyond read-only.
## Failure modes / risks accepted
- Party-mean embeddings compress each party to one point; intra-party variation is
ignored. Acceptable for one sign bit; unacceptable for scoring — which is why it
doesn't score.
- With thin reference or news corpora the proxy correlation will hover near zero
and orientation will stay flagged unreliable; ratings remain publishable only as
explicitly-unreliable dry runs.
- An unoriented-but-strong axis with a weak proxy correlation likely means the
axis captured something other than politics (topic mix); the D7 gate would catch
this as low ρ against reference ratings.
+134
View File
@@ -0,0 +1,134 @@
# Feasibility: reproducible factuality measurement
Status: **planning document** — no factuality signal is implemented. This is the
feasibility analysis for whether tiltmeter can ever rate reliability the way it
rates lean: reproducibly, with no panel and no judge. Sources surveyed 2026-07-10.
## 1. The anchor problem
The lean pipeline works because ideology is **relational** and has a behavioral
public record: politicians reveal their positions by voting, DW-NOMINATE turns
roll calls into coordinates, and the Congressional Record ties party to language.
The anchor is (a) a public record, (b) behavior rather than anyone's rating,
(c) attributable to known actors, and (d) enormous.
Factuality is different in kind: it is **correspondence to world-states**, not
position among positions. There is no roll call for truth. Any feasible design
must either find data with the four anchor properties above, or honestly rename
what it measures. This document evaluates every candidate anchor we could find.
## 2. Candidate anchors, evaluated
| Anchor | Public record? | Judgment-free? | Outlet-attributable? | Coverage | Verdict |
|---|---|---|---|---|---|
| A. Court-adjudicated falsehoods (defamation judgments) | yes | yes (adjudicated) | yes | ~zero per outlet-year | footnote, not a signal |
| B. Resolvable claims vs. official statistics & event outcomes | yes | comparison is mechanical | yes | narrow slice of articles | **the structural twin — research-grade effort** |
| C. Revision/correction behavior (outlet's own edit history) | self-generated, capturable | yes | yes | every article | **cheapest honest signal — measures process, not truth** |
| D. Cross-outlet corroboration structure (our own clusters) | our corpus | mostly (alignment via embeddings) | yes | every story | **free by-product — measures isolation, not falsity** |
| E. Fact-checker verdicts (ClaimReview corpus) | published | no — human judgments | weakly (checks target claims/politicians) | sparse per outlet | validation target only, like AllSides for lean |
### B. Resolvable claims — the Congress-method equivalent
Some published claims resolve against public records with no judgment involved:
economic figures (BLS/BEA/FRED releases), election results (state returns, FEC),
census numbers, court outcomes (PACER/CourtListener), weather/disaster tolls
(official counts). "The unemployment rate fell to 3.9%" is checkable the way a
roll-call vote is checkable. Prior art exists and is active: the QuanTemp
benchmark for numerical claim verification (arXiv 2403.17169), Full Fact's
prototype Stats Checker verifying claims against official statistics, and the
CLEF CheckThat! lab series.
The costs, honestly: (1) **claim extraction requires an NLP model**, which
collides with METHODOLOGY D8 — resolvable only via a documented carve-out where
models may *extract and align* claims but never *evaluate* them, the mechanical
record-comparison doing all evaluation, with published human-audited extraction
samples; (2) only a minority of articles make resolvable claims, so per-outlet
sample sizes build slowly; (3) connectors to each statistical source must be
built and pinned. This is a research-grade pipeline — the strongest possible
anchor, and the most expensive.
### C. Revision behavior — cheap, behavioral, already half-built
An outlet's own edit history is a behavioral public record it generates about
itself. Track article revisions (we already poll every 6h; a revision mode
re-fetches each article on a decaying schedule for ~72h and diffs by content
hash) and measure: substantive-edit rate, **stealth-edit rate** (substantive
changes without a correction notice), correction latency, and correction-notice
practice. Strong prior art: NewsDiffs (2012), the NewsEdits dataset (1.2M+
revision histories, NAACL 2022), DiffEngine/NewsSniffer.
The honest limit: this measures **process transparency, not accuracy**. Many
corrections can mean error-prone *or* conscientious. The literature (and every
incumbent's rubric) treats correction practice as a credibility criterion, but
it must be published under its real name: process behavior.
### D. Corroboration structure — free by-product of the lean pipeline
We already cluster same-event articles across 20 outlets. Within a cluster,
an outlet's *distinctive* claims (present in its version, absent from all
others) are measurable via embedding alignment — no truth judgment. Track the
**time-lagged corroboration rate**: distinctive claims that other outlets later
confirm (scoops) vs. those that never get picked up. Persistent epistemic
isolation is a signal; it is not falsity, and scoop-heavy outlets need the time
lag to avoid punishment for being first. Also measurable per cluster:
**wire fidelity** — drift between an outlet's rendering and the wire original
it credits.
### E. Fact-checker cross-reference — validation, not signal
The ClaimReview corpus (Google Fact Check Tools API, Data Commons feed;
FactCheck.org, PolitiFact, WaPo judgments) is open and machine-readable, but it
is human judgment with severe selection bias: checkers check what went viral,
which correlates with audience size and topic, not with outlet accuracy.
Feeding it into scoring would import the panel we exist to remove — the same
circularity rule as lean (D7): **incumbent factuality ratings (MBFC factual
reporting, Ad Fontes reliability) and ClaimReview hit-rates are validation
targets only.**
## 3. Proposed architecture (v0.5+, strictly gated)
- **F1 — process reliability** (from C): revision tracking, stealth-edit rate,
correction latency/notice practice. Ships first; infrastructure is a small
delta on the collector.
- **F2 — corroboration structure** (from D): unilateral-claim rate with time
lag, wire fidelity. Ships from existing cluster data.
- **F3 — resolvable-claim accuracy** (from B): pilot on one domain first
(economic statistics — cleanest official sources), expand only if the pilot
survives audit. Requires the D8 extraction carve-out as a new ADR before any
code.
- **Naming rule**: the published axis is called **process reliability**, never
"factuality" or "truth", until/unless F3 matures enough to carry an accuracy
component. A score is a claim; the name is part of the claim.
- **Validation gate** (mirror of M3): composite F-signal must rank-correlate
ρ ≥ 0.6 with MBFC factual-reporting and Ad Fontes reliability ratings over
the outlet sample. Below gate ⇒ iterate or kill, published either way. The
bar is lower than lean's 0.7 because factuality prediction is documented in
the literature as the harder task, and the incumbent ratings we validate
against are themselves noisier on this axis.
## 4. What this can never do
No reproducible system measures truth at scale; claiming otherwise would just
hide the judgment somewhere. The incumbents' "factuality" grades are rubric
judgments of process plus reputation. Ours would be: measured process behavior
(F1), measured corroboration structure (F2), and — for one auditable slice of
claims — mechanical comparison against official records (F3). Narrower than
what a "truth score" implies, and honest about it. That honesty is the product.
## 5. Sequencing
Nothing here starts before the lean M3 gate reports. Then: F1 (revision
tracking is cheap and its data, like the corpus, gains value with every day it
runs — worth starting early), F2, F3 pilot. Each phase gets its own ADR with
kill criteria before code.
## 6. Sources
- QuanTemp numerical-claim benchmark: <https://arxiv.org/abs/2403.17169>
- Full Fact automated stats-checking: <https://fullfact.org/blog/2022/feb/claim-challenge-update/>
- CLEF CheckThat! fact-checking labs: <https://ceur-ws.org/Vol-4038/paper_53.pdf>
- NewsEdits revision dataset: <https://arxiv.org/abs/2206.07106>
- NewsDiffs: <https://en.wikipedia.org/wiki/NewsDiffs>
- Google Fact Check Tools API: <https://developers.google.com/fact-check/tools/api>
- Data Commons fact-check corpus: <https://datacommons.org/factcheck/blog>
- Baly et al., predicting factuality of sources (EMNLP 2018): <https://aclanthology.org/D18-1389/>
+26 -22
View File
@@ -10,27 +10,31 @@ swap policy for dead feeds are documented in
[METHODOLOGY.md, decision D4](../METHODOLOGY.md#d4-twenty-outlets-deliberately-spread-chosen-once-and-openly);
individual swaps are recorded in the [CHANGELOG](../CHANGELOG.md).
| Outlet | Homepage | Feed |
|---|---|---|
| axios | <https://www.axios.com> | <https://api.axios.com/feed/> |
| breitbart | <https://www.breitbart.com> | <https://feeds.feedburner.com/breitbart> |
| cnn | <https://www.cnn.com> | <http://rss.cnn.com/rss/cnn_allpolitics.rss> |
| cs-monitor | <https://www.csmonitor.com> | <https://rss.csmonitor.com/feeds/politics> |
| daily-wire | <https://www.dailywire.com> | <https://www.dailywire.com/feeds/rss.xml> |
| fox-news | <https://www.foxnews.com> | <https://moxie.foxnews.com/google-publisher/politics.xml> |
| huffpost | <https://www.huffpost.com> | <https://www.huffpost.com/section/politics/feed> |
| mother-jones | <https://www.motherjones.com> | <https://www.motherjones.com/politics/feed/> |
| national-review | <https://www.nationalreview.com> | <https://www.nationalreview.com/feed/> |
| nbc-news | <https://www.nbcnews.com> | <https://feeds.nbcnews.com/nbcnews/public/politics> |
| newsmax | <https://www.newsmax.com> | <https://www.newsmax.com/rss/Politics/1/> |
| npr | <https://www.npr.org> | <https://feeds.npr.org/1014/rss.xml> |
| ny-post | <https://nypost.com> | <https://nypost.com/politics/feed/> |
| nytimes | <https://www.nytimes.com> | <https://rss.nytimes.com/services/xml/rss/nyt/Politics.xml> |
| pbs-newshour | <https://www.pbs.org/newshour> | <https://www.pbs.org/newshour/feeds/rss/politics> |
| the-hill | <https://thehill.com> | <https://thehill.com/homenews/feed/> |
| the-nation | <https://www.thenation.com> | <https://www.thenation.com/feed/?post_type=article> |
| washington-examiner | <https://www.washingtonexaminer.com> | <https://www.washingtonexaminer.com/feed> |
| washington-post | <https://www.washingtonpost.com> | <https://feeds.washingtonpost.com/rss/politics> |
| wsj | <https://www.wsj.com> | <https://feeds.a.dj.com/rss/RSSWorldNews.xml> |
Ownership is sourced factual context (see the `ownership` blocks in the
config for source URLs, retrieval dates, and verification status); it is
never a scoring input.
| Outlet | Owner | Type | Feed |
|---|---|---|---|
| [axios](https://www.axios.com) | Cox Enterprises | private-company | <https://api.axios.com/feed/> |
| [breitbart](https://www.breitbart.com) | Breitbart News Network, LLC | private-company | <https://feeds.feedburner.com/breitbart> |
| [cnn](https://www.cnn.com) | Warner Bros. Discovery | public-company | <http://rss.cnn.com/rss/cnn_allpolitics.rss> |
| [cs-monitor](https://www.csmonitor.com) | The Christian Science Publishing Society (First Church of Christ, Scientist) | nonprofit | <https://rss.csmonitor.com/feeds/politics> |
| [daily-wire](https://www.dailywire.com) | The Daily Wire, LLC | private-company | <https://www.dailywire.com/feeds/rss.xml> |
| [fox-news](https://www.foxnews.com) | Fox News Media (Fox Corporation) | public-company | <https://moxie.foxnews.com/google-publisher/politics.xml> |
| [huffpost](https://www.huffpost.com) | BuzzFeed, Inc. | public-company | <https://www.huffpost.com/section/politics/feed> |
| [mother-jones](https://www.motherjones.com) | Center for Investigative Reporting | nonprofit | <https://www.motherjones.com/politics/feed/> |
| [national-review](https://www.nationalreview.com) | National Review Institute | nonprofit | <https://www.nationalreview.com/feed/> |
| [nbc-news](https://www.nbcnews.com) | NBCUniversal (Comcast) | public-company | <https://feeds.nbcnews.com/nbcnews/public/politics> |
| [newsmax](https://www.newsmax.com) | Newsmax, Inc. | public-company | <https://www.newsmax.com/rss/Politics/1/> |
| [npr](https://www.npr.org) | National Public Radio, Inc. | nonprofit | <https://feeds.npr.org/1014/rss.xml> |
| [ny-post](https://nypost.com) | NYP Holdings (News Corp) | public-company | <https://nypost.com/politics/feed/> |
| [nytimes](https://www.nytimes.com) | The New York Times Company | public-company | <https://rss.nytimes.com/services/xml/rss/nyt/Politics.xml> |
| [pbs-newshour](https://www.pbs.org/newshour) | NewsHour Productions LLC (WETA, PBS member station) | nonprofit | <https://www.pbs.org/newshour/feeds/rss/politics> |
| [the-hill](https://thehill.com) | Nexstar Media Group | public-company | <https://thehill.com/homenews/feed/> |
| [the-nation](https://www.thenation.com) | The Nation Company, L.P. | private-company | <https://www.thenation.com/feed/?post_type=article> |
| [washington-examiner](https://www.washingtonexaminer.com) | Clarity Media Group (Anschutz Corporation) | private-company | <https://www.washingtonexaminer.com/feed> |
| [washington-post](https://www.washingtonpost.com) | Nash Holdings (Jeff Bezos) | private-company | <https://feeds.washingtonpost.com/rss/politics> |
| [wsj](https://www.wsj.com) | Dow Jones & Company (News Corp) | public-company | <https://feeds.a.dj.com/rss/RSSWorldNews.xml> |
Total: 20 outlets.
+6 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "tiltmeter"
version = "0.2.0"
version = "0.3.0"
description = "Auditable, reproducible political-lean ratings for news outlets"
readme = "README.md"
requires-python = ">=3.13"
@@ -11,6 +11,11 @@ dependencies = [
"trafilatura>=1.12",
"pyyaml>=6.0",
"sentence-transformers>=3.0",
# torch is transitive via sentence-transformers, but must be a direct
# dependency for the pytorch-cpu index pin below to apply (uv resolves
# tool.uv.sources for direct dependencies only) — otherwise the lock
# pulls CUDA torch and the Docker image balloons to ~7GB
"torch>=2.6",
"scikit-learn>=1.5",
"numpy>=2.0",
"requests>=2.32",
+157
View File
@@ -0,0 +1,157 @@
{
"axis_inertia_share": 0.119917,
"corpus_hash": "626c32fd35709f7174672c22fa86b56227986e0d4560306bddd49b1a11b03b76",
"n_articles": 583,
"n_stories": 63,
"orientation": {
"correlation": 0.183459,
"method": "party-mean speech embeddings (ADR-0003)",
"reliable": false
},
"outlets": [
{
"ci_high": 1.0,
"ci_low": -1.0,
"outlet": "national-review",
"score": -0.056998,
"stories_covered": 4
},
{
"ci_high": 0.370098,
"ci_low": -0.531857,
"outlet": "newsmax",
"score": -0.052117,
"stories_covered": 12
},
{
"ci_high": 0.228278,
"ci_low": -0.436618,
"outlet": "ny-post",
"score": -0.050542,
"stories_covered": 6
},
{
"ci_high": 0.228342,
"ci_low": -0.495406,
"outlet": "npr",
"score": -0.049238,
"stories_covered": 7
},
{
"ci_high": 0.263456,
"ci_low": -0.370786,
"outlet": "the-hill",
"score": -0.047202,
"stories_covered": 12
},
{
"ci_high": 0.218725,
"ci_low": -0.516228,
"outlet": "breitbart",
"score": -0.044667,
"stories_covered": 9
},
{
"ci_high": 0.480408,
"ci_low": -0.647752,
"outlet": "the-nation",
"score": -0.044493,
"stories_covered": 11
},
{
"ci_high": 0.141765,
"ci_low": -0.240933,
"outlet": "daily-wire",
"score": -0.043512,
"stories_covered": 14
},
{
"ci_high": 0.785451,
"ci_low": -0.898349,
"outlet": "washington-post",
"score": -0.036812,
"stories_covered": 7
},
{
"ci_high": 0.401493,
"ci_low": -0.447497,
"outlet": "pbs-newshour",
"score": -0.035984,
"stories_covered": 11
},
{
"ci_high": 0.570331,
"ci_low": -0.60672,
"outlet": "fox-news",
"score": -0.03245,
"stories_covered": 9
},
{
"ci_high": 0.813997,
"ci_low": -0.869201,
"outlet": "washington-examiner",
"score": -0.030359,
"stories_covered": 8
},
{
"ci_high": 0.320726,
"ci_low": -0.239156,
"outlet": "huffpost",
"score": -0.00761,
"stories_covered": 25
},
{
"ci_high": 0.0,
"ci_low": 0.0,
"outlet": "cnn",
"score": -0.0,
"stories_covered": 0
},
{
"ci_high": 0.0,
"ci_low": 0.0,
"outlet": "wsj",
"score": -0.0,
"stories_covered": 0
},
{
"ci_high": 0.557721,
"ci_low": -0.434797,
"outlet": "nytimes",
"score": 0.01963,
"stories_covered": 9
},
{
"ci_high": 0.495591,
"ci_low": -0.278963,
"outlet": "nbc-news",
"score": 0.019704,
"stories_covered": 11
},
{
"ci_high": 0.352731,
"ci_low": -0.286694,
"outlet": "axios",
"score": 0.032479,
"stories_covered": 23
},
{
"ci_high": 1.0,
"ci_low": -0.458178,
"outlet": "mother-jones",
"score": 0.451646,
"stories_covered": 4
},
{
"ci_high": 1.0,
"ci_low": -0.0,
"outlet": "cs-monitor",
"score": 1.0,
"stories_covered": 2
}
],
"pipeline_version": "0.3.0",
"reference_frame": "lean relative to contemporary US congressional party discourse; negative = left, positive = right",
"schema_version": 1,
"snapshot_id": "2026-07-10_2026-07-11"
}
@@ -0,0 +1,32 @@
# Evidence: axios
- **Score**: +0.032 (95% CI -0.287 … +0.353); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 23 of 63 cross-outlet stories
- **Nearest neighbors**: nbc-news (+0.020), nytimes (+0.020), cnn (-0.000)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **covered** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **covered** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **covered** — [axios] Ukraine proves it can hit Russia almost anywhere
- **covered** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **covered** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: breitbart
- **Score**: -0.045 (95% CI -0.516 … +0.219); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 9 of 63 cross-outlet stories
- **Nearest neighbors**: the-nation (-0.044), daily-wire (-0.044), the-hill (-0.047)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **covered** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **covered** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: cnn
- **Score**: -0.000 (95% CI +0.000 … +0.000); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 0 of 63 cross-outlet stories
- **Nearest neighbors**: wsj (-0.000), huffpost (-0.008), nytimes (+0.020)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: cs-monitor
- **Score**: +1.000 (95% CI -0.000 … +1.000); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 2 of 63 cross-outlet stories
- **Nearest neighbors**: mother-jones (+0.452), axios (+0.032), nbc-news (+0.020)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **covered** — [mother-jones] Trump Cant Stop Talking About Communists
- **covered** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: daily-wire
- **Score**: -0.044 (95% CI -0.241 … +0.142); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 14 of 63 cross-outlet stories
- **Nearest neighbors**: the-nation (-0.044), breitbart (-0.045), the-hill (-0.047)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **covered** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: fox-news
- **Score**: -0.032 (95% CI -0.607 … +0.570); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 9 of 63 cross-outlet stories
- **Nearest neighbors**: washington-examiner (-0.030), pbs-newshour (-0.036), washington-post (-0.037)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **covered** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **covered** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: huffpost
- **Score**: -0.008 (95% CI -0.239 … +0.321); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 25 of 63 cross-outlet stories
- **Nearest neighbors**: cnn (-0.000), wsj (-0.000), washington-examiner (-0.030)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **covered** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **covered** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **covered** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,26 @@
# Evidence pages — snapshot 2026-07-10_2026-07-11
Generated by the pipeline (never hand-edited). Reference frame: lean relative to contemporary US congressional party discourse; negative = left, positive = right.
| Outlet | Score | 95% CI |
|---|---|---|
| [national-review](national-review.md) | -0.057 | -1.000 … +1.000 |
| [newsmax](newsmax.md) | -0.052 | -0.532 … +0.370 |
| [ny-post](ny-post.md) | -0.051 | -0.437 … +0.228 |
| [npr](npr.md) | -0.049 | -0.495 … +0.228 |
| [the-hill](the-hill.md) | -0.047 | -0.371 … +0.263 |
| [breitbart](breitbart.md) | -0.045 | -0.516 … +0.219 |
| [the-nation](the-nation.md) | -0.044 | -0.648 … +0.480 |
| [daily-wire](daily-wire.md) | -0.044 | -0.241 … +0.142 |
| [washington-post](washington-post.md) | -0.037 | -0.898 … +0.785 |
| [pbs-newshour](pbs-newshour.md) | -0.036 | -0.447 … +0.401 |
| [fox-news](fox-news.md) | -0.032 | -0.607 … +0.570 |
| [washington-examiner](washington-examiner.md) | -0.030 | -0.869 … +0.814 |
| [huffpost](huffpost.md) | -0.008 | -0.239 … +0.321 |
| [cnn](cnn.md) | -0.000 | +0.000 … +0.000 |
| [wsj](wsj.md) | -0.000 | +0.000 … +0.000 |
| [nytimes](nytimes.md) | +0.020 | -0.435 … +0.558 |
| [nbc-news](nbc-news.md) | +0.020 | -0.279 … +0.496 |
| [axios](axios.md) | +0.032 | -0.287 … +0.353 |
| [mother-jones](mother-jones.md) | +0.452 | -0.458 … +1.000 |
| [cs-monitor](cs-monitor.md) | +1.000 | -0.000 … +1.000 |
@@ -0,0 +1,32 @@
# Evidence: mother-jones
- **Score**: +0.452 (95% CI -0.458 … +1.000); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 4 of 63 cross-outlet stories
- **Nearest neighbors**: axios (+0.032), nbc-news (+0.020), nytimes (+0.020)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **covered** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **covered** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **covered** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: national-review
- **Score**: -0.057 (95% CI -1.000 … +1.000); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 4 of 63 cross-outlet stories
- **Nearest neighbors**: newsmax (-0.052), ny-post (-0.051), npr (-0.049)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **covered** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **covered** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: nbc-news
- **Score**: +0.020 (95% CI -0.279 … +0.496); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 11 of 63 cross-outlet stories
- **Nearest neighbors**: nytimes (+0.020), axios (+0.032), cnn (-0.000)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **covered** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **covered** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **covered** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **covered** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: newsmax
- **Score**: -0.052 (95% CI -0.532 … +0.370); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 12 of 63 cross-outlet stories
- **Nearest neighbors**: ny-post (-0.051), npr (-0.049), national-review (-0.057)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **covered** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **covered** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **covered** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **covered** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **covered** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **covered** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: npr
- **Score**: -0.049 (95% CI -0.495 … +0.228); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 7 of 63 cross-outlet stories
- **Nearest neighbors**: ny-post (-0.051), the-hill (-0.047), newsmax (-0.052)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **covered** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **covered** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **covered** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: ny-post
- **Score**: -0.051 (95% CI -0.437 … +0.228); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 6 of 63 cross-outlet stories
- **Nearest neighbors**: npr (-0.049), newsmax (-0.052), the-hill (-0.047)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **covered** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **covered** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: nytimes
- **Score**: +0.020 (95% CI -0.435 … +0.558); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 9 of 63 cross-outlet stories
- **Nearest neighbors**: nbc-news (+0.020), axios (+0.032), cnn (-0.000)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **covered** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **covered** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: pbs-newshour
- **Score**: -0.036 (95% CI -0.447 … +0.401); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 11 of 63 cross-outlet stories
- **Nearest neighbors**: washington-post (-0.037), fox-news (-0.032), washington-examiner (-0.030)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **covered** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: the-hill
- **Score**: -0.047 (95% CI -0.371 … +0.263); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 12 of 63 cross-outlet stories
- **Nearest neighbors**: npr (-0.049), breitbart (-0.045), the-nation (-0.044)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **covered** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: the-nation
- **Score**: -0.044 (95% CI -0.648 … +0.480); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 11 of 63 cross-outlet stories
- **Nearest neighbors**: breitbart (-0.045), daily-wire (-0.044), the-hill (-0.047)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **covered** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **covered** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: washington-examiner
- **Score**: -0.030 (95% CI -0.869 … +0.814); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 8 of 63 cross-outlet stories
- **Nearest neighbors**: fox-news (-0.032), pbs-newshour (-0.036), washington-post (-0.037)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: washington-post
- **Score**: -0.037 (95% CI -0.898 … +0.785); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 7 of 63 cross-outlet stories
- **Nearest neighbors**: pbs-newshour (-0.036), fox-news (-0.032), washington-examiner (-0.030)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
@@ -0,0 +1,32 @@
# Evidence: wsj
- **Score**: -0.000 (95% CI +0.000 … +0.000); negative = left, positive = right
- **Snapshot**: 2026-07-10_2026-07-11 (corpus 626c32fd3570…), pipeline v0.3.0
- **Coverage**: 0 of 63 cross-outlet stories
- **Nearest neighbors**: cnn (-0.000), huffpost (-0.008), nytimes (+0.020)
The stories below are the most axis-distinguishing in this snapshot —
the ones outlets' choices disagree about most. What this outlet covered
and skipped among them is what placed it where it is.
## Left-pole stories
- **skipped** — [national-review] Does the WNBA Not Like Caitlin Clark?
- **skipped** — [the-nation] The Supreme Court Undermines Immigrant Rights
- **skipped** — [npr] Campaign staffers keep trying to bet on races despite push to curb insider trading
- **skipped** — [newsmax] RNC Midterm Convention Becomes Fundraiser
- **skipped** — [npr] Sen. Jeanne Shaheen talks about Trump at NATO and the renewed strikes against Iran
- **skipped** — [the-nation] Rahm Emanuels Speech in Tel Aviv Breaks With a “No Daylight” Approach
- **skipped** — [daily-wire] Israel Warns U.S. Of New Iranian Plot To Assassinate Trump: Report
- **skipped** — [fox-news] Jeb Bush praises Trump for crippling Irans military, but warns of threat to US from reported drones in Cuba
## Right-pole stories
- **skipped** — [mother-jones] Trump Cant Stop Talking About Communists
- **skipped** — [cs-monitor] The hands-off era of AI oversight is ending. What comes next?
- **skipped** — [mother-jones] ICE Keeps Using The Same Justification For Killing Drivers
- **skipped** — [nbc-news] U.S. strikes Iran in major escalation as fears grow of a return to full-scale conflict
- **skipped** — [nbc-news] Mallory McMorrow ends bid for Democratic Senate nomination in Michigan
- **skipped** — [axios] Ukraine proves it can hit Russia almost anywhere
- **skipped** — [huffpost] Kentucky Governor Asks Mitch McConnell To Fully Update People On His Health
- **skipped** — [axios] UEFA says FIFA crossed a line on Balogun decision
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -28,8 +28,12 @@ swap policy for dead feeds are documented in
[METHODOLOGY.md, decision D4](../METHODOLOGY.md#d4-twenty-outlets-deliberately-spread-chosen-once-and-openly);
individual swaps are recorded in the [CHANGELOG](../CHANGELOG.md).
| Outlet | Homepage | Feed |
|---|---|---|
Ownership is sourced factual context (see the `ownership` blocks in the
config for source URLs, retrieval dates, and verification status); it is
never a scoring input.
| Outlet | Owner | Type | Feed |
|---|---|---|---|
"""
@@ -37,7 +41,8 @@ def render() -> str:
with open(CONFIG) as f:
outlets = yaml.safe_load(f)["outlets"]
rows = [
f"| {o['name']} | <{o['homepage']}> | <{o['feed']}> |"
f"| [{o['name']}]({o['homepage']}) | {o['ownership']['owner']} "
f"| {o['ownership']['type']} | <{o['feed']}> |"
for o in sorted(outlets, key=lambda o: o["name"])
]
return HEADER + "\n".join(rows) + f"\n\nTotal: {len(outlets)} outlets.\n"
+1 -1
View File
@@ -4,4 +4,4 @@ Every module in this package opens with a plain-language docstring stating the
question it answers. See docs/how-it-works.md for the full plain-language tour.
"""
__version__ = "0.2.0"
__version__ = "0.3.0"
+47 -1
View File
@@ -6,8 +6,10 @@ The command-line interface:
tiltmeter status — show how many articles we hold per outlet
tiltmeter snapshot — freeze a window of the corpus into a manifest
tiltmeter reference — fetch congressional floor speeches (the D5 anchor)
tiltmeter run — manifest → ratings.json + evidence pages
tiltmeter serve — read-only HTTP API over computed releases
Later milestones add: run, validate, report.
Later milestones add: validate (the M3 gate).
"""
import argparse
@@ -61,6 +63,39 @@ def cmd_reference(args: argparse.Namespace) -> int:
return 0
def cmd_run(args: argparse.Namespace) -> int:
from tiltmeter import __version__, report, score, snapshot
manifest = snapshot.load(args.manifest)
conn = db.connect(args.db)
ratings = score.compute(conn, manifest, __version__)
ratings_path = score.write(ratings, args.out)
stories, matrix, articles = score.story_details(conn, manifest)
stories_path = score.write_stories(score.stories_json(stories, articles, manifest), args.out)
report_dir = report.write(report.render(ratings, stories, matrix, articles), ratings, args.out)
print(f"ratings: {ratings_path}\nstories: {stories_path}\nevidence: {report_dir}/")
o = ratings["orientation"]
flag = "" if o["reliable"] else " [UNRELIABLE — do not interpret]"
print(
f"stories: {ratings['n_stories']}, axis inertia {ratings['axis_inertia_share']:.0%}, "
f"orientation rho {o['correlation']:+.2f}{flag}"
)
for entry in ratings["outlets"]:
print(
f" {entry['score']:+.3f} [{entry['ci_low']:+.3f} {entry['ci_high']:+.3f}]"
f" {entry['outlet']}"
)
return 0
def cmd_serve(args: argparse.Namespace) -> int:
from tiltmeter import serve
serve.run(args.releases, args.host, args.port)
return 0
def cmd_status(args: argparse.Namespace) -> int:
conn = db.connect(args.db)
rows = db.outlet_counts(conn)
@@ -103,6 +138,17 @@ def main(argv: list[str] | None = None) -> int:
p_reference.add_argument("--congress", type=int, default=119)
p_reference.set_defaults(func=cmd_reference)
p_run = sub.add_parser("run", help="compute ratings + evidence pages from a manifest")
p_run.add_argument("--manifest", required=True, help="path to a snapshot manifest")
p_run.add_argument("--out", default="releases", help="output directory")
p_run.set_defaults(func=cmd_run)
p_serve = sub.add_parser("serve", help="read-only HTTP API over computed releases")
p_serve.add_argument("--releases", default="releases")
p_serve.add_argument("--host", default="0.0.0.0")
p_serve.add_argument("--port", type=int, default=8477)
p_serve.set_defaults(func=cmd_serve)
args = parser.parse_args(argv)
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
return args.func(args)
+107
View File
@@ -0,0 +1,107 @@
"""Which end of the axis is politically left, and which is right?
The selection axis (signals/selection.py) has an arbitrary sign — the math
doesn't know left from right. Orientation comes from public records
(METHODOLOGY.md D5): we embed Democratic and Republican floor speeches from
the Congressional Record, compute each outlet's average article embedding,
and check which axis pole sits closer in language to which party. If the
axis is backwards, we flip it. Convention: negative = left, positive = right
(matches how AllSides and Ad Fontes draw their scales).
The strength of the axis↔party-language agreement is reported as a
diagnostic: a weak agreement means orientation (and possibly the axis
itself) shouldn't be trusted, and the output says so rather than hiding it.
"""
import hashlib
import sqlite3
from dataclasses import dataclass
import numpy as np
SPEECH_EMBED_WORDS = 200 # MiniLM reads ~256 tokens; the opening covers the topic
MIN_ABS_CORRELATION = 0.3 # below this, orientation is flagged unreliable
@dataclass(frozen=True)
class Orientation:
sign: int # +1 keep, -1 flip
correlation: float # spearman rho between axis and party-language proxy
reliable: bool
proxy_by_outlet: tuple[float, ...] # cos-to-R minus cos-to-D per outlet
def _spearman(a: np.ndarray, b: np.ndarray) -> float:
ra = np.argsort(np.argsort(a)).astype(np.float64)
rb = np.argsort(np.argsort(b)).astype(np.float64)
ra -= ra.mean()
rb -= rb.mean()
denom = np.sqrt((ra**2).sum() * (rb**2).sum())
return float((ra * rb).sum() / denom) if denom > 0 else 0.0
def _cached_embed_texts(conn: sqlite3.Connection, texts: list[str]) -> np.ndarray:
"""Embed with the same fingerprint-keyed cache articles use."""
from tiltmeter import embed
hashes = [hashlib.sha256(t.encode()).hexdigest() for t in texts]
conn.executescript(embed.CACHE_SCHEMA)
cached = {}
for h in hashes:
row = conn.execute(
"SELECT vector FROM embeddings WHERE content_hash = ? AND model = ?",
(h, embed.MODEL_NAME),
).fetchone()
if row:
cached[h] = np.frombuffer(row[0], dtype=np.float32)
missing = [(h, t) for h, t in zip(hashes, texts) if h not in cached]
if missing:
vectors = embed.embed_texts([t for _, t in missing])
conn.executemany(
"INSERT OR IGNORE INTO embeddings (content_hash, model, vector) VALUES (?, ?, ?)",
[(h, embed.MODEL_NAME, v.tobytes()) for (h, _), v in zip(missing, vectors)],
)
conn.commit()
cached.update({h: v for (h, _), v in zip(missing, vectors)})
return np.stack([cached[h] for h in hashes])
def party_means(conn: sqlite3.Connection) -> dict[str, np.ndarray]:
"""Average embedding of each party's floor speeches (unit-normalized)."""
rows = conn.execute("SELECT party, text FROM reference_speeches").fetchall()
if not rows:
raise ValueError("no reference speeches; run: tiltmeter reference")
means = {}
for party in ("D", "R"):
texts = [
" ".join(text.split()[:SPEECH_EMBED_WORDS])
for p, text in rows
if p == party
]
if len(texts) < 20:
raise ValueError(f"only {len(texts)} {party} speeches; reference corpus too thin")
mean = _cached_embed_texts(conn, texts).mean(axis=0)
means[party] = mean / np.linalg.norm(mean)
return means
def outlet_proxy(
outlet_vectors: dict[str, np.ndarray], means: dict[str, np.ndarray]
) -> dict[str, float]:
"""Per outlet: cosine-to-Republican minus cosine-to-Democratic language."""
proxy = {}
for outlet, vec in outlet_vectors.items():
unit = vec / np.linalg.norm(vec)
proxy[outlet] = float(unit @ means["R"] - unit @ means["D"])
return proxy
def orient_sign(axis_positions: list[float], proxy_values: list[float]) -> Orientation:
"""Pure decision: flip the axis if it anti-correlates with party language."""
rho = _spearman(np.asarray(axis_positions), np.asarray(proxy_values))
return Orientation(
sign=-1 if rho < 0 else 1,
correlation=rho,
reliable=abs(rho) >= MIN_ABS_CORRELATION,
proxy_by_outlet=tuple(float(p) for p in proxy_values),
)
+105
View File
@@ -0,0 +1,105 @@
"""Why is this outlet where it is? Show the reader actual headlines.
Evidence pages (METHODOLOGY.md D10): for every outlet, the stories that most
distinguish the two ends of the axis — which of them this outlet covered and
which it skipped, with real example headlines — plus its nearest neighbors
and its confidence interval. Generated by the same deterministic pipeline as
the scores; never hand-edited.
"""
from pathlib import Path
import numpy as np
from tiltmeter.signals.selection import _first_axis # same axis, story side
TOP_STORIES = 8
def _story_axis_coords(matrix: np.ndarray) -> np.ndarray:
"""Story (column) positions on the same axis the outlets were scaled on."""
return _first_axis(matrix.T)
def render(ratings: dict, stories: list, matrix: np.ndarray, articles: list) -> dict[str, str]:
"""Markdown evidence page per outlet, plus an index. Returns {filename: content}."""
outlet_order = [o["outlet"] for o in sorted(ratings["outlets"], key=lambda o: o["score"])]
scores = {o["outlet"]: o for o in ratings["outlets"]}
# matrix rows follow manifest outlet order, which is sorted outlet names
row_names = sorted(scores)
row_index = {name: i for i, name in enumerate(row_names)}
coords = _story_axis_coords(matrix)
# align story-axis sign with outlet orientation: a story mostly covered by
# right-scored outlets should sit at the right pole
row_scores = np.array([scores[n]["score"] for n in row_names])
covered_lean = (matrix.T @ row_scores) / np.maximum(matrix.sum(axis=0), 1.0)
if float(np.dot(coords, covered_lean)) < 0:
coords = -coords
ranked_stories = np.argsort(coords)
pages = {}
headline = lambda i: f"[{articles[i]['outlet']}] {articles[i]['title']}" # noqa: E731
for name in outlet_order:
row = row_index[name]
entry = scores[name]
neighbors = sorted(
(o for o in ratings["outlets"] if o["outlet"] != name),
key=lambda o: abs(o["score"] - entry["score"]),
)[:3]
lines = [
f"# Evidence: {name}",
"",
f"- **Score**: {entry['score']:+.3f} "
f"(95% CI {entry['ci_low']:+.3f}{entry['ci_high']:+.3f}); "
f"negative = left, positive = right",
f"- **Snapshot**: {ratings['snapshot_id']} (corpus {ratings['corpus_hash'][:12]}…), "
f"pipeline v{ratings['pipeline_version']}",
f"- **Coverage**: {entry['stories_covered']} of {ratings['n_stories']} "
f"cross-outlet stories",
"- **Nearest neighbors**: "
+ ", ".join(f"{n['outlet']} ({n['score']:+.3f})" for n in neighbors),
"",
"The stories below are the most axis-distinguishing in this snapshot —",
"the ones outlets' choices disagree about most. What this outlet covered",
"and skipped among them is what placed it where it is.",
"",
]
for label, indices in (
("Left-pole stories", ranked_stories[:TOP_STORIES]),
("Right-pole stories", ranked_stories[::-1][:TOP_STORIES]),
):
lines.append(f"## {label}")
lines.append("")
for sid in indices:
story = stories[int(sid)]
mark = "covered" if matrix[row, int(sid)] else "skipped"
lines.append(f"- **{mark}** — {headline(story.article_indices[0])}")
lines.append("")
pages[f"{name}.md"] = "\n".join(lines)
index = [
f"# Evidence pages — snapshot {ratings['snapshot_id']}",
"",
"Generated by the pipeline (never hand-edited). Reference frame: "
+ ratings["reference_frame"] + ".",
"",
"| Outlet | Score | 95% CI |",
"|---|---|---|",
]
for name in outlet_order:
e = scores[name]
index.append(
f"| [{name}]({name}.md) | {e['score']:+.3f} | "
f"{e['ci_low']:+.3f}{e['ci_high']:+.3f} |"
)
pages["index.md"] = "\n".join(index) + "\n"
return pages
def write(pages: dict[str, str], ratings: dict, out_dir: str | Path) -> Path:
directory = Path(out_dir) / f"report-{ratings['snapshot_id']}"
directory.mkdir(parents=True, exist_ok=True)
for filename, content in pages.items():
(directory / filename).write_text(content)
return directory
+128
View File
@@ -0,0 +1,128 @@
"""How does a snapshot become ratings.json?
The assembly line, end to end: manifest → embeddings → story clusters →
coverage matrix → selection axis with confidence intervals → orientation by
congressional language → one deterministic JSON file. No timestamps, no
randomness outside the fixed bootstrap seed: rerunning on the same snapshot
must produce the same bytes (METHODOLOGY.md D1, D10).
"""
import json
import sqlite3
from pathlib import Path
import numpy as np
from tiltmeter import embed, orient
from tiltmeter.cluster import cluster_articles, coverage_matrix
from tiltmeter.signals import selection
RATINGS_SCHEMA_VERSION = 1
REFERENCE_FRAME = (
"lean relative to contemporary US congressional party discourse; "
"negative = left, positive = right"
)
def compute(conn: sqlite3.Connection, manifest: dict, pipeline_version: str) -> dict:
"""Run the full pipeline on a loaded manifest; return the ratings dict."""
articles = manifest["articles"]
outlet_order = manifest["outlets"]
vectors = embed.embed_hashes(conn, [a["content_hash"] for a in articles])
stories = cluster_articles(vectors, [a["outlet"] for a in articles])
matrix = coverage_matrix(stories, outlet_order)
axis = selection.compute(matrix, outlet_order)
outlet_vectors = {}
for name in outlet_order:
rows = [i for i, a in enumerate(articles) if a["outlet"] == name]
outlet_vectors[name] = vectors[rows].mean(axis=0)
party = orient.party_means(conn)
proxy = orient.outlet_proxy(outlet_vectors, party)
orientation = orient.orient_sign(
list(axis.positions), [proxy[name] for name in axis.outlets]
)
s = orientation.sign
covered_counts = matrix.sum(axis=1)
outlets_out = [
{
"outlet": name,
"score": round(s * pos, 6),
"ci_low": round(min(s * lo, s * hi), 6),
"ci_high": round(max(s * lo, s * hi), 6),
"stories_covered": int(covered_counts[i]),
}
for i, (name, pos, lo, hi) in enumerate(
zip(axis.outlets, axis.positions, axis.ci_low, axis.ci_high)
)
]
outlets_out.sort(key=lambda o: o["score"])
return {
"schema_version": RATINGS_SCHEMA_VERSION,
"pipeline_version": pipeline_version,
"snapshot_id": manifest["snapshot_id"],
"corpus_hash": manifest["corpus_hash"],
"reference_frame": REFERENCE_FRAME,
"n_articles": len(articles),
"n_stories": len(stories),
"axis_inertia_share": round(axis.inertia_share, 6),
"orientation": {
"method": "party-mean speech embeddings (ADR-0003)",
"correlation": round(orientation.correlation, 6),
"reliable": orientation.reliable,
},
"outlets": outlets_out,
}
def write(ratings: dict, out_dir: str | Path) -> Path:
"""Deterministic serialization: same ratings dict, same bytes."""
path = Path(out_dir) / f"ratings-{ratings['snapshot_id']}.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(ratings, indent=1, sort_keys=True, ensure_ascii=False) + "\n")
return path
def story_details(conn: sqlite3.Connection, manifest: dict) -> tuple[list, np.ndarray, list]:
"""Recompute stories + matrix + story axis coords for evidence pages."""
articles = manifest["articles"]
vectors = embed.embed_hashes(conn, [a["content_hash"] for a in articles])
stories = cluster_articles(vectors, [a["outlet"] for a in articles])
matrix = coverage_matrix(stories, manifest["outlets"])
return stories, matrix, articles
def stories_json(stories: list, articles: list, manifest: dict) -> dict:
"""The side-by-side primitive for consumers: who covered each story, how
each headlined it. Deterministic; same clusters the scores were built on."""
return {
"schema_version": RATINGS_SCHEMA_VERSION,
"snapshot_id": manifest["snapshot_id"],
"corpus_hash": manifest["corpus_hash"],
"stories": [
{
"story_id": s.story_id,
"n_outlets": len(s.outlets),
"articles": [
{
"outlet": articles[i]["outlet"],
"title": articles[i]["title"],
"url": articles[i]["url"],
"published": articles[i]["published"],
}
for i in s.article_indices
],
}
for s in stories
],
}
def write_stories(payload: dict, out_dir: str | Path) -> Path:
path = Path(out_dir) / f"stories-{payload['snapshot_id']}.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=1, sort_keys=True, ensure_ascii=False) + "\n")
return path
+113
View File
@@ -0,0 +1,113 @@
"""How does other software get the numbers?
tiltmeter is a data layer: it computes ratings; separate software visualizes
them. This module is the delivery end — a small, read-only HTTP API over the
releases directory. No framework, no state, no writes: every response is a
file the pipeline already produced, so serving adds nothing to audit.
GET /health liveness + what's available
GET /outlets outlet list incl. sourced ownership data
GET /ratings list of snapshot ids with ratings
GET /ratings/latest newest ratings.json
GET /ratings/{snapshot_id} specific ratings.json
GET /stories/{snapshot_id} story clusters: who covered what, headlines
GET /manifests/{snapshot_id} corpus manifest (for verifiers)
GET /evidence/{snapshot_id}/ evidence index + per-outlet pages
CORS is wide open: the data is public and consumers are other people's
frontends. Snapshot ids sort lexicographically by date, so "latest" is just
the maximum.
"""
import json
import logging
import re
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
log = logging.getLogger("tiltmeter.serve")
SNAPSHOT_ID_RE = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{4}-\d{2}-\d{2}$")
OUTLET_PAGE_RE = re.compile(r"^[a-z0-9-]+\.md$|^index\.md$")
DEFAULT_PORT = 8477
def _ratings_ids(releases: Path) -> list[str]:
return sorted(
p.stem.removeprefix("ratings-") for p in releases.glob("ratings-*.json")
)
def make_handler(releases: Path, outlets_config: Path | None = None):
outlets_payload = None
if outlets_config and outlets_config.is_file():
import yaml
outlets_payload = {"outlets": yaml.safe_load(outlets_config.read_text())["outlets"]}
class Handler(BaseHTTPRequestHandler):
server_version = "tiltmeter"
def _send(self, code: int, body: bytes, content_type: str) -> None:
self.send_response(code)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Cache-Control", "public, max-age=300")
self.end_headers()
self.wfile.write(body)
def _json(self, code: int, payload) -> None:
self._send(code, json.dumps(payload).encode(), "application/json")
def _file(self, path: Path, content_type: str) -> None:
if path.is_file():
self._send(200, path.read_bytes(), content_type)
else:
self._json(404, {"error": "not found"})
def do_GET(self) -> None: # noqa: N802 - stdlib API
parts = [p for p in self.path.split("?")[0].split("/") if p]
ids = _ratings_ids(releases)
match parts:
case ["health"]:
self._json(200, {"status": "ok", "ratings": ids})
case ["ratings"]:
self._json(200, {"snapshots": ids})
case ["ratings", "latest"] if ids:
self._file(releases / f"ratings-{ids[-1]}.json", "application/json")
case ["ratings", sid] if SNAPSHOT_ID_RE.match(sid):
self._file(releases / f"ratings-{sid}.json", "application/json")
case ["outlets"] if outlets_payload:
self._json(200, outlets_payload)
case ["stories", sid] if SNAPSHOT_ID_RE.match(sid):
self._file(releases / f"stories-{sid}.json", "application/json")
case ["manifests", sid] if SNAPSHOT_ID_RE.match(sid):
self._file(releases / f"manifest-{sid}.json", "application/json")
case ["evidence", sid] if SNAPSHOT_ID_RE.match(sid):
self._file(releases / f"report-{sid}" / "index.md", "text/markdown")
case ["evidence", sid, page] if (
SNAPSHOT_ID_RE.match(sid) and OUTLET_PAGE_RE.match(page)
):
self._file(releases / f"report-{sid}" / page, "text/markdown")
case _:
self._json(404, {"error": "not found"})
def log_message(self, fmt: str, *args) -> None:
log.info("%s %s", self.address_string(), fmt % args)
return Handler
def run(
releases_dir: str | Path,
host: str = "0.0.0.0",
port: int = DEFAULT_PORT,
outlets_config: str | Path = "config/outlets.yaml",
) -> None:
releases = Path(releases_dir)
server = ThreadingHTTPServer(
(host, port), make_handler(releases, Path(outlets_config))
)
log.info("serving %s on %s:%d", releases, host, port)
server.serve_forever()
+43
View File
@@ -0,0 +1,43 @@
"""Does orientation flip the axis when — and only when — it should?
Pure-function tests: no model, no network. The embedding-dependent parts
(party means from real speeches) are exercised by the live `tiltmeter run`;
what must never regress silently is the flip decision and its honesty about
weak agreement.
"""
import numpy as np
from tiltmeter import orient
def test_correctly_oriented_axis_is_kept():
axis = [-0.9, -0.5, 0.0, 0.4, 0.8] # already: left negative
proxy = [-0.10, -0.06, 0.01, 0.05, 0.09] # closer to R language as we go right
result = orient.orient_sign(axis, proxy)
assert result.sign == 1
assert result.correlation > 0.9
assert result.reliable
def test_backwards_axis_is_flipped():
axis = [0.9, 0.5, 0.0, -0.4, -0.8] # backwards: left ended up positive
proxy = [-0.10, -0.06, 0.01, 0.05, 0.09]
result = orient.orient_sign(axis, proxy)
assert result.sign == -1
assert result.correlation < -0.9
def test_weak_agreement_is_flagged_unreliable():
rng = np.random.default_rng(3)
axis = list(rng.normal(size=12))
proxy = list(rng.normal(size=12)) # unrelated
result = orient.orient_sign(axis, proxy)
assert not result.reliable
def test_spearman_matches_known_value():
# perfect monotone but nonlinear relation: rank correlation must be 1
a = np.array([1.0, 2.0, 3.0, 4.0])
assert orient._spearman(a, a**3) == 1.0
assert orient._spearman(a, -(a**3)) == -1.0
+89
View File
@@ -0,0 +1,89 @@
"""Does the API serve exactly what the pipeline produced — and nothing else?
Spins the real server on an ephemeral port over a fixture releases dir.
The contract: pipeline outputs are served verbatim, unknown paths 404,
path traversal is impossible by construction (strict id/page regexes).
"""
import http.client
import json
import threading
from http.server import ThreadingHTTPServer
import pytest
from tiltmeter import serve
@pytest.fixture()
def api(tmp_path):
(tmp_path / "ratings-2026-07-01_2026-07-15.json").write_text('{"snapshot_id": "old"}')
(tmp_path / "ratings-2026-07-10_2026-07-24.json").write_text('{"snapshot_id": "new"}')
(tmp_path / "manifest-2026-07-10_2026-07-24.json").write_text('{"articles": []}')
(tmp_path / "stories-2026-07-10_2026-07-24.json").write_text('{"stories": []}')
report = tmp_path / "report-2026-07-10_2026-07-24"
report.mkdir()
(report / "index.md").write_text("# Evidence index")
(report / "fox-news.md").write_text("# Evidence: fox-news")
outlets = tmp_path / "outlets.yaml"
outlets.write_text(
"outlets:\n"
" - name: fox-news\n"
" homepage: https://www.foxnews.com\n"
" feed: https://example.com/feed\n"
" ownership: {owner: Fox Corporation, type: public-company}\n"
)
server = ThreadingHTTPServer(
("127.0.0.1", 0), serve.make_handler(tmp_path, outlets)
)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
yield server.server_address[1]
server.shutdown()
def get(port, path):
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
conn.request("GET", path)
resp = conn.getresponse()
return resp.status, resp.read(), dict(resp.getheaders())
def test_health_and_listing(api):
status, body, headers = get(api, "/health")
assert status == 200
assert json.loads(body)["ratings"] == [
"2026-07-01_2026-07-15",
"2026-07-10_2026-07-24",
]
assert headers["Access-Control-Allow-Origin"] == "*"
def test_latest_is_newest_snapshot(api):
status, body, _ = get(api, "/ratings/latest")
assert status == 200
assert json.loads(body)["snapshot_id"] == "new"
def test_specific_ratings_manifest_and_evidence(api):
assert json.loads(get(api, "/ratings/2026-07-01_2026-07-15")[1])["snapshot_id"] == "old"
assert get(api, "/manifests/2026-07-10_2026-07-24")[0] == 200
assert b"Evidence index" in get(api, "/evidence/2026-07-10_2026-07-24")[1]
assert b"fox-news" in get(api, "/evidence/2026-07-10_2026-07-24/fox-news.md")[1]
def test_outlets_and_stories_endpoints(api):
status, body, _ = get(api, "/outlets")
assert status == 200
outlet = json.loads(body)["outlets"][0]
assert outlet["ownership"]["owner"] == "Fox Corporation"
assert json.loads(get(api, "/stories/2026-07-10_2026-07-24")[1]) == {"stories": []}
assert get(api, "/stories/nonsense")[0] == 404
def test_unknown_and_hostile_paths_404(api):
assert get(api, "/ratings/nonsense")[0] == 404
assert get(api, "/evidence/2026-07-10_2026-07-24/../../etc/passwd")[0] == 404
assert get(api, "/evidence/2026-07-10_2026-07-24/%2e%2e%2fsecrets.md")[0] == 404
assert get(api, "/")[0] == 404
Generated
+60 -267
View File
@@ -1,6 +1,10 @@
version = 1
revision = 3
requires-python = ">=3.13"
resolution-markers = [
"sys_platform != 'darwin'",
"sys_platform == 'darwin'",
]
[[package]]
name = "annotated-doc"
@@ -124,79 +128,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/38/ce65091ff20a16e06d17418c4353af5f56d3190821b1a06983c79ae79274/courlan-1.4.0-py3-none-any.whl", hash = "sha256:ad1dbdefd912ca7238d4607dc855df5df097f56bac175dd662c84eed3802f49e", size = 34193, upload-time = "2026-06-01T17:30:14.984Z" },
]
[[package]]
name = "cuda-bindings"
version = "13.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" },
{ url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" },
{ url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" },
{ url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" },
{ url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" },
]
[[package]]
name = "cuda-pathfinder"
version = "1.5.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" },
]
[[package]]
name = "cuda-toolkit"
version = "13.0.3.0"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" },
]
[package.optional-dependencies]
cublas = [
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
cudart = [
{ name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
cufft = [
{ name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
cufile = [
{ name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
cupti = [
{ name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
curand = [
{ name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
cusolver = [
{ name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
cusparse = [
{ name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
nvjitlink = [
{ name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
nvrtc = [
{ name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
nvtx = [
{ name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" },
]
[[package]]
name = "dateparser"
version = "1.4.1"
@@ -642,158 +573,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" },
]
[[package]]
name = "nvidia-cublas"
version = "13.1.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cuda-nvrtc" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
{ url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" },
]
[[package]]
name = "nvidia-cuda-cupti"
version = "13.0.85"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" },
{ url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" },
]
[[package]]
name = "nvidia-cuda-nvrtc"
version = "13.0.88"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" },
{ url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" },
]
[[package]]
name = "nvidia-cuda-runtime"
version = "13.0.96"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" },
{ url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" },
]
[[package]]
name = "nvidia-cudnn-cu13"
version = "9.20.0.48"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" },
{ url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" },
]
[[package]]
name = "nvidia-cufft"
version = "12.0.0.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
{ url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" },
]
[[package]]
name = "nvidia-cufile"
version = "1.15.1.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" },
{ url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" },
]
[[package]]
name = "nvidia-curand"
version = "10.4.0.35"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" },
{ url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" },
]
[[package]]
name = "nvidia-cusolver"
version = "12.0.4.66"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas" },
{ name = "nvidia-cusparse" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
{ url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" },
]
[[package]]
name = "nvidia-cusparse"
version = "12.6.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
{ url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" },
]
[[package]]
name = "nvidia-cusparselt-cu13"
version = "0.8.1"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" },
{ url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" },
]
[[package]]
name = "nvidia-nccl-cu13"
version = "2.29.7"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" },
{ url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" },
]
[[package]]
name = "nvidia-nvjitlink"
version = "13.3.33"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" },
{ url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" },
]
[[package]]
name = "nvidia-nvshmem-cu13"
version = "3.4.5"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" },
{ url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" },
]
[[package]]
name = "nvidia-nvtx"
version = "13.0.85"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" },
{ url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" },
]
[[package]]
name = "packaging"
version = "26.2"
@@ -1135,7 +914,8 @@ dependencies = [
{ name = "numpy" },
{ name = "scikit-learn" },
{ name = "scipy" },
{ name = "torch" },
{ name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
{ name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" },
{ name = "tqdm" },
{ name = "transformers" },
{ name = "typing-extensions" },
@@ -1215,7 +995,7 @@ wheels = [
[[package]]
name = "tiltmeter"
version = "0.2.0"
version = "0.3.0"
source = { editable = "." }
dependencies = [
{ name = "feedparser" },
@@ -1224,6 +1004,8 @@ dependencies = [
{ name = "requests" },
{ name = "scikit-learn" },
{ name = "sentence-transformers" },
{ name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
{ name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" },
{ name = "trafilatura" },
]
@@ -1242,6 +1024,7 @@ requires-dist = [
{ name = "requests", specifier = ">=2.32" },
{ name = "scikit-learn", specifier = ">=1.5" },
{ name = "sentence-transformers", specifier = ">=3.0" },
{ name = "torch", specifier = ">=2.6", index = "https://download.pytorch.org/whl/cpu" },
{ name = "trafilatura", specifier = ">=1.12" },
]
@@ -1290,36 +1073,59 @@ wheels = [
[[package]]
name = "torch"
version = "2.13.0"
source = { registry = "https://pypi.org/simple" }
source = { registry = "https://download.pytorch.org/whl/cpu" }
resolution-markers = [
"sys_platform == 'darwin'",
]
dependencies = [
{ name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" },
{ name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" },
{ name = "filelock" },
{ name = "fsspec" },
{ name = "jinja2" },
{ name = "networkx" },
{ name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" },
{ name = "setuptools" },
{ name = "sympy" },
{ name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" },
{ name = "typing-extensions" },
{ name = "filelock", marker = "sys_platform == 'darwin'" },
{ name = "fsspec", marker = "sys_platform == 'darwin'" },
{ name = "jinja2", marker = "sys_platform == 'darwin'" },
{ name = "networkx", marker = "sys_platform == 'darwin'" },
{ name = "setuptools", marker = "sys_platform == 'darwin'" },
{ name = "sympy", marker = "sys_platform == 'darwin'" },
{ name = "typing-extensions", marker = "sys_platform == 'darwin'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" },
{ url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" },
{ url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" },
{ url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" },
{ url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" },
{ url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" },
{ url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" },
{ url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" },
{ url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" },
{ url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" },
{ url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", upload-time = "2026-07-08T12:26:23Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", upload-time = "2026-07-08T12:26:28Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", upload-time = "2026-07-08T12:26:33Z" },
]
[[package]]
name = "torch"
version = "2.13.0+cpu"
source = { registry = "https://download.pytorch.org/whl/cpu" }
resolution-markers = [
"sys_platform != 'darwin'",
]
dependencies = [
{ name = "filelock", marker = "sys_platform != 'darwin'" },
{ name = "fsspec", marker = "sys_platform != 'darwin'" },
{ name = "jinja2", marker = "sys_platform != 'darwin'" },
{ name = "networkx", marker = "sys_platform != 'darwin'" },
{ name = "setuptools", marker = "sys_platform != 'darwin'" },
{ name = "sympy", marker = "sys_platform != 'darwin'" },
{ name = "typing-extensions", marker = "sys_platform != 'darwin'" },
]
wheels = [
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:966d020354f465672dc7dd10d3a5c6cd17d7eb48620aa1d265b48a1f78f06898", upload-time = "2026-07-08T19:29:30Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0b8f7d0423027ae8b90c7977c627f3379f325363a08224dffad9b4b2d684a83d", upload-time = "2026-07-08T19:29:40Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3fbf9c9d1f3c10c2d59d04aca426dee9ccc6ceb32d255c61e93acc3b4f75fae6", upload-time = "2026-07-08T19:29:54Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:a17ff48608634db245e17e8bb00a9558554a49aeb1e4f5fe6cd039af2a10515b", upload-time = "2026-07-08T19:30:05Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:ac7aaf322be4777765a53bed7264a214dd81b3a1d276b93150515a3c5f75e4b0", upload-time = "2026-07-08T19:30:12Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:dec241fef3984c0d1edadd1f58708e218d4eae881ceef7bc10cf9964d41b68b9", upload-time = "2026-07-08T19:30:20Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ca021f9eb2f8345c83fa03e3a04587308afb8df71bd472670b3ece00df58621c", upload-time = "2026-07-08T19:30:32Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d20fa53ee744502fa4c69818a720b05ca0d37abd055d4f6e66cae155114bc691", upload-time = "2026-07-08T19:30:45Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:e2e5134decf00e218da62318f3dc5df156231d367871918e91eba95ab0ad43ab", upload-time = "2026-07-08T19:30:58Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:991cc14b39e751122c01f017be6448533989868731cb5eecd1006893d26787c2", upload-time = "2026-07-08T19:31:09Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7b8d26e29bceafbdaa8d63bfe7612f23875b5af2cc07e13f809c3ed890bbe1d8", upload-time = "2026-07-08T19:31:21Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b222c15a0fc2ce207d1c1a59700b46c8fa6748df1f447ad11e5c870dde0933d9", upload-time = "2026-07-08T19:31:35Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:a43376bd094124ef626bfdd3d4c2c62eacb0b5ddc99776f4a32d4fd16f1f3420", upload-time = "2026-07-08T19:31:48Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5002ca81af00ae69b57540f615b58b8ae922b6d4848176b366a52bd2196e6", upload-time = "2026-07-08T19:32:00Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:1a3a35229fdc13446b4eab50e7fcf9399ff941e89a3b761497786297a5d8dde5", upload-time = "2026-07-08T19:32:16Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:8e109528e6bab044815daebaf71770fbaace3a66ef1c816cb55c875350f78a60", upload-time = "2026-07-08T19:32:30Z" },
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:222a6681467cc7f6f05cd3068dfbc603def3a1e46d1d4620c1c8cdf6178bd563", upload-time = "2026-07-08T19:32:44Z" },
]
[[package]]
@@ -1372,19 +1178,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/3a/d99704c5effe10c6339c98cb236259161103e159bb99a78468b6729572ec/transformers-5.13.0-py3-none-any.whl", hash = "sha256:8adbc1d20bd5463cd6876b2eb7cb31971e1065788e7dc6bc12bab597a7c504b7", size = 11503730, upload-time = "2026-07-03T16:05:35.569Z" },
]
[[package]]
name = "triton"
version = "3.7.1"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" },
{ url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" },
{ url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" },
{ url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" },
{ url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" },
{ url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" },
]
[[package]]
name = "typer"
version = "0.26.8"