Files
survey/api/statistics.php
T

309 lines
11 KiB
PHP

<?php
/**
* Statistics API
* GET ?action=summary&survey_id=X — Summary statistics
* GET ?action=detail&survey_id=X — Detailed per-question stats
*/
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
requireLogin();
$action = $_GET['action'] ?? 'summary';
$surveyId = (int)($_GET['survey_id'] ?? 0);
if (!$surveyId) jsonResponse(['error' => 'Missing survey_id'], 400);
// Check access
$db = getDB();
if (!isAdmin()) {
$stmt = $db->prepare("SELECT user_id FROM surveys WHERE id = ?");
$stmt->execute([$surveyId]);
$survey = $stmt->fetch();
if (!$survey || $survey['user_id'] != $_SESSION['user_id']) {
jsonResponse(['error' => 'ไม่มีสิทธิ์'], 403);
}
}
/**
* Helper to parse Likert rating values from numbers or Thai text
*/
function parseRatingScore($str) {
$trimmed = trim((string)$str);
if ($trimmed === '') return null;
if (is_numeric($trimmed)) {
$num = (int)$trimmed;
return ($num >= 1 && $num <= 5) ? $num : null;
}
if (preg_match('/^([1-5])\s*[\.\:\-\(\s]/u', $trimmed, $m)) {
return (int)$m[1];
}
if (mb_strpos($trimmed, 'มากที่สุด') !== false || mb_strpos($trimmed, 'ดีมาก') !== false || mb_strpos($trimmed, 'ยอดเยี่ยม') !== false || mb_strpos($trimmed, 'ดีเยี่ยม') !== false || mb_strpos($trimmed, 'เห็นด้วยอย่างยิ่ง') !== false) {
return 5;
}
if (mb_strpos($trimmed, 'น้อยที่สุด') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วยอย่างยิ่ง') !== false) {
return 1;
}
if (mb_strpos($trimmed, 'มาก') !== false || mb_strpos($trimmed, 'ดี') !== false || mb_strpos($trimmed, 'เห็นด้วย') !== false) {
return 4;
}
if (mb_strpos($trimmed, 'ปานกลาง') !== false || mb_strpos($trimmed, 'พอใช้') !== false || mb_strpos($trimmed, 'ไม่แน่ใจ') !== false) {
return 3;
}
if (mb_strpos($trimmed, 'น้อย') !== false || mb_strpos($trimmed, 'ควรปรับปรุง') !== false || mb_strpos($trimmed, 'ต้องปรับปรุง') !== false || mb_strpos($trimmed, 'ไม่เห็นด้วย') !== false) {
return 2;
}
if (mb_strpos($trimmed, 'ปรับปรุง') !== false) {
return 1;
}
return null;
}
switch ($action) {
case 'summary':
getSummary($surveyId);
break;
case 'detail':
getDetail($surveyId);
break;
default:
jsonResponse(['error' => 'Invalid action'], 400);
}
function getSummary($surveyId) {
$db = getDB();
// Total responses
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
$stmt->execute([$surveyId]);
$total = $stmt->fetch()['total'];
// Get survey info
$stmt = $db->prepare("SELECT title, status, created_at FROM surveys WHERE id = ?");
$stmt->execute([$surveyId]);
$surveyInfo = $stmt->fetch();
// Fetch all answers for rating questions in this survey and compute mean/sd
$stmt = $db->prepare("
SELECT ra.answer_value
FROM response_answers ra
JOIN survey_questions sq ON ra.question_id = sq.id
JOIN survey_sections ss ON sq.section_id = ss.id
WHERE ss.survey_id = ? AND sq.question_type = 'radio' AND sq.options IS NULL
");
$stmt->execute([$surveyId]);
$rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
$scores = [];
foreach ($rows as $ans) {
$sc = parseRatingScore($ans);
if ($sc !== null) {
$scores[] = $sc;
}
}
$overallMean = 0;
$overallSd = 0;
if (count($scores) > 0) {
$overallMean = array_sum($scores) / count($scores);
if (count($scores) > 1) {
$sumSq = 0;
foreach ($scores as $s) {
$sumSq += pow($s - $overallMean, 2);
}
$overallSd = sqrt($sumSq / (count($scores) - 1));
}
}
jsonResponse([
'survey' => $surveyInfo,
'total_responses' => (int)$total,
'overall_mean' => round($overallMean, 2),
'overall_sd' => round($overallSd, 2)
]);
}
function getDetail($surveyId) {
$db = getDB();
// Total responses
$stmt = $db->prepare("SELECT COUNT(*) as total FROM survey_responses WHERE survey_id = ?");
$stmt->execute([$surveyId]);
$totalResponses = (int)$stmt->fetch()['total'];
// Get sections with questions
$stmt = $db->prepare("SELECT * FROM survey_sections WHERE survey_id = ? ORDER BY section_number");
$stmt->execute([$surveyId]);
$sections = $stmt->fetchAll();
$result = [];
foreach ($sections as $section) {
$qStmt = $db->prepare("SELECT * FROM survey_questions WHERE section_id = ? ORDER BY order_num");
$qStmt->execute([$section['id']]);
$questions = $qStmt->fetchAll();
$sectionStats = [
'section_number' => $section['section_number'],
'title' => $section['title'],
'headings' => [],
'questions' => []
];
// Load headings for this section
$headingStmt = $db->prepare("SELECT * FROM survey_headings WHERE section_id = ? ORDER BY order_num");
$headingStmt->execute([$section['id']]);
$headings = $headingStmt->fetchAll();
foreach ($questions as $q) {
$qStat = [
'id' => $q['id'],
'heading_id' => $q['heading_id'] ?? null,
'question_text' => $q['question_text'],
'question_type' => $q['question_type']
];
if ($q['question_type'] === 'radio' && empty($q['options'])) {
// Rating scale question (1-5)
$aStmt = $db->prepare("
SELECT answer_value, COUNT(*) as cnt
FROM response_answers
WHERE question_id = ? AND answer_value != ''
GROUP BY answer_value
ORDER BY answer_value
");
$aStmt->execute([$q['id']]);
$freqData = $aStmt->fetchAll();
$frequencies = [];
$values = [];
$totalAnswers = 0;
for ($i = 1; $i <= 5; $i++) {
$frequencies[$i] = ['value' => $i, 'count' => 0, 'percentage' => 0];
}
foreach ($freqData as $fd) {
$rawVal = trim($fd['answer_value']);
$cnt = (int)$fd['cnt'];
$val = parseRatingScore($rawVal);
if ($val >= 1 && $val <= 5) {
$frequencies[$val]['count'] += $cnt;
$totalAnswers += $cnt;
for ($j = 0; $j < $cnt; $j++) {
$values[] = $val;
}
}
}
// Calculate percentages
foreach ($frequencies as &$f) {
$f['percentage'] = $totalAnswers > 0 ? round(($f['count'] / $totalAnswers) * 100, 1) : 0;
}
unset($f);
// Calculate mean
$mean = count($values) > 0 ? array_sum($values) / count($values) : 0;
// Calculate SD
$sd = 0;
if (count($values) > 1) {
$sumSquaredDiff = 0;
foreach ($values as $v) {
$sumSquaredDiff += pow($v - $mean, 2);
}
$sd = sqrt($sumSquaredDiff / (count($values) - 1));
}
// Interpretation
$interpretation = '';
if ($mean >= 4.51) $interpretation = 'มากที่สุด';
elseif ($mean >= 3.51) $interpretation = 'มาก';
elseif ($mean >= 2.51) $interpretation = 'ปานกลาง';
elseif ($mean >= 1.51) $interpretation = 'น้อย';
else $interpretation = 'น้อยที่สุด';
$qStat['mean'] = round($mean, 2);
$qStat['sd'] = round($sd, 2);
$qStat['interpretation'] = $interpretation;
$qStat['frequencies'] = array_values($frequencies);
$qStat['total_answers'] = $totalAnswers;
} elseif ($q['question_type'] === 'text') {
// Collect text responses
$aStmt = $db->prepare("
SELECT answer_value FROM response_answers
WHERE question_id = ? AND answer_value != ''
ORDER BY id DESC
");
$aStmt->execute([$q['id']]);
$qStat['text_responses'] = array_column($aStmt->fetchAll(), 'answer_value');
$qStat['total_answers'] = count($qStat['text_responses']);
} elseif (in_array($q['question_type'], ['dropdown', 'checkbox', 'radio'])) {
// Frequency for dropdown/checkbox/custom radio
$aStmt = $db->prepare("
SELECT answer_value, COUNT(*) as cnt
FROM response_answers
WHERE question_id = ? AND answer_value != ''
GROUP BY answer_value
ORDER BY cnt DESC
");
$aStmt->execute([$q['id']]);
$freqData = $aStmt->fetchAll();
$totalAnswers = array_sum(array_column($freqData, 'cnt'));
$qStat['frequencies'] = array_map(function($fd) use ($totalAnswers) {
return [
'value' => $fd['answer_value'],
'count' => (int)$fd['cnt'],
'percentage' => $totalAnswers > 0 ? round(((int)$fd['cnt'] / $totalAnswers) * 100, 1) : 0
];
}, $freqData);
$qStat['total_answers'] = $totalAnswers;
}
$sectionStats['questions'][] = $qStat;
}
// Build heading stats groups
$headingStats = [];
foreach ($headings as $h) {
$hStat = [
'id' => $h['id'],
'title' => $h['title'],
'description' => $h['description'],
'questions' => []
];
foreach ($sectionStats['questions'] as $qs) {
if (($qs['heading_id'] ?? null) == $h['id']) {
$hStat['questions'][] = $qs;
}
}
$headingStats[] = $hStat;
}
$sectionStats['headings'] = $headingStats;
// Filter loose questions (no heading)
$sectionStats['questions'] = array_values(array_filter($sectionStats['questions'], function($qs) {
return empty($qs['heading_id']);
}));
$result[] = $sectionStats;
}
jsonResponse([
'total_responses' => $totalResponses,
'sections' => $result
]);
}