Skip to content

Commit a5dfe6a

Browse files
committed
chore: make template/validator phase checks task-generic and CI-clean
1 parent b4a3c99 commit a5dfe6a

7 files changed

Lines changed: 83 additions & 25 deletions

File tree

ChangLog.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# psyflow change log
22

3+
## 0.1.13 (2026-02-18)
4+
5+
### Summary
6+
- Updated responder context contract/validator to support truly generic, task-specific phase labels via phase-token checks instead of fixed phase values.
7+
- Extended template compliance so CI validation passes on the cookiecutter task scaffold:
8+
- README now includes required section heading `## 4. Methods (for academic publication)` and `Language` metadata row.
9+
- Template `.gitignore` now includes recommended housekeeping ignores (`.pytest_cache`, `.mypy_cache`, virtualenv dirs).
10+
- Template `CHANGELOG.md` now includes recommended `Changed` and `Fixed` sections.
11+
- Template run-trial phase labels updated to generic task-ready names (`pre_response_fixation`, `response_window`).
12+
- Added template `outputs/.gitkeep` to satisfy recommended path checks.
13+
14+
### Validation
15+
- `python -m psyflow.validate 'psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}'` now passes with `FAIL=0`.
16+
317
## 0.1.12 (2026-02-18)
418

519
### Summary

psyflow/contracts/v0.1.0/responder_context.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ required_context_fields_any:
1414
- phase=
1515
- deadline_s=
1616
- valid_keys=
17-
required_context_phase_values_any:
17+
required_context_phase_tokens_any:
1818
- choice
1919
- response
2020
- decision
2121
- offer
2222
- probe
2323
- selection
24-
- anticipation
25-
- target
24+
- signal
25+
- reveal
26+
- playback
27+
- rest
2628
recommended_context_fields_any:
2729
- condition_id=
2830
- block_id=

psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
__pycache__/
55
*.pyc
66
src/__pycache__/
7+
.pytest_cache/
8+
.mypy_cache/
9+
.venv/
10+
venv/

psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ All notable development changes for `{{cookiecutter.project_name}}` are document
1010
- Base configs under `config/` and trial logic under `src/`.
1111
- Contract adoption metadata in `taskbeacon.yaml` (`contracts.psyflow_taps: v0.1.0`).
1212

13+
### Changed
14+
- None yet.
15+
16+
### Fixed
17+
- None yet.

psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/README.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
| PsyFlow Version |0.1.0 |
1212
| PsychoPy Version |2025.1.1 |
1313
| Modality |Behavior/EEG |
14+
| Language | Chinese |
1415

1516

1617
## 1. Task Overview
@@ -51,7 +52,7 @@ The Monetary Incentive Delay (MID) Task is designed to assess reward processing
5152
| Feature | Description |
5253
|---------------------|-----------------------------------------------------------------------------|
5354
| Adaptive Duration | Target duration adjusts between 0.04 and 0.37 seconds |
54-
| Step Size | ±0.03 seconds |
55+
| Step Size | ±0.03 seconds |
5556
| Accuracy Target | 66% accuracy threshold |
5657
| Condition Specific | Tracks performance separately by condition (win/lose/neutral) |
5758
| Logging | Performance logs are printed to PsychoPy console |
@@ -62,9 +63,9 @@ The Monetary Incentive Delay (MID) Task is designed to assess reward processing
6263

6364
| Field | Meaning |
6465
|-------------|----------------------------|
65-
| subject_id | Unique participant number (101�99, 3 digits) |
66+
| subject_id | Unique participant number (101�99, 3 digits) |
6667
| subname | Participant name (pinyin) |
67-
| age | Participant age (5�0) |
68+
| age | Participant age (5�0) |
6869
| gender | Participant gender (Male/Female) |
6970

