Skoðanakannanir — RÚV + Vísir + Heimildin Opinion-Poll Aggregators

SkillDev tools

RÚV + Vísir + Heimildin opinion-poll aggregators — all-pollster party support (Alþingi + Reykjavík) and ESB; try maskina first.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Skoðanakannanir — RÚV + Vísir + Heimildin Opinion-Poll Aggregators skill

What this skill tells your AI

The instructions your AI receives, as published by jokull/icelandic-data in .agents/skills/skodanakannanir/SKILL.md and read by ahel’s review.

Three outlets' discovery mechanisms, each surfacing news coverage of opinion polls from every major Icelandic pollster — not just one firm's own dashboard. Use this skill for "what's the latest party support / fylgi flokka" questions, at either national (Alþingi) or Reykjavík city (borgarstjórn) level — and, via --topic esb, for EU-membership support/oppose polling ahead of the 2026-08-29 referendum (þjóðaratkvæðagreiðsla); see "Topic System" below.

Check the maskina skill first. This skill is the heavy tool: it extracts numbers out of news articles (Playwright for RÚV, prose/chart parsing, false-positive filtering). Maskína's own skill reads structured data straight from the pollster — the Tableau fylgi dashboard and the WordPress article API (fylgi, Borgarviti, ESB/aðild, trust series) — with no browser and no article scraping. If the question is answerable from Maskína's own data, answer it there. Reach for this skill for what maskina cannot give: the other pollsters (Gallup, Prósent, Félagsvísindastofnun), cross-pollster comparison, Reykjavík polling breadth, or polls that only ever appeared as one-off news stories.

Use Vísir/Heimildin for discovery, RÚV for numbers. Verified: RÚV's tag page holds only ~51 recent items with no working pagination (see Caveat 7), while Vísir's is genuinely paginated back to at least September 2021 and Heimildin's search goes back to 2019+ for this query. list --source visir --since 2025 --scope reykjavik alone found 40 Reykjavík polls against RÚV's 4 — including the entire Feb–May 2026 city-election polling season RÚV's own tag had already dropped. RÚV and Vísir are both wired into fetch's chart/prose number-extraction (see Vísir Extraction below) — Heimildin is still discovery-only. Vísir is actually the simpler of the two to fetch: its article bodies are server-rendered, so fetch_visir_article() is plain httpx, no Playwright, no browser at all (RÚV needs one — see the RÚV fetch section).

Two-Stage Pipeline

  1. list — plain HTTP GET of the tag page, no browser needed. The page is server-rendered: https://www.ruv.is/frettir/tag/skodanakonnun embeds a full __NEXT_DATA__ JSON blob containing every listed article (id, title, subtitle, url, first_published_at) as GraphQL Article objects. Walk the JSON tree for "__typename": "Article" nodes — do not regex the raw HTML, the JSON is already there and clean.

  2. fetch <id> — one article's party-support numbers, via Playwright. Article bodies are client-side renderedhttpx/curl sees only the page chrome (nav, footer, ~0 chars of article text); the real content only exists in the DOM after JS hydration. Confirmed by comparing a raw curl fetch (empty <main>) against a Chrome DevTools MCP snapshot of the same URL (full prose + chart).

Where the Numbers Actually Live

Poll articles that include a chart render it as Highcharts, and each bar is an SVG <path> with a real aria-label attribute:

<path aria-label="Samfylking, 22.2%." ...>

This is the extraction target — not OCR, not Highcharts internals, not color-matching against Iceland's standard party colors (which the chart also uses consistently, but the aria-labels are simpler and don't require a color lookup table). In Playwright:

bars = await page.eval_on_selector_all(
    'path[aria-label*="%"]',
    "els => els.map(e => e.getAttribute('aria-label'))",
)
# ["Samfylking, 22.2%.", "Sjálfstæðisflokkur, 19.3%.", ...]

Parse with r"^(.+?),\s*([\d.,]+)\s*%\.?$".

Prose Fallback — When There's No Chart

