Clarify WVS release panels and provenance
Co-Authored-By: PI[gpt-5.6-terra] <288921227+claudypoo@users.noreply.github.com>
@@ -0,0 +1 @@
|
||||
:root{--ink:#333;--muted:#8a857a;--rule:#eceadf;--canvas:#faf8f2}*{box-sizing:border-box}body{background:var(--canvas);color:var(--ink);margin:0;font:16px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}main{max-width:1200px;margin:auto;padding:1.5rem 1rem 3rem}h1{margin:0 0 .35rem;font-size:1.65rem;line-height:1.2}.lede{color:#59544b;max-width:75ch}.controls{flex-wrap:wrap;gap:.35rem;margin:1rem 0;display:flex}.chip{color:var(--ink);cursor:pointer;background:#fff;border:1px solid #d9d5ca;border-radius:999px;align-items:center;gap:.3rem;padding:.22rem .48rem;font:600 .78rem/1 inherit;display:flex}.chip img{width:13px;height:13px}.chip:hover{border-color:#8a857a}.chip:focus-visible,.model-mark:focus-visible{outline-offset:2px;outline:2px solid #2563eb}.chip[aria-pressed=false]{color:#8a857a;background:#f6f4ef;text-decoration:line-through}.chip[aria-pressed=false] img{opacity:.3}.release-axis-selector{color:#5b554b;align-items:baseline;gap:.65rem;margin:.15rem 0 .65rem;font-size:.82rem;display:flex}.release-axis-selector select{font:inherit}.release-axis-selector span{color:#756f64}.chart-shell{position:relative}.chart-shell svg{background:var(--canvas);border:1px solid #ded9cc;width:100%;height:auto;display:block}.canvas{fill:var(--canvas)}.grid line{stroke:var(--rule);stroke-width:.7px}.median{stroke:#c9c4b4;stroke-width:1.2px}.zone{fill:none;stroke-width:2px;opacity:.9}.country{stroke:#fff;stroke-width:.7px;opacity:.88}.country-label,.model-label,.zone-label,.poles text{paint-order:stroke;stroke:var(--canvas);stroke-width:4px;stroke-linejoin:round}.country-label{fill:#222;font-size:11px}.model-ring{fill:#fff;stroke-width:1.3px}.model-mark{cursor:default}.model-mark:focus{outline:none}.model-label{fill:#222;font-size:13px;font-weight:700}.leader{stroke:#aaa398;stroke-width:1px}.zone-label{font-size:15px;font-style:italic;font-weight:700}.poles line{stroke:#999;stroke-width:1.8px}.arrow{fill:#999}.poles text{fill:#555;font-size:18px;font-weight:700}.map-title{fill:#333;font-size:14px;font-weight:700}.map-note{fill:var(--muted);font-size:10px}.release-panels{grid-template-columns:1fr;gap:1rem;margin-top:1.5rem;display:grid}.release-panel{border-top:1px solid #ded9cc;padding-top:1rem}.release-panel h2{margin:0;font-size:1rem}.release-panel p{color:#756f64;margin:.2rem 0 .5rem;font-size:.82rem}.scatter-shell{position:relative}.scatter-shell svg{background:var(--canvas);border:1px solid #ded9cc;width:100%;height:auto;display:block}.scatter-grid line{stroke:var(--rule);stroke-width:.7px}.scatter-axis{stroke:#aaa398;stroke-width:1px}.scatter-arrow{fill:#aaa398}.scatter-y-label{fill:#555;font-size:12px;font-weight:700}.scatter-tick{fill:#756f64;font-size:11px}.release-fit line{stroke:#56514a;stroke-width:1.4px;stroke-dasharray:4 3}.release-fit text,.release-fit-unavailable{fill:#56514a;font-size:11px;font-weight:700}.release-mark{cursor:default}.release-mark:focus{outline:none}.tooltip{z-index:2;pointer-events:none;background:#fffffff7;border:1px solid #ddd7ca;border-radius:5px;max-width:275px;padding:.45rem .6rem;font-size:.82rem;line-height:1.35;position:absolute;transform:translate(14px,14px);box-shadow:0 2px 8px #0002}.tooltip strong,.tooltip span{display:block}.tooltip span{color:#5b554b}@media (width<=700px){main{padding:.9rem .5rem 2rem}.release-axis-selector{flex-direction:column;align-items:flex-start;gap:.15rem}.chip{font-size:.7rem}.poles text{font-size:14px}.country-label,.model-label{font-size:10px}.tooltip{max-width:230px;font-size:.75rem}}
|
||||
@@ -1 +0,0 @@
|
||||
:root{--ink:#333;--muted:#8a857a;--rule:#eceadf;--canvas:#faf8f2}*{box-sizing:border-box}body{background:var(--canvas);color:var(--ink);margin:0;font:16px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}main{max-width:1200px;margin:auto;padding:1.5rem 1rem 3rem}h1{margin:0 0 .35rem;font-size:1.65rem;line-height:1.2}.lede{color:#59544b;max-width:75ch}.controls{flex-wrap:wrap;gap:.35rem;margin:1rem 0;display:flex}.chip{color:var(--ink);cursor:pointer;background:#fff;border:1px solid #d9d5ca;border-radius:999px;align-items:center;gap:.3rem;padding:.22rem .48rem;font:600 .78rem/1 inherit;display:flex}.chip img{width:13px;height:13px}.chip:hover{border-color:#8a857a}.chip:focus-visible,.model-mark:focus-visible{outline-offset:2px;outline:2px solid #2563eb}.chip[aria-pressed=false]{color:#8a857a;background:#f6f4ef;text-decoration:line-through}.chip[aria-pressed=false] img{opacity:.3}.chart-shell{position:relative}.chart-shell svg{background:var(--canvas);border:1px solid #ded9cc;width:100%;height:auto;display:block}.canvas{fill:var(--canvas)}.grid line{stroke:var(--rule);stroke-width:.7px}.median{stroke:#c9c4b4;stroke-width:1.2px}.zone{fill:none;stroke-width:2px;opacity:.9}.country{stroke:#fff;stroke-width:.7px;opacity:.88}.country-label,.model-label,.zone-label,.poles text{paint-order:stroke;stroke:var(--canvas);stroke-width:4px;stroke-linejoin:round}.country-label{fill:#222;font-size:11px}.model-ring{fill:#fff;stroke-width:1.3px}.model-mark{cursor:default}.model-mark:focus{outline:none}.model-label{fill:#222;font-size:13px;font-weight:700}.leader{stroke:#aaa398;stroke-width:1px}.zone-label{font-size:15px;font-style:italic;font-weight:700}.poles line{stroke:#999;stroke-width:1.8px}.arrow{fill:#999}.poles text{fill:#555;font-size:18px;font-weight:700}.map-title{fill:#333;font-size:14px;font-weight:700}.map-note{fill:var(--muted);font-size:10px}.release-panels{grid-template-columns:1fr;gap:1rem;margin-top:1.5rem;display:grid}.release-panel{border-top:1px solid #ded9cc;padding-top:1rem}.release-panel h2{margin:0;font-size:1rem}.release-panel p{color:#756f64;margin:.2rem 0 .5rem;font-size:.82rem}.scatter-shell{position:relative}.scatter-shell svg{background:var(--canvas);border:1px solid #ded9cc;width:100%;height:auto;display:block}.scatter-grid line{stroke:var(--rule);stroke-width:.7px}.scatter-axis{stroke:#aaa398;stroke-width:1px}.scatter-arrow{fill:#aaa398}.scatter-y-label{fill:#555;font-size:12px;font-weight:700}.scatter-tick{fill:#756f64;font-size:11px}.release-fit line{stroke:#56514a;stroke-width:1.4px;stroke-dasharray:4 3}.release-fit text,.release-fit-unavailable{fill:#56514a;font-size:11px;font-weight:700}.release-mark{cursor:default}.release-mark:focus{outline:none}.tooltip{z-index:2;pointer-events:none;background:#fffffff7;border:1px solid #ddd7ca;border-radius:5px;max-width:275px;padding:.45rem .6rem;font-size:.82rem;line-height:1.35;position:absolute;transform:translate(14px,14px);box-shadow:0 2px 8px #0002}.tooltip strong,.tooltip span{display:block}.tooltip span{color:#5b554b}@media (width<=700px){main{padding:.9rem .5rem 2rem}.chip{font-size:.7rem}.poles text{font-size:14px}.country-label,.model-label{font-size:10px}.tooltip{max-width:230px;font-size:.75rem}}
|
||||
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Frontier LLMs on the World Values Survey</title>
|
||||
<script type="module" crossorigin src="./assets/index-Co4wqFEb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CyPTsa3p.css">
|
||||
<script type="module" crossorigin src="./assets/index-xQcEVNVX.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-B27UlIE_.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
"Secular-Rational"
|
||||
]
|
||||
},
|
||||
"capability_x": {
|
||||
"blocker": "Capability scores are omitted until the source owner confirms redistribution permission.",
|
||||
"label": "Artificial Analysis Intelligence Index",
|
||||
"source_url": "https://artificialanalysis.ai/models",
|
||||
"status": "blocked"
|
||||
},
|
||||
"countries": [
|
||||
{
|
||||
"color": "#888888",
|
||||
@@ -860,8 +866,8 @@
|
||||
"items": null,
|
||||
"protocol_id": null,
|
||||
"readout": "recovered rounded historical coordinate",
|
||||
"release_created": null,
|
||||
"release_source": "historical coordinate",
|
||||
"release_created": "2026-03-31",
|
||||
"release_source": "saved OpenRouter catalog 2026-09-17: x-ai/grok-4.20",
|
||||
"run_id": null,
|
||||
"samples": null
|
||||
},
|
||||
@@ -911,8 +917,8 @@
|
||||
"items": null,
|
||||
"protocol_id": null,
|
||||
"readout": "recovered rounded historical coordinate",
|
||||
"release_created": null,
|
||||
"release_source": "historical coordinate",
|
||||
"release_created": "2026-04-30",
|
||||
"release_source": "saved OpenRouter catalog 2026-09-17: x-ai/grok-4.3",
|
||||
"run_id": null,
|
||||
"samples": null
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ import ast
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import dotenv
|
||||
@@ -63,6 +64,15 @@ LEGACY_LATEST = {
|
||||
"mistral": "mistral-large-2512",
|
||||
}
|
||||
|
||||
# These historical Grok coordinates predate the local metadata file. Their dates remain traceable
|
||||
# to exact IDs in the saved 2026-09-17 OpenRouter catalog, not inferred from version strings.
|
||||
LEGACY_CATALOG_IDS = {
|
||||
"grok-4.20": "x-ai/grok-4.20",
|
||||
"grok-4.3": "x-ai/grok-4.3",
|
||||
}
|
||||
SAVED_CATALOG_PATH = Path("slop/research/wvs/20260917_openrouter_models.json")
|
||||
RATED_PROTOCOL_DIAGNOSTICS = {"gpt-5-nano (rated)"}
|
||||
|
||||
API_MODEL_SETS = {
|
||||
"fable-astra": (
|
||||
"anthropic/claude-fable-5.1",
|
||||
@@ -307,7 +317,8 @@ def main() -> None:
|
||||
models: dict[str, tuple] = published_models(published_ci) if published_ci.exists() else {}
|
||||
if args.include_all_cached:
|
||||
for entry in cache["completed"].values():
|
||||
models[entry["display_key"]] = tuple(entry["coords"])
|
||||
if entry["display_key"] not in RATED_PROTOCOL_DIAGNOSTICS:
|
||||
models[entry["display_key"]] = tuple(entry["coords"])
|
||||
|
||||
def save_cache() -> None:
|
||||
"""Atomic cache replacement after a complete model panel, so interruption cannot fabricate a hit."""
|
||||
@@ -411,6 +422,11 @@ def main() -> None:
|
||||
raise ValueError(f"model has no explicit family: {k}")
|
||||
fams.setdefault(family, []).append(k)
|
||||
metadata = json.loads(Path("docs/img/wvs/wvs_model_metadata.json").read_text())["models"]
|
||||
saved_catalog = {entry["id"]: entry for entry in json.loads(SAVED_CATALOG_PATH.read_text())["data"]}
|
||||
legacy_release_dates = {
|
||||
name: datetime.fromtimestamp(saved_catalog[model_id]["created"], tz=timezone.utc).date().isoformat()
|
||||
for name, model_id in LEGACY_CATALOG_IDS.items()
|
||||
}
|
||||
model_labels: dict[str, str] = {}
|
||||
label_sources: dict[str, str] = {}
|
||||
for family, names in fams.items():
|
||||
@@ -463,9 +479,12 @@ def main() -> None:
|
||||
panel = completed.get(name)
|
||||
catalog = metadata.get(name)
|
||||
if panel is None:
|
||||
legacy_date = legacy_release_dates.get(name)
|
||||
return {"readout": "recovered rounded historical coordinate", "items": None,
|
||||
"samples": None, "run_id": None, "protocol_id": None,
|
||||
"release_created": None, "release_source": "historical coordinate"}
|
||||
"release_created": legacy_date,
|
||||
"release_source": (f"saved OpenRouter catalog 2026-09-17: {LEGACY_CATALOG_IDS[name]}"
|
||||
if legacy_date else "historical coordinate")}
|
||||
return {"readout": "rated categorical response", "items": panel["n_items"],
|
||||
"samples": panel["n_samples"], "run_id": panel["run_id"],
|
||||
"protocol_id": panel["protocol_id"],
|
||||
@@ -475,6 +494,12 @@ def main() -> None:
|
||||
args.web_data.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.web_data.write_text(json.dumps({
|
||||
"schema": 2,
|
||||
"capability_x": {
|
||||
"label": "Artificial Analysis Intelligence Index",
|
||||
"source_url": "https://artificialanalysis.ai/models",
|
||||
"status": "blocked",
|
||||
"blocker": "Capability scores are omitted until the source owner confirms redistribution permission.",
|
||||
},
|
||||
"title": "Frontier LLMs on the\nWorld Values Survey",
|
||||
"note": "source: github.com/wassname/moral-maps",
|
||||
"axis": {"x": (["Self-expression", "Survival"] if sx < 0 else ["Survival", "Self-expression"]),
|
||||
|
||||
@@ -9,19 +9,17 @@ function visibleFamilyNames(groups, hidden) {
|
||||
return Object.keys(groups).filter(family => !hidden.has(family));
|
||||
}
|
||||
|
||||
function Tooltip({ active, geometry, data, id = 'model-tooltip' }) {
|
||||
function Tooltip({ active, geometry, id = 'model-tooltip', panel }) {
|
||||
if (!active) return null;
|
||||
const { provenance } = active;
|
||||
const left = active.tooltipLeft ?? `${Math.min(86, geometry.x(active.x) / VIEW.width * 100)}%`;
|
||||
const top = active.tooltipTop ?? `${Math.min(82, geometry.y(active.y) / VIEW.height * 100)}%`;
|
||||
const samples = provenance.items === null ? 'historical coordinate' : `${provenance.items} items x ${provenance.samples} samples`;
|
||||
const release = active.provenance.release_created && <span>release {active.provenance.release_created}</span>;
|
||||
return <aside id={id} className="tooltip" style={{ left, top }} role="status">
|
||||
<strong>{active.name}</strong>
|
||||
<span>{active.family}</span>
|
||||
<span>{data.axis.x[0]} {'->'} {data.axis.x[1]}: {active.x.toFixed(3)}</span>
|
||||
<span>{data.axis.y[0]} {'->'} {data.axis.y[1]}: {active.y.toFixed(3)}</span>
|
||||
<span>{provenance.readout}, {samples}</span>
|
||||
<span>{provenance.release_created ? `release ${provenance.release_created}` : provenance.release_source}</span>
|
||||
{panel === 'secular' && <span>Secular-Rational: {active.y.toFixed(3)}</span>}
|
||||
{panel === 'self-expression' && <span>Self-expression: {active.tooltipValue.toFixed(3)}</span>}
|
||||
{!panel && <><span>Self-expression/Survival: {active.x.toFixed(3)}</span><span>Traditional/Secular-Rational: {active.y.toFixed(3)}</span></>}
|
||||
{release}
|
||||
</aside>;
|
||||
}
|
||||
|
||||
@@ -40,9 +38,9 @@ function ModelMarker({ model, placement, geometry, setActive, clearActive, marke
|
||||
</g>;
|
||||
}
|
||||
|
||||
function ordinaryLeastSquares(models, field) {
|
||||
function ordinaryLeastSquares(models, value) {
|
||||
const x = models.map(model => Date.parse(model.provenance.release_created));
|
||||
const y = models.map(model => model[field]);
|
||||
const y = models.map(value);
|
||||
const uniqueX = new Set(x);
|
||||
if (uniqueX.size < 2) return null;
|
||||
const xMean = x.reduce((sum, value) => sum + value, 0) / x.length;
|
||||
@@ -59,29 +57,30 @@ function ReleaseScatter({ data, hidden, field, title }) {
|
||||
const dated = useMemo(() => data.models.filter(model => Number.isFinite(Date.parse(model.provenance.release_created ?? '')))
|
||||
.toSorted((a, b) => a.provenance.release_created.localeCompare(b.provenance.release_created) || a.name.localeCompare(b.name)), [data]);
|
||||
const visible = dated.filter(model => !hidden.has(model.family));
|
||||
const fit = ordinaryLeastSquares(visible, field);
|
||||
const coordinate = model => field === 'x' ? -model.x : model.y;
|
||||
const fit = ordinaryLeastSquares(visible, coordinate);
|
||||
const [active, setActive] = useState(null);
|
||||
const width = 1200, height = 320, left = 96, right = 35, top = 42, bottom = 48;
|
||||
const dates = dated.map(model => Date.parse(model.provenance.release_created));
|
||||
const values = dated.map(model => model[field]);
|
||||
const values = dated.map(coordinate);
|
||||
const minDate = Math.min(...dates), maxDate = Math.max(...dates);
|
||||
const minValue = Math.min(...values), maxValue = Math.max(...values);
|
||||
const dateX = date => left + (date - minDate) / (maxDate - minDate) * (width - left - right);
|
||||
const valueY = value => top + (maxValue - value) / (maxValue - minValue || 1) * (height - top - bottom);
|
||||
const panelId = `release-${field}`;
|
||||
const tooltipId = `${panelId}-tooltip`;
|
||||
const yDirection = field === 'y' ? data.axis.y : data.axis.x;
|
||||
const yDirection = field === 'y' ? data.axis.y : [...data.axis.x].reverse();
|
||||
const visibleDates = visible.map(model => Date.parse(model.provenance.release_created));
|
||||
const fitEnd = fit && [Math.min(...visibleDates), Math.max(...visibleDates)].map(date => [dateX(date), valueY(fit.intercept + fit.slope * date)]);
|
||||
const activate = (model, event) => {
|
||||
const box = event.currentTarget.closest('.scatter-shell').getBoundingClientRect();
|
||||
const point = event.currentTarget.getBoundingClientRect();
|
||||
setActive({ ...model, tooltipLeft: `${Math.min(82, (point.left - box.left) / box.width * 100)}%`, tooltipTop: `${Math.min(78, (point.top - box.top) / box.height * 100)}%` });
|
||||
setActive({ ...model, tooltipValue: coordinate(model), tooltipLeft: `${Math.min(82, (point.left - box.left) / box.width * 100)}%`, tooltipTop: `${Math.min(78, (point.top - box.top) / box.height * 100)}%` });
|
||||
};
|
||||
return <section className="release-panel" data-coordinate={field} aria-labelledby={`${panelId}-heading`}>
|
||||
<h2 id={`${panelId}-heading`}>{title}</h2>
|
||||
<div className="scatter-shell">
|
||||
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-labelledby={`${panelId}-svg-title ${panelId}-svg-desc`} data-panel-model-count={visible.length} data-fit-n={fit?.n ?? 0} data-fit-r2={fit?.r2 ?? ''}>
|
||||
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-labelledby={`${panelId}-svg-title ${panelId}-svg-desc`} data-panel-model-count={visible.length} data-fit-n={fit?.n ?? 0} data-fit-r2={fit?.r2 ?? ''} data-fit-slope={fit?.slope ?? ''}>
|
||||
<title id={`${panelId}-svg-title`}>{title}</title>
|
||||
<desc id={`${panelId}-svg-desc`}>Scatter plot with release date on the horizontal axis and {yDirection.join(' to ')} increasing upward on the vertical axis. {visible.length} dated models are visible from {visibleFamilyNames(Object.groupBy(data.models, model => model.family), hidden).join(', ') || 'no families'}. Each white-ring logo mark is a model. {fit ? `The thin line is an ordinary least squares descriptive fit to the ${fit.n} currently visible dated models${fit.r2 === null ? '; R squared is unavailable because the y values are constant' : `; R squared is ${fit.r2.toFixed(2)}`}.` : 'The fit is hidden because fewer than two distinct release dates are visible.'} Hover or keyboard focus a mark for model-specific details.</desc>
|
||||
<defs><marker id={`${panelId}-arrow`} markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" className="scatter-arrow" /></marker><clipPath id={`${panelId}-fit-clip`}><rect x={left} y={top} width={width - left - right} height={height - top - bottom} /></clipPath></defs>
|
||||
@@ -96,14 +95,14 @@ function ReleaseScatter({ data, hidden, field, title }) {
|
||||
<text className="scatter-tick" x={left - 8} y={height - bottom} textAnchor="end">{minValue.toFixed(2)}</text>
|
||||
{fitEnd && <g className="release-fit" data-fit-n={fit.n} data-fit-r2={fit.r2 ?? ''}><line clipPath={`url(#${panelId}-fit-clip)`} x1={fitEnd[0][0]} y1={fitEnd[0][1]} x2={fitEnd[1][0]} y2={fitEnd[1][1]} /> <text x={left + 6} y={top + 13}>OLS, n={fit.n}, R² {fit.r2 === null ? 'unavailable' : fit.r2.toFixed(2)}</text></g>}
|
||||
{!fit && <text className="release-fit-unavailable" x={left + 6} y={top + 13}>Fit unavailable: fewer than two release dates</text>}
|
||||
{dated.map(model => <g key={model.name} className="release-mark" data-family={model.family} data-release-model={model.name} data-release-date={model.provenance.release_created} display={hidden.has(model.family) ? 'none' : 'inline'}
|
||||
{dated.map(model => <g key={model.name} className="release-mark" data-family={model.family} data-release-model={model.name} data-release-date={model.provenance.release_created} data-coordinate-value={coordinate(model)} display={hidden.has(model.family) ? 'none' : 'inline'}
|
||||
tabIndex="0" role="button" aria-label={`${model.name}, ${model.family}, ${model.provenance.release_created}`} aria-describedby={tooltipId}
|
||||
onPointerEnter={event => activate(model, event)} onPointerLeave={() => setActive(null)} onFocus={event => activate(model, event)} onBlur={() => setActive(null)}>
|
||||
<circle className="model-ring" cx={dateX(Date.parse(model.provenance.release_created))} cy={valueY(model[field])} r="8" stroke={model.color} />
|
||||
<image href={`${LOGO_ROOT}${data.logos[model.family]}`} x={dateX(Date.parse(model.provenance.release_created)) - 5} y={valueY(model[field]) - 5} width="10" height="10" preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" />
|
||||
<circle className="model-ring" cx={dateX(Date.parse(model.provenance.release_created))} cy={valueY(coordinate(model))} r="8" stroke={model.color} />
|
||||
<image href={`${LOGO_ROOT}${data.logos[model.family]}`} x={dateX(Date.parse(model.provenance.release_created)) - 5} y={valueY(coordinate(model)) - 5} width="10" height="10" preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" />
|
||||
</g>)}
|
||||
</svg>
|
||||
<Tooltip active={active} geometry={null} data={data} id={tooltipId} />
|
||||
<Tooltip active={active} geometry={null} id={tooltipId} panel={field === 'y' ? 'secular' : 'self-expression'} />
|
||||
</div>
|
||||
</section>;
|
||||
}
|
||||
@@ -147,6 +146,13 @@ function Map({ data }) {
|
||||
<img src={`${LOGO_ROOT}${data.logos[family]}`} alt="" aria-hidden="true" />{family}
|
||||
</button>;
|
||||
})}</section>
|
||||
<div className="release-axis-selector">
|
||||
<label>Release-panel x axis <select aria-label="Release-panel x axis" value="release-date" onChange={() => {}}>
|
||||
<option value="release-date">Release date</option>
|
||||
<option value="capability" disabled>{data.capability_x.label}, unavailable pending redistribution permission</option>
|
||||
</select></label>
|
||||
<span>{data.capability_x.blocker}</span>
|
||||
</div>
|
||||
<div className="chart-shell">
|
||||
<svg viewBox={`0 0 ${VIEW.width} ${VIEW.height}`} role="img" aria-labelledby="map-svg-title map-svg-desc" data-median-x={data.median.x} data-median-y={data.median.y} data-visible-model-count={visibleModelCount}>
|
||||
<title id="map-svg-title">Frontier LLMs on the World Values Survey</title>
|
||||
@@ -164,9 +170,11 @@ function Map({ data }) {
|
||||
<text className="map-title" x={geometry.bounds.left + 8} y={geometry.bounds.bottom - 34}>{data.title.split('\n').map((line, index) => <tspan key={line} x={geometry.bounds.left + 8} dy={index ? 17 : 0}>{line}</tspan>)}</text>
|
||||
<text className="map-note" x={geometry.bounds.right - 8} y={geometry.bounds.bottom - 20} textAnchor="end">{data.note.split('\n').map((line, index) => <tspan key={line} x={geometry.bounds.right - 8} dy={index ? 11 : 0}>{line}</tspan>)}</text>
|
||||
</svg>
|
||||
<Tooltip active={active} geometry={geometry} data={data} />
|
||||
<Tooltip active={active} geometry={geometry} />
|
||||
</div>
|
||||
<p className="map-explanation">Since 1981, the World Values Survey has asked people in about ninety countries the same questions. Its axes run from Traditional to Secular-Rational and from Self-expression to Survival.</p>
|
||||
<ReleaseScatters data={data} hidden={hidden} />
|
||||
<p className="caption">Use the family controls to compare saved rated coordinates. The lines are weak descriptive correlations for the dated models currently shown; n and R² update with visibility. Hover or keyboard focus a mark for its coordinates and recorded release date. See the <a href="https://github.com/wassname/moral-maps">code and records</a>.</p>
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -175,8 +183,7 @@ function App() {
|
||||
useEffect(() => { fetch('wvs/wvs_map_data.json').then(response => response.json()).then(setData); }, []);
|
||||
return <main>
|
||||
<h1>How do AI models score on human values surveys? Which culture are they most similar to? Is it changing over time?</h1>
|
||||
<p className="lede">To answer these we start with the <a href="https://www.worldvaluessurvey.org/">World Values Survey</a>, the standard culture map of the world. Since 1981 it has asked people in about ninety countries the same questions. Two axes drawn from it sort societies by how traditional or secular they are and how much they weigh survival over self-expression.</p>
|
||||
<p className="caption">Use the family controls to compare saved model coordinates. The release-date panels fit ordinary least squares lines only to the dated models currently shown; n and R² update with visibility and describe correlation, not cause. Hover or keyboard focus a mark for model-specific provenance. See the <a href="https://github.com/wassname/moral-maps">code and records</a>.</p>
|
||||
<p className="lede">We start with the <a href="https://www.worldvaluessurvey.org/">World Values Survey</a>, a map of human values across about ninety countries.</p>
|
||||
{data && <Map data={data} />}
|
||||
</main>;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
:root{--ink:#333;--muted:#8a857a;--rule:#eceadf;--canvas:#faf8f2}*{box-sizing:border-box}body{margin:0;background:var(--canvas);color:var(--ink);font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{max-width:1200px;margin:auto;padding:1.5rem 1rem 3rem}h1{font-size:1.65rem;line-height:1.2;margin:0 0 .35rem}.lede{max-width:75ch;color:#59544b}.controls{display:flex;flex-wrap:wrap;gap:.35rem;margin:1rem 0}.chip{display:flex;align-items:center;gap:.3rem;padding:.22rem .48rem;border:1px solid #d9d5ca;border-radius:999px;background:white;color:var(--ink);font:600 .78rem/1 inherit;cursor:pointer}.chip img{width:13px;height:13px}.chip:hover{border-color:#8a857a}.chip:focus-visible,.model-mark:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.chip[aria-pressed="false"]{color:#8a857a;background:#f6f4ef;text-decoration:line-through}.chip[aria-pressed="false"] img{opacity:.3}.chart-shell{position:relative}.chart-shell svg{display:block;width:100%;height:auto;border:1px solid #ded9cc;background:var(--canvas)}.canvas{fill:var(--canvas)}.grid line{stroke:var(--rule);stroke-width:.7}.median{stroke:#c9c4b4;stroke-width:1.2}.zone{fill:none;stroke-width:2;opacity:.9}.country{stroke:white;stroke-width:.7;opacity:.88}.country-label,.model-label,.zone-label,.poles text{paint-order:stroke;stroke:var(--canvas);stroke-width:4px;stroke-linejoin:round}.country-label{fill:#222;font-size:11px}.model-ring{fill:white;stroke-width:1.3}.model-mark{cursor:default}.model-mark:focus{outline:none}.model-label{fill:#222;font-size:13px;font-weight:700}.leader{stroke:#aaa398;stroke-width:1}.zone-label{font-size:15px;font-style:italic;font-weight:700}.poles line{stroke:#999;stroke-width:1.8}.arrow{fill:#999}.poles text{fill:#555;font-size:18px;font-weight:700}.map-title{fill:#333;font-size:14px;font-weight:700}.map-note{fill:var(--muted);font-size:10px}.release-panels{display:grid;grid-template-columns:1fr;gap:1rem;margin-top:1.5rem}.release-panel{border-top:1px solid #ded9cc;padding-top:1rem}.release-panel h2{font-size:1rem;margin:0}.release-panel p{margin:.2rem 0 .5rem;color:#756f64;font-size:.82rem}.scatter-shell{position:relative}.scatter-shell svg{display:block;width:100%;height:auto;border:1px solid #ded9cc;background:var(--canvas)}.scatter-grid line{stroke:var(--rule);stroke-width:.7}.scatter-axis{stroke:#aaa398;stroke-width:1}.scatter-arrow{fill:#aaa398}.scatter-y-label{fill:#555;font-size:12px;font-weight:700}.scatter-tick{fill:#756f64;font-size:11px}.release-fit line{stroke:#56514a;stroke-width:1.4;stroke-dasharray:4 3}.release-fit text,.release-fit-unavailable{fill:#56514a;font-size:11px;font-weight:700}.release-mark{cursor:default}.release-mark:focus{outline:none}.tooltip{position:absolute;z-index:2;max-width:275px;transform:translate(14px,14px);pointer-events:none;border:1px solid #ddd7ca;border-radius:5px;padding:.45rem .6rem;background:rgba(255,255,255,.97);box-shadow:0 2px 8px #0002;font-size:.82rem;line-height:1.35}.tooltip strong,.tooltip span{display:block}.tooltip span{color:#5b554b}@media(max-width:700px){main{padding:.9rem .5rem 2rem}.chip{font-size:.7rem}.poles text{font-size:14px}.country-label,.model-label{font-size:10px}.tooltip{max-width:230px;font-size:.75rem}}
|
||||
:root{--ink:#333;--muted:#8a857a;--rule:#eceadf;--canvas:#faf8f2}*{box-sizing:border-box}body{margin:0;background:var(--canvas);color:var(--ink);font:16px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}main{max-width:1200px;margin:auto;padding:1.5rem 1rem 3rem}h1{font-size:1.65rem;line-height:1.2;margin:0 0 .35rem}.lede{max-width:75ch;color:#59544b}.controls{display:flex;flex-wrap:wrap;gap:.35rem;margin:1rem 0}.chip{display:flex;align-items:center;gap:.3rem;padding:.22rem .48rem;border:1px solid #d9d5ca;border-radius:999px;background:white;color:var(--ink);font:600 .78rem/1 inherit;cursor:pointer}.chip img{width:13px;height:13px}.chip:hover{border-color:#8a857a}.chip:focus-visible,.model-mark:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.chip[aria-pressed="false"]{color:#8a857a;background:#f6f4ef;text-decoration:line-through}.chip[aria-pressed="false"] img{opacity:.3}.release-axis-selector{display:flex;align-items:baseline;gap:.65rem;margin:.15rem 0 .65rem;color:#5b554b;font-size:.82rem}.release-axis-selector select{font:inherit}.release-axis-selector span{color:#756f64}.chart-shell{position:relative}.chart-shell svg{display:block;width:100%;height:auto;border:1px solid #ded9cc;background:var(--canvas)}.canvas{fill:var(--canvas)}.grid line{stroke:var(--rule);stroke-width:.7}.median{stroke:#c9c4b4;stroke-width:1.2}.zone{fill:none;stroke-width:2;opacity:.9}.country{stroke:white;stroke-width:.7;opacity:.88}.country-label,.model-label,.zone-label,.poles text{paint-order:stroke;stroke:var(--canvas);stroke-width:4px;stroke-linejoin:round}.country-label{fill:#222;font-size:11px}.model-ring{fill:white;stroke-width:1.3}.model-mark{cursor:default}.model-mark:focus{outline:none}.model-label{fill:#222;font-size:13px;font-weight:700}.leader{stroke:#aaa398;stroke-width:1}.zone-label{font-size:15px;font-style:italic;font-weight:700}.poles line{stroke:#999;stroke-width:1.8}.arrow{fill:#999}.poles text{fill:#555;font-size:18px;font-weight:700}.map-title{fill:#333;font-size:14px;font-weight:700}.map-note{fill:var(--muted);font-size:10px}.release-panels{display:grid;grid-template-columns:1fr;gap:1rem;margin-top:1.5rem}.release-panel{border-top:1px solid #ded9cc;padding-top:1rem}.release-panel h2{font-size:1rem;margin:0}.release-panel p{margin:.2rem 0 .5rem;color:#756f64;font-size:.82rem}.scatter-shell{position:relative}.scatter-shell svg{display:block;width:100%;height:auto;border:1px solid #ded9cc;background:var(--canvas)}.scatter-grid line{stroke:var(--rule);stroke-width:.7}.scatter-axis{stroke:#aaa398;stroke-width:1}.scatter-arrow{fill:#aaa398}.scatter-y-label{fill:#555;font-size:12px;font-weight:700}.scatter-tick{fill:#756f64;font-size:11px}.release-fit line{stroke:#56514a;stroke-width:1.4;stroke-dasharray:4 3}.release-fit text,.release-fit-unavailable{fill:#56514a;font-size:11px;font-weight:700}.release-mark{cursor:default}.release-mark:focus{outline:none}.tooltip{position:absolute;z-index:2;max-width:275px;transform:translate(14px,14px);pointer-events:none;border:1px solid #ddd7ca;border-radius:5px;padding:.45rem .6rem;background:rgba(255,255,255,.97);box-shadow:0 2px 8px #0002;font-size:.82rem;line-height:1.35}.tooltip strong,.tooltip span{display:block}.tooltip span{color:#5b554b}@media(max-width:700px){main{padding:.9rem .5rem 2rem}.release-axis-selector{align-items:flex-start;flex-direction:column;gap:.15rem}.chip{font-size:.7rem}.poles text{font-size:14px}.country-label,.model-label{font-size:10px}.tooltip{max-width:230px;font-size:.75rem}}
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
from datetime import date
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -36,6 +37,14 @@ def country_positions(page) -> list[list[str]]:
|
||||
)
|
||||
|
||||
|
||||
def ols_slope(models: list[dict], value: str) -> float:
|
||||
dates = [date.fromisoformat(model["provenance"]["release_created"]).toordinal() for model in models]
|
||||
coordinates = [model[value] for model in models]
|
||||
date_mean = sum(dates) / len(dates)
|
||||
coordinate_mean = sum(coordinates) / len(coordinates)
|
||||
return sum((date - date_mean) * (coordinate - coordinate_mean) for date, coordinate in zip(dates, coordinates)) / sum((date - date_mean) ** 2 for date in dates)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
chrome = shutil.which("google-chrome")
|
||||
@@ -43,12 +52,18 @@ def main() -> None:
|
||||
raise RuntimeError("google-chrome is required for the Playwright UAT")
|
||||
data = json.loads((ROOT / "docs/wvs/wvs_map_data.json").read_text())
|
||||
assert data["schema"] == 2
|
||||
assert data["capability_x"]["status"] == "blocked"
|
||||
assert data["capability_x"]["source_url"] == "https://artificialanalysis.ai/models"
|
||||
assert len(data["models"]) == 64
|
||||
assert len(data["countries"]) == 90
|
||||
assert len(data["zone_hulls"]) == 4
|
||||
assert len(data["latest_by_family"]) == 13
|
||||
assert all(len(zone["points"]) >= 30 for zone in data["zone_hulls"])
|
||||
dated = [model for model in data["models"] if model["provenance"]["release_created"]]
|
||||
grok_dates = {model["name"]: model["provenance"]["release_created"] for model in data["models"] if model["name"] in {"grok-4.20", "grok-4.3"}}
|
||||
assert grok_dates == {"grok-4.20": "2026-03-31", "grok-4.3": "2026-04-30"}
|
||||
assert all(model["provenance"]["release_source"].startswith("saved OpenRouter catalog 2026-09-17: x-ai/")
|
||||
for model in data["models"] if model["name"] in grok_dates)
|
||||
families = {model["family"] for model in data["models"]}
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
@@ -68,10 +83,18 @@ def main() -> None:
|
||||
assert page.locator("html").get_attribute("lang") == "en"
|
||||
assert page.locator('meta[name="viewport"]').count() == 1
|
||||
assert page.locator("h1").inner_text().startswith("How do AI models score on human values surveys?")
|
||||
assert page.locator(".lede").inner_text() == "We start with the World Values Survey, a map of human values across about ninety countries."
|
||||
visible_copy = page.locator("main").inner_text()
|
||||
for absent in ("React/SVG rendering", "Artificial Analysis", "Historical coordinates", "Dated releases only"):
|
||||
for absent in ("React/SVG rendering", "Historical coordinates", "Dated releases only"):
|
||||
assert absent not in visible_copy
|
||||
assert "ordinary least squares" in visible_copy
|
||||
assert "Artificial Analysis Intelligence Index, unavailable pending redistribution permission" in visible_copy
|
||||
assert data["capability_x"]["blocker"] in visible_copy
|
||||
capability_option = page.get_by_label("Release-panel x axis").locator('option[value="capability"]')
|
||||
assert capability_option.get_attribute("disabled") is not None
|
||||
layout_order = page.locator("main > *").evaluate_all("nodes => nodes.map(node => node.className.baseVal || node.className || node.tagName)")
|
||||
assert layout_order.index("chart-shell") < layout_order.index("map-explanation") < layout_order.index("release-panels") < layout_order.index("caption")
|
||||
assert "weak descriptive correlations" in page.locator(".caption").inner_text()
|
||||
assert "code and records" in page.locator(".caption").inner_text()
|
||||
|
||||
map_svg = page.locator("svg[data-median-x]")
|
||||
svg_description(page, "svg[data-median-x]", "map-svg-title", "map-svg-desc")
|
||||
@@ -100,6 +123,16 @@ def main() -> None:
|
||||
svg_description(page, '.release-panel[data-coordinate="y"] svg', "release-y-svg-title", "release-y-svg-desc")
|
||||
svg_description(page, '.release-panel[data-coordinate="x"] svg', "release-x-svg-title", "release-x-svg-desc")
|
||||
assert page.locator(".release-mark").count() == len(dated) * 2
|
||||
for grok_name in grok_dates:
|
||||
assert page.locator(f'[data-release-model="{grok_name}"]').count() == 2
|
||||
self_expression_panel = page.locator('.release-panel[data-coordinate="x"]')
|
||||
assert self_expression_panel.locator(".scatter-y-label").text_content() == "Survival -> Self-expression ↑"
|
||||
qwen = next(model for model in dated if model["name"] == "qwen3.8-flash")
|
||||
qwen_self_expression = self_expression_panel.locator('[data-release-model="qwen3.8-flash"]')
|
||||
assert float(qwen_self_expression.get_attribute("data-coordinate-value")) == -qwen["x"]
|
||||
rendered_slope = float(self_expression_panel.locator("svg").get_attribute("data-fit-slope"))
|
||||
expected_slope = -ols_slope(dated, "x") / 86_400_000
|
||||
assert abs(rendered_slope - expected_slope) < 1e-18
|
||||
for panel in page.locator(".release-panel").all():
|
||||
dates = panel.locator(".release-mark").evaluate_all("nodes => nodes.map(node => node.dataset.releaseDate)")
|
||||
assert dates == sorted(dates)
|
||||
@@ -123,8 +156,10 @@ def main() -> None:
|
||||
marker = page.locator('[data-model="qwen3.8-flash"]')
|
||||
marker.locator(".model-ring").hover()
|
||||
page.wait_for_selector("#model-tooltip")
|
||||
for required in ("qwen3.8-flash", "qwen", "rated categorical response", "12 items x 12 samples"):
|
||||
assert required in page.locator("#model-tooltip").inner_text()
|
||||
assert page.locator("#model-tooltip strong").inner_text() == "qwen3.8-flash"
|
||||
assert page.locator("#model-tooltip span").all_text_contents() == [
|
||||
"Self-expression/Survival: -0.415", "Traditional/Secular-Rational: 0.635", "release 2026-08-26",
|
||||
]
|
||||
page.screenshot(path=OUT / "wvs_react_root_playwright_tooltip_hover.png", full_page=True)
|
||||
marker.focus()
|
||||
assert page.evaluate("document.activeElement.dataset.model") == "qwen3.8-flash"
|
||||
@@ -134,7 +169,8 @@ def main() -> None:
|
||||
release_y_marker = page.locator('.release-panel[data-coordinate="y"] [data-release-model="qwen3.8-flash"]')
|
||||
release_y_marker.locator(".model-ring").hover()
|
||||
page.wait_for_selector("#release-y-tooltip")
|
||||
assert "qwen3.8-flash" in page.locator("#release-y-tooltip").inner_text()
|
||||
assert page.locator("#release-y-tooltip strong").inner_text() == "qwen3.8-flash"
|
||||
assert page.locator("#release-y-tooltip span").all_text_contents() == ["Secular-Rational: 0.635", "release 2026-08-26"]
|
||||
page.screenshot(path=OUT / "wvs_react_root_playwright_release_panel_hover.png", full_page=True)
|
||||
release_y_marker.focus()
|
||||
assert page.evaluate("document.activeElement.dataset.releaseModel") == "qwen3.8-flash"
|
||||
@@ -145,6 +181,17 @@ def main() -> None:
|
||||
page.wait_for_selector("#release-x-tooltip")
|
||||
assert release_y_marker.get_attribute("aria-describedby") == "release-y-tooltip"
|
||||
assert release_x_marker.get_attribute("aria-describedby") == "release-x-tooltip"
|
||||
assert page.locator("#release-x-tooltip strong").inner_text() == "qwen3.8-flash"
|
||||
assert page.locator("#release-x-tooltip span").all_text_contents() == ["Self-expression: 0.415", "release 2026-08-26"]
|
||||
page.screenshot(path=OUT / "wvs_react_root_playwright_release_self_expression_hover.png", full_page=True)
|
||||
for grok_name, grok_date in grok_dates.items():
|
||||
grok = next(model for model in dated if model["name"] == grok_name)
|
||||
grok_marker = page.locator(f'.release-panel[data-coordinate="y"] [data-release-model="{grok_name}"]')
|
||||
grok_marker.locator(".model-ring").hover()
|
||||
assert page.locator("#release-y-tooltip strong").inner_text() == grok_name
|
||||
assert page.locator("#release-y-tooltip span").all_text_contents() == [
|
||||
f"Secular-Rational: {grok['y']:.3f}", f"release {grok_date}",
|
||||
]
|
||||
|
||||
qwen_dated = sum(model["family"] == "qwen" and bool(model["provenance"]["release_created"]) for model in data["models"])
|
||||
for family in sorted(families - {"qwen"}):
|
||||
@@ -194,10 +241,10 @@ def main() -> None:
|
||||
"numeric_model_country_median_equality": "verified against DOM data attributes"},
|
||||
"svg_accessibility": "main map and both release panels have stable title/desc aria-labelledby; descriptions update after family visibility change",
|
||||
"qwen_toggle": "clicked, map and dated-panel family groups hidden, country coordinates invariant",
|
||||
"tooltip": "map and release-panel pointer hover and focus show model-specific fields",
|
||||
"release_panels": {"panels": 2, "dated_models": len(dated), "date_order": "DOM order verified",
|
||||
"ols": "line, n, and R squared recompute from currently visible dated models; fewer than two distinct dates hide the fit"},
|
||||
"copy": "reader-facing WVS introduction; no model-count, archaeological, implementation, or Artificial Analysis text",
|
||||
"tooltip": "map and release-panel pointer hover and focus show panel-specific model name, coordinate, and release date only",
|
||||
"release_panels": {"panels": 2, "dated_models": len(dated), "date_order": "DOM order verified; both catalog-dated Grok models rendered in each panel",
|
||||
"ols": "line, n, and R squared recompute from currently visible dated models; the Self-expression panel renders negative stored x, so upward means Self-expression; fewer than two distinct dates hide the fit"},
|
||||
"copy": "one short linked WVS sentence above the map; history and axes below it; weak descriptive-fit method and code link below the release panels; capability selector remains visible but scores are absent pending redistribution permission",
|
||||
"hashes": hashes,
|
||||
}, indent=2))
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Capability-score publication blocker
|
||||
|
||||
Status: blocked, no capability scores are stored or rendered.
|
||||
|
||||
The React release-panel selector reserves `Artificial Analysis Intelligence Index` as an x-axis option. The shared data contains only this interface status, the source URL, and the blocker text. It contains no Artificial Analysis score, model mapping, fit, or derived value.
|
||||
|
||||
- requested source: <https://artificialanalysis.ai/models>
|
||||
- blocker: redistribution permission for Intelligence Index values has not been confirmed.
|
||||
- owner decision needed: may this repository store, publish on GitHub Pages, and redistribute the matched Intelligence Index values and their model-ID mapping?
|
||||
|
||||
Until the source owner confirms this scope, release date remains the only enabled panel x axis.
|
||||
|
||||
-- PI[gpt-5.6-terra]
|
||||
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 322 KiB After Width: | Height: | Size: 329 KiB |
@@ -10,16 +10,16 @@
|
||||
},
|
||||
"svg_accessibility": "main map and both release panels have stable title/desc aria-labelledby; descriptions update after family visibility change",
|
||||
"qwen_toggle": "clicked, map and dated-panel family groups hidden, country coordinates invariant",
|
||||
"tooltip": "map and release-panel pointer hover and focus show model-specific fields",
|
||||
"tooltip": "map and release-panel pointer hover and focus show panel-specific model name, coordinate, and release date only",
|
||||
"release_panels": {
|
||||
"panels": 2,
|
||||
"dated_models": 48,
|
||||
"date_order": "DOM order verified",
|
||||
"ols": "line, n, and R squared recompute from currently visible dated models; fewer than two distinct dates hide the fit"
|
||||
"dated_models": 50,
|
||||
"date_order": "DOM order verified; both catalog-dated Grok models rendered in each panel",
|
||||
"ols": "line, n, and R squared recompute from currently visible dated models; the Self-expression panel renders negative stored x, so upward means Self-expression; fewer than two distinct dates hide the fit"
|
||||
},
|
||||
"copy": "reader-facing WVS introduction; no model-count, archaeological, implementation, or Artificial Analysis text",
|
||||
"copy": "one short linked WVS sentence above the map; history and axes below it; weak descriptive-fit method and code link below the release panels; capability selector remains visible but scores are absent pending redistribution permission",
|
||||
"hashes": {
|
||||
"wvs_map_data.json": "c0b7aa0554ebbf5d819308932e058f4ff6e19d1e817ffebbbe381dbd2d063ffe",
|
||||
"index.html": "324afb7ec7e686e1eb9b7f660f51665475ffcd55e9f546f528674781aab771a6"
|
||||
"wvs_map_data.json": "f67f80fb535655e27d1943ca0709cb56aa3f64bc43e6c6943be34badd2fddc4b",
|
||||
"index.html": "a0465beadb26958485695ea11f5f77957626ff333538c1792e8cf222c6b170e2"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 377 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 389 KiB |
|
Before Width: | Height: | Size: 383 KiB After Width: | Height: | Size: 383 KiB |
|
After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 773 KiB After Width: | Height: | Size: 782 KiB |
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 377 KiB |
@@ -0,0 +1,21 @@
|
||||
# Fresh-eyes review: React WVS tooltips, Grok dates, capability blocker
|
||||
|
||||
Reviewer: reviewer-openai, image-only, 2026-09-17.
|
||||
|
||||
Verdict: PASS.
|
||||
|
||||
The reviewer inspected the current default, Qwen-hidden, tooltip, focus, OLS-state, static, and side-by-side captures after reading `AGENTS.md`.
|
||||
|
||||
Observed:
|
||||
|
||||
- Short copy, family controls, the Release date selector, and the capability-permission blocker are readable.
|
||||
- Both Grok symbols appear in each release panel, including Qwen-hidden.
|
||||
- The map tooltip gives the model and two coordinates; each release-panel tooltip gives its panel coordinate and release date only.
|
||||
- `Survival -> Self-expression` visibly increases upward in the second release panel.
|
||||
- OLS lines are thin/dashed and described as weak descriptive correlations. The all-family, Qwen-only, Qwen-hidden, and insufficient-data visual states are distinct and readable.
|
||||
- Qwen-hidden preserves country outlines, axes, and other marks.
|
||||
- Static and React maps agree visually in orientation and relative placement.
|
||||
|
||||
Limits from the reviewer: images do not prove catalog-date correctness, regression arithmetic, disabled-selector behavior, keyboard operation, or other viewport layouts. These are covered separately by the local Playwright UAT and saved catalog records.
|
||||
|
||||
-- reviewer-openai, copied by PI[gpt-5.6-terra]
|
||||