import { useEffect, useState } from "react"; import { api } from "./api.js"; export default function ResultsView({ jobId, onBack }) { const [data, setData] = useState(null); const [error, setError] = useState(""); useEffect(() => { api.jobResults(jobId).then(setData).catch((e) => setError(e.message)); }, [jobId]); if (error) return (
{anchored ? ( <> Anchored (bipolar) score = {summary.metric === "dot" ? "dot product" : "cosine"} of each text with the anchor vector (target centroid minus opposite centroid). Positive = toward {summary.target_name}; negative = toward {summary.opposite_name}. The two per-pole CCR scores are in the export. > ) : ( <> CCR score = mean cosine similarity between each text and a construct's scale items.{" "} {/* A construct whose items are ALL reverse-keyed scores in the opposite direction, and the backend says so with CONSTRUCT_ALL_ITEMS_REVERSED. Do not state the direction here when that warning is present - the amber panel names which construct is affected. */} {summary.warnings?.some((w) => w.code === "CONSTRUCT_ALL_ITEMS_REVERSED") ? "Score direction depends on how each construct's items are keyed - see the warning below." : "Higher = the text expresses the construct more strongly."} {multi && " All constructs were scored on the same pass over the corpus, so scores are row-aligned and directly comparable."} > )}
{/* Cautionary wording approved by the PI (2026-08-05); source_type comes from the construct snapshot in the run metadata (top-level construct_snapshot on single runs, constructs[].snapshot on multi). */} {[ metadata.construct_snapshot, metadata.target_construct?.snapshot, metadata.opposite_construct?.snapshot, ...(metadata.constructs || []).map((c) => c.snapshot), ].some((s) => s?.source_type === "llm_generated") && (⚠ This run uses a construct whose items were AI-generated and have not been psychometrically validated. Interpret scores with appropriate caution.
)}{w.code} - {w.message}
>
)}
{c.column_prefix}_*
Mean similarity of the corpus to each scale item - a face-validity check on which items drive the construct signal.
{metadata.model} (dim{" "}
{metadata.embedding_dim})
{!multi && !anchored && (
<>
{" "}· items hash: {metadata.items_sha256_16}
>
)}
{anchored && (
<>
{" "}· metric: {summary.metric} · anchor vector norm{" "}
{metadata.anchor_vector_norm}
>
)}{" "}
· text column: {metadata.text_column} · run:{" "}
{metadata.started_at} → {metadata.finished_at} ({metadata.duration_seconds}s) ·
numpy {metadata.numpy}
{metadata.sentence_transformers &&
` · sentence-transformers ${metadata.sentence_transformers}`}
{anchored ? (
{metadata.target_items_sha256_16}
{metadata.target_construct?.reference ? ` · ${metadata.target_construct.reference}` : ""}
{metadata.opposite_items_sha256_16}
{metadata.opposite_construct?.reference
? ` · ${metadata.opposite_construct.reference}`
: ""}
{c.items_sha256_16}
{c.reference ? ` · ${c.reference}` : ""}
Centered on zero. Texts to the right lean toward {summary.target_name}; to the left, toward {summary.opposite_name}.
Mean similarity of the corpus to each target-pole item.
Mean similarity to each opposite-pole item.
Pearson correlation between per-text CCR scores ({n_texts.toLocaleString()} texts). Positive r = the constructs rise and fall together in your corpus; negative r = texts high on one tend to be low on the other. The exported CSV contains every per-text score, so these are fully recomputable.
| {names.map((n, i) => ( | {i + 1} | ))}
|---|---|
| {i + 1}. {rowName} | {matrix[i].map((r, j) => ({i === j ? "-" : r == null ? "n/a" : r.toFixed(2)} | ))}
| Score | Text |
|---|---|
| {d.score.toFixed(3)} | {d.text} |