mirror of
https://github.com/wassname/moral-maps.git
synced 2026-09-09 11:27:22 +08:00
85 lines
3.9 KiB
Python
85 lines
3.9 KiB
Python
"""Instrument registry: build `Instrument` objects (instrument.py schema) from the bundled survey
|
|
data. MFQ-2 plus three reused questionnaires (Big-Five, 16PF, Humor Styles), all ordinal 1-5.
|
|
|
|
Ported from the weight_steer_honesty experiment (`mft_honesty.instruments`). Each instrument is
|
|
three framing files (forward / inverted / negated) of the SAME items reworded; we load all three
|
|
into InstrItems tagged with `frame`, so the reader scores every (item, frame) and
|
|
`per_item_categorical` canonicalizes them to one forward distribution per item.
|
|
|
|
Per-instrument variation:
|
|
- data dir + framing filenames (MFQ-2: forward/inverted/negated.json; the others:
|
|
questionnaire{,_inverted,_negated}.json).
|
|
- dimensions: derived from the forward file in first-appearance order.
|
|
- keying: MFQ-2 items are all +1; IPIP/HSQ are mixed-key within a factor, sign read from
|
|
keying.json (written by the experiment's human-IPIP pipeline). Reverse items are reflected at
|
|
reduce_ordinal time, not here.
|
|
- human reference scale: all administered at 1-5; the HSQ human reference is on 1-7
|
|
(`human_scale_max`), reconciled to a 0-1 fraction at MAP time, not here. Per-item human_label is
|
|
left None (the maps use the per-country CSVs in `data/human/`, not per-item histograms).
|
|
"""
|
|
from __future__ import annotations
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from .instrument import Instrument, InstrItem
|
|
|
|
DATA = Path(__file__).resolve().parent / "data"
|
|
SURVEYS = DATA / "surveys"
|
|
HUMAN = DATA / "human"
|
|
|
|
DIGITS_1_5 = ["1", "2", "3", "4", "5"]
|
|
# Same response-format prefill the experiment used: the model answers "(N)", we prefill "(".
|
|
PREFILL = "("
|
|
|
|
# instrument name -> (survey subdir, {frame: filename stem}, human_csv, display, human_scale_max)
|
|
_F3 = {"forward": "questionnaire", "inverted": "questionnaire_inverted", "negated": "questionnaire_negated"}
|
|
_SPECS = {
|
|
"mfq2": ("mfq2", {"forward": "forward", "inverted": "inverted", "negated": "negated"},
|
|
"mfq2_country_foundations.csv", "Moral Foundations", 5),
|
|
"big5": ("big5", _F3, "big5_country_factors.csv", "Big Five", 5),
|
|
"16pf": ("16pf", _F3, "16pf_country_factors.csv", "16PF", 5),
|
|
"humor_styles": ("humor_styles", _F3, "humor_styles_country_factors.csv", "Humor Styles", 7),
|
|
}
|
|
|
|
|
|
def _load_keying(survey_dir: Path, default_forward_ids: list[str]) -> dict[str, int]:
|
|
p = survey_dir / "keying.json"
|
|
if not p.exists():
|
|
return {i: 1 for i in default_forward_ids} # MFQ-2: every item is +1
|
|
return {str(k): int(v) for k, v in json.loads(p.read_text()).items()}
|
|
|
|
|
|
def build_instrument(name: str) -> Instrument:
|
|
sub, frame_files, human_csv, display, hmax = _SPECS[name]
|
|
sdir = SURVEYS / sub
|
|
frames = {fr: json.loads((sdir / f"{stem}.json").read_text())[0] for fr, stem in frame_files.items()}
|
|
|
|
fwd = frames["forward"]
|
|
dims: list[str] = [] # subscales in first-appearance order (forward file)
|
|
for q in fwd["questions"]:
|
|
if q["dimension"] not in dims:
|
|
dims.append(q["dimension"])
|
|
keying = _load_keying(sdir, [str(q["id"]) for q in fwd["questions"]])
|
|
|
|
items: list[InstrItem] = []
|
|
for fr, doc in frames.items():
|
|
for q in doc["questions"]:
|
|
items.append(InstrItem(
|
|
id=str(q["id"]), prompt=q["content"], dimension=q["dimension"],
|
|
sign=keying[str(q["id"])], frame=fr, human_label=None,
|
|
meta={"task": doc["task"]})) # frame-specific response-scale legend
|
|
return Instrument(
|
|
name=name, construct="endorsement", kind="ordinal",
|
|
answer_space=DIGITS_1_5, dimensions=dims, items=items, prefill=PREFILL,
|
|
scale_max=5, human_scale_max=hmax,
|
|
display=display, human_csv=str(HUMAN / human_csv))
|
|
|
|
|
|
INSTRUMENTS: dict[str, Instrument] = {}
|
|
|
|
|
|
def get(name: str) -> Instrument:
|
|
if name not in INSTRUMENTS:
|
|
INSTRUMENTS[name] = build_instrument(name)
|
|
return INSTRUMENTS[name]
|