# Building a survey instrument: questions, plan, and model

An `sframe` instrument records the research design, not only the questions. This vignette builds a slice of the published Thailand digital marketing study (Sharafuddin, Madhavan, and Wangtueai 2024) with the constructors shown one at a time, then adds the analysis plan and a measurement model. The full study is assembled in the worked-study vignette.

## Choice sets

Choice sets define reusable response options. Items reference them through `choice_set`.

    likert5 <- sf_choices(
      "likert5",
      values = 1:5,
      labels = c("Strongly disagree", "Disagree",
                 "Neither agree nor disagree", "Agree", "Strongly agree")
    )

    visitor <- sf_choices("visitor", c("first_time", "repeat"),
                          c("First-time visitor", "Repeat visitor"))

## Items and item types

Items are response variables unless their type is `section_break` or `text_block`. Required items carry `required = TRUE`. Item IDs should stay stable, because response columns reuse them and the analysis plan refers to them. These items measure perceived value (DMPV) and tourist satisfaction (TS).

    intro <- sf_item(
      "intro", "About your trip",
      type          = "section_break",
      section_intro = "Please rate your experience of digital booking and your visit."
    )

    dmpv_1 <- sf_item("dmpv_1", "The destination's online content was trustworthy.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
    dmpv_2 <- sf_item("dmpv_2", "The online information was consistent across platforms.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
    dmpv_3 <- sf_item("dmpv_3", "The digital channels offered good value for money.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")
    dmpv_4 <- sf_item("dmpv_4", "I was aware of the destination through digital channels.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "DMPV")

    ts_1 <- sf_item("ts_1", "The trip met my expectations.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")
    ts_2 <- sf_item("ts_2", "My overall travel experience was satisfying.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")
    ts_3 <- sf_item("ts_3", "I felt comfortable at the destination.",
      type = "likert", required = TRUE, choice_set = "likert5", scale_id = "TS")

    visitor_type <- sf_item("visitor_type", "I am a",
      type = "single_choice", required = TRUE, choice_set = "visitor")

    attention <- sf_item("attention", "For quality control, please select Agree.",
      type = "single_choice", required = TRUE, choice_set = "likert5")

## Scales, reverse coding, and minimum valid items

A scale records item membership, the scoring method, any reverse-coded items, and the minimum number of valid items needed to compute a score.

    dmpv <- sf_scale("DMPV", "Digital marketing perceived value",
      items = c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4"), method = "mean", min_valid = 3)

    ts <- sf_scale("TS", "Tourist satisfaction",
      items = c("ts_1", "ts_2", "ts_3"), method = "mean", min_valid = 2)

## Attention checks

Checks keep the quality-control logic with the instrument.

    attention_check <- sf_check(
      id = "attention_agree", item_id = "attention", type = "attention",
      pass_values = 4, fail_action = "flag", label = "Instructional attention check"
    )

## Branching

A branch shows or hides an item based on an earlier answer. This one is a placeholder for a repeat-visitor follow-up.

    repeat_branch <- sf_branch(
      item_id    = "ts_3",
      depends_on = "visitor_type",
      operator   = "==",
      value      = "repeat",
      action     = "show"
    )

## The analysis plan

Each block binds a research question to a technique and to the variables that fill each role. A reliability check expects `items`. A group comparison expects a `group` and an `outcome`.

    analysis_plan <- list(
      list(id = "M1", research_question = "Is perceived value internally consistent?",
           family = "measurement", method = "reliability_alpha",
           roles = list(items = c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4"))),
      list(id = "RQ1", research_question = "Do repeat visitors report higher satisfaction?",
           family = "group_comparison", method = "mann_whitney",
           roles = list(group = "visitor_type", outcome = "TS"),
           options = list(alpha = 0.05))
    )

## Assemble the instrument

`sf_instrument()` takes the questions through `components` and the research design through `analysis_plan` and `models`. The model is added with `add_model()`, which checks each indicator against the instrument items.

    instr <- sf_instrument(
      title       = "Digital marketing study (teaching slice)",
      version     = "1.0.0",
      description = "A slice of the published Thailand study for teaching the constructors.",
      authors     = "Research team",
      languages   = "en",
      components  = list(
        likert5, visitor,
        intro, dmpv_1, dmpv_2, dmpv_3, dmpv_4, ts_1, ts_2, ts_3,
        visitor_type, attention, dmpv, ts, attention_check, repeat_branch
      ),
      analysis_plan = analysis_plan
    )

    ts_model <- sf_model(
      id    = "ts_cfa",
      label = "Satisfaction measurement model",
      type  = "cfa",
      constructs = list(
        sf_construct("DMPV", "Perceived value", c("dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4")),
        sf_construct("TS",   "Tourist satisfaction", c("ts_1", "ts_2", "ts_3"))
      )
    )

    instr <- add_model(instr, ts_model)

## Validate the instrument

`validate_sframe()` returns a diagnostic. Print it to read the result, and use the accessors to get at the parts.

    validate_sframe(instr, strict = FALSE)
    #> <sframe validation>
    #>   Instrument:  Digital marketing study (teaching slice) (1.0.0)
    #>   Status:      valid
    #>   Checks:      18 run, 0 with problems

    validation <- validate_sframe(instr, strict = FALSE)
    sf_is_valid(validation)
    #> [1] TRUE
    sf_problems(validation)
    #> character(0)

In production, strict validation stops with a structured error when anything is wrong. When it passes, `as_sframe()` recovers the instrument with its validated stamp set.

    instr <- as_sframe(validate_sframe(instr))
    sf_meta(instr)$validated
    #> [1] TRUE

## Write and read `.sframe` files

The `.sframe` file is the portable instrument file. It keeps the validated metadata, the analysis plan, and the model in one place.

    path <- tempfile(fileext = ".sframe")
    write_sframe(instr, path, overwrite = TRUE)

    loaded <- read_sframe(path)
    inherits(loaded, "sframe")
    #> [1] TRUE
    sf_meta(loaded)$title
    #> [1] "Digital marketing study (teaching slice)"
    length(sf_plan(loaded))
    #> [1] 2
    length(loaded$models)
    #> [1] 1

## GUI option

The same instrument, plan, and model can be authored in SurveyBuilder, which saves a `.sframe` file for use in R.

    launch_builder()
    launch_builder_demo(open = FALSE)