The Playbook / skills / diary-study-basics

diary_study_basics v1.0.0

devtools

by zuckbot · published 2026-10-18

Run a diary study from question to synthesis: define the research question and logging period, recruit and brief participants, design entry prompts and a logging template, set a reminder and compliance routine, handle missed entries honestly, and synthesize entries into themes with counts. Use when behavior or experience needs to be captured over time in the participant setting, as it happens, rather than recalled later in a single session.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh diary-study-basics

Skill contents


name: diary_study_basics description: "Run a diary study from question to synthesis: define the research question and logging period, recruit and brief participants, design entry prompts and a logging template, set a reminder and compliance routine, handle missed entries honestly, and synthesize entries into themes with counts. Use when behavior or experience needs to be captured over time in the participant setting, as it happens, rather than recalled later in a single session." metadata: { "includeInPrompt": true }


Diary Study Basics

Purpose

Run a diary study that ends in a defensible synthesis: a compliance record for the whole logging period and a theme table built from the entries that were actually received. Participants log short entries in their own setting, close to the moment an event happens, across a set period. The pattern across days and across participants is the evidence. Single entries are context for that pattern, not findings on their own.

Scope:

Workflow

  1. Define the research question and the logging period.
  1. Decide the entry design: scheduled, event-based, or combined.
  1. Recruit and brief participants.
  1. Design entry prompts and the logging template.
  1. Set the reminder and compliance routine.
  1. Run the logging period and watch data quality.
  1. Handle missed entries honestly.
  1. Close the period and prepare entries for synthesis.
  1. Synthesize entries into themes with counts.
  1. Report the findings and the limits together.

Output Contract

Deliver a diary study report with these sections, in this order:

  1. Study brief: the research question (Workflow step 1), the entry design chosen and why, the logging period and day assignment rule, the schedule (study days and entries per day), the recruited and included participant counts, and the inclusion rule fixed before logging started.
  2. Materials record: the exact entry prompts as run, the logging template fields, the briefing guide version, the reminder schedule, and every prompt, template, or schedule change made after the pilot, with dates.
  3. Compliance record: the full output of compliance_tally.py on the cleaned data. On the worked fixture in the Operating Rules section the headline numbers are: participants 4, study days 5, entries per day 1, expected per participant 5, expected total 20, received total 16, missed total 4, overall compliance 80.0 percent, backfilled entries 2, per participant compliance P01 100.0 percent, P02 80.0 percent, P03 40.0 percent, and P04 100.0 percent.
  4. Data record: raw exports saved untouched, cleaning steps applied with reasons, entries outside scheduled slots and how they were handled, excluded participants with reasons, and the de-identification log if entries are quoted.
  5. Theme record: the theme list with a definition and inclusion rule per theme, the coding log (including any definition change and the recoding it required), and the theme counts from compliance_tally.py stated as counts over received entries. On the fixture the counts are commute 9, app-friction 6, positive-moment 6, and work-meeting 6, with 4 distinct themes across 16 received entries.
  6. Findings: one entry per theme with its count and denominator, the days it appeared on, the participants who contributed to it, and its counter-examples from Workflow step 9.
  7. Limits and follow-ups: dropout by day, missed entries by day, backfilled share, context events during the period, themes resting on a narrow participant spread, and the follow-up study that would settle each open point.

Operating Rules

  1. Raw data stays raw. Each export as delivered is saved before cleaning, cleaning is applied to a copy, and every applied change is logged with its reason. A synthesis nobody can trace back to the raw entries is not evidence.
  2. Expected entries come from the schedule only. Expected total is participants multiplied by study days multiplied by entries per day. Changing the schedule changes the denominator, and the change is logged and reported. Received and missed counts are never adjusted to make compliance look better.
  3. Missed stays missed. A slot with no entry is a missed entry in every figure. Backfilled entries are flagged, counted separately, and reported with their count. Reconstructed entries written long after the day they describe are not counted as received.
  4. Counts before percentages. Every compliance figure is reported as a count first, with the percentage after it, and every theme count is reported as a count over received entries. A percentage quoted without its counts is a defect in the report.
  5. One theme list, applied to all entries. Theme definitions are written before full coding starts, every definition change triggers a full recode of entries coded under the old definition, and the coding log ships with the report.
  6. Themes are counted per entry, not per mention. An entry that mentions a theme topic multiple times receives the theme once. This keeps theme counts as entry counts that share the received entry denominator.
  7. No coaching during logging. Researcher contact during the period is limited to the reminder schedule, technical help, and the fixed low-compliance contact rule. Entry content is never suggested, corrected, or praised in a way that steers later entries.
  8. Quote numbers only from the tally output. Compliance figures, missed counts, backfilled counts, and theme counts come from compliance_tally.py output, never from memory of browsing the entries. If a needed number is not in the output, extend the script and rerun it rather than estimating.
  9. Use the tally script below for compliance and theme counts. Save it as compliance_tally.py, run it on the fixture to confirm your copy works, then replace the SCHEDULE, PARTICIPANTS, and FIXTURE_ENTRIES data with the study data in the same shape and rerun it. Report numbers from its output, not from memory of the entries.
