Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ Implemented measures include:
- **Information sufficiency**: perceived adequacy of available information before the reveal.
- **Structural impact**: post-reveal perceived effect of the hidden cost difference.
- **Perspective change**: post-reveal reported change in views of lower-scoring players.
- **Remembered initial judgment**: post-reveal memory of the participant's pre-reveal attribution, responsibility rating, constraint-suspicion rating, and confidence in that memory.

## Computed metrics

Expand All @@ -384,6 +385,11 @@ Computed metrics are derived from game and survey responses for prototype analys
- **`burden`**: total treatment cost paid divided by total available income.
- **`careAvoidance`**: skipped treatments plus half of partial treatments.
- **`attributionCategoryShift`**: pre-reveal primary attribution compared with post-reveal revised primary attribution.
- **`rememberedResponsibilityError`**: remembered pre-reveal responsibility minus the original pre-reveal responsibility rating.
- **`rememberedConstraintSuspicionError`**: remembered pre-reveal constraint suspicion minus the original pre-reveal constraint-suspicion rating.
- **`rememberedPrimaryAttributionMatchesOriginal`**: whether remembered and original pre-reveal primary attribution match.
- **`memoryConfidence`**: confidence in memory of the initial interpretation.
- **`memoryDistortionMagnitude`**: absolute remembered responsibility error plus absolute remembered constraint-suspicion error.

## Contact and collaboration

