Files
2018-lpon-site/_prj_blueprint/metadata_architecture.md
T

142 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура метаданных и схема каталога товаров (LPON Blueprint)
**Дата и время создания/обновления:** 2026-10-18 20:30
**Проект:** LPON.RU (Django 6.0+, Python 3.12)
**Статус:** Согласованная архитектурная концепция
**Связанный документ:** `_planstep_for_cassettes.md`
---
### 1. Фундаментальный принцип: Трехуровневая модель данных
Для обеспечения масштабируемости каталога на тысячи позиций разных форматов (Кассеты, Минидиски, Винил, CD, Аудиотехника) данные разделены на три уровня:
```text
[ Уровень 1: Реляционная БД ] ──► Базовые ключи (ID, Цены, Связи, Количество, Статусы)
[ Уровень 2: Универсальные JSON ] ──► Формат, Рынок, Год, Состояние, Упаковка, Комплектация
[ Уровень 3: Специфика формата ] ──► Тип ленты, Длина (Кассеты/MD), Скорость/Вес (Винил), Шторка (MD)
```
---
### 2. Разделение сущностей: «Заводской паспорт» vs «Физический экземпляр»
#### Тест-вопрос: «Это с конвейера или от жизни?»
#### Упрощенная типизация `media_type`:
* **`album`** — Музыкальный альбом / мастер-релиз (The Doors, Pink Floyd, Кино).
* **`media`** — Физический носитель под запись (кассеты, минидиски, бобины — чистые или б/у с записями).
---
### Симметрия сущностей: Музыкальный альбом vs Модель носителя
Чтобы не было путаницы между уровнями, в каталоге соблюдается идеальная симметрия:
```text
[ МУЗЫКАЛЬНЫЙ МИР ] [ МИР НОСИТЕЛЕЙ (КАССЕТЫ / MD) ]
1. TbArtist: The Doors 1. TbLabel: Sony / TDK / Maxell
│ │
▼ ▼
2. TbItem (Альбом): L.A. Woman (1971) 2. TbItem (Модель): Sony UX 90 (1986, JP)
• Название, автор, треклист • Тип ленты: Type II (High)
• Год первого релиза • Хронометраж: 90 мин
• Обзорная статья • Год ревизии модели: 1986
│ │
▼ ▼
3. TbOffer (Издание в продаже): 3. TbOffer (Экземпляр в продаже):
• Винил LP US Elektra 1971 (VG+/VG+, OBI) • Экземпляр А: Запечатанный (Sealed), 2200 ₽
• CD Japan 1991 (M/NM) • Экземпляр Б: Б/У с записью Doors, 350 ₽
```
#### А. Модель товара (`TbItem` → `j_item_metadata`)
* **Что хранит:** Неизменяемые характеристики альбома или заводской модели носителя.
* **Для альбомов (`media_type = 'album'`):** музыкальная суть (треклист, авторы, год оригинального релиза).
* **Для носителей (`media_type = 'media'`):** заводские параметры конкретной модели (`tape_type`, `length`, `release_year`, `market`, `shell_color`, `shell_screws`, `window_type`).
#### Б. Коммерческий лот (`TbOffer` → `j_offer_metadata`)
* **Что хранит:** Индивидуальные свойства конкретного физического экземпляра у продавца.
* **Примеры:**
* `packaging`: сохранность упаковки (`sealed` — запечатан, `opened` — вскрыт/б-у);
* `is_recorded`: признак аудиозаписи (`true`/`false`);
* `recording_info`: аннотация к аудиозаписи (радиоэфир, альбом, бутлег);
* `write_protect_tabs`: статус язычков защиты от записи (`intact` — целы, `broken` — выломаны);
* `inlay_condition`: состояние вкладыша J-Card (`clean`, `pencil`, `written`, `missing`);
* `series_tag`: слаг серии или тематической коллекции (например, `oobu-joobu`);
* `extras`: словарь комплектации и опций издания (наличие и грейд компонентов).
---
### 3. Комплектация и опции издания (`extras` / `OfferExtra`)
В `j_offer_metadata['extras']` хранится словарь кодовых ключей из `OfferExtra`:
```json
{
"extras": {
"obi": {"condition": "vg", "qty": 1, "note": "Японское оригинальное OBI"},
"insert": {"condition": "clean"},
"case_original": {"condition": "vg"},
"stickers_sheet": {}
}
}
```
#### Допустимые коды комплектации:
* `obi` — ОБИ-лента (OBI strip);
* `insert` — вкладыш / лирика / статья (Lyric sheet / Insert);
* `poster` — оригинальный постер;
* `stickers_sheet` — чистый лист наклеек;
* `stickers_applied` — наклейки нанесены на носитель;
* `postcards` — коллекционные открытки / карточки;
* `booklet` — буклет / книжка;
* `case_original` — оригинальный родной футляр / подкассетник;
* `case_generic` — стандартный / неоригинальный футляр;
* `no_case` — без футляра;
* `slipcase` — картонный слипкейс / O-Card;
* `box_set` — подарочный бокс / коробка;
* `inner_sleeve` — оригинальный иллюстрированный внутренний конверт (для винила);
* `outer_sleeve` — внешний защитный пластиковый пакет;
* `autograph` — автограф исполнителя / музыканта.
---
### 4. Концепция ввода данных: Шаблоны с подсказками через «трубу» (`|`) и защита от лени
Для удобства оператора и 100% защиты от невалидных данных используется механика **«Inline Choice Placeholder»**:
1. **Генерация заготовки:** При создании новой записи в поле JSON подставляется готовый шаблон, где для каждого ключа через символ `|` перечислены все допустимые варианты:
```json
{
"media_type": "blank_media",
"tape_type": "type_1|type_2|type_3|type_4",
"length": "46|54|60|74|90|100|120",
"market": "jp|us|eu|global"
}
```
2. **Правка оператором:** Оператор стирает лишние варианты, оставляя строго один: `"type_2"`.
3. **Строгая валидация (`clean()`):** Если запись сохранена с невыбранными вариантами (содержит `|`), валидатор блокирует сохранение и выводит понятную ошибку.
4. **Хранение в БД:** В базе данных всегда сохраняется только чистый, валидный код без лишних символов.
---
### 5. Каскадное наследование свойств (Fallback Cascade)
Оффер `TbOffer` автоматически наследует эталонные параметры от родительского `TbItem`. Если лот имеет индивидуальную особенность, значение в `j_offer_metadata` перекрывает заводское значение:
```python
def get_tape_type(self):
"""Возвращает тип ленты лота или эталонный тип ленты из модели Item."""
return self.j_offer_metadata.get('tape_type') or (
self.k_offer_to_item.j_item_metadata.get('tape_type')
if self.k_offer_to_item else None
)
```
---
### 6. Единый источник истины в коде
Все константы, перечисления (`models.TextChoices`, `models.IntegerChoices`), ключи метаданных и функции дефолтных шаблонов зафиксированы в модуле:
* **`lpon_site/lpon_site/constants.py`** (с реэкспортом в `settings.py` и `frontend/models.py`).