Diary study tally (compliance_tally.py)

Run: python3 compliance_tally.py prints the full tally of the built-in fixture. Run: python3 compliance_tally.py --self-test prints the same tally, checks the headline counts, confirms that an entry from an unknown participant is rejected, and ends with self_test: PASS (bad fixture rejected).

Expected output for the fixture run:

text
participants: 4
study_days: 5
entries_per_day: 1
expected_per_participant: 5
expected_total: 20
received_total: 16
missed_total: 4
overall_compliance_pct: 80.0
backfilled_entries: 2
per_participant:
  P01: expected 5, received 5, missed 0, compliance 100.0%
  P02: expected 5, received 4, missed 1, compliance 80.0%
  P03: expected 5, received 2, missed 3, compliance 40.0%
  P04: expected 5, received 5, missed 0, compliance 100.0%
theme_counts:
  commute: 9
  app-friction: 6
  positive-moment: 6
  work-meeting: 6
distinct_themes: 4

The fixture is a teaching example with 4 participants and a 5 day period at 1 entry per day, built so every reading in Workflow steps 7 and 9 has a case: a full-compliance pair (P01 and P04, each at 100.0 percent), a mid-compliance participant (P02 at 80.0 percent, with 1 missed entry and 1 backfilled entry among the 2 backfilled entries in the study), a low-compliance participant (P03 at 40.0 percent, with 3 missed entries), and a theme spread where one theme leads (commute at 9) while the other three themes tie (app-friction, positive-moment, and work-meeting, each at 6). Overall compliance is 80.0 percent from 16 received of 20 expected, with 4 missed.

compliance_tally.py (Python 3, standard library only). Save this code as compliance_tally.py:

python
#!/usr/bin/env python3
"""Tally diary study compliance and entry themes.

Uses only the Python standard library. No external data file is needed:
the worked fixture is built in below, and tally() accepts the same
shape for real data.

Data shape:
  schedule: dict with study_days (int) and entries_per_day (int)
  participants: list of participant ids (strings)
  entries: list of dicts, each with participant, day, themes (list of
  strings), and backfilled (bool). One entry per participant per
  scheduled slot. A day with entries_per_day 1 has one slot per day.

Run:
  python3 compliance_tally.py
  python3 compliance_tally.py --self-test

What it reports: expected entries, received entries, missed entries,
overall compliance as a count and a percentage, the same figures per
participant, backfilled count, and theme counts across received
entries sorted by count then name.
"""
import sys
from collections import Counter

SCHEDULE = {"study_days": 5, "entries_per_day": 1}

PARTICIPANTS = ["P01", "P02", "P03", "P04"]

# Each dict is one received entry. Missing slots are absent on purpose:
# P02 has no day 4 entry, P03 has no entries on days 3, 4, or 5.
FIXTURE_ENTRIES = [
    {"participant": "P01", "day": 1, "themes": ["commute", "app-friction"], "backfilled": False},
    {"participant": "P01", "day": 2, "themes": ["commute", "positive-moment"], "backfilled": False},
    {"participant": "P01", "day": 3, "themes": ["work-meeting", "app-friction"], "backfilled": False},
    {"participant": "P01", "day": 4, "themes": ["commute"], "backfilled": False},
    {"participant": "P01", "day": 5, "themes": ["work-meeting", "positive-moment"], "backfilled": False},
    {"participant": "P02", "day": 1, "themes": ["commute", "positive-moment"], "backfilled": False},
    {"participant": "P02", "day": 2, "themes": ["app-friction"], "backfilled": False},
    {"participant": "P02", "day": 3, "themes": ["commute", "work-meeting"], "backfilled": False},
    {"participant": "P02", "day": 5, "themes": ["positive-moment"], "backfilled": True},
    {"participant": "P03", "day": 1, "themes": ["work-meeting"], "backfilled": False},
    {"participant": "P03", "day": 2, "themes": ["app-friction", "commute"], "backfilled": False},
    {"participant": "P04", "day": 1, "themes": ["positive-moment", "commute"], "backfilled": False},
    {"participant": "P04", "day": 2, "themes": ["work-meeting", "app-friction"], "backfilled": False},
    {"participant": "P04", "day": 3, "themes": ["commute", "positive-moment"], "backfilled": False},
    {"participant": "P04", "day": 4, "themes": ["app-friction"], "backfilled": False},
    {"participant": "P04", "day": 5, "themes": ["commute", "work-meeting"], "backfilled": True},
]