Expand Down
49 changes: 39 additions & 10 deletions components/PostRevealSurveyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import { HelperNote, LikertQuestion, PrimaryButton, SingleChoiceQuestion, TextQu
import { getStoredSession, saveStoredSession } from "@/utils/session";
import type { PostRevealSurveyAnswers, ResearchSession } from "@/types/research";

const rememberedPrimaryAttributionOptions = [
"They made less effective decisions during the game",
"They accepted too much risk",
"Factors not shown in the score table may have influenced the results",
"Random variation or luck may have played a role",
"I did not have enough information to judge",
"I am not sure what I initially thought",
];

const revisedPrimaryAttributionOptions = [
"Their decisions still seem to be the main explanation",
"Hidden cost conditions seem to be the main explanation",
Expand All @@ -17,6 +26,10 @@ const revisedPrimaryAttributionOptions = [
];

const initialAnswers: PostRevealSurveyAnswers = {
rememberedPrimaryAttribution: "",
rememberedIndividualResponsibility: 0,
rememberedConstraintSuspicion: 0,
rememberedConfidence: 0,
revisedPrimaryAttribution: "",
revisedIndividualResponsibility: 0,
perceivedStructuralImpact: 0,
Expand Down Expand Up @@ -56,7 +69,7 @@ export function PostRevealSurveyForm() {

saveStoredSession(nextSession);
setSession(nextSession);
setAnswers(nextSession.postRevealSurvey ?? initialAnswers);
setAnswers({ ...initialAnswers, ...nextSession.postRevealSurvey });
}, [router]);

useEffect(() => {
Expand All @@ -71,6 +84,10 @@ export function PostRevealSurveyForm() {

const openLength = answers.openRevision.trim().length;
const isComplete =
answers.rememberedPrimaryAttribution.length > 0 &&
answers.rememberedIndividualResponsibility > 0 &&
answers.rememberedConstraintSuspicion > 0 &&
answers.rememberedConfidence > 0 &&
answers.revisedPrimaryAttribution.length > 0 &&
answers.revisedIndividualResponsibility > 0 &&
answers.perceivedStructuralImpact > 0 &&
Expand Down Expand Up @@ -117,22 +134,34 @@ export function PostRevealSurveyForm() {
<HelperNote tone="neutral">Please answer after considering the cost rule that was just disclosed. These responses describe interpretations, not personal worth, fairness, or character.</HelperNote>

<SingleChoiceQuestion
legend="1. After learning that players faced different cost conditions, what do you now think best explains why some players ended with lower scores?"
legend="1. Before the hidden rule was revealed, what do you remember thinking was the main reason some players ended with lower scores?"
name="rememberedPrimaryAttribution"
options={rememberedPrimaryAttributionOptions}
value={answers.rememberedPrimaryAttribution}
onChange={(value) => updateAnswer("rememberedPrimaryAttribution", value)}
/>

<LikertQuestion name="rememberedIndividualResponsibility" legend="2. Before the reveal, how responsible do you remember thinking lower-scoring players were for their final scores?" leftLabel="Not responsible at all" rightLabel="Fully responsible" value={answers.rememberedIndividualResponsibility} onChange={(value) => updateAnswer("rememberedIndividualResponsibility", value)} />
<LikertQuestion name="rememberedConstraintSuspicion" legend="3. Before the reveal, how likely do you remember thinking it was that hidden or unshown factors influenced the results?" leftLabel="Very unlikely" rightLabel="Very likely" value={answers.rememberedConstraintSuspicion} onChange={(value) => updateAnswer("rememberedConstraintSuspicion", value)} />
<LikertQuestion name="rememberedConfidence" legend="4. How confident are you in your memory of your initial interpretation?" leftLabel="Not confident at all" rightLabel="Completely confident" value={answers.rememberedConfidence} onChange={(value) => updateAnswer("rememberedConfidence", value)} />

<SingleChoiceQuestion
legend="5. After learning that players faced different cost conditions, what do you now think best explains why some players ended with lower scores?"
name="revisedPrimaryAttribution"
options={revisedPrimaryAttributionOptions}
value={answers.revisedPrimaryAttribution}
onChange={(value) => updateAnswer("revisedPrimaryAttribution", value)}
/>

<LikertQuestion name="revisedIndividualResponsibility" legend="2. After the reveal, how responsible do you think lower-scoring players were for their final scores?" leftLabel="Not responsible at all" rightLabel="Fully responsible" value={answers.revisedIndividualResponsibility} onChange={(value) => updateAnswer("revisedIndividualResponsibility", value)} />
<LikertQuestion name="perceivedStructuralImpact" legend="3. How much do you think the hidden cost difference affected the final scores?" leftLabel="Not at all" rightLabel="A great deal" value={answers.perceivedStructuralImpact} onChange={(value) => updateAnswer("perceivedStructuralImpact", value)} />
<LikertQuestion name="postProtestLegitimacy" legend="4. After the reveal, if lower-scoring players objected to the outcome, how legitimate would their objection seem?" leftLabel="Not legitimate at all" rightLabel="Completely legitimate" value={answers.postProtestLegitimacy} onChange={(value) => updateAnswer("postProtestLegitimacy", value)} />
<LikertQuestion name="postRuleCorrectionSupport" legend="5. After the reveal, would it be fair to adjust the rules or scoring system to account for the hidden cost difference?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.postRuleCorrectionSupport} onChange={(value) => updateAnswer("postRuleCorrectionSupport", value)} />
<LikertQuestion name="postRedistributionSupport" legend="6. After the reveal, would it be fair to transfer some points from higher-scoring players to lower-scoring players?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.postRedistributionSupport} onChange={(value) => updateAnswer("postRedistributionSupport", value)} />
<LikertQuestion name="initialJudgmentAccuracy" legend="7. Looking back, how accurate does your initial interpretation seem now?" leftLabel="Not accurate at all" rightLabel="Very accurate" value={answers.initialJudgmentAccuracy} onChange={(value) => updateAnswer("initialJudgmentAccuracy", value)} />
<LikertQuestion name="perspectiveChange" legend="8. How much did the reveal change how you view the lower-scoring players?" leftLabel="Did not change at all" rightLabel="Changed a lot" value={answers.perspectiveChange} onChange={(value) => updateAnswer("perspectiveChange", value)} />
<LikertQuestion name="revisedIndividualResponsibility" legend="6. After the reveal, how responsible do you think lower-scoring players were for their final scores?" leftLabel="Not responsible at all" rightLabel="Fully responsible" value={answers.revisedIndividualResponsibility} onChange={(value) => updateAnswer("revisedIndividualResponsibility", value)} />
<LikertQuestion name="perceivedStructuralImpact" legend="7. How much do you think the hidden cost difference affected the final scores?" leftLabel="Not at all" rightLabel="A great deal" value={answers.perceivedStructuralImpact} onChange={(value) => updateAnswer("perceivedStructuralImpact", value)} />
<LikertQuestion name="postProtestLegitimacy" legend="8. After the reveal, if lower-scoring players objected to the outcome, how legitimate would their objection seem?" leftLabel="Not legitimate at all" rightLabel="Completely legitimate" value={answers.postProtestLegitimacy} onChange={(value) => updateAnswer("postProtestLegitimacy", value)} />
<LikertQuestion name="postRuleCorrectionSupport" legend="9. After the reveal, would it be fair to adjust the rules or scoring system to account for the hidden cost difference?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.postRuleCorrectionSupport} onChange={(value) => updateAnswer("postRuleCorrectionSupport", value)} />
<LikertQuestion name="postRedistributionSupport" legend="10. After the reveal, would it be fair to transfer some points from higher-scoring players to lower-scoring players?" leftLabel="Strongly disagree" rightLabel="Strongly agree" value={answers.postRedistributionSupport} onChange={(value) => updateAnswer("postRedistributionSupport", value)} />
<LikertQuestion name="initialJudgmentAccuracy" legend="11. Looking back, how accurate does your initial interpretation seem now?" leftLabel="Not accurate at all" rightLabel="Very accurate" value={answers.initialJudgmentAccuracy} onChange={(value) => updateAnswer("initialJudgmentAccuracy", value)} />
<LikertQuestion name="perspectiveChange" legend="12. How much did the reveal change how you view the lower-scoring players?" leftLabel="Did not change at all" rightLabel="Changed a lot" value={answers.perspectiveChange} onChange={(value) => updateAnswer("perspectiveChange", value)} />

<TextQuestion label="9. In one or two sentences, describe how the reveal changed, confirmed, or complicated your interpretation." value={answers.openRevision} onChange={(value) => updateAnswer("openRevision", value)} minLength={10} maxLength={500} />
<TextQuestion label="13. In one or two sentences, describe how the reveal changed, confirmed, or complicated your interpretation." value={answers.openRevision} onChange={(value) => updateAnswer("openRevision", value)} minLength={10} maxLength={500} />

{showValidation && !isComplete ? <HelperNote tone="warning">Please answer all closed-ended items and write 10–500 characters in the revision. Your draft has been saved in this browser.</HelperNote> : null}

Expand Down
7 changes: 6 additions & 1 deletion components/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function IndividualResults({
postRevealSurvey: PostRevealSurveyAnswers;
}) {
const gameSummary = calculateGameSummary(game);
const computedMetrics = calculateComputedResearchMetrics({ game, preRevealSurvey, postRevealSurvey });
const computedMetrics = calculateComputedResearchMetrics({ game, preRevealSurvey, postRevealSurvey, preRevealSurveyOriginal: session.preRevealSurveyOriginal });
const interpretations = buildParticipantInterpretation(computedMetrics);

return (
Expand Down Expand Up @@ -221,6 +221,11 @@ function MetricsGrid({ metrics }: { metrics: ComputedResearchMetrics }) {
["Cost burden ratio", metrics.burden],
["Care avoidance index", metrics.careAvoidance],
["Attribution category shift", `${metrics.attributionCategoryShift.pre} → ${metrics.attributionCategoryShift.post}`],
["Remembered responsibility error", metrics.rememberedResponsibilityError],
["Remembered constraint suspicion error", metrics.rememberedConstraintSuspicionError],
["Remembered attribution matches original", metrics.rememberedPrimaryAttributionMatchesOriginal ? "Yes" : "No"],
["Memory confidence", metrics.memoryConfidence],
["Memory distortion magnitude", metrics.memoryDistortionMagnitude],
] as const;

return (
Expand Down
1 change: 1 addition & 0 deletions docs/RESEARCH_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Computed metrics summarize analysis-ready outcomes, including:
- `ruleCorrectionSupportShift` — change in support for correcting the rules.
- `redistributionSupportShift` — change in redistributive support.
- `certaintyCorrection`, `informationCaution`, and `perspectiveChange` — additional post-reveal interpretation and reflection measures.
- `rememberedResponsibilityError`, `rememberedConstraintSuspicionError`, `rememberedPrimaryAttributionMatchesOriginal`, `memoryConfidence`, and `memoryDistortionMagnitude` — post-reveal memory-of-initial-judgment measures compared against the original pre-reveal responses when available.

## Ethics and limitations

Expand Down
9 changes: 9 additions & 0 deletions lib/adminSubmissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,19 +313,28 @@ const CSV_COLUMNS: CsvColumn[] = [
payloadColumn("certainty_correction", ["computedMetrics", "certaintyCorrection"]),
payloadColumn("information_caution", ["computedMetrics", "informationCaution"]),
payloadColumn("perspective_change", ["computedMetrics", "perspectiveChange"]),
payloadColumn("remembered_responsibility_error", ["computedMetrics", "rememberedResponsibilityError"]),
payloadColumn("remembered_constraint_suspicion_error", ["computedMetrics", "rememberedConstraintSuspicionError"]),
payloadColumn("remembered_primary_attribution_matches_original", ["computedMetrics", "rememberedPrimaryAttributionMatchesOriginal"]),
payloadColumn("memory_confidence", ["computedMetrics", "memoryConfidence"]),
payloadColumn("memory_distortion_magnitude", ["computedMetrics", "memoryDistortionMagnitude"]),
payloadColumn("pre_primary_attribution", ["preRevealSurvey", "primaryAttribution"]),
payloadColumn("remembered_primary_attribution", ["postRevealSurvey", "rememberedPrimaryAttribution"]),
payloadColumn("post_revised_primary_attribution", ["postRevealSurvey", "revisedPrimaryAttribution"]),
payloadColumn("pre_individual_responsibility", ["preRevealSurvey", "individualResponsibility"]),
payloadColumn("post_revised_individual_responsibility", ["postRevealSurvey", "revisedIndividualResponsibility"]),
payloadColumn("remembered_individual_responsibility", ["postRevealSurvey", "rememberedIndividualResponsibility"]),
payloadColumn("pre_constraint_suspicion", ["preRevealSurvey", "constraintSuspicion"]),
payloadColumn("post_perceived_structural_impact", ["postRevealSurvey", "perceivedStructuralImpact"]),
payloadColumn("remembered_constraint_suspicion", ["postRevealSurvey", "rememberedConstraintSuspicion"]),
payloadColumn("pre_protest_legitimacy", ["preRevealSurvey", "protestLegitimacy"]),
payloadColumn("post_protest_legitimacy", ["postRevealSurvey", "postProtestLegitimacy"]),
payloadColumn("pre_rule_correction_support", ["preRevealSurvey", "ruleCorrectionSupport"]),
payloadColumn("post_rule_correction_support", ["postRevealSurvey", "postRuleCorrectionSupport"]),
payloadColumn("pre_redistribution_support", ["preRevealSurvey", "redistributionSupport"]),
payloadColumn("post_redistribution_support", ["postRevealSurvey", "postRedistributionSupport"]),
payloadColumn("pre_confidence", ["preRevealSurvey", "confidence"]),
payloadColumn("remembered_confidence", ["postRevealSurvey", "rememberedConfidence"]),
payloadColumn("post_initial_judgment_accuracy", ["postRevealSurvey", "initialJudgmentAccuracy"]),
payloadColumn("pre_information_sufficiency", ["preRevealSurvey", "informationSufficiency"]),
payloadColumn("post_perspective_change", ["postRevealSurvey", "perspectiveChange"]),
Expand Down
9 changes: 9 additions & 0 deletions lib/researchExportSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ export const preRevealSurveySchema = z

export const postRevealSurveySchema = z
.object({
rememberedPrimaryAttribution: z.string().min(1),
rememberedIndividualResponsibility: likertSchema,
rememberedConstraintSuspicion: likertSchema,
rememberedConfidence: likertSchema,
revisedPrimaryAttribution: z.string().min(1),
revisedIndividualResponsibility: likertSchema,
perceivedStructuralImpact: likertSchema,
Expand All @@ -148,6 +152,11 @@ export const computedMetricsSchema = z
careAvoidance: z.number(),
delayedReveal: z.boolean(),
standByInitialInterpretation: likertSchema.optional(),
rememberedResponsibilityError: z.number(),
rememberedConstraintSuspicionError: z.number(),
rememberedPrimaryAttributionMatchesOriginal: z.boolean(),
memoryConfidence: likertSchema,
memoryDistortionMagnitude: z.number().nonnegative(),
attributionCategoryShift: z
.object({
pre: z.string().min(1),
Expand Down
14 changes: 12 additions & 2 deletions sample-data/complete-research-export.example.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"exportVersion": "prototype-1.1",
"schemaVersion": "research-export-v1",
"exportVersion": "prototype-1.2",
"schemaVersion": "hidden-cost-game-research-schema-3",
"sessionId": "hcg-example-session",
"consentVersion": "pilot-consent-v1",
"serverSubmissionStatus": "not_submitted",
Expand Down Expand Up @@ -143,6 +143,10 @@
"openExplanation": "This is fake sample text for validating a complete export."
},
"postRevealSurvey": {
"rememberedPrimaryAttribution": "individual choices",
"rememberedIndividualResponsibility": 4,
"rememberedConstraintSuspicion": 4,
"rememberedConfidence": 5,
"revisedPrimaryAttribution": "structural constraints",
"revisedIndividualResponsibility": 3,
"perceivedStructuralImpact": 6,
Expand All @@ -164,6 +168,12 @@
"perspectiveChange": 6,
"burden": 0.775,
"careAvoidance": 1.5,
"delayedReveal": false,
"rememberedResponsibilityError": -1,
"rememberedConstraintSuspicionError": 1,
"rememberedPrimaryAttributionMatchesOriginal": true,
"memoryConfidence": 5,
"memoryDistortionMagnitude": 2,
"attributionCategoryShift": {
"pre": "individual choices",
"post": "structural constraints"
Expand Down
9 changes: 9 additions & 0 deletions types/research.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export type PreRevealSurveyAnswers = {
};

export type PostRevealSurveyAnswers = {
rememberedPrimaryAttribution: string;
rememberedIndividualResponsibility: number;
rememberedConstraintSuspicion: number;
rememberedConfidence: number;
revisedPrimaryAttribution: string;
revisedIndividualResponsibility: number;
perceivedStructuralImpact: number;
Expand Down Expand Up @@ -188,6 +192,11 @@ export interface ComputedResearchMetrics {
revisionMagnitude?: number;
delayedReveal: boolean;
standByInitialInterpretation?: number;
rememberedResponsibilityError: number;
rememberedConstraintSuspicionError: number;
rememberedPrimaryAttributionMatchesOriginal: boolean;
memoryConfidence: number;
memoryDistortionMagnitude: number;
}

export interface ResearchExportAssignedProfile {
Expand Down
Loading