309 lines
11 KiB
PHP
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
|
|
]);
|
|
}
|