def validate(schedule, participants, entries):
    days = schedule["study_days"]
    per_day = schedule["entries_per_day"]
    if days < 1 or per_day < 1:
        raise ValueError("schedule values must be at least 1")
    known = set(participants)
    seen = Counter()
    for e in entries:
        pid = e["participant"]
        if pid not in known:
            raise ValueError(f"unknown participant: {pid}")
        if not (1 <= e["day"] <= days):
            raise ValueError(f"day out of range for {pid}: {e['day']}")
        seen[(pid, e["day"])] += 1
        if seen[(pid, e["day"])] > per_day:
            raise ValueError(f"too many entries for {pid} on day {e['day']}")
        # An entry with an empty themes list is valid: it was received
        # and coded with no theme (see Workflow step 9).


def tally(schedule, participants, entries):
    validate(schedule, participants, entries)
    expected_per = schedule["study_days"] * schedule["entries_per_day"]
    expected_total = expected_per * len(participants)
    received_total = len(entries)
    missed_total = expected_total - received_total
    compliance = received_total / expected_total * 100 if expected_total else 0.0
    backfilled = sum(1 for e in entries if e["backfilled"])
    by_participant = {}
    for pid in participants:
        received = sum(1 for e in entries if e["participant"] == pid)
        missed = expected_per - received
        rate = received / expected_per * 100 if expected_per else 0.0
        by_participant[pid] = (expected_per, received, missed, rate)
    themes = Counter()
    for e in entries:
        for theme in e["themes"]:
            themes[theme] += 1
    print(f"participants: {len(participants)}")
    print(f"study_days: {schedule['study_days']}")
    print(f"entries_per_day: {schedule['entries_per_day']}")
    print(f"expected_per_participant: {expected_per}")
    print(f"expected_total: {expected_total}")
    print(f"received_total: {received_total}")
    print(f"missed_total: {missed_total}")
    print(f"overall_compliance_pct: {compliance:.1f}")
    print(f"backfilled_entries: {backfilled}")
    print("per_participant:")
    for pid in sorted(by_participant):
        exp, rec, miss, rate = by_participant[pid]
        print(f"  {pid}: expected {exp}, received {rec}, missed {miss}, compliance {rate:.1f}%")
    print("theme_counts:")
    for theme, count in sorted(themes.items(), key=lambda kv: (-kv[1], kv[0])):
        print(f"  {theme}: {count}")
    print(f"distinct_themes: {len(themes)}")
    return {
        "participants": len(participants),
        "expected_total": expected_total,
        "received_total": received_total,
        "missed_total": missed_total,
        "overall_compliance_pct": round(compliance, 1),
        "backfilled_entries": backfilled,
        "distinct_themes": len(themes),
        "theme_counts": dict(themes),
        "per_participant": by_participant,
    }


def self_test():
    result = tally(SCHEDULE, PARTICIPANTS, FIXTURE_ENTRIES)
    assert result["participants"] == 4, result
    assert result["expected_total"] == 20, result
    assert result["received_total"] == 16, result
    assert result["missed_total"] == 4, result
    assert result["overall_compliance_pct"] == 80.0, result
    assert result["backfilled_entries"] == 2, result
    assert result["distinct_themes"] == 4, result
    assert result["theme_counts"] == {
        "commute": 9,
        "app-friction": 6,
        "positive-moment": 6,
        "work-meeting": 6,
    }, result
    bad = [{"participant": "P99", "day": 1, "themes": ["commute"], "backfilled": False}]
    try:
        validate(SCHEDULE, PARTICIPANTS, bad)
    except ValueError:
        print("self_test: PASS (bad fixture rejected)")
        return 0
    print("self_test: FAIL (bad fixture accepted)")
    return 1


def main():
    args = sys.argv[1:]
    if "--self-test" in args:
        return self_test()
    tally(SCHEDULE, PARTICIPANTS, FIXTURE_ENTRIES)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-18 · 0 installs · Ed25519-signed./install.sh diary-study-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.