Skip to main content

ADR-002: LineDanceSpec Model (Spec, Components, Sequence)

CampValor
Data2026-01-02
EstatAccepted
DecisorsEquip LDP
MigracionsV46__create_line_dance_spec_tables.sql

Context

Un ball de línia (line dance) té una especificació que descriu els passos i l'ordre en què s'executen. Volem emmagatzemar aquesta informació de forma estructurada per:

  1. Mostrar la seqüència de passos a la UI.
  2. Permetre edició per part de professors/admins.
  3. Indicar l'estat de la transcripció (esborrany, publicat).
  4. Suportar el símbol restart (#) que indica tornar a l'inici.

Decisió

Creem tres taules relacionades:

1. line_dance_specs (1:1 amb Dance)

CREATE TABLE line_dance_specs (
dance_id BIGINT PRIMARY KEY REFERENCES dances(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
sequence_completeness VARCHAR(20) NOT NULL DEFAULT 'PARTIAL',
source_text TEXT,
notes TEXT,
created_by VARCHAR(255),
last_modified_by VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

Decisió clau: dance_id és la PK, no un ID autogenerat. Això garanteix relació 1:1 i simplifica queries.

2. line_dance_components (passos individuals)

CREATE TABLE line_dance_components (
id BIGSERIAL PRIMARY KEY,
spec_dance_id BIGINT NOT NULL REFERENCES line_dance_specs(dance_id) ON DELETE CASCADE,
component_type VARCHAR(20) NOT NULL, -- STEP, TURN, TAG, etc.
code VARCHAR(10) NOT NULL, -- VR, CS, WV, etc.
counts INT,
label VARCHAR(100),
sort_order INT NOT NULL DEFAULT 0,
UNIQUE(spec_dance_id, code)
);

Components són els "blocs" de la coreografia: VR (Vine Right), CS (Coaster Step), etc.

3. line_dance_sequence_items (ordre d'execució)

CREATE TABLE line_dance_sequence_items (
id BIGSERIAL PRIMARY KEY,
spec_dance_id BIGINT NOT NULL REFERENCES line_dance_specs(dance_id) ON DELETE CASCADE,
order_index INT NOT NULL,
component_id BIGINT REFERENCES line_dance_components(id) ON DELETE SET NULL,
restart BOOLEAN NOT NULL DEFAULT FALSE,
note TEXT
);

El camp restart indica que en aquest punt de la seqüència es torna a l'inici (símbol # als stepsheets).

Enums

public enum LineDanceSpecStatus {
DRAFT, // En edició
PUBLISHED // Visible públicament
}

public enum SequenceCompleteness {
NONE, // Sense seqüència definida
PARTIAL, // Alguns passos documentats
FULL // Seqüència completa
}

public enum ComponentType {
STEP, // Pas bàsic
TURN, // Gir
TAG, // Addició/extensió
BRIDGE, // Pont entre seccions
OTHER // Altres
}

Per què NO implementem sequence_rules

El disseny original incloïa una taula sequence_rules per definir regles com "després de 4 repeticions, afegir un TAG". Decidim NO implementar-ho ara perquè:

  1. Complexitat prematura - Cap usuari ho ha demanat.
  2. Casos d'ús poc clars - No sabem exactament quines regles caldrien.
  3. El camp notes pot documentar regles de forma textual temporalment.
  4. Evolució futura - Quan tinguem feedback real, podem afegir-ho amb una migració.

Principi YAGNI: You Ain't Gonna Need It. Millor afegir-ho quan sigui necessari que mantenir codi/taules sense ús.

Alternatives considerades

1. JSON blob per tota l'especificació

  • Pro: Flexibilitat total.
  • Contra: No es pot cercar ni validar a nivell de BD.

2. Taula única amb passos inline

  • Pro: Menys joins.
  • Contra: Components duplicats si s'usen múltiples vegades.

3. ID autogenerat per spec

  • Pro: Convencional.
  • Contra: Permet múltiples specs per dance, que no volem.

Conseqüències

Positives

  • ✅ Model normalitzat i cercable.
  • ✅ Components reutilitzables dins d'un ball.
  • ✅ Seqüència ordenada amb suport per restart.
  • ✅ Metadata d'autoria (created_by, last_modified_by).
  • ✅ Escalable amb futures extensions (sequence_rules, etc.).

Negatives

  • ⚠️ Tres taules per un concepte (més joins).
  • ⚠️ La UI ha de gestionar la relació components ↔ sequence.

API Endpoints

GET    /api/dances/{id}/spec           → LineDanceSpecDto
PUT /api/dances/{id}/spec → LineDanceSpecDto (upsert)
PUT /api/dances/{id}/spec/components → LineDanceSpecDto (replace all)
PUT /api/dances/{id}/spec/sequence → LineDanceSpecDto (replace all)

Tots requereixen rol ADMIN.

DTO Shape

record LineDanceSpecDto(
Long danceId,
String danceName,
LineDanceSpecStatus status,
SequenceCompleteness sequenceCompleteness,
String sourceText,
String notes,
List<LineDanceComponentDto> components,
List<LineDanceSequenceItemDto> sequence,
String createdBy,
String lastModifiedBy,
Instant createdAt,
Instant updatedAt
)

record LineDanceComponentDto(
Long id,
ComponentType type,
String code,
Integer counts,
String label,
Integer sortOrder
)

record LineDanceSequenceItemDto(
Long id,
Integer orderIndex,
Long componentId,
String componentCode, // Denormalized for UI
Boolean restart,
String note
)

Enllaços