From 87cab4cad5a34cd2241fd805e61b7882e1285020 Mon Sep 17 00:00:00 2001 From: E98Developer Date: Fri, 28 Aug 2026 07:05:35 +0200 Subject: [PATCH] =?UTF-8?q?ADD=20EV3-357=20a=20laza=20javaslatok=20elt?= =?UTF-8?q?=C3=A9r=C3=A9s-jelleg=20szerint=20kategoriz=C3=A1lva?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 101 javaslat egy kupacban túl sok egy kézi átnézéshez, ráadásul nagyon eltérő mérlegelést igényelnek: a "Békás Kft." és a "Békás Kft" között nincs mit eldönteni, a "Dénes Natura" és a "Dénes Natura Kft" között viszont van. - ProducerDeduplicator::differenceCategory(): a LEGKEVÉSBÉ agresszív átalakítás, ami már egy kulcsra hozza a csoport tagjait - csak írásjel / egybeírás / ékezet / cégforma / vegyes. - a riport ebben a sorrendben írja ki a csoportokat (elöl a gyakorlatilag eldöntött esetek), és a "csak írásjel" kategóriánál előre beírja a javaslatot; a többinél a végleges név üresen marad, a javaslat a Megjegyzés oszlopban. A d2d másolaton: 14 csak írásjel (előre kitöltve, azonnal végrehajtható), 7 egybeírás, 8 ékezet, 72 cégforma - utóbbi 87 vár a megrendelő döntésére. Co-Authored-By: Claude Opus 5 --- .../Commands/ProducersDedupeReport.php | 50 +++++++++++---- app/Services/ProducerDeduplicator.php | 63 +++++++++++++++++-- 2 files changed, 95 insertions(+), 18 deletions(-) diff --git a/app/Console/Commands/ProducersDedupeReport.php b/app/Console/Commands/ProducersDedupeReport.php index 05e8fa9..b750a94 100644 --- a/app/Console/Commands/ProducersDedupeReport.php +++ b/app/Console/Commands/ProducersDedupeReport.php @@ -33,8 +33,6 @@ class ProducersDedupeReport extends Command private const TYPE_CERTAIN = 'biztos'; - private const TYPE_REVIEW = 'ellenőrizendő'; - public function handle(ProducerDeduplicator $deduplicator): int { $certain = $deduplicator->duplicateGroups(); @@ -107,12 +105,14 @@ private function writeIntro($sheet, bool $hasLoose): void $sheet->getRowDimension(6)->setRowHeight(46); if ($hasLoose) { - $sheet->setCellValue('A7', 'A "Típus" oszlop kétféle sort különböztet meg. A "' . self::TYPE_CERTAIN - . '" csoportoknál a nevek csak írásmódban térnek el, ott már beírtuk a javaslatunkat. ' - . 'Az "' . self::TYPE_REVIEW . '" csoportoknál a nevek jobban különböznek (elírás, kötőjel, ' - . 'egybeírás, hiányzó ékezet), ezért NEM töltöttük ki a végleges nevet: ' + $sheet->setCellValue('A7', 'A "Típus" oszlop megmutatja, miben térnek el a nevek – így kötegelve ' + . 'tudsz haladni. A "' . ProducerDeduplicator::DIFF_PUNCTUATION . '" csoportoknál csak pont, ' + . 'kötőjel vagy vessző a különbség ("Békás Kft." és "Békás Kft"), ott már beírtuk a ' + . 'javaslatot. A többinél ("' . ProducerDeduplicator::DIFF_SPACING . '", "' + . ProducerDeduplicator::DIFF_ACCENT . '", "' . ProducerDeduplicator::DIFF_COMPANY_FORM + . '") NEM töltöttük ki a végleges nevet, mert ott valódi döntés kell: ' . 'ha ugyanaz a cég, írd be a nevet (a javaslat a "Megjegyzés" oszlopban van); ' - . 'HA NEM UGYANAZ, HAGYD ÜRESEN – akkor nem nyúlunk hozzájuk.'); + . 'HA NEM UGYANAZ A CÉG, HAGYD ÜRESEN – akkor nem nyúlunk hozzájuk.'); $sheet->mergeCells('A7:J7'); $sheet->getStyle('A7')->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP); $sheet->getStyle('A7')->getFont()->setBold(true)->getColor()->setARGB('FF9C5700'); @@ -138,24 +138,50 @@ private function writeHeader($sheet, int $headerRow): void $sheet->getRowDimension($headerRow)->setRowHeight(32); } + /** + * A laza javaslatok sorrendje: elöl a gyakorlatilag eldöntött esetek, hátul azok, + * amik valódi cégismeretet igényelnek. Így a megrendelő kötegelve tud haladni. + */ + private const CATEGORY_ORDER = [ + ProducerDeduplicator::DIFF_PUNCTUATION, + ProducerDeduplicator::DIFF_SPACING, + ProducerDeduplicator::DIFF_ACCENT, + ProducerDeduplicator::DIFF_COMPANY_FORM, + ProducerDeduplicator::DIFF_MIXED, + ]; + private function writeGroups($sheet, int $headerRow, array $certain, array $loose): int { $row = $headerRow + 1; $groupIndex = 0; - foreach ([self::TYPE_CERTAIN => $certain, self::TYPE_REVIEW => $loose] as $type => $groups) { - foreach ($groups as $members) { + foreach ($certain as $members) { + $groupIndex++; + $row = $this->writeGroup($sheet, $row, $groupIndex, self::TYPE_CERTAIN, $members, false); + } + + $deduplicator = app(ProducerDeduplicator::class); + $byCategory = []; + foreach ($loose as $key => $members) { + $byCategory[$deduplicator->differenceCategory($members)][$key] = $members; + } + + foreach (self::CATEGORY_ORDER as $category) { + foreach ($byCategory[$category] ?? [] as $members) { $groupIndex++; - $row = $this->writeGroup($sheet, $row, $groupIndex, $type, $members); + // Az írásjel-eltérésnél nincs mérlegelnivaló (a "Kft." és a "Kft" ugyanaz), + // ezért ott előre beírjuk a javaslatot - a megrendelő így csak a valóban + // kérdéses eseteket nézi át tételesen. + $prefill = $category === ProducerDeduplicator::DIFF_PUNCTUATION; + $row = $this->writeGroup($sheet, $row, $groupIndex, $category, $members, ! $prefill); } } return $row - 1; } - private function writeGroup($sheet, int $row, int $groupIndex, string $type, array $members): int + private function writeGroup($sheet, int $row, int $groupIndex, string $type, array $members, bool $isReview): int { - $isReview = $type === self::TYPE_REVIEW; $proposed = trim((string) $members[0]['producer']->name); $shade = $isReview ? 'FFFDF2E9' : ($groupIndex % 2 === 0 ? 'FFF2F2F2' : 'FFFFFFFF'); $groupFirstRow = $row; diff --git a/app/Services/ProducerDeduplicator.php b/app/Services/ProducerDeduplicator.php index 1c8395f..31037c9 100644 --- a/app/Services/ProducerDeduplicator.php +++ b/app/Services/ProducerDeduplicator.php @@ -32,6 +32,17 @@ class ProducerDeduplicator private const UPDATE_CHUNK = 5000; + /** Az eltérés jellege egy laza jelölt-csoportban (lásd differenceCategory) */ + public const DIFF_PUNCTUATION = 'csak írásjel'; + + public const DIFF_SPACING = 'egybeírás'; + + public const DIFF_ACCENT = 'ékezet'; + + public const DIFF_COMPANY_FORM = 'cégforma'; + + public const DIFF_MIXED = 'vegyes'; + /** * Duplikált névcsoportok, csoportonként a tagokkal és a hivatkozás-darabszámokkal. * A tagok rendezettek: az első a javasolt megtartandó (legtöbb termék). @@ -129,6 +140,51 @@ public function looseCandidateGroups(): array return $result; } + /** + * Egy laza jelölt-csoport eltérésének jellege - a LEGKEVÉSBÉ agresszív átalakítás, + * ami már egy kulcsra hozza a tagokat. + * + * Ez adja meg a megrendelőnek, mennyi mérlegelést igényel a sor: a "csak írásjel" + * eseteknél gyakorlatilag nincs döntés, a "cégforma" és a "vegyes" viszont valódi + * ismeretet kíván a cégekről. + * + * @param array}> $members + */ + public function differenceCategory(array $members): string + { + $names = array_map(fn ($m) => (string) $m['producer']->name, $members); + + $levels = [ + self::DIFF_PUNCTUATION => fn ($n) => $this->stripPunctuation(NameNormalizer::normalize($n)), + self::DIFF_SPACING => fn ($n) => preg_replace('/\s+/u', '', + $this->stripPunctuation(NameNormalizer::normalize($n))), + self::DIFF_ACCENT => fn ($n) => preg_replace('/\s+/u', '', + $this->stripAccents($this->stripPunctuation(NameNormalizer::normalize($n)))), + self::DIFF_COMPANY_FORM => fn ($n) => $this->splitCompanyForm($n)[0], + ]; + + foreach ($levels as $category => $key) { + if (count(array_unique(array_map($key, $names))) === 1) { + return $category; + } + } + + return self::DIFF_MIXED; + } + + private function stripPunctuation(string $text): string + { + return trim(preg_replace('/\s+/u', ' ', preg_replace('/[.,\-\'"()]/u', ' ', $text) ?? $text) ?? $text); + } + + private function stripAccents(string $text): string + { + return strtr($text, [ + 'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ö' => 'O', + 'Ő' => 'O', 'Ú' => 'U', 'Ü' => 'U', 'Ű' => 'U', + ]); + } + /** * Név szétbontása alapnévre és cégforma-toldatra, a "csak elírás" eltérések * kiszűrésével: írásjelek, ékezetek és szóközök elhagyásával. @@ -137,12 +193,7 @@ public function looseCandidateGroups(): array */ private function splitCompanyForm(string $name): array { - $normalized = NameNormalizer::normalize($name); - $normalized = preg_replace('/[.,\-\'"()]/u', ' ', $normalized) ?? $normalized; - $normalized = strtr($normalized, [ - 'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ö' => 'O', - 'Ő' => 'O', 'Ú' => 'U', 'Ü' => 'U', 'Ű' => 'U', - ]); + $normalized = $this->stripAccents($this->stripPunctuation(NameNormalizer::normalize($name))); $form = ''; $pattern = '/\b(KFT|ZRT|BT|NYRT|KKT|RT|GMBH|LTD|INC|SRL|NV|BV|AG|SA|SPA)\b/u';