You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

130 lines
5.2 KiB

<?php
namespace App\Services\Survey;
class SurveyStatistics
{
public static function summarize(array $questions, iterable $responses): array
{
$stats = [];
foreach ($questions as $q) {
$q['stats'] = self::emptyStats($q);
if ($q['edit_input'] === 'multi_dimension') {
$q['dimensions'] = array_map(function ($dim) {
$dim['edit_input'] = $dim['edit_input'] ?? 'radio';
$dim['stats'] = self::emptyStats($dim);
return $dim;
}, $q['dimensions'] ?? []);
}
$stats[(string) $q['id']] = $q;
}
$count = 0;
foreach ($responses as $response) {
$count++;
$map = SurveySchema::answerMap($response['data'] ?? []);
$snapshot = SurveySchema::arrayValue($response['snapshot'] ?? []);
$keyed = ($snapshot['version'] ?? 0) >= 2;
foreach ($stats as $id => &$q) {
if ($q['edit_input'] === 'section') {
continue;
}
$raw = $map[$id] ?? [];
$value = $raw['value'] ?? null;
if ($q['edit_input'] === 'multi_dimension') {
$values = SurveySchema::arrayValue($value);
$hasAnswer = false;
foreach ($q['dimensions'] as &$dim) {
$v = $values[$dim['field']] ?? null;
self::add($dim['stats'], $dim, $v, $keyed);
$hasAnswer = $hasAnswer || !SurveySchema::blank($v);
}
unset($dim);
if ($hasAnswer) {
$q['stats']['valid_count']++;
}
} else {
self::add($q['stats'], $q, $value, $keyed);
}
if (!SurveySchema::blank($raw['other_text'] ?? $raw['allow_input_text'] ?? null)) {
$q['stats']['other_count']++;
}
}
unset($q);
}
$scoreSum = 0;
$scoreCount = 0;
foreach ($stats as &$q) {
if ($q['edit_input'] === 'multi_dimension') {
$q['stats']['score_sum'] = 0;
$q['stats']['score_count'] = 0;
foreach ($q['dimensions'] as &$dim) {
self::finish($dim['stats']);
$q['stats']['score_sum'] += $dim['stats']['score_sum'];
$q['stats']['score_count'] += $dim['stats']['score_count'];
}
unset($dim);
}
self::finish($q['stats']);
$scoreSum += $q['stats']['score_sum'];
$scoreCount += $q['stats']['score_count'];
}
unset($q);
return [
'responses' => $count,
'questions_count' => count(array_filter($questions, fn ($q) => $q['edit_input'] !== 'section')),
'average' => $scoreCount ? round($scoreSum / $scoreCount, 2) : null,
'score_count' => $scoreCount,
'questions' => array_values($stats),
];
}
private static function emptyStats(array $field): array
{
return [
'valid_count' => 0, 'other_count' => 0, 'sum' => 0, 'score_sum' => 0, 'score_count' => 0, 'average' => null,
'options' => array_map(fn ($o) => ['key' => (string) $o['key'], 'label' => $o['value'], 'count' => 0, 'percentage' => 0], $field['select_item'] ?? []),
];
}
private static function add(array &$stats, array $field, $value, bool $keyed): void
{
if (SurveySchema::blank($value)) {
return;
}
$type = $field['edit_input'] ?? 'radio';
if (in_array($type, ['radio', 'checkbox'], true)) {
$values = is_array($value) ? $value : ($type === 'checkbox' ? explode(',', (string) $value) : [$value]);
$matched = false;
foreach ($field['select_item'] ?? [] as $index => $option) {
$target = (string) ($keyed ? $option['key'] : $option['value']);
if (in_array($target, array_map('strval', $values), true)) {
$stats['options'][$index]['count']++;
$matched = true;
if (!empty($field['config']['is_score']) && isset($option['score']) && is_numeric($option['score'])) {
$stats['score_count']++;
$stats['score_sum'] += (float) $option['score'];
}
}
}
if ($matched) {
$stats['valid_count']++;
}
} elseif ($type === 'number') {
if (is_numeric($value)) {
$stats['valid_count']++;
$stats['sum'] += (float) $value;
}
} else {
$stats['valid_count']++;
}
}
private static function finish(array &$stats): void
{
$stats['average'] = $stats['score_count'] ? round($stats['score_sum'] / $stats['score_count'], 2) : null;
foreach ($stats['options'] as &$option) {
$option['percentage'] = $stats['valid_count'] ? round($option['count'] * 100 / $stats['valid_count'], 1) : 0;
}
}
}