원클릭으로
maskina
Maskína public opinion polls — structured data via Tableau VizQL, articles via the WordPress API.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Maskína public opinion polls — structured data via Tableau VizQL, articles via the WordPress API.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Iceland energy authority — electricity generation, use, fuel sales, power plants and licences. Use for energy-system analysis.
Fiskistofa — public WFS layers for fishing closures, regulations and fishing areas; paid REST catch/quota data is excluded.
Hafrannsóknastofnun / MFRI — annual fish-stock assessments, advice, landings and survey series in embedded tables.
Environment Agency of Iceland GIS — open WFS layers for contaminated land, water, protected areas, noise and wastewater.
Icelandic Met Office (Veðurstofa) — weather observations, stations, forecasts and earthquakes via the modern JSON API at api.vedur.is.
Iceland Tax Authority — annual reports (ársreikningar), company registry, ownership chain mapping by kennitala.
| name | maskina |
| description | Maskína public opinion polls — structured data via Tableau VizQL, articles via the WordPress API. |
Public opinion polls and surveys from Iceland's leading polling company. Two data sources: WordPress articles (prose) and Tableau Public dashboard (structured).
Base URL: https://maskina.is/wp-json/wp/v2/posts
Open API, no authentication. Standard WP REST endpoints.
# Latest posts
curl 'https://maskina.is/wp-json/wp/v2/posts?per_page=5&orderby=date&order=desc&_fields=id,title,date,link,excerpt'
# Search for party support polls
curl 'https://maskina.is/wp-json/wp/v2/posts?search=fylgi&per_page=5&_fields=id,title,date,link,excerpt'
# Full article by ID
curl 'https://maskina.is/wp-json/wp/v2/posts/6412?_fields=id,title,date,link,content'
Content format: HTML in content.rendered. Requires HTML stripping. Poll data is embedded as prose text — "Samfylkingin stendur nú í um 25% fylgi".
Key search terms:
fylgi — party support (monthly polls)borgarviti — Reykjavík politicsstofnanaviti — institutional trustDashboard: FylgiFlokka-heimasa on public.tableau.com
Sheet: Nýjasta mæling (latest poll)
The dashboard provides structured party support data — percentages, month-over-month changes, and election comparison. Updated automatically when Maskína publishes a new poll.
Tableau Public uses an undocumented VizQL API. Two-step session flow:
Step 1: startSession
POST https://public.tableau.com/vizql/w/FylgiFlokka-heimasa/v/Njastamling/startSession/viewing
?:display_static_image=y&:bootstrapWhenNotified=true&:embed=true
&:language=en-US&:embed=y&:showVizHome=n&:apiID=host0&:redirect=auth
Body: empty
Response: JSON with sessionid, stickySessionKey
Headers: x-session-id, global-session-header, set-cookie
Key response fields:
sessionid — session identifier (also in x-session-id header)stickySessionKey — JSON string for server affinityglobal-session-header — Base64-encoded routing value (NOT the same as session ID)Step 2: bootstrapSession
POST https://public.tableau.com/vizql/w/FylgiFlokka-heimasa/v/Njastamling
/bootstrapSession/sessions/{sessionId}
Headers:
Content-Type: application/x-www-form-urlencoded
Cookie: {set-cookie values from step 1}
global-session-header: {from step 1 response header}
x-tsi-active-tab: N%C3%BDjasta%20m%C3%A6ling
Body (form-encoded):
worksheetPortSize={"w":1100,"h":1800}
dashboardPortSize={"w":1100,"h":1800}
clientDimension={"w":1003,"h":1022}
sheet_id=N%C3%BDjasta%20m%C3%A6ling
stickySessionKey={from step 1 JSON}
renderMapsClientSide=true
isBrowserRendering=true
browserRenderingThreshold=100
formatDataValueLocally=false
locale=en_US
language=en
Response: ~650KB proprietary format (length-prefixed JSON chunks)
The response contains two length-prefixed JSON chunks:
590031;{...chunk 0 (layout/metadata)...}63012;{...chunk 1 (data)...}
Split on /\d+;(?=\{)/ regex. Chunk 0 (~576KB) is layout metadata — skip. Chunk 1 (~61KB) contains secondaryInfo with actual poll data.
Data dictionary path:
secondaryInfo.presModelMap.dataDictionary.presModelHolder
.genDataDictionaryPresModel.dataSegments["0"].dataColumns
Two arrays:
real (529 values) — percentages as fractions (0–1), bar heights, and other numericscstring (93 values) — party names, dates, labelsViz data path (column mapping):
secondaryInfo.presModelMap.vizData.presModelHolder
.genPresModelMapPresModel.presModelMap["bar kosningar"]
.presModelHolder.genVizDataPresModel.paneColumnsData.paneColumnsList
The bar kosningar worksheet has three columns via valueIndices:
| Column | Type | Pattern | Example |
|---|---|---|---|
| Dates | cstring indices | 3× per party | [11,12,14, 11,12,14, ...] → "20260301", "20260201", "20241130" |
| Party names | cstring indices | 3× repeated | [2,2,2, 3,3,3, ...] → "Samfylkinguna", "Miðflokkinn", ... |
| Percentages | real indices | sequential triplets | [27,28,29, 30,31,32, ...] → latest, previous month, election |
Each party has 3 data points: latest poll, previous month, last election ("Kosningar '24").
Tableau uses accusative case. Map to nominative for display:
| Tableau (þolfall) | Display (nefnifall) |
|---|---|
| Samfylkinguna | Samfylkingin |
| Miðflokkinn | Miðflokkurinn |
| Sjálfstæðisflokkinn | Sjálfstæðisflokkurinn |
| Viðreisn | Viðreisn |
| Framsóknarflokkinn | Framsóknarflokkurinn |
| Flokk fólksins | Flokkur fólksins |
| Pírata | Píratar |
| VG | Vinstrihreyfingin – grænt framboð |
| Sósíalistaflokkinn | Sósíalistaflokkurinn |
import httpx
import re
import json
TABLEAU_BASE = "https://public.tableau.com"
WORKBOOK = "FylgiFlokka-heimasa"
SHEET = "Njastamling"
ACTIVE_TAB = "N%C3%BDjasta%20m%C3%A6ling"
PARTY_NAMES = {
"Samfylkinguna": "Samfylkingin",
"Miðflokkinn": "Miðflokkurinn",
"Sjálfstæðisflokkinn": "Sjálfstæðisflokkurinn",
"Viðreisn": "Viðreisn",
"Framsóknarflokkinn": "Framsóknarflokkurinn",
"Flokk fólksins": "Flokkur fólksins",
"Pírata": "Píratar",
"VG": "Vinstrihreyfingin – grænt framboð",
"Sósíalistaflokkinn": "Sósíalistaflokkurinn",
}
def fetch_polls():
"""Fetch structured poll data from Maskína's Tableau dashboard."""
client = httpx.Client(follow_redirects=True)
# Step 1: startSession
start_url = (
f"{TABLEAU_BASE}/vizql/w/{WORKBOOK}/v/{SHEET}/startSession/viewing"
f"?:display_static_image=y&:bootstrapWhenNotified=true&:embed=true"
f"&:language=en-US&:embed=y&:showVizHome=n&:apiID=host0&:redirect=auth"
)
r1 = client.post(start_url, headers={"Accept": "application/json"}, content=b"")
r1.raise_for_status()
body = r1.json()
session_id = r1.headers.get("x-session-id", body.get("sessionid"))
sticky_key = body.get("stickySessionKey", "")
global_header = r1.headers.get("global-session-header", "")
cookies = "; ".join(f"{c.name}={c.value}" for c in client.cookies.jar)
# Step 2: bootstrapSession
boot_url = (
f"{TABLEAU_BASE}/vizql/w/{WORKBOOK}/v/{SHEET}"
f"/bootstrapSession/sessions/{session_id}"
)
form_data = {
"worksheetPortSize": '{"w":1100,"h":1800}',
"dashboardPortSize": '{"w":1100,"h":1800}',
"clientDimension": '{"w":1003,"h":1022}',
"sheet_id": ACTIVE_TAB,
"stickySessionKey": sticky_key,
"renderMapsClientSide": "true",
"isBrowserRendering": "true",
"browserRenderingThreshold": "100",
"formatDataValueLocally": "false",
"locale": "en_US",
"language": "en",
}
r2 = client.post(
boot_url,
data=form_data,
headers={
"Cookie": cookies,
"global-session-header": global_header,
"x-tsi-active-tab": ACTIVE_TAB,
},
)
r2.raise_for_status()
# Parse response
chunks = re.split(r"\d+;(?=\{)", r2.text)
json_chunks = [c for c in chunks if c.startswith("{")]
data = json.loads(json_chunks[1])
seg = (
data["secondaryInfo"]["presModelMap"]["dataDictionary"]["presModelHolder"]
["genDataDictionaryPresModel"]["dataSegments"]["0"]
)
reals, strings = [], []
for col in seg["dataColumns"]:
if col["dataType"] in ("real", "float"):
reals = col["dataValues"]
elif col["dataType"] in ("cstring", "string"):
strings = col["dataValues"]
# Find bar kosningar worksheet pane columns
viz_map = (
data["secondaryInfo"]["presModelMap"]["vizData"]["presModelHolder"]
["genPresModelMapPresModel"]["presModelMap"]
)
bar_ws = viz_map["bar kosningar"]["presModelHolder"]["genVizDataPresModel"]
pane_cols = bar_ws["paneColumnsData"]["paneColumnsList"]
party_indices, pct_indices = None, None
for pane in pane_cols:
for vpc in pane.get("vizPaneColumns", []):
indices = vpc.get("valueIndices", [])
if not indices:
continue
first = indices[0]
if first < len(strings) and strings[first] in PARTY_NAMES:
party_indices = indices
elif first < len(reals) and 0 < reals[first] < 1:
sample = [reals[i] for i in indices[:6]]
if all(0 < v < 1 for v in sample):
pct_indices = indices
# Extract triplets: [latest, previous_month, election] per party
results = []
for i in range(0, min(len(party_indices), len(pct_indices)) - 2, 3):
party_acc = strings[party_indices[i]]
latest = reals[pct_indices[i]]
previous = reals[pct_indices[i + 1]]
election = reals[pct_indices[i + 2]]
results.append({
"party": PARTY_NAMES.get(party_acc, party_acc),
"latest_pct": round(latest * 100, 1),
"previous_pct": round(previous * 100, 1),
"election_pct": round(election * 100, 1),
"change": round((latest - previous) * 100, 1),
})
return sorted(results, key=lambda r: -r["latest_pct"])
if __name__ == "__main__":
for row in fetch_polls():
print(f"{row['party']:<35} {row['latest_pct']:>5.1f}% ({row['change']:+.1f}%)")
Samfylkingin 25.5% (-1.6%)
Miðflokkurinn 18.4% (-0.6%)
Sjálfstæðisflokkurinn 16.1% (-0.1%)
Viðreisn 14.0% (+0.6%)
Framsóknarflokkurinn 7.1% (+0.2%)
Flokkur fólksins 5.8% (+1.1%)
Píratar 5.0% (-0.2%)
Vinstrihreyfingin – grænt framboð 4.4% (+0.3%)
Sósíalistaflokkurinn 3.5% (+0.4%)
global-session-header is NOT the session ID — It is a Base64-encoded routing value. Using the session ID instead causes 410 Gone on bootstrapSession.set-cookie headers from startSession must be passed as Cookie header to bootstrapSession.