# =============================================================================
# HSCI 841 Qualitative Research Methods and Analysis in Public Health  -  Lesson 11: Analytic Induction, QCA and Decision Models
# Answer key for the in-lesson R activities
# Data file(s): term projects/HSCI_841/qca_loneliness.csv
#               (download from the lesson page; paths are relative to the course
#                repository root, so set your working directory there first)
# Packages: QCA, SetMethods, tidyverse, DiagrammeR, DiagrammeRsvg, rsvg
#           (install once with install.packages(c("QCA","SetMethods","venn",
#            "DiagrammeR","DiagrammeRsvg","rsvg")))
# Reproduces every code block in the lesson: truth table, the three minimizations,
# the necessity analysis, and the decision tree.
# =============================================================================

suppressPackageStartupMessages({
  library(QCA)
  library(SetMethods)
  library(tidyverse)
})

dir.create("figures", showWarnings = FALSE)   # the decision tree is written here

# ==== Section 1: Install and load the QCA toolchain ====

# install.packages(c("QCA", "SetMethods", "venn"))   # one-time; already installed here

# The 20 loneliness transcripts, 4 conditions, 1 outcome
# B = bereaved, L = lives alone, I = immigrant, C = caregiving role
# Y = existential loneliness
# NOTE (answer key): the page used to hold these 20 rows in a tribble. They now
# live in qca_loneliness.csv, which generator 841_05 builds from the transcript
# headers, participant_metadata.csv and the week 8 code
# loneliness-as-existential-fact, so the coding is auditable.
loneliness_qca <- read.csv("term projects/HSCI_841/qca_loneliness.csv",
                           stringsAsFactors = FALSE)
print(loneliness_qca)

# Move case ID to row names (required by QCA package)
df <- as.data.frame(loneliness_qca)
rownames(df) <- df$case
df$case <- NULL

cat("\nCondition and outcome totals (out of 20 cases):\n")
print(colSums(df))

# ==== Section 2: Build the truth table ====

# Truth table for sufficient conditions for Y=1
tt <- truthTable(
  df,
  outcome  = "Y",
  conditions = c("B", "L", "I", "C"),
  incl.cut = 0.8,           # consistency threshold
  n.cut    = 1,             # minimum cases per row
  show.cases = TRUE,
  sort.by  = "OUT, n"
)
print(tt)

# Read the truth table: each row is one configuration of B,L,I,C
# OUT = 1 means the configuration produces Y at consistency >= 0.8
# OUT = 0 means it does not
# OUT = ? means the row has fewer than n.cut cases (logical remainder)
# Five configurations reach OUT = 1, all of them with consistency 1.00. The row
# ~B*L*~I*~C (six cases: P01, P02, P09, P11, P14, P19) is contradictory:
# four of the six show the outcome, consistency 0.667, so it is coded OUT = 0.

# ==== Section 3: Minimize the truth table to a sufficiency formula ====

# Conservative solution (no remainders)
sol_cons <- minimize(tt, details = TRUE)
print(sol_cons)

# Parsimonious solution (all remainders available)
sol_pars <- minimize(tt, details = TRUE, include = "?")
print(sol_pars)

# Intermediate solution (remainders consistent with theory)
sol_int <- minimize(tt, details = TRUE, include = "?",
                    dir.exp = c("B" = 1, "L" = 1, "I" = 1, "C" = 0))
print(sol_int)

# The output gives the minimized formula plus consistency and coverage
# for the overall solution and for each path.
cat("\nParsimonious solution: B + L*I -> Y, consistency 1.000, coverage 0.455.\n",
    "Bereavement is sufficient on its own (P05, P15, P17, P20); living alone as an\n",
    "immigrant is the second path (P18). The six uncovered Y = 1 cases sit in the\n",
    "contradictory row, which is where the next round of case reading belongs.\n", sep = "")

# ==== Section 4: Necessity analysis ====

# Necessity for Y=1: which conditions are present in nearly every Y=1 case?
nec <- superSubset(df, outcome = "Y",
                   conditions = c("B", "L", "I", "C"),
                   incl.cut = 0.9,
                   cov.cut  = 0.5)
print(nec)

# Read: which single conditions, or simple combinations, are necessary?
# A condition is necessary if consistency >= 0.9
cat("\n~C (no caregiving role) is necessary at inclN = 0.909 with coverage 0.667:\n",
    "10 of the 11 existential-loneliness cases are non-caregivers. Jacob (P17) is\n",
    "the single case that contradicts it, which makes him the case to read next.\n", sep = "")

# ==== Section 5: Visualize the decision tree with DiagrammeR ====

# install.packages("DiagrammeR")   # one-time; already installed here
library(DiagrammeR)

tree <- "
digraph loneliness_helpseek {
  graph [rankdir = TB, fontname = 'Open Sans']
  node  [shape = box, style = filled, fillcolor = '#E6F3F0', fontname = 'Open Sans']

  Q1 [label = 'Close/safe relationship?']
  Q2 [label = 'Tried before, felt rebuffed?']
  Q3 [label = 'Stigma-permissive frame?']
  Q4 [label = 'Language for loneliness?']

  W [label = 'Withdraw', fillcolor = '#FDEAEF', shape = oval]
  R [label = 'Reach out', fillcolor = '#D1F0EA', shape = oval]
  H [label = 'Reach out (hedged)', fillcolor = '#FFF8E1', shape = oval]

  Q1 -> W [label = 'No']
  Q1 -> Q2 [label = 'Yes']
  Q2 -> W [label = 'Yes']
  Q2 -> Q3 [label = 'No']
  Q3 -> R [label = 'Yes']
  Q3 -> Q4 [label = 'No']
  Q4 -> H [label = 'Yes']
  Q4 -> W [label = 'No']
}
"

# NOTE (answer key): grViz() draws the tree in the RStudio Viewer. Under Rscript
# there is no viewer, so the widget is only assigned here and the PNG below is
# what you look at.
tree_widget <- grViz(tree)

# Export to PNG for inclusion in the capstone memo
library(DiagrammeRsvg)
library(rsvg)
svg <- export_svg(tree_widget)
rsvg_png(charToRaw(svg), file = "figures/loneliness_decision_tree.png", width = 1200)
cat("\nDecision tree written to figures/loneliness_decision_tree.png\n")

cat("\nDone: Lesson 11 answer key completed.\n")
