Explorez les graphiques de distribution des sentiments et l'analyse de polarité dans les annotations de sentiment produites par IA sur la Collection Islam Afrique de l'Ouest, un corpus de presse ouest
翻譯準備中
資料TypeScript · SvelteGitHub @fmadore上線中營運者未確認
試用過了嗎? 點一下就好,不需要登入。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Interactive dashboard comparing how five LLMs — GPT-5.6 Luna, Mistral Small 4, DeepSeek v4 Flash, Gemma 4 31B and Qwen3.8 27B — annotated 12,349 francophone West African press articles, with inter-rater agreement, discrepancy analysis and blind arbitration. Bilingual FR/EN; the earlier three-model campaign stays archived.
翻譯準備中
正在載入完整 README。 以下為已儲存的摘錄。
A SvelteKit dashboard for exploring how large language models annotated the same corpus — 12,349 francophone West African press articles from 57 newspapers in Benin, Burkina Faso, Côte d'Ivoire, Niger and Togo, published 1961–2025 — on three dimensions: polarity, subjectivity, and the centrality of Islam and Muslims to the article. The current panel is five models; the archived one is three.檢視完整 README ↗
A SvelteKit dashboard for exploring how large language models annotated the same corpus — 12,348 francophone West African press articles from 57 newspapers in Benin, Burkina Faso, Côte d'Ivoire, Niger and Togo, published 1961–2025 — on three dimensions: polarity, subjectivity, and the centrality of Islam and Muslims to the article. The current panel is six models; the archived one is three.
The corpus is the Islam West Africa Collection (IWAC). The dashboard reports each model's distributions, their agreement, where they diverge, and blind third-party arbiter verdicts on the sharpest disagreements.
Live site: https://iwac.frederickmadore.com/sentiment-analysis/
Sharing analyses: Citable URLs, supported state and citation limits. Article, comparison and arbiter CSVs append release, model, selection and URL provenance. Downloads capture the selection at click time and load only its required justification shards.
Two top-level areas: ma-visualisation-sentiments/ (the SvelteKit app) and data-preprocess/ (the Python that builds its JSON payloads).
The corpus has been annotated twice, under two different prompts. Both are published side by side. Generation 2 is what the dashboard shows by default; generation 1 stays online, unchanged, so figures and URLs published from it keep resolving.
| v2 — current | v1 — archived | |
|---|---|---|
| Models | GPT-5.6 Luna, Mistral Small 4 (2603), DeepSeek v4 Flash, Gemma 4 31B, Qwen3.8 27B, Claude Haiku 5.5 | GPT-5 mini, Gemini 3 Flash preview, Ministral 14B 2512 |
| Repo ids | luna, mistral-small, deepseek, gemma, qwen, haiku | chatgpt, gemini, mistral |
| Pairs | 15 | 3 |
| Articles annotated | 12,298 of 12,348 — except Qwen3.8 27B, at 12,098, and Claude Haiku 5.5, at 12,251 | 12,279 of 12,348 |
| Subjectivity | ordinal label upstream, mapped to rank 1–5; declined → null | integer 1–5 |
| Arbiter | Claude Opus 5, whole panel (one verdict per article); Claude Haiku 5.5 recused | Gemini 3 Pro, pairwise (one verdict per model pair) |
| Contract | src/lib/data/sentiment-v2.json | src/lib/data/sentiment-v1.json |
The generation is derived from the id, never stored. There is no generation URL parameter: generationOf() in src/lib/domain/sentimentContract.ts reads it off the dataset or pair id, which is why the two generations' ids must never collide (an import-time invariant asserts this). ?dataset=chatgpt and ?pair=chatgpt-gemini therefore keep working exactly as before and land you in the archive.
Reaching the archive: the model pickers offer only the current generation. A quiet footnote link in the methodology card switches to v1, after which a banner makes the state unmistakable with one click back.
The v2 prompt is not the v1 prompt. Subjectivity is answered as a label (Très objectif … Très subjectif) rather than an integer, the self-checklist instruction is gone, and boundary rules were added for Muslim actors in secular stories, Arab-state cooperation, and armed groups. A difference between the two generations confounds the model change with the prompt change — say so when reporting one. The app never mixes them: agreement statistics, prefetching and comparison pairs are all scoped to the active generation, because a six-rater kappa would measure the prompt rewrite as if it were disagreement between models.
Four further caveats specific to v2:
Marginal, against 0.7–2.6% of every other centrality band). Anything computed on complete cases — Fleiss' κ, the consensus charts, the arbiter's selection frame — therefore leans towards material where Islam is central. Do not present the gap as repairable.
chatgpt/gemini/mistralare this repo's ids, not the dataset's column prefixes — the Hugging Face dataset renamed its sentiment columns from vendor slots to the model that actually produced each annotation.iwac_preprocess.contractholds the mapping andsentiment_column()is the only place a column name is assembled.
Thirteen views, all sharing one filter rail (country → newspaper → polarity → subjectivity → centrality) except the three self-contained ones. Filter state round-trips through the URL, so any view is shareable.
| View | What it answers |
|---|---|
| Charts | Polarity and subjectivity distributions per newspaper — bars or global pie |
| Trends | Cross-model historical shares with common or available cohorts; single-model counts/shares |
| Distribution | Polarity × subjectivity cross-tabulation, with Spearman's ρ, p and n |
| Volume | Newspaper-by-period collection timeline; publication volume per country |
| Heatmap | Centrality by country and year, with sample counts and a minimum-count threshold |
| Seasonality | Coverage across the twelve Hijri months — polar cycle or bars |
| Newspapers | Titles ranked by mean polarity, subjectivity or centrality, with 95% CIs |
| Map | Bubble map of geocoded places cited by the corpus, coloured by any dimension |
| Table | The article list, sortable and paginated; detail modal carries the model's justifications |
| Comparison★ | Two models side by side, with per-dimension discrepancies and where they diverge |
| Agreement★ | Pair overview, full label patterns with article drill-down, κ, confusion matrices and calibration |
| Extremes | Keywords characterising the most extreme cases per category and model |
| Arbiter★ | Blind third-party verdicts on articles the models disagreed about most |
★ self-contained: full-width, own internal filters, no shared rail.
dcterms:spatial is item-level tagging, ~3.8 places per article), never articles about it. Non applicable is excluded from the mean but the article is still counted — it means "no stance expressed", so averaging it in would drag heavily-covered places toward the negative pole.Non applicable; the categorical matrix and unweighted agreement retain it, with their own denominators. The archived v1 calculation remains unchanged. A higher weighted κ alone does not establish a directional offset. Panel-scope statistics count only articles every model rated, so on v2 they inherit Qwen's coverage gap.# Charts, filtered to one country
?view=charts&countries=Togo
# Trends in English, positive polarity only
?view=trends&lang=en&polarities=Positif
# Comparison mode on a generation-2 pair, wide discrepancies only
# (the v2 panel has fifteen pairs; `pair` takes any of them)
?view=comparison&compare=true&pair=deepseek-qwen&diffMin=2&diffMax=11
# Heatmap restricted to high centrality
?view=heatmap¢ralities=Central¢ralities=Très%20central
# The archived generation-1 analysis
?dataset=chatgpt
Core parameters: view, lang, dataset, compare, pair, countries, journals, polarities, subjectivities, centralities, diffMin, diffMax. Each view adds its own analytical and presentation state (comparison dimensions/excludeNA, agreement scope/dimension/declined, detail ids, sort and pagination, chartState); the full per-view list is in Citable URLs. Every name the app reads or writes is declared in src/lib/stores/url/constants.ts (URL_PARAMS) or, for presentation options, as a key of VIEW_OPTIONS.
Everything ships as static JSON under ma-visualisation-sentiments/static/data/. The split is for load time, not tidiness.
| File | Contents |
|---|---|
iwac_articles_base.json | Shared article metadata, stored once for both generations (3.8 MiB, 598 KiB gzip) |
iwac_sentiment_{model}.json | One model's scores keyed by article id (~58 KiB gzip) |
iwac_justifications_{model}_{00..31}.json | The ~90% prose portion, deterministically sharded by article id |
iwac_extreme_analysis_{model}.json | Keyword analysis of extreme cases, normalised (index + per-category id lists) |
iwac_arbiter_evaluations_{pair}.json | v1 pairwise arbiter verdicts (Gemini 3 Pro) |
iwac_arbiter_evaluations_v2.json | v2 panel arbiter verdicts (Claude Opus 5) — see below |
iwac_places.json | Geocoded Lieux authority records plus an article → place edge list |
world-110m.geojson | Natural Earth 1:110m basemap (public domain), minified on purpose |
iwac_data_manifest.json / _v2.json | Per-generation contract version, immutable HF revision, byte sizes and SHA-256 for every file |
The frontend validates each JSON boundary at runtime, joins base metadata to the selected model's score map by article id, and exposes a retryable error state on failure. Only the selected model's scores load at startup; prose loads when an article detail opens (one shard) or a CSV is exported (all shards, in bounded batches); other models load when comparison mode activates or the panel arbiter view opens; the map, arbiter and extremes data load with their views.
Places ship as edges rather than pre-computed averages so the map answers to the same filter rail as every other view; aggregation happens in the browser.
iwac_arbiter_evaluations_v2.json is the published --rule valence run of data-preprocess/arbiter-evaluation-v2.py: every article where one model reads the coverage as positive and another as negative — 301 articles, judged in 281 paid calls for US$17.61 at effort medium, as recorded in the file's metadata.usage. The arbiter reads the full article text and the five analyses under blind labels, rates each dimension itself, names the analysis it prefers per dimension and overall, and explains why. The arbiter view under v2 lists the articles with their verdicts; opening one shows the article itself (IIIF viewer where a manifest exists), why it was selected, and every model's rating against the arbiter's, with the models' own reasoning on demand. A build that omits the file shows an empty state pointing here. The v1 pairwise arbiter data is complete and unaffected.
Two things to keep in mind when reading the verdicts:
arbiter.recusal therefore keeps Haiku out of the arbiter's panel altogether — selection, prompt, blind labels and fingerprints run over the other five, which is exactly the panel the published run judged, so no verdict was invalidated and nothing was re-bought. Haiku is compared with the verdicts afterwards: the verdict panel shows its rating beside the arbiter's, unlettered and marked Recused. On the 301 articles its polarity coincides with the arbiter's in 192, inside the 190–205 of the four members other than Mistral Small 4, so there is no sign of a reading the two Anthropic models share. What recusal cannot do is hear Haiku's own dissents: with six models the panel has 346 valence flips, and the 45 that exist only because of Haiku (38 as the sole negative reader, 7 as the sole positive one) are outside the frame. Settling those needs a judge from another developer — and since every frontier developer now has a model on the panel (OpenAI, Google, Anthropic, Mistral, DeepSeek, Alibaba), that judge must recuse its own developer's member in turn.sentiment-v2.json names its developer, the arbiter names its own, and arbiter.recusal.rule is same-developer; SentimentContract.recused_for() / recusedFor() compute the recused set in both languages. An unregistered developer is an error rather than a judge that recuses nobody. The published fingerprints are unchanged (a test pins one).data-preprocess/arbiter-evaluation-v2-second-judge.py runs a judge from another developer — Gemini 4 Argon by default, recusing Gemma — over the six-model valence-flip frame, with the same prompt, labels, presentation order and schema as the published run, and its own output file (iwac_arbiter_evaluations_v2_<judge>.json), durable cache and lock. --frame recused judges only Haiku's 45; judges all 346, so the 301 shared with the published run give a judge-agreement figure and a difference-in-differences estimate of each judge's affinity for its own developer's model. The dashboard does not read that file yet.How the v2 arbiter differs from v1, and why:
Positif/Négatif ones; the contract's second rule, valenceFlip (--rule valence), is what selects all 301. --rule, --dimensions and --threshold narrow the frame; they can only ever tighten it, because the validator recomputes eligibility from the contract.OCR per row, so a large share of v1-arbitrated articles were judged on an empty string. v2 reads the private mirror (needs HF_TOKEN) and records both revisions — public scores and private text — in the metadata and the cache fingerprint. Only verdicts and justifications are published; no OCR is ever serialised.parseArbiterV2EvaluationData rejects any file whose permutation is not a bijection over the contract's models, which is also what keeps the label list and the panel the same size.Python 3.12+, from the repo root:
# Development and CI, including all runtime dependencies
pip install --require-hashes -r data-preprocess/requirements-dev.lock
# Runtime-only environment for generating data
pip install --require-hashes -r data-preprocess/requirements.lock
The .lock files pin every dependency and its hashes. The .txt files are the human-maintained version constraints; after changing them, regenerate both locks with the commands documented in data-preprocess/requirements.txt. The development lock is constrained to the runtime lock so generation and CI use the same runtime versions. CI audits that exact runtime set with pip-audit -r data-preprocess/requirements.lock --disable-pip --require-hashes.
Newly generated data manifests also record the runtime lock's filename and SHA-256 under environment.requirements_lock, identifying the dependency specification available to that generation. This optional field is absent when no lock is available; it does not verify the packages installed in the running interpreter. Existing published manifests are unchanged and make no claim to have been generated with the new lock.
Source reads resolve a single immutable Hugging Face commit per repository before loading. Set IWAC_HF_REVISION for the public dataset and IWAC_HF_FULL_REVISION for the private OCR mirror to reproduce a prior run; both the parquet and datasets fallback paths use the resolved commit.
Every export — scores and prose, extreme analysis, places, basemap and the panel arbiter — is staged and validated before publication, then promoted under one writer lock, so two exports can never interleave a mixed set. A recovery journal restores the previous complete set after an interrupted promotion. The lock, temporary export stages and retained panel-arbiter cache live in ma-visualisation-sentiments/.data-staging/ (gitignored), never under static/, whose contents are published with the site. Run the same export command again to recover. Builds refuse a pending journal. Production builds bind the frontend to content-addressed data/releases/<release>/ URLs; the service worker never substitutes files from a different release. The source static/data/ layout and frozen v1 files remain unchanged.
--generation is required and deliberately has no default. An unflagged re-run would rewrite the frozen v1 files from whatever revision is current. A v2 run does not write iwac_articles_base.json at all — it fails loudly if an article in the base was deleted upstream.
The base is the corpus, and it never grows by itself. Every export is scoped to the base's article ids: articles ingested upstream since the panel ran are excluded and logged, because only part of the panel has read them — in October 2026 the collection gained 4,367 Burkina Faso articles that carry Claude Haiku 5.5's ratings and no other model's. A --generation v1 run drops articles deleted upstream and admits new ones only with --admit-new-articles, which gives every model that has not annotated them null rows. Run v1 first, then v2, then the extremes and places exports, as the 2026-10-27 refresh did when article 78587 was deleted.
# Scores, prose shards and the per-generation manifest
python data-preprocess/data-fetch.py --generation v2
# Keyword analysis of extreme cases
python data-preprocess/extreme-analysis.py --generation v2
# Map payload (generation-independent)
python data-preprocess/places-export.py
# Basemap — only when the Natural Earth source itself changes
python data-preprocess/basemap-export.py
Arbiter runs cost money. Use a dry run first; a paid run requires confirmation or --yes:
# v2, whole panel: counts and a cost estimate, no API call
python data-preprocess/arbiter-evaluation-v2.py --dry-run
# ...then, to actually spend (needs ANTHROPIC_API_KEY and HF_TOKEN)
python data-preprocess/arbiter-evaluation-v2.py --rule valence --yes
# Republish a narrower v2 selection from cache, without paid calls
python data-preprocess/arbiter-evaluation-v2.py --rule valence --limit 20 --prune-cache-only
# v2 second judge: frame counts and a cost estimate for both frames and every
# registered judge, no API call
python data-preprocess/arbiter-evaluation-v2-second-judge.py --dry-run
# ...then a pilot and the full frame (needs GOOGLE_API_KEY and HF_TOKEN)
python data-preprocess/arbiter-evaluation-v2-second-judge.py --frame full --limit 20 --yes
python data-preprocess/arbiter-evaluation-v2-second-judge.py --frame full --yes
# v1, pairwise (needs GOOGLE_API_KEY); frozen — reconcile only
python data-preprocess/arbiter-evaluation.py --prune-cache-only
The v2 arbiter bootstraps its durable cache from the existing published file and retains it at ma-visualisation-sentiments/.data-staging/arbiter-v2/evaluations.json. Each input fingerprint retains its verdict and source/model/prompt/effort provenance, including evaluations omitted by a later selection or belonging to an earlier source revision. Narrowing --limit or changing the selection rule only changes the public projection; expanding it again reuses matching paid work. --prune-cache-only makes no paid calls and never prunes this durable history. Recorded usage remains cumulative, rather than becoming the cost of the narrower selection.
A populated cache must retain its original valid blind-label mapping and recorded effort. A missing/invalid mapping or a different --effort stops the run before either cache is rewritten; restore the mapping or resume at the recorded effort. Run a different-effort experiment in a separate checkout with its own output/cache instead of relabelling an existing run. --limit must be a positive integer and is checked before source loading. A dedicated lock covers the whole panel-arbiter session, and paid verdicts are checkpointed before the public export, so a failed publication can resume without repeating those calls. A dry run does not write the durable cache.
Back up the arbiter-v2/ directory before deleting ignored files or changing checkouts. Git does not retain it, and the selected public file cannot recover paid evaluations that were never published or were omitted from the current selection. Restore the directory alongside the matching checkout to continue that history. Neither cache nor publication stores article OCR.
data-preprocess/significant-differences-export.py is a side helper rather than part of the pipeline: it writes ad-hoc CSVs of the sharpest pairwise disagreements into exports/, nothing the dashboard reads. It resolves its pairs through shared.py, so it still covers generation 1 only.
Validate before committing generated data:
python -m pytest data-preprocess -q && python data-preprocess/validate_generated_data.py
An offline test suite, plus a validator that checks category domains, article-id coverage, prose shard placement, arbiter eligibility and fingerprints, and manifest hashes — entirely offline, for both generations. Both Python and TypeScript read the same checked-in contracts, and shared fixtures assert identical discrepancy, label-mapping and prose-shard behaviour across the two languages.
Environment variables live in a root .env — copy .env.example: ANTHROPIC_API_KEY (v2 arbiter), HF_TOKEN (private mirror), GOOGLE_API_KEY (v1 arbiter). Only the arbiter scripts load that file.
Node 24.15+ (24.x LTS) or 26+. .nvmrc and CI use Node 24 LTS. The Vitest 5
component suite uses jsdom 30, which requires this minimum patch level.
cd ma-visualisation-sentiments && npm install && npm run dev
| Script | What it does |
|---|---|
npm run dev | Dev server at localhost:5173 |
npm run build | Production build, then nesting, service-worker stamping and artifact checks |
npm run preview | Serve the final Pages artifact (including postbuild releases) |
npm run check | svelte-check — must report 0 errors, 0 warnings |
npm run lint | Prettier, ESLint, store-cycle detection, design-token validation |
npm run test:run | Vitest 5 unit and integration tests |
npm run test:e2e | Playwright deep-link, failure-state and axe accessibility smoke tests |
Run the checks in .claude/skills/verify/SKILL.md before committing anything that ships through CI — the ordering and pass criteria are not guessable. CSS and component rules are in DESIGN.md, and npm run lint enforces most of them.
Two repo-level helpers run against the root .venv:
python scripts/social-preview.py regenerates static/social-preview.png from the shipped data. Re-run after a data refresh.python scripts/oklch-to-hex.py prints the sRGB renderings of the OKLCH design tokens, for the chart palette and theme-color meta tags.src/lib/components/ — grouped by role: common/, layout/, filters/, viz/ (ECharts charts and the MapLibre map), ui/, data-display/.src/lib/stores/ — Svelte 5 runes accessor objects, one per domain (filters, articles, datasets, comparison, arbiter, arbiterV2, extreme-analysis, ui), plus url/ for filter-state ↔ URL synchronisation. Leaf stores import from no other store; modules inside stores/ must never import the barrel.src/lib/utils/ — pure helpers, including the statistics modules: agreement.ts (Cohen's/Fleiss' κ), correlation.ts (Spearman's ρ), newspaperRanking.ts, hijri.ts, placeAggregation.ts.src/lib/domain/sentimentContract.ts — the dual-generation registry, with import-time invariants for id collisions, pair membership, shared scales and shard counts.data-preprocess/iwac_preprocess/ — importable package split into the per-generation contract, revision-pinned source loading, discrepancy rules, recoverable generation publication, panel candidate selection and prompt construction, atomic serialisation, v1 cache reconciliation and the durable v2 cache lifecycle (arbiter_lifecycle.py). shared.py remains a compatibility facade for the command-line scripts.Built with Svelte 5 (runes), SvelteKit 3 + adapter-static, TypeScript in strict mode, Tailwind CSS v4, ECharts 6, MapLibre GL v6, and Vite 8 (Rolldown). The design system is repository-owned: CSS tokens plus reusable Svelte controls, with automated token and class checks.
HTML-referenced JavaScript is measured at build time against a 300 KiB gzip budget — currently 139 KiB. The production browser tests additionally count actual transitive and dynamic imports: default charts, direct table and map links have budgets of 450, 180 and 600 KiB gzip, respectively. Data transfer is reported separately, including all six background score prefetches. The tests verify chart/map isolation using the generated chunk manifest. These measurements are build-size budgets, not claims about latency on every device.
Views load their modules on demand and offer a localized reload action when a chunk fails. Data URLs include a content-derived release identifier; unchanged data keeps its cache across code-only deployments, while a changed dataset gets a new namespace. Offline requests never substitute another release's files. The build intentionally emits no .gz/.br siblings — GitHub Pages negotiates transfer compression. npm run preview serves the final static artifact so browser tests exercise the same release paths and stamped service worker that Pages receives.
Every push to main runs .github/workflows/deploy.yml. Frontend and Python jobs run in parallel: type checks, lint, unit tests, the measured production build, Playwright/axe, Python tests, Ruff, npm audit, pip-audit and pull-request dependency review all gate deployment. Actions are pinned to immutable commit SHAs, and only the deploy job receives Pages permissions.
The site is served from a custom subdomain at a sub-path, so the build output is nested: the adapter writes the app into build/sentiment-analysis/, and scripts/nest-build.mjs populates the build root with the three files GitHub Pages only reads from there (CNAME, a hoisted 404.html, and a redirect index.html). This is necessary because a GitHub project page supplies the /repo-name/ prefix itself, whereas a custom domain serves the artifact at the subdomain root.
ma-visualisation-sentiments/deploy.config.js is the single source of truth for the path and the domain — changing where the dashboard is served means editing that file and nothing else. DEPLOY_PATH = '' serves it at the subdomain root instead.
Each release is archived on Zenodo. Machine-readable metadata lives in CITATION.cff; GitHub's Cite this repository button renders APA and BibTeX from it.
Cite the concept DOI — 10.5281/zenodo.21806223 — unless you need to pin a specific version; it always resolves to the most recent release.
Madore, F. (2026). IWAC Sentiment Analysis Visualization (Version 5.0.0) [Computer software]. University of Bayreuth. https://doi.org/10.5281/zenodo.21806223
@software{madore_iwac_sentiment_analysis,
author = {Madore, Frédérick},
title = {IWAC Sentiment Analysis Visualization},
version = {5.0.0},
year = {2026},
publisher = {Zenodo},
doi = {10.5281/zenodo.21806223},
url = {https://github.com/fmadore/IWAC-sentiment-analysis},
license = {MIT}
}
Cite the underlying corpus separately — the Islam West Africa Collection (islam.zmo.de) and its machine-readable export (doi:10.57967/hf/9857).
Eight of the nine panel members across the two generations ran at a vendor's API. Qwen3.8 27B did not: it was self-hosted with vLLM on festus, the cluster of the Bayreuth Centre for High Performance Computing, which is why its column in the panel carries no invoice. The centre states that its funding acknowledgement is mandatory rather than a courtesy, so any publication reporting generation-2 results carries it verbatim:
Calculations were performed using the festus-cluster of the Bayreuth Centre for High Performance Computing (https://www.bzhpc.uni-bayreuth.de), funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) - 523317330.
The dashboard carries the same sentence in its generation-2 run configuration. Wording checked against the festus system guide on 15 September 2026; CITATION.cff has no field for a funding acknowledgement under CFF 1.2.0, so it is not recorded there.
The application code is released under the MIT License. The IWAC corpus it visualizes is distributed under CC BY-NC-SA 4.0 and is not covered by that licence — reuse of the data follows the collection's own terms.
Non applicable and Non abordé are treated as non-comparable and exclude the row rather than counting as maximal disagreement; missing subjectivity skips only that dimension.Non applicable. Non applicable is a verdict: the model read the article and found no stance. A null rating is one the model never produced — Qwen's declined rows, Claude Haiku 5.5's 47 skipped ones, the 50 articles no model annotates. The filter rail keeps them apart with a separate "Not annotated" chip on each dimension, so the table's counts under "Not applicable" match the charts, which skip nulls.--frame full