Verified: articles 451831 and 428434 (both real Reykjavík polls, ~5,900+ chars each) have zero chart <path> elements. The numbers only exist in prose, and extracting them correctly needs three Icelandic-grammar signals, not proximity alone — verified against two full real articles end-to-end (hand-traced, then run through Playwright, then cross-checked against a manual reading of the rendered page):

  1. Verb mood distinguishes a current poll figure from a historical election result. mælist/mælast/fengi/fengju/stæði/stæðu/ stendur (conditional/present — "if the election were held now") mark a poll number; fékk/fengu (simple past) mark a result from an actual past election mentioned for comparison. These are genuinely distinct word forms in Icelandic, not a fuzzy heuristic.
  2. "nú" (now) beats verb-cue proximity when both appear in one sentence. Historical baselines get phrased too many ways to enumerate as a verb list — "fékk þá 19 prósent", "tæp tólf í síðasta mánuði", "hafa verið stöðugir í tæplega tólf prósent frá kosningum" — but the current number is consistently marked whenever a sentence states both. When is present with 2+ percent numbers, it wins outright over the verb-proximity check.
  3. Nearest-party-to-number pairing, not first-party-in-sentence — except for strict enumerations, which need positional pairing instead. Comparison sentences ("Sjálfstæðisflokkurinn er langstærstur austan Elliðaáa með 39%, ... Samfylkingin mælist með 19%") name two parties — pairing every number in the sentence with whichever party is named first silently reattributes the second party's number to the first. Distance must be measured edge-to-edge (min over the four start/end combinations), not start-to-start — start-to-start systematically penalizes a long party name immediately before the number in favor of a short one further away. But nearest-gap itself breaks on a longer enumeration — verified live user-testing on a real 9-party sentence (visir-20262884529: "...mælist Sjálfstæðisflokkurinn með 31,3% fylgi, Samfylking með 21,5%, Vinstrið í 11,3%, Miðflokkur 10,9%, Viðreisn 9,8%, Sósíalistar með 4,8%, Framsókn 4,7%, Píratar 2,3% og Flokkur fólksins 2,3%...") — because the connector before a number ("... með ", 5 chars) is longer than the separator before the next party name (", ", 2 chars), nearest-gap silently attaches each number to the following party instead of its own: Samfylking's 21.5% landed on Vinstrið, Vinstrið's 11.3% on Miðflokkur, Sósíalistaflokkur's 4.8% on Framsóknarflokkur — and Samfylking + Sósíalistaflokkur vanished from the output entirely (their real numbers stolen by their neighbors). The same silent shift was independently confirmed on a 2-party case (visir-20262884571: "Framsókn mælist með 6,1 prósent og Sósíalistaflokkurinn 4,5 prósent fylgi." — Sósíalistaflokkur had been getting Framsókn's 6.1%, and Framsókn was dropped outright — this exact article was already in the regression set for three prior rounds without anyone noticing). Fix: when a sentence has exactly as many party matches as percent matches (and more than one of each), pair them by strict left-to-right position instead of nearest-gap — verified against both the original two-party motivating case (still pairs identically) and both enumeration bugs above (now pairs correctly). Nearest-gap is kept as the fallback for every case where the counts don't match.
  4. First mention per party wins; later re-mentions are ignored, not merged. A party's topline citywide number is always stated once, early. Later re-mentions in the same article are sub-group breakdowns — verified example: article 451831 restates "Sjálfstæðisflokkurinn ... 39%" in a district-level paragraph ("east of Elliðaá") after already stating the citywide 29% earlier. Overwriting on second mention would silently replace the correct citywide topline with a geographic subset.

extract_prose_poll_figures() implements all four and returns (results, skipped) — every sentence it declines to use is logged with a reason (no poll cue, historical, no poll cue, <party> already recorded, later mention ignored, no party in context, unparsed number), printed via fetch's prose_skipped count and saved in the raw {id}.json. Nothing is silently dropped or guessed — a skip means "read this one by hand."

Fixed cue gaps (originally documented here as residual gaps, closed since):

  • "úr X í Y" (trend framing)"Fylgi Sósíalistaflokksins er farið úr 7,8 í 10,4 prósent" ("went from X to Y") names no poll-cue verb at all; _TREND_CUE_RE recognizes the úr … í construction as an independent cue class rather than requiring _POLL_CUE_RE to also match. Verified against article 428434, the case that originally surfaced the gap.
  • "er með"/"eru með" (present "is/are with")"Sjálfstæðisflokkur er með 31,3 prósenta fylgi" doesn't contain any of mælist/fengi/stendur etc. Found independently in two real articles (round 2 and round 3) before being added, per this skill's rule of never adding a cue on a single example. Added to _POLL_CUE_RE as (?:er|eru)\s+með, with the tense-symmetric historical counterpart (?:var|voru)\s+með added to _HISTORICAL_CUE_RE at the same time (a party's number stated with "var með" is a past/comparison figure, not the current poll result — same present/past distinction as mælist vs fékk). Verified: visir-20262884571 went from 5 to 6 extracted parties (Sjálfstæðisflokkur 31.3% newly captured), with no change to any other previously-verified article.

Extending the cue-verb list further is still a real (if small) NLP-scope increase each time — each addition needs 2-3 independent real examples before being added, not a single occurrence.

A conditional-mood gap found and deliberately left unfixed (single example): "Báðir flokkarnir slyppu naumlega inn á þing með um 5,5 prósenta fylgi" ("both parties would barely make it into parliament with about 5.5% support") and "Píratar féllu af þingi með 3,4%" ("Pirates fell out of parliament with 3.4%") — article 427650 — are genuine current-poll figures (electoral-threshold framing: "would make it in" / "would fall out of" parliament) that _POLL_CUE_RE doesn't recognize (slyppu/féllu aren't in the verb list) and _TREND_CUE_RE doesn't cover either (no "úr X í Y"). Correctly logged as [no poll cue] and skipped rather than guessed — this is the second cue-verb gap found this way, and per the rule above it's still only one article's worth of evidence, so it stays a documented gap, not a fix.

Non-Party-Support Articles — RÚV's Tag Isn't Scoped to Fylgi

RÚV's skoðanakönnun tag catches every public-opinion poll, not just party-support ones — leader-trust, minister job-approval, and policy questions ("Hversu ánægð/ur ertu með X?") all get tagged the same way and share the exact same verb vocabulary (mælist, stendur) as genuine fylgi-flokka sentences. Found as three independent real false positives in one round-5 sweep, each a different article entirely about something other than party support:

  • ruv-458088 ("traust" — trust in individual ministers): "Þeim sem bera lítið traust til hennar fjölgar allnokkuð, úr 15 prósentum í 24 prósent." has a trend cue and no in-sentence party, so it fell back to current_party — stale from an earlier sentence naming "Flokks fólksins" only as a possessive modifier ("Ráðherrar Flokks fólksins eru þeir sem flestir vantreysta", about that party's ministers, not its poll number). Produced a bogus Flokkur fólksins: 24% row.
  • ruv-453144 ("ánægja" — satisfaction with the taxi market, broken down by which party each respondent voted for): "Minnst mælist hún í röðum fylgismanna Vinstri grænna, 61 prósent." and "Mest mælist ánægjan ... meðal Pírata." both have a real poll-cue verb (mælist) and a party name, but the party is a voter-subgroup descriptor ("supporters of X"), not the sentence's own topic. Produced two bogus rows.
  • ruv-458497 ("staðið sig vel/illa" — job-approval ratings for party leaders): "Sigurður Ingi stendur sig litlu betur, 58 prósent segja hann hafa staðið sig illa."stendur (here the idiom "stendur sig" = "performs", not "flokkurinn stendur í X prósentum") fired as a poll cue with no in-sentence party, inheriting current_party from a "formaður Framsóknarflokksins" mention two sentences earlier.

Two guards, added together, close all three without touching any verified real party-support sentence (checked against the full regression set: 451831, 428434, 467932, 468092, visir-20262884571, visir-20262904348 — none of them use this vocabulary):

  1. _NON_SUPPORT_TOPIC_RE (ánægj\w*|óánægj\w*|traust\w*|vantreyst\w*| staðið\s+sig|fylgismann\w*|kjósend\w*|kusu|kaus) — a sentence whose own topic is satisfaction/trust/approval, or which frames its number as a voter-subgroup breakdown ("fylgismenn/kjósendur/kusu X"), is skipped entirely regardless of which cue verb it contains. Same discipline as _AGGREGATE_RE, different false-attribution shape.
  2. The pre-existing party = current_party pronoun fallback now also requires an actual poll-cue verb in the sentence (not just a trend cue) — a trend-cue-only sentence with no in-sentence party and no poll verb skips rather than guessing (ruv-458088's exact failure mode; both real verified trend-cue examples, article 428434's "Fylgi Framsóknarflokksins fór úr..." and "Stuðningur við Sósíalistaflokkinn eykst úr...", name their party in-sentence, so this costs nothing against evidence seen so far).

A related, deliberately unexplored generalization: whether an entire article should be skipped up front (e.g. if its title/subtitle never mentions fylgi at all) rather than filtering sentence-by-sentence. Not attempted — the per-sentence guards above already resolve every real false-positive case found, and an article-level topic classifier is a bigger, unverified step past what evidence currently supports.

Two more instances of the same class, found live user-testing the skill (article visir-20262884487, a Vísir "kosningaspá" — election-forecast model by a named mathematician, Baldur Héðinsson, run on top of the underlying Maskína/Gallup polls):

  • "líkur" (probability/odds of winning a council seat) — a forecast's native unit, phrased with the exact same cue verbs as a real fylgi sentence: "Stefán Pálsson, þriðji maður Vinstrisins er með 53 prósent líkur..." and "...Einar Þorsteinsson mælist með áttatíu prósent líkur..." both matched _POLL_CUE_RE with an in-sentence party name, producing two bogus rows (Vinstrið: 53%, Framsóknarflokkur: 80%) for two individual candidates' seat-probabilities, not their parties' fylgi.
  • "forskot" (lead/margin between two parties)"Flokkurinn er með rúmlega ellefu prósentustiga forskot á Samfylkinguna" ("The party has just over an eleven-point lead over Samfylking") attributed the gap between two parties to whichever party happened to be named in the sentence (Samfylking, the trailing party — the leading party was only a pronoun), producing a bogus Samfylking: 11% row (real support that week was closer to 19-20%, per the same day's other Maskína/Gallup coverage). Verified as a recurring headline pattern, not this one article's phrasing — several other real Vísir article titles use the identical "mælist með N prósentustiga forskot á X" construction. Note: "prósentustig" (percentage point) alone was tried first and reverted — verified live to break a real regression article (428434's "Flokkurinn fengi 25,0 prósent, tæplega prósentustigi minna en..." is Samfylking's genuine 25% figure with a harmless comparison clause) — "forskot" is the specific, load-bearing word.

Both added to _NON_SUPPORT_TOPIC_RE alongside the round-5 terms. Checked against the full regression set plus every other article in the local cache that mentions "prósentustig" (428434, 429057, visir-20262859852, visir-20262847801, visir-20262904348) — all unchanged after the fix.

A remaining gap, found the same session and deliberately left unfixed (single example): "(?:er|eru)\s+með" is generic Icelandic for "to have," not specific to polls — visir-20262884111 (an income-bracket breakdown of majority preference, "hversu margir vilja vinstri/hægri meirihluta eftir tekjum") contains "...þeirra sem eru með 800 til 999 þúsund krónur í tekjur á mánuði, 55 prósent vilja vinstrimeirihluta en 45 prósent hægri""eru með" here means "have [an income of]," grammatically unconnected to the 55%/45% later in the same sentence, but it's still enough to satisfy the poll-cue check, and with no in-sentence party the 55% fell back to a stale current_party ("Sósíalistaflokkur," set by an earlier "kjósenda Sósíalistaflokksins" sentence — itself correctly skipped by the kjósend\w* guard, since it has no percent to attach to). One occurrence so far, not this session's 2-3-independent-examples bar, and not needed to answer the request that surfaced it (a different article covering the same underlying poll already has this poll's real numbers) — documented rather than guarded.

Article JSON Shape (from __NEXT_DATA__)

{
  "__typename": "Article",
  "id": 479261,
  "title": "Samfylkingin stærst en Sjálfstæðisflokkur vinnur á",
  "subtitle": "Samfylkingin mælist með mest fylgi í könnun Maskínu en Sjálfstæðisflokkurinn er tveimur og hálfu prósentustigi á eftir. ...",
  "url": "https://nyr.ruv.is/frettir/innlent/2026-06-24-samfylkingin-staerst-en-sjalfstaedisflokkur-vinnur-a-479261/",
  "first_published_at": "2026-06-24T08:04:27.617332Z",
  "topic": {"category": {"slug": "innlent", "title": "Innlendar fréttir"}}
}
  • url uses the nyr.ruv.is staging host in the embedded JSON — swap for www.ruv.is, both serve the same content but the public site is the documented one.
  • topic.category.slug is always innlent for both national and Reykjavík polls — it does not distinguish scope. Guess scope from title/subtitle keywords instead (reykjavík, borgarstjórn, í borginni).
  • tags is null in the listing query (per-article tags like maskína, Skoðanakönnun, party names only appear on the rendered article page, not in this JSON).

Pollster Detection — the "Prósent" Trap

"Prósent" is both a pollster's proper name and the ordinary Icelandic word for "percent." A case-insensitive substring match on prósent false-fires on nearly every poll subtitle, because generic phrases like "...prósentustigi á eftir" contain the substring. The fix verified against real data: match case-sensitively on the capitalized stem (\bPrósent\w*\b), since the common noun is lowercase mid-sentence in practice and the company name is not:

_POLLSTER_RE = re.compile(r"\b(Maskín\w*|Prósent\w*|Gallup\w*|Félagsvísindastofnun\w*)\b")

Maskína/Gallup/Félagsvísindastofnun have no ordinary-word collision and would work case-insensitively too, but the shared regex is simpler to keep one way.

Scope Detection

national vs reykjavik, guessed from title+subtitle: r"reykjav[ií]k|borgarst[jó]órn|í borginni" (case-insensitive). This is a geography classifier, not a topic classifier — a Reykjavík-scope poll about airport siting (Reykjavíkurflugvöllur) will be tagged reykjavik even though it isn't a party-support question. That's expected: the skill scopes by where, not what.

Vísir Discovery

https://www.visir.is/t/2296/skodanakannanir/{page} — server-rendered HTML (no JSON blob), <article class="article-item"> cards, genuinely paginated: verified by fetching pages 1/5/10/20/40 and finding distinct, chronologically-descending content from July 2026 (page 1) back through September 2021 (page 20), with page 40 empty (end of history reached). fetch_visir_article_list() walks pages until an empty page, or — with --since <year> — until a whole page's articles are all older than the cutoff (pages are date-descending, so that's a safe stopping point without walking all ~35+ pages every time).

<article class="article-item ...">
  <h2 class="article-item__title"><a href="/g/20262904348d/fylgi-...">Fylgi Sjálfstæðis­flokks ekki meira í sex ár</a></h2>
  <p class="article-item__text">Sjálfstæðisflokkurinn mælist með 24,9 prósenta fylgi í nýjum þjóðarpúlsi Gallups. ...</p>
  <time class="article-item__time">1.7.2026 19:45</time>
</article>
  • The listing subtitle (article-item__text) often states the headline number directly — unlike RÚV's subtitles, which are usually pure prose summary. For a quick "what's the latest" answer, list's output alone may already be enough; no need to visit the article page.
  • Titles/subtitles carry HTML entities (&#xF6; = ö) and literal soft hyphens (\xad, rendered as ­) mid-word from the source markup — both must be stripped/unescaped (html.unescape + .replace("\xad", "")) or matching against _PARTY_RE/_POLLSTER_RE silently fails on words that are visually identical but byte-different.
  • Dates are Icelandic D.M.YYYY HH:MM ("1.7.2026 19:45"), converted to ISO 8601 by _visir_date_to_iso().
  • IDs are the numeric prefix of the /g/<id>d/<slug> URL path, stored as visir-<id> (RÚV ids are stored as ruv-<id> for the same reason — disambiguating which source's numbering a bare id belongs to once both are combined in one cache file).
  • Recurring feature to know about: "Kosningaspá Vísis" (Vísir's own election-forecast/projection series) — not a raw poll report, an aggregated model. Shows up correctly under this skill's scope/pollster guessing as pollster: null (no known-pollster name in the text) since it isn't a single firm's poll.
  • The same underlying poll is frequently reported by both RÚV and Vísir/Heimildin. cross_reference_duplicates() (run automatically when --source all) flags this: a Vísir or Heimildin article gets duplicate_of: "<ruv-id>" and the matched RÚV article gets also_reported_by: [{source, id, url}, ...] when — and only when — all three hold: same non-null pollster (exact string match), same scope, and published within 48 hours of each other. RÚV is always the anchor side of the match (it's the only source with number-extraction wired up — see Extraction Status below). list's printed view and its distinct-article count exclude anything with duplicate_of set; the saved articles.json keeps every row either way, so nothing is lost, just marked. Verified match (2026-03-24): RÚV's "Samfylkingin dalar enn" and Vísir's "Fylgi Samfylkingar ekki verið minna í eitt ár", ~15 hours apart, both Maskína, both national — genuinely the same poll release covered two ways. When there's more than one same-pollster/same-scope candidate in the window, nothing is merged — logged as ambiguous, N candidates instead. This is common and expected, not a bug: Vísir regularly runs a follow-up angle piece on the same poll a day or two after the first report (verified across RÚV+Vísir+Heimildin combined, 2024–2026: 7 ambiguous cases, each with 2-5 genuinely distinct stories about one poll release). Picking the "closest in time" candidate would be a guess dressed up as a match — left for manual reconciliation instead.

Vísir Extraction

fetch_visir_article(url) — plain httpx, no browser. Verified against a real article (visir-20262904348): the full prose, including the methodology paragraph, is present in a curl-only fetch. No Highcharts/aria-label chart was found on that article either (the chart check still runs first, in case some Vísir articles do embed one) — Vísir's house style leans on thorough prose instead, and extract_prose_poll_figures() (the exact same function RÚV's prose fallback uses — this is Icelandic-grammar logic, not RÚV-specific) is the primary path here, not a fallback.

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
53
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
skodanakannanir
Source
github.com/jokull/icelandic-data