# Contract: Identifier Normalization Version: 1.0 ## Purpose Правила хранения и сравнения идентификаторов оборудования: серийные номера, вендоры, версии прошивок, партномера, артикулы. --- ## Правило Оригинальное значение **сохраняется как пришло** — регистр не меняется. Все сравнения, поиск и дедупликация выполняются **без учёта регистра**. ``` Пришло: "SN-001-ABC" → хранится: "SN-001-ABC" Пришло: "sn-001-abc" → это тот же объект, не дубликат Пришло: "Sn-001-Abc" → то же самое ``` --- ## Применяется к полям - Серийный номер (`serial_number`, `serial`) - Вендор / производитель (`vendor`, `manufacturer`) - Версия прошивки (`firmware_version`, `fw_version`) - Партномер (`part_number`, `part_no`) - Артикул (`article`, `sku`) --- ## Реализация ### Go — сравнение ```go import "strings" func SameIdentifier(a, b string) bool { return strings.EqualFold(a, b) } ``` ### Go — дедупликация ```go func deduplicateBySerial(items []Device) []Device { seen := make(map[string]struct{}) result := items[:0] for _, item := range items { key := strings.ToLower(item.SerialNumber) if _, exists := seen[key]; !exists { seen[key] = struct{}{} result = append(result, item) } } return result } ``` Ключ в map — всегда `strings.ToLower(value)`. Сам объект сохраняется с оригинальным значением. ### SQL — поиск и уникальность Поиск: ```sql SELECT * FROM devices WHERE LOWER(serial_number) = LOWER(?); ``` Уникальный индекс (MySQL / MariaDB): ```sql -- Collation ci обеспечивает case-insensitive уникальность ALTER TABLE devices MODIFY serial_number VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE devices ADD UNIQUE INDEX uniq_serial (serial_number); ``` SQLite: ```sql CREATE UNIQUE INDEX uniq_serial ON devices (LOWER(serial_number)); ``` --- ## Что не делать - Не приводить значение к нижнему или верхнему регистру перед сохранением. - Не считать `"SN-001"` и `"sn-001"` разными объектами. - Не использовать `==` для сравнения идентификаторов в Go — только `strings.EqualFold`.