feat: feedback statistics
addresses #869bgtz2d
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Feedback;
|
||||
use App\Repository\FeedbackRepository;
|
||||
use App\Repository\FeedbackSetRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:feedback:populate-feedback-set',
|
||||
description: 'One-time command to populate missing feedbackSet for existing feedbacks',
|
||||
)]
|
||||
class PopulateFeedbackSetCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FeedbackRepository $feedbackRepository,
|
||||
private readonly FeedbackSetRepository $feedbackSetRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption(
|
||||
'dry-run',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Run without persisting changes to the database'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$isDryRun = true === $input->getOption('dry-run');
|
||||
|
||||
if ($isDryRun) {
|
||||
$io->note('Running in dry-run mode. No changes will be persisted.');
|
||||
}
|
||||
|
||||
// Build lookup table: question texts -> FeedbackSet
|
||||
$feedbackSets = $this->feedbackSetRepository->findAll();
|
||||
$lookup = [];
|
||||
|
||||
foreach ($feedbackSets as $feedbackSet) {
|
||||
$key = $this->buildQuestionKey($feedbackSet->getRatings());
|
||||
$lookup[$key] = $feedbackSet;
|
||||
$io->writeln(sprintf('Registered FeedbackSet #%d "%s" with %d questions', $feedbackSet->getId(), $feedbackSet->getName(), count($feedbackSet->getRatings())));
|
||||
}
|
||||
|
||||
// Find feedbacks without feedbackSet
|
||||
$qb = $this->feedbackRepository->createQueryBuilder('f');
|
||||
$feedbacks = $qb
|
||||
->where($qb->expr()->isNull('f.feedbackSet'))
|
||||
->andWhere($qb->expr()->isNotNull('f.ratings'))
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
|
||||
$totalCount = count($feedbacks);
|
||||
|
||||
if (0 === $totalCount) {
|
||||
$io->success('No feedbacks found that need feedbackSet assignment.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->info(sprintf('Found %d feedback(s) without feedbackSet.', $totalCount));
|
||||
|
||||
$matchedCount = 0;
|
||||
$fallbackCount = 0;
|
||||
$unmatchedCount = 0;
|
||||
|
||||
/** @var Feedback $feedback */
|
||||
foreach ($feedbacks as $feedback) {
|
||||
$feedbackSet = $this->matchByQuestions($feedback, $lookup);
|
||||
|
||||
if (null !== $feedbackSet) {
|
||||
$feedback->setFeedbackSet($feedbackSet);
|
||||
++$matchedCount;
|
||||
|
||||
if ($output->isVerbose()) {
|
||||
$io->writeln(sprintf(
|
||||
'Matched feedback #%d to FeedbackSet "%s" by questions',
|
||||
$feedback->getId(),
|
||||
$feedbackSet->getName()
|
||||
));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback: try via Assignment -> JobProfile -> FeedbackSet
|
||||
$feedbackSet = $this->matchByAssignment($feedback);
|
||||
|
||||
if (null !== $feedbackSet) {
|
||||
$feedback->setFeedbackSet($feedbackSet);
|
||||
++$fallbackCount;
|
||||
|
||||
if ($output->isVerbose()) {
|
||||
$io->writeln(sprintf(
|
||||
'Matched feedback #%d to FeedbackSet "%s" via Assignment fallback',
|
||||
$feedback->getId(),
|
||||
$feedbackSet->getName()
|
||||
));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
++$unmatchedCount;
|
||||
$io->warning(sprintf(
|
||||
'Could not match feedback #%d (no matching FeedbackSet found)',
|
||||
$feedback->getId()
|
||||
));
|
||||
}
|
||||
|
||||
if (false === $isDryRun && ($matchedCount > 0 || $fallbackCount > 0)) {
|
||||
$this->entityManager->flush();
|
||||
$io->success(sprintf(
|
||||
'Successfully updated %d feedback(s): %d by questions, %d by assignment fallback. %d unmatched.',
|
||||
$matchedCount + $fallbackCount,
|
||||
$matchedCount,
|
||||
$fallbackCount,
|
||||
$unmatchedCount
|
||||
));
|
||||
} elseif ($isDryRun) {
|
||||
$io->success(sprintf(
|
||||
'Dry-run complete. Would update %d feedback(s): %d by questions, %d by assignment fallback. %d unmatched.',
|
||||
$matchedCount + $fallbackCount,
|
||||
$matchedCount,
|
||||
$fallbackCount,
|
||||
$unmatchedCount
|
||||
));
|
||||
} else {
|
||||
$io->info(sprintf('No feedbacks updated. %d unmatched.', $unmatchedCount));
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{rating: string, mark: int}> $ratings
|
||||
*/
|
||||
private function buildQuestionKey(array $ratings): string
|
||||
{
|
||||
// For FeedbackSet, ratings is array of strings
|
||||
if (isset($ratings[0]) && is_string($ratings[0])) {
|
||||
return implode('|', $ratings);
|
||||
}
|
||||
|
||||
// For Feedback, ratings is array of {rating, mark}
|
||||
$questions = array_map(fn (array $r): string => $r['rating'], $ratings);
|
||||
|
||||
return implode('|', $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, \App\Entity\FeedbackSet> $lookup
|
||||
*/
|
||||
private function matchByQuestions(Feedback $feedback, array $lookup): ?\App\Entity\FeedbackSet
|
||||
{
|
||||
$ratings = $feedback->getRatings();
|
||||
|
||||
if (0 === count($ratings)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = $this->buildQuestionKey($ratings);
|
||||
|
||||
return $lookup[$key] ?? null;
|
||||
}
|
||||
|
||||
private function matchByAssignment(Feedback $feedback): ?\App\Entity\FeedbackSet
|
||||
{
|
||||
$assignment = $feedback->getAssignment();
|
||||
|
||||
if (null === $assignment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$jobProfile = $assignment->getJobProfile();
|
||||
|
||||
if (null === $jobProfile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $jobProfile->getFeedbackSet();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user