Add identifier normalization contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 22:07:19 +03:00
parent af4d0f353b
commit 91a1cc182d

View File

@@ -0,0 +1,93 @@
# 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`.