7071
### b. Window Settings
@@ -90,12 +91,12 @@ The Monetary Incentive Delay (MID) Task is designed to assess reward processing
9091
| win_target | circle | Black circle target |
9192
| lose_target | rect | Black square target |
9293
| neut_target | triangle | Black triangle target |
93-
| win_hit_feedback | textbox | “击�+10 分�(black text, SimHei) |
94-
| win_miss_feedback | textbox | “未击中 +0 分� |
95-
| lose_hit_feedback | textbox | “击�-0 分� |
96-
| lose_miss_feedback | textbox | “未击中 -10 分� |
97-
| neut_hit_feedback | textbox | “击�+0 分� |
98-
| neut_miss_feedback | textbox | “未击中 -0 分� |
94+
| win_hit_feedback | textbox | “击�+10 分�(black text, SimHei) |
95+
| win_miss_feedback | textbox | “未击中 +0 分� |
96+
| lose_hit_feedback | textbox | “击�-0 分� |
97+
| lose_miss_feedback | textbox | “未击中 -10 分� |
98+
| neut_hit_feedback | textbox | “击�+0 分� |
99+
| neut_miss_feedback | textbox | “未击中 -0 分� |
99100
| instruction_text | textbox | Multi-line Chinese instructions (includes scoring rules) |
100101
| block_break | text | Inter-block message showing block, accuracy, and score |
101102
| good_bye | text | End screen showing final score |
@@ -105,9 +106,9 @@ The Monetary Incentive Delay (MID) Task is designed to assess reward processing
105106
| Phase | Duration (s) |
106107
|------------------------|--------------------|
107108
| cue | 0.3 |
108-
| anticipation | random 1.0�.2 |
109-
| target | adaptive (0.04�.37)|
110-
| prefeedback fixation | random 0.6�.8 |
109+
| anticipation | random 1.0�.2 |
110+
| target | adaptive (0.04�.37)|
111+
| prefeedback fixation | random 0.6�.8 |
111112
| feedback | 1.0 |
112113

113114
### e. Triggers
@@ -152,13 +153,13 @@ The Monetary Incentive Delay (MID) Task is designed to assess reward processing
152153
| target_accuracy | 0.66 |
153154
| condition_specific | true |
154155

155-
## 4. Methods
156+
## 4. Methods (for academic publication)
156157

157-
Participants performed a computerized Monetary Incentive Delay (MID) task to assess motivational processing under different reward contingencies. The task consisted of **3 blocks**, each comprising **60 trials**, totaling **180 trials**. Each trial began with a cue, a colored shape (circle, square, or triangle), signaling whether the trial was a reward, punishment, or neutral condition. Following a variable anticipation phase (1.0�.2 s), a black target appeared briefly. Participants were instructed to press the spacebar as quickly as possible upon target onset.
158+
Participants performed a computerized Monetary Incentive Delay (MID) task to assess motivational processing under different reward contingencies. The task consisted of **3 blocks**, each comprising **60 trials**, totaling **180 trials**. Each trial began with a cue, a colored shape (circle, square, or triangle), signaling whether the trial was a reward, punishment, or neutral condition. Following a variable anticipation phase (1.0�.2 s), a black target appeared briefly. Participants were instructed to press the spacebar as quickly as possible upon target onset.
158159

159-
The target's presentation duration was controlled by an adaptive algorithm that updated the duration after each trial based on performance. Initial target duration was set to 0.2 s and was adjusted between 0.04 and 0.37 s using ±0.03 s increments to stabilize performance at a target accuracy of 66%, tracked separately by condition.
160+
The target's presentation duration was controlled by an adaptive algorithm that updated the duration after each trial based on performance. Initial target duration was set to 0.2 s and was adjusted between 0.04 and 0.37 s using ±0.03 s increments to stabilize performance at a target accuracy of 66%, tracked separately by condition.
160161

161-
Feedback followed the target phase, based on whether participants responded within the target duration. “击中�(“hit� and “未击中�(“miss� messages were shown with point gain/loss specific to the cue type. After each block, a break screen summarized the participant's accuracy and cumulative score. The task began with audio-visual instructions and ended with a final message displaying total score.
162+
Feedback followed the target phase, based on whether participants responded within the target duration. “击中�(“hit� and “未击中�(“miss� messages were shown with point gain/loss specific to the cue type. After each block, a break screen summarized the participant's accuracy and cumulative score. The task began with audio-visual instructions and ended with a final message displaying total score.
162163

163164
## 5. References
164165
The task is originally developed by Knutson 2000:

psyflow/templates/cookiecutter-psyflow/{{cookiecutter.project_name}}/src/run_trial.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ def run_trial(
4949
onset_trigger=settings.triggers.get(f"{condition}_cue_onset"),
5050
).to_dict(trial_data)
5151

52-
# --- Anticipation ---
52+
# phase: pre_response_fixation
5353
anti = make_unit(unit_label="anticipation").add_stim(stim_bank.get("fixation"))
5454
set_trial_context(
5555
anti,
5656
trial_id=trial_id,
57-
phase="anticipation",
57+
phase="pre_response_fixation",
5858
deadline_s=_deadline_s(settings.anticipation_duration),
5959
valid_keys=list(settings.key_list),
6060
block_id=block_id,
6161
condition_id=str(condition),
62-
task_factors={"condition": str(condition), "stage": "anticipation", "block_idx": block_idx},
62+
task_factors={"condition": str(condition), "stage": "pre_response_fixation", "block_idx": block_idx},
6363
stim_id="fixation",
6464
)
6565
anti.capture_response(
@@ -73,20 +73,20 @@ def run_trial(
7373
anti.set_state(early_response=early_response)
7474
anti.to_dict(trial_data)
7575

76-
# --- Target ---
76+
# phase: response_window
7777
duration = controller.get_duration(condition)
7878
target = make_unit(unit_label="target").add_stim(stim_bank.get(f"{condition}_target"))
7979
set_trial_context(
8080
target,
8181
trial_id=trial_id,
82-
phase="target",
82+
phase="response_window",
8383
deadline_s=_deadline_s(duration),
8484
valid_keys=list(settings.key_list),
8585
block_id=block_id,
8686
condition_id=str(condition),
8787
task_factors={
8888
"condition": str(condition),
89-
"stage": "target",
89+
"stage": "response_window",
9090
"block_idx": block_idx,
9191
"target_duration_s": float(duration),
9292
},

psyflow/validate.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,22 @@ def _check_responder_context(task_dir: Path, cfg: dict[str, Any]) -> ContractRes
906906
f"expected one of {sorted(required_phase_any)}, got {sorted(phase_values) or 'none'}"
907907
)
908908

909+
required_phase_tokens_any = [
910+
str(x or "").strip().lower()
911+
for x in list(cfg.get("required_context_phase_tokens_any") or [])
912+
if str(x or "").strip()
913+
]
914+
if required_phase_tokens_any:
915+
has_required_token = any(
916+
any(tok in phase for tok in required_phase_tokens_any)
917+
for phase in phase_values
918+
)
919+
if not has_required_token:
920+
fails.append(
921+
"Missing required context phase token (any): "
922+
f"expected one of {sorted(required_phase_tokens_any)}, got {sorted(phase_values) or 'none'}"
923+
)
924+
909925
forbidden_phase = {
910926
str(x or "").strip().lower()
911927
for x in list(cfg.get("forbidden_context_phase_values") or [])
@@ -945,6 +961,22 @@ def _check_responder_context(task_dir: Path, cfg: dict[str, Any]) -> ContractRes
945961
f"{sorted(recommended_phase_any)}; got {sorted(phase_values) or 'none'}"
946962
)
947963

964+
recommended_phase_tokens_any = [
965+
str(x or "").strip().lower()
966+
for x in list(cfg.get("recommended_context_phase_tokens_any") or [])
967+
if str(x or "").strip()
968+
]
969+
if recommended_phase_tokens_any:
970+
has_recommended_token = any(
971+
any(tok in phase for tok in recommended_phase_tokens_any)
972+
for phase in phase_values
973+
)
974+
if not has_recommended_token:
975+
warns.append(
976+
"No recommended phase tokens found (any): "
977+
f"{sorted(recommended_phase_tokens_any)}; got {sorted(phase_values) or 'none'}"
978+
)
979+
948980
required_stage_any = [str(x).strip().lower() for x in list(cfg.get("required_stage_tokens_any") or []) if str(x).strip()]
949981
if required_stage_any and not any(tok in low for tok in required_stage_any):
950982
fails.append(f"Missing required trial stage token (any): {required_stage_any}")

0 commit comments

Comments
 (0)