-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpatch_plain_language.py
More file actions
180 lines (144 loc) · 5.54 KB
/
patch_plain_language.py
File metadata and controls
180 lines (144 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
"""
patch_plain_language.py — Add plain_language_outputs to all extracted records.
For each mechanism, reads its existing extraction and calls Claude to produce
6-10 short everyday English phrases describing what the mechanism produces —
phrased in the commonsense vocabulary that ATOMIC uses (not academic language).
Uses the same claude --print subprocess as extract.py (Max plan, no API key).
Usage:
python patch_plain_language.py # patch all
python patch_plain_language.py --id gratitude # single mechanism
python patch_plain_language.py --skip-existing # skip already-patched
"""
import argparse
import json
import sys
import time
from pathlib import Path
ROOT = Path(__file__).parent
EXTRACTED_DIR = ROOT / "extracted"
sys.path.insert(0, str(ROOT))
from extract import call_claude
PATCH_PROMPT_TEMPLATE = """\
Add a vocabulary bridge field to this psychological mechanism record.
Mechanism: {name}
Domain: {domain}
Definition: {definition}
Behavioral outputs (academic language): {outputs}
---
Generate a list of 6-10 short, everyday English phrases (1-4 words each) that
describe what happens when this mechanism activates. Phrase them as an ordinary
person would — matching the vocabulary of a commonsense knowledge base like ATOMIC.
Style guide:
GOOD: "feels grateful", "helps others", "avoids the situation", "cries",
"gets angry", "apologizes", "pays it forward", "seeks reassurance",
"blames others", "withdraws", "feels proud", "takes risks",
"seeks approval", "loses interest", "bonds with others"
BAD: "prosocial behavioral output", "reparatory cognition", "inhibition
activation", "differential encoding of valenced stimuli"
Cover both internal states ("feels relieved") and external actions
("avoids conflict"). Include effects on the person and on others where relevant.
Output a JSON array only — no explanation, no prose, just the array.
Let me repeat the task:
Generate 6-10 plain everyday English phrases for: {name}
Academic outputs for context: {outputs}
JSON array only.
"""
def patch_mechanism(mid: str) -> bool:
path = EXTRACTED_DIR / f"{mid}.json"
if not path.exists():
print(f" {mid}: no extracted file, skipping")
return False
record = json.loads(path.read_text())
ext = record.get("extraction", {})
name = record.get("name") or ext.get("mechanism") or mid
domain = record.get("domain") or ext.get("domain") or ""
definition = (
ext.get("definition")
or ext.get("core_definition")
or ext.get("core_claim")
or ext.get("description")
or ""
)
outputs = (
ext.get("behavioral_outputs")
or ext.get("outputs")
or ext.get("behavioral_outcomes")
or ext.get("resulting_behaviors")
or ext.get("downstream_effects")
or ""
)
if isinstance(outputs, dict):
outputs = "; ".join(f"{k}: {v}" for k, v in outputs.items())
elif isinstance(outputs, list):
outputs = "; ".join(str(x) for x in outputs)
prompt = PATCH_PROMPT_TEMPLATE.format(
name=name,
domain=domain,
definition=str(definition)[:400],
outputs=str(outputs)[:600],
)
result = call_claude(prompt, model="haiku", timeout=60)
if not result["ok"]:
print(f" {mid}: error — {result.get('error')}")
return False
phrases = result["parsed"]
if phrases is None:
# Try parsing from raw text if JSON parsing failed
text = result.get("text", "")
try:
phrases = json.loads(text)
except Exception:
print(f" {mid}: could not parse output: {text[:100]!r}")
return False
if not isinstance(phrases, list) or not phrases:
print(f" {mid}: unexpected output type: {type(phrases)}")
return False
# Keep only strings, deduplicate, limit to 12
phrases = list(dict.fromkeys(p for p in phrases if isinstance(p, str)))[:12]
record["extraction"]["plain_language_outputs"] = phrases
path.write_text(json.dumps(record, indent=2, ensure_ascii=False))
print(f" {mid}: {phrases[:6]}")
return True
def main():
parser = argparse.ArgumentParser(
description="Add plain_language_outputs to extracted mechanism records"
)
parser.add_argument("--id", help="Patch single mechanism by ID")
parser.add_argument(
"--skip-existing",
action="store_true",
help="Skip mechanisms that already have plain_language_outputs",
)
parser.add_argument(
"--delay", type=float, default=1.0, help="Seconds between calls (default 1.0)"
)
args = parser.parse_args()
if args.id:
ids = [args.id]
else:
ids = sorted(p.stem for p in EXTRACTED_DIR.glob("*.json"))
if args.skip_existing:
def has_field(mid):
p = EXTRACTED_DIR / f"{mid}.json"
if not p.exists():
return False
d = json.loads(p.read_text())
return "plain_language_outputs" in d.get("extraction", {})
ids = [i for i in ids if not has_field(i)]
if not ids:
print("Nothing to patch.")
return
print(f"\nPatching {len(ids)} mechanism(s) (using haiku for speed)...\n")
ok = fail = 0
for i, mid in enumerate(ids, 1):
print(f"[{i}/{len(ids)}] {mid}")
if patch_mechanism(mid):
ok += 1
else:
fail += 1
if i < len(ids):
time.sleep(args.delay)
print(f"\nDone. {ok} patched, {fail} failed.")
if __name__ == "__main__":
main()