Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
199
app/Services/Asm3MigrationPlanner.php
Normal file
199
app/Services/Asm3MigrationPlanner.php
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Asm3MigrationPlanner
|
||||
{
|
||||
public static function plan(string $path, PDO $db): array
|
||||
{
|
||||
$source = Asm3Analyzer::rows($path, ['animal', 'species', 'basecolour', 'lksex']);
|
||||
$species = self::lookup($source['species'] ?? [], 'ID', 'SPECIESNAME');
|
||||
$colors = self::lookup($source['basecolour'] ?? [], 'ID', 'BASECOLOUR');
|
||||
$sexes = self::lookup($source['lksex'] ?? [], 'ID', 'SEX');
|
||||
$existing = $db
|
||||
->query('SELECT id,name,internal_code,chip_id,birth_date FROM animals')
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
$byChip = [];
|
||||
$byCode = [];
|
||||
$byIdentity = [];
|
||||
$byName = [];
|
||||
foreach ($existing as $animal) {
|
||||
$chip = self::identifier($animal['chip_id'] ?? '');
|
||||
if ($chip !== '') {
|
||||
$byChip[$chip][] = $animal;
|
||||
}
|
||||
$code = self::text($animal['internal_code'] ?? '');
|
||||
if ($code !== '') {
|
||||
$byCode[$code][] = $animal;
|
||||
}
|
||||
$identity = self::identity($animal['name'] ?? '', $animal['birth_date'] ?? '');
|
||||
if ($identity !== '') {
|
||||
$byIdentity[$identity][] = $animal;
|
||||
}
|
||||
$name = self::text($animal['name'] ?? '');
|
||||
if ($name !== '') {
|
||||
$byName[$name][] = $animal;
|
||||
}
|
||||
}
|
||||
$items = [];
|
||||
$summary = ['create' => 0, 'match' => 0, 'review' => 0];
|
||||
foreach ($source['animal'] ?? [] as $row) {
|
||||
$mapped = self::mapAnimal($row, $species, $colors, $sexes);
|
||||
$candidates = [];
|
||||
$reasons = [];
|
||||
$chip = self::identifier($mapped['chip_id']);
|
||||
if ($chip !== '' && !empty($byChip[$chip])) {
|
||||
foreach ($byChip[$chip] as $a) {
|
||||
$candidates[(int) $a['id']] = $a;
|
||||
}
|
||||
$reasons[] = 'même identification';
|
||||
}
|
||||
// Les codes refuge peuvent être réutilisés après une remise à zéro d’ASM3.
|
||||
// Un code seul n’est donc jamais une preuve suffisante entre deux sauvegardes historiques.
|
||||
$code = self::text($mapped['internal_code']);
|
||||
$identity = self::identity($mapped['name'], $mapped['birth_date']);
|
||||
if ($identity !== '' && !empty($byIdentity[$identity])) {
|
||||
foreach ($byIdentity[$identity] as $a) {
|
||||
$candidates[(int) $a['id']] = $a;
|
||||
}
|
||||
$reasons[] = 'même nom et naissance';
|
||||
}
|
||||
$strongMatch = count($candidates) > 0;
|
||||
if (!$strongMatch) {
|
||||
$name = self::text($mapped['name']);
|
||||
if ($name !== '' && !empty($byName[$name])) {
|
||||
$weakByName = $byName[$name];
|
||||
$knownDifferentBirths = $mapped['birth_date'] !== '' && $mapped['birth_date'] !== null;
|
||||
foreach ($weakByName as $a) {
|
||||
$knownDifferentBirths =
|
||||
$knownDifferentBirths &&
|
||||
!empty($a['birth_date']) &&
|
||||
$a['birth_date'] !== $mapped['birth_date'];
|
||||
}
|
||||
if (!$knownDifferentBirths) {
|
||||
foreach ($weakByName as $a) {
|
||||
$candidates[(int) $a['id']] = $a;
|
||||
}
|
||||
$reasons[] = 'même nom uniquement';
|
||||
}
|
||||
}
|
||||
}
|
||||
// Un code isolé et réutilisé ne doit ni fusionner, ni bloquer la création.
|
||||
// Asm3AnimalImporter suffixera le code technique si nécessaire.
|
||||
if (!$strongMatch && count($candidates) === 0) {
|
||||
$action = 'create';
|
||||
} elseif ($strongMatch && count($candidates) === 1) {
|
||||
$action = 'match';
|
||||
} else {
|
||||
$action = 'review';
|
||||
}
|
||||
$summary[$action]++;
|
||||
$items[] = [
|
||||
'asm3_id' => (int) ($row['ID'] ?? 0),
|
||||
'action' => $action,
|
||||
'match_reasons' => array_values(array_unique($reasons)),
|
||||
'globinours_candidates' => array_values(
|
||||
array_map(
|
||||
static fn($a) => ['id' => (int) $a['id'], 'name' => $a['name'], 'code' => $a['internal_code']],
|
||||
$candidates,
|
||||
),
|
||||
),
|
||||
'mapped' => $mapped,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'source' => basename($path),
|
||||
'generated_at' => date(DATE_ATOM),
|
||||
'read_only' => true,
|
||||
'summary' => $summary,
|
||||
'items' => $items,
|
||||
'lookups' => ['species' => $species, 'colors' => $colors, 'sexes' => $sexes],
|
||||
];
|
||||
}
|
||||
|
||||
private static function mapAnimal(array $r, array $species, array $colors, array $sexes): array
|
||||
{
|
||||
$date = static fn($v) => $v && preg_match('/^\d{4}-\d{2}-\d{2}/', (string) $v, $m) ? $m[0] : null;
|
||||
$yes = static fn($v) => (string) $v === '1';
|
||||
$tri = static fn($v) => match ((string) $v) {
|
||||
'0' => 'no',
|
||||
'1' => 'yes',
|
||||
default => 'unknown',
|
||||
};
|
||||
$chip = trim((string) ($r['IDENTICHIPNUMBER'] ?? ''));
|
||||
$tattoo = trim((string) ($r['TATTOONUMBER'] ?? ''));
|
||||
$identifier = $chip !== '' ? $chip : $tattoo;
|
||||
$deceased = $date($r['DECEASEDDATE'] ?? null) !== null || $yes($r['PUTTOSLEEP'] ?? 0);
|
||||
$availability = $yes($r['ISNOTAVAILABLEFORADOPTION'] ?? 0)
|
||||
? 'not_available'
|
||||
: ($yes($r['ADOPTABLE'] ?? 0)
|
||||
? 'available'
|
||||
: 'unknown');
|
||||
return [
|
||||
'name' => trim((string) ($r['ANIMALNAME'] ?? '')),
|
||||
'internal_code' => trim((string) ($r['SHELTERCODE'] ?? '' ?: $r['SHORTCODE'] ?? '')),
|
||||
'species' => self::species($species[(string) ($r['SPECIESID'] ?? '')] ?? ''),
|
||||
'sex' => match (mb_strtolower((string) ($sexes[(string) ($r['SEX'] ?? '')] ?? ''))) {
|
||||
'mâle' => 'M',
|
||||
'femelle' => 'F',
|
||||
default => 'U',
|
||||
},
|
||||
'birth_date' => $date($r['DATEOFBIRTH'] ?? null),
|
||||
'birth_is_estimated' => $yes($r['ESTIMATEDDOB'] ?? 0) ? 1 : 0,
|
||||
'breed' => trim((string) ($r['BREEDNAME'] ?? '')),
|
||||
'color' => $colors[(string) ($r['BASECOLOURID'] ?? '')] ?? null,
|
||||
'notes' => trim((string) ($r['ANIMALCOMMENTS'] ?? '')),
|
||||
'intake_date' => $date($r['MOSTRECENTENTRYDATE'] ?? null ?: $r['DATEBROUGHTIN'] ?? null),
|
||||
'status' => $deceased ? 'decede' : ($yes($r['ISQUARANTINE'] ?? 0) ? 'quarantaine' : 'refuge'),
|
||||
'archived' => $yes($r['ARCHIVED'] ?? 0),
|
||||
'deceased_date' => $date($r['DECEASEDDATE'] ?? null),
|
||||
'identification_type' => $chip !== '' ? 'microchip' : ($tattoo !== '' ? 'tattoo' : 'unknown'),
|
||||
'chip_id' => $identifier,
|
||||
'identification_date' => $date($chip !== '' ? $r['IDENTICHIPDATE'] ?? null : $r['TATTOODATE'] ?? null),
|
||||
'identification_registration_status' => match ((string) ($r['IDENTICHIPSTATUS'] ?? '')) {
|
||||
'1' => 'registered',
|
||||
'0' => 'pending',
|
||||
default => 'unknown',
|
||||
},
|
||||
'sterilization_status' => $yes($r['NEUTERED'] ?? 0) ? 'yes' : 'unknown',
|
||||
'sterilization_date' => $date($r['NEUTEREDDATE'] ?? null),
|
||||
'adoption_availability' => $availability,
|
||||
'adoption_available_from' => $date($r['HOLDUNTILDATE'] ?? null),
|
||||
'adoption_unavailability_reason' => trim((string) ($r['REASONNO'] ?? '')),
|
||||
'compatibility_dogs' => $tri($r['ISGOODWITHDOGS'] ?? null),
|
||||
'compatibility_cats' => $tri($r['ISGOODWITHCATS'] ?? null),
|
||||
'compatibility_children' => $tri($r['ISGOODWITHCHILDREN'] ?? null),
|
||||
'house_trained' => $tri($r['ISHOUSETRAINED'] ?? null),
|
||||
];
|
||||
}
|
||||
private static function lookup(array $rows, string $key, string $value): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$out[(string) $row[$key]] = (string) $row[$value];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
private static function species(string $value): string
|
||||
{
|
||||
return match (mb_strtolower($value)) {
|
||||
'chat' => 'chat',
|
||||
'chien' => 'chien',
|
||||
default => mb_strtolower($value) ?: 'inconnu',
|
||||
};
|
||||
}
|
||||
private static function identifier(mixed $value): string
|
||||
{
|
||||
return strtoupper((string) preg_replace('/[^a-zA-Z0-9]/', '', (string) $value));
|
||||
}
|
||||
private static function text(mixed $value): string
|
||||
{
|
||||
return mb_strtolower(trim((string) $value));
|
||||
}
|
||||
private static function identity(mixed $name, mixed $birth): string
|
||||
{
|
||||
$name = self::text($name);
|
||||
$birth = trim((string) $birth);
|
||||
return $name !== '' && $birth !== '' ? $name . '|' . $birth : '';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue