Fix statistics calculation for imported Likert scale responses and support text rating answers

This commit is contained in:
pongpon pitisuk
2026-09-12 16:11:27 +07:00
parent fb5b4b4c95
commit a68bddb5c0
4 changed files with 217 additions and 39 deletions
+69 -8
View File
@@ -41,7 +41,7 @@ if (!$handle) {
jsonResponse(['error' => 'ไม่สามารถเปิดไฟล์ CSV ได้'], 500);
}
// Check UTF-8 BOM or UTF-16
// Check UTF-8 BOM
$bom = fread($handle, 3);
if ($bom !== "\xEF\xBB\xBF") {
rewind($handle);
@@ -61,7 +61,7 @@ $cleanHeaders = array_map(function($h) {
// Fetch all questions for this survey
$stmt = $db->prepare("
SELECT q.id, q.question_text, q.question_type
SELECT q.id, q.question_text, q.question_type, q.options
FROM survey_questions q
JOIN survey_sections s ON q.section_id = s.id
WHERE s.survey_id = ?
@@ -75,16 +75,57 @@ if (empty($questions)) {
jsonResponse(['error' => 'ไม่พบข้อคำถามในแบบสอบถามนี้'], 400);
}
// Map CSV Column Index -> question_id
$colToQuestionMap = [];
// Helper: normalize text for comparison
function normalizeText($str) {
$str = mb_strtolower($str, 'UTF-8');
// Remove leading numbers or prefix like "1. ", "ข้อ 1. "
$str = preg_replace('/^[\d๑-๙]+[\.\)\s\-:\x{00A0}]*/u', '', $str);
$str = preg_replace('/^[\d๑-๙\.]*[\.\)\s\-:\x{00A0}]*/u', '', $str);
return trim($str);
}
// 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;
}
// Map CSV Column Index -> question_id
$colToQuestionMap = [];
$questionsById = [];
foreach ($questions as $q) {
$questionsById[$q['id']] = $q;
}
foreach ($cleanHeaders as $colIdx => $headerText) {
if (empty($headerText) || strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') {
continue;
@@ -92,11 +133,22 @@ foreach ($cleanHeaders as $colIdx => $headerText) {
$normHeader = normalizeText($headerText);
// Extract inside bracket if Google Forms grid header e.g. "Heading [Question]"
$bracketText = '';
if (preg_match('/\[(.*?)\]/', $headerText, $m)) {
$bracketText = normalizeText($m[1]);
}
// Try exact or normalized matching against survey questions
foreach ($questions as $q) {
$normQText = normalizeText($q['question_text']);
if ($normHeader === $normQText || strpos($normHeader, $normQText) !== false || strpos($normQText, $normHeader) !== false) {
if (
$normHeader === $normQText ||
($bracketText !== '' && ($bracketText === $normQText || strpos($bracketText, $normQText) !== false || strpos($normQText, $bracketText) !== false)) ||
strpos($normHeader, $normQText) !== false ||
strpos($normQText, $normHeader) !== false
) {
$colToQuestionMap[$colIdx] = $q['id'];
break;
}
@@ -104,7 +156,7 @@ foreach ($cleanHeaders as $colIdx => $headerText) {
}
// Fallback: If column mapping count is low, map columns sequentially to questions
if (count($colToQuestionMap) === 0) {
if (count($colToQuestionMap) < count($questions) / 2) {
$qIdx = 0;
foreach ($cleanHeaders as $colIdx => $headerText) {
if (strtolower($headerText) === 'timestamp' || strtolower($headerText) === 'ประทับเวลา') continue;
@@ -145,6 +197,15 @@ try {
foreach ($colToQuestionMap as $colIdx => $qId) {
$val = isset($row[$colIdx]) ? trim($row[$colIdx]) : '';
if ($val !== '') {
$targetQ = $questionsById[$qId] ?? null;
// Normalize rating score if it's a 5-point rating scale question
if ($targetQ && $targetQ['question_type'] === 'radio' && empty($targetQ['options'])) {
$score = parseRatingScore($val);
if ($score !== null) {
$val = (string)$score;
}
}
$stmtAns = $db->prepare("INSERT INTO response_answers (response_id, question_id, answer_value) VALUES (?, ?, ?)");
$stmtAns->execute([$responseId, $qId, $val]);
}