Migrazione dati — script e validazioni
Trasferire 62K analisi, 11K lotti, 109K conte senza perdere nulla
Mapping campo-per-campo
semiorto_analisi → test_germinabilita
| Legacy | TopSeed | Trasformazione |
|---|---|---|
id | legacy_id | copy + set source='diego' |
id_lotto | lot_id | lookup via mapLotto[legacy_id] |
id_tecnico | user_id | lookup via mapTecnico |
id_tipologia_seme | tipologia_seme_id | lookup via mapTipologia |
ft | ft | direct |
lotto (denormalizzato) | — | SKIP (redundant: già in lots.num) |
prova | lookup in tipologie_prova | mapping ENUM → id |
tipologiaAnalisi | replica_analisi | direct (int 1/2/3) |
tipologiaSeme (denormalizzato) | tipologia_seme_legacy | copy come backup (FK è in tipologia_seme_id) |
calibratura | calibratura | direct |
data | data_chiusura | direct (NULL=in corso) |
dataArrivoLaboratorio | data_arrivo_laboratorio | direct |
dataInizio | data_inizio | direct |
scostamento | scostamento | direct ENUM |
ur | ur | direct |
totaleGiorniDiProva | totale_giorni_prova | direct |
rifNumero/rifData/rifGerminabilita/rifProvaLaboratorio/germinabilitaProvaLaboratorio | idem snake_case | direct |
risultato | risultato | direct ENUM |
vigore | vigore | direct ENUM |
tecnico (denormalizzato) | — | SKIP |
pillole | pillole | direct |
osservazioni | osservazioni | CONVERT(... USING utf8mb4) |
semePuro/materialeInerte/altriSemi/altriSemiString | idem snake_case | direct |
categoria | categoria_analisi | direct ENUM |
osservazioniPurezza | osservazioni_purezza | direct |
germinabilita | germinabilita | direct (valore pre-calcolato) |
cancella | cancella_note | direct (NULL=attiva) |
semiorto_conte → test_germinabilita_conte (1:1)
id | (auto) | nuovo AUTO_INCREMENT |
analisi | analisi_id | mapAnalisi[legacy_analisi_id] |
lettera | lettera | A/B/C/D |
giorni_1..morti_1, vigore_1 | idem | direct |
giorni_2..morti_2, vigore_2 | idem | direct |
semiorto_lotto → lots
id_lotto | legacy_id | copy + legacy_source='diego' |
id (codice testo) | num | direct |
varieta | seed_id | mapVarieta[legacy_id] |
id_produttore | produttore_id | mapProduttore[legacy_id] |
esaurito | esaurito | direct (0/1) |
provenienza | provenienza | direct |
kg | kg / qty (unified) | direct |
dataArrivo/dataProssimaAnalisi/dataEsaurito | idem snake_case | direct |
semiorto_specie → categories
Match fuzzy su italiano ≈ categories.name. Se match: UPDATE con campi aggiuntivi. Se no match: INSERT nuova.
semiorto_varieta → seeds
id | legacy_id | copy |
nome | name | direct |
codice | cr (cod ricerca) | direct |
specie | categoryId | mapSpecie[legacy_id] |
| — | type | HARDCODED 'op' (assumiamo varietà legacy sono OP) |
| — | legacy_source | HARDCODED 'diego' |
semiorto_produttore → produttori
Match fuzzy Levenshtein su nome (soglia distanza ≤ 2). Se match: aggiungi solo codice, legacy_id. Se no: INSERT nuovo.
semiorto_tecnico → users
id | legacy_id | copy, legacy_source='diego_tecnico' |
nome | nome | direct (split nome+cognome su spazio) |
| — | username | auto = lowercase(nome senza spazi) |
| — | role | HARDCODED 'GENETISTA' |
| — | attivo | 1 |
utente → users (merge)
Per ciascun utente Semiorto, trova il corrispondente TopSeed (se esiste) o crea nuovo. Mapping ruoli:
| Amministrazione + Magazzino | ADMIN |
| Amministrazione | ADMIN |
| Laboratorio | GENETISTA |
| Magazzino | MAGAZZINIERE |
| multi-ruolo | ADMIN (conservativo) |
Password SHA-1 salvato come legacy_sha_hash. Al primo login, rehash a bcrypt.
semiorto_map_specie_analisi_ripetizione_tipologia_seme → specie_calendario_analisi
id_specie | categoria_id | mapSpecie[legacy_id] |
id_analisi_ripetizione | mese | direct (1-12) |
id_tipologia_seme | tipologia_seme_id | mapTipologia[legacy_id] |
Script migrate_semiorto.php (pseudo-codice)
<?php
// migrate_semiorto_to_topseed.php
// Esegue una tantum quando si è pronti per il taglio finale.
// Connessioni
$aws = new PDO('mysql:host=127.0.0.1;port=3307;dbname=semiorto;charset=utf8',
'root', 'iron666M', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]);
$ts = new PDO('mysql:host=localhost;dbname=topseed_db;charset=utf8mb4',
'topseed_db', '@Korea2022',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$ts->beginTransaction();
try {
echo "1. Importing specie...\n";
$mapSpecie = importSpecie($aws, $ts);
echo "2. Importing varieta...\n";
$mapVarieta = importVarieta($aws, $ts, $mapSpecie);
echo "3. Importing produttori...\n";
$mapProduttore = importProduttori($aws, $ts);
echo "4. Importing tecnici...\n";
$mapTecnico = importTecnici($aws, $ts);
echo "5. Importing utenti...\n";
importUtenti($aws, $ts);
echo "6. Importing tipologie seme...\n";
$mapTipologia = importTipologieSeme($aws, $ts);
echo "7. Importing lotti...\n";
$mapLotto = importLotti($aws, $ts, $mapVarieta, $mapProduttore);
echo "8. Importing analisi...\n";
$mapAnalisi = importAnalisi($aws, $ts, $mapLotto, $mapTecnico, $mapTipologia);
echo "9. Importing conte...\n";
importConte($aws, $ts, $mapAnalisi);
echo "10. Importing calendario specie...\n";
importCalendario($aws, $ts, $mapSpecie, $mapTipologia);
$ts->commit();
echo "OK. Migration completed.\n";
} catch (Throwable $e) {
$ts->rollBack();
echo "FAILED: " . $e->getMessage() . "\n";
exit(1);
}
// ==========================================
function importSpecie(PDO $aws, PDO $ts): array {
$map = [];
foreach ($aws->query("SELECT * FROM semiorto_specie") as $s) {
$name = trim($s['italiano']);
$stmt = $ts->prepare("SELECT id FROM categories WHERE LOWER(name) = LOWER(?)");
$stmt->execute([$name]);
$existing = $stmt->fetchColumn();
if ($existing) {
$ts->prepare("UPDATE categories SET italiano=?, inglese=?, latino=?,
codice_specie=?, giorni_1_conta=?, giorni_tot_conta=?, ur_target=?,
note_specie=?, legacy_source='diego', legacy_id=? WHERE id=?
")->execute([$s['italiano'], $s['inglese'], $s['latino'],
$s['codice'], $s['giorni1Conta'], $s['giorniTConta'], $s['ur'],
$s['note'], $s['id'], $existing]);
$map[$s['id']] = $existing;
} else {
$ts->prepare("INSERT INTO categories
(name, italiano, inglese, latino, codice_specie,
giorni_1_conta, giorni_tot_conta, ur_target, note_specie,
legacy_source, legacy_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'diego', ?)
")->execute([$name, $s['italiano'], $s['inglese'], $s['latino'],
$s['codice'], $s['giorni1Conta'], $s['giorniTConta'], $s['ur'],
$s['note'], $s['id']]);
$map[$s['id']] = (int)$ts->lastInsertId();
}
}
return $map;
}
function importAnalisi(PDO $aws, PDO $ts, array $mapLotto,
array $mapTecnico, array $mapTipologia): array {
$map = [];
$stmt = $ts->prepare("INSERT INTO test_germinabilita (
legacy_id, source, lot_id, user_id, tipologia_seme_id,
ft, prova, replica_analisi, tipologia_seme_legacy, calibratura,
data_chiusura, data_arrivo_laboratorio, data_inizio,
scostamento, ur, totale_giorni_prova,
rif_numero, rif_data, rif_germinabilita, rif_prova_laboratorio,
germinabilita_prova_laboratorio,
risultato, vigore, pillole, osservazioni,
seme_puro, materiale_inerte, altri_semi, altri_semi_string,
categoria_analisi, osservazioni_purezza, germinabilita, cancella_note
) VALUES (
?, 'diego', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)");
foreach ($aws->query("SELECT * FROM semiorto_analisi") as $a) {
$stmt->execute([
$a['id'],
$mapLotto[$a['id_lotto']] ?? null,
$mapTecnico[$a['id_tecnico']] ?? null,
$mapTipologia[$a['id_tipologia_seme']] ?? null,
$a['ft'], $a['prova'], $a['tipologiaAnalisi'], $a['tipologiaSeme'],
$a['calibratura'], $a['data'], $a['dataArrivoLaboratorio'], $a['dataInizio'],
$a['scostamento'], $a['ur'], $a['totaleGiorniDiProva'],
$a['rifNumero'], $a['rifData'], $a['rifGerminabilita'], $a['rifProvaLaboratorio'],
$a['germinabilitaProvaLaboratorio'],
$a['risultato'], $a['vigore'], $a['pillole'], $a['osservazioni'],
$a['semePuro'], $a['materialeInerte'], $a['altriSemi'], $a['altriSemiString'],
$a['categoria'], $a['osservazioniPurezza'], $a['germinabilita'], $a['cancella']
]);
$map[$a['id']] = (int)$ts->lastInsertId();
}
return $map;
}
// ...idem per altre entità
Query di validazione post-migrazione
-- 1. Counts identici
SELECT 'legacy' src, COUNT(*) FROM semiorto.semiorto_analisi
UNION ALL
SELECT 'topseed', COUNT(*) FROM topseed_db.test_germinabilita WHERE source='diego';
-- Expected: entrambi 61998
SELECT 'legacy', COUNT(*) FROM semiorto.semiorto_lotto
UNION ALL
SELECT 'topseed', COUNT(*) FROM topseed_db.lots WHERE legacy_source='diego';
-- Expected: 11138
SELECT 'legacy', COUNT(*) FROM semiorto.semiorto_conte
UNION ALL
SELECT 'topseed', COUNT(*) FROM topseed_db.test_germinabilita_conte c
JOIN topseed_db.test_germinabilita a ON a.id=c.analisi_id WHERE a.source='diego';
-- Expected: 109204
-- 2. Media germinabilità (deve coincidere)
SELECT
(SELECT AVG(germinabilita) FROM semiorto.semiorto_analisi WHERE germinabilita IS NOT NULL),
(SELECT AVG(germinabilita) FROM topseed_db.test_germinabilita WHERE source='diego' AND germinabilita IS NOT NULL);
-- Expected: entrambi ≈ 77.8
-- 3. Spot check su 50 analisi random
SELECT
l.id legacy_id, l.ft legacy_ft, l.germinabilita legacy_germ,
t.legacy_id topseed_legacy_id, t.ft topseed_ft, t.germinabilita topseed_germ
FROM (SELECT * FROM semiorto.semiorto_analisi ORDER BY RAND() LIMIT 50) l
LEFT JOIN topseed_db.test_germinabilita t ON t.legacy_id = l.id AND t.source='diego'
WHERE l.ft != t.ft OR l.germinabilita != t.germinabilita OR t.legacy_id IS NULL;
-- Expected: 0 righe (tutto match)
-- 4. FK integrity
SELECT COUNT(*) FROM topseed_db.test_germinabilita WHERE source='diego' AND lot_id IS NULL;
-- Expected: 0 (ogni analisi ha un lotto valido)
SELECT COUNT(*) FROM topseed_db.test_germinabilita_conte c
WHERE NOT EXISTS (SELECT 1 FROM topseed_db.test_germinabilita a WHERE a.id = c.analisi_id);
-- Expected: 0 (nessuna conta orfana)
-- 5. Data range
SELECT MIN(data_chiusura), MAX(data_chiusura) FROM topseed_db.test_germinabilita WHERE source='diego';
-- Expected: 2013-09-10 → 2026-04-20
Rollback
Se la migrazione fallisce validazione:
BEGIN;
DELETE FROM topseed_db.test_germinabilita_conte
WHERE analisi_id IN (SELECT id FROM topseed_db.test_germinabilita WHERE source='diego');
DELETE FROM topseed_db.test_germinabilita WHERE source='diego';
DELETE FROM topseed_db.lots WHERE legacy_source='diego';
DELETE FROM topseed_db.seeds WHERE legacy_source='diego';
DELETE FROM topseed_db.produttori WHERE legacy_source='diego';
DELETE FROM topseed_db.users WHERE legacy_source LIKE 'diego%';
DELETE FROM topseed_db.specie_calendario_analisi;
-- categories: UPDATE categories SET italiano=NULL, ... WHERE legacy_source='diego';
COMMIT;
La transazione unica nello script fa già rollback automatico in caso di errore durante l'esecuzione. Questo è un secondo fallback per dopo-commit se serve.
Stima durata
- Specie + varietà + produttori + tecnici: ~30 sec
- Lotti: ~1 min
- Analisi: ~3 min
- Conte: ~5 min
- Totale: ~10 min
- Validazione: ~2 min
Lo script è bulk insert, non record-by-record, quindi scala lineare.