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
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;
|
|
}
|
|
}
|
|
}
|