---
phase: dataModel
kind: level
level: attributes
---

# Aspect — ATTRIBUTES (incl. computed formulas)

For each entity, define its `tablePrefix`, `tenantMode`, business attributes
(type, length, required, unique), computed formulas, indexes and an optional code
pattern. You auto-deduce attributes from the module's `use-case.md` and
`règles-métier.md` and present the complete result for review — you do NOT ask
the user field by field.

## How to deduce attributes (closed sources only)

- **Business rules** — `salary >= minimumWage` → `Salary` (decimal); `when
  status = 'active'` → `Status` (enum); "email unique per tenant" → `Email`
  (string, unique).
- **Use cases** — "fills in their leave dates" → `StartDate`, `EndDate`; "enters
  an approval note" → `ApprovalNote` (text); "the system calculates the total" →
  a **computed** attribute (see below).

**NEVER from domain knowledge or web search.** "Standard" fields (`FirstName`,
`Email`, `Status`, `TotalExclTax`…) are not a source: if no module UC/BR mentions
the field, do not add it — surface the gap and defer. For a `person: mandatory`
entity, identity fields (`FirstName`, `LastName`, `Email`) come from `auth_Users`
— never redeclare them.

## tablePrefix (functional domain, not module location)

| Prefix | Domain | Prefix | Domain |
|--------|--------|--------|--------|
| `hr_` | Employee, Department, Timesheet | `crm_` | Customer, Lead, Opportunity |
| `ops_` | Project, Task, Resource | `fin_` | Invoice, Payment, Budget |
| `inv_` | Product, Warehouse, StockMovement | `cfg_` | tenant lookups |

Invent 2-5 lowercase letters + `_` for new domains (`edu_`, `med_`…). Match the
prefix of the module's existing entities when in doubt. Reserved prefixes are
blocked — see SKILL.md § Collision detection.

## tenantMode

| Mode | Use for |
|------|---------|
| `strict` (default) | tenant-isolated business data (employees, orders, invoices) |
| `optional` | shared resources with per-tenant overrides (email templates) |
| `scoped` | tri-level visibility (tenant + shared catalog + platform) |
| `none` | platform lookups single-set for all tenants (`Language`, `Country`) |

Default `strict`. Use AskUserQuestion only when tenancy is genuinely ambiguous.

## Types & lengths — stick to these

**Strings:** 50 (codes), 100 (names/labels), 200 (titles), 256 (email/URL),
500/1000/2000 (descriptions), 4000 (JSON), `text` (unbounded). Never arbitrary
lengths — round to the nearest standard.

**Decimals:** money `decimal(18,8)`; percentages `decimal(5,4)`; quantities
`decimal(18,4)`; coordinates `decimal(9,6)`. Never `float`/`double` for money.

**Palette:** `string text int long short decimal float double bool datetime date
time guid enum json binary`.

## Computed attributes (the `Calculé` column)

When a value is **deterministically derived from other attributes of the SAME
entity** (line total, consumption rate, age from a birth date), declare it as a
normal attribute and put the **C# formula** in the `Calculé` column of
`entité.md`. The dev cascade picks it up: DTO projection, repository LINQ
`.Select(…)` (single query, no N+1), read-only React column.

**Rules (load-bearing — they cascade to the dev pipeline):**
- The formula is a C# expression referencing **PascalCase properties of the same
  entity** (`Amount`, `Probability`, `BirthDate`).
- **Cross-entity references are forbidden** (the audit rejects them). A value
  needing data from a related entity stays a backend service / business rule.
- Side-effect-free, EF-translatable (pure arithmetic / string concat). No method
  calls EF can't translate. Max ~1000 chars.
- A computed attribute has **no DB column and no setter** — it is read-only.

| Use case | Type | Formula |
|---|---|---|
| Weighted amount of an opportunity | decimal | `Amount * Probability` |
| Line total of an invoice item | decimal | `Quantity * UnitPrice * (1 + VatRate)` |
| Full name of a contact | string | `FirstName + " " + LastName` |
| Consumption rate of a budget | decimal | `(InitialAmount - CurrentBalance) / InitialAmount` |

**When NOT computed:** the value is stored & user-editable; or it needs a related
entity; or it's non-deterministic (random, time-now without an input attribute) →
omit the formula (`—` in the column).

## Framework fields — DO NOT list

`Id`, `CreatedAt`, `UpdatedAt`, `CreatedBy`, `UpdatedBy`, `DeletedAt`, `TenantId`,
`Scope` are implicit (base classes add them). Show only `Id Guid PK` in the table
for readability; never the others.

## Indexes & code patterns

- **Indexes** — only those a UC/BR query pattern justifies. Unique for business
  uniqueness ("no two users share an email" → `(Email) unique`); composite for
  frequent filters (`(Status, CreatedAt)`). No need to index FK columns (EF
  auto-indexes them). In `entité.md`, list as `(Field)` / `(Field) unique`.
- **Code pattern** — only for entities users refer to by number (Order, Invoice,
  Ticket, Employee matricule, Product). A `codePattern` is the data-model side of a
  `numbering` business rule (`/ba-create-business-rules`) and carries:
  - **format** — template with tokens (`AFF-{YY}-{SEQ:4}` → prefix + 2-digit year
    + 4-digit zero-padded counter) ;
  - **scope** — `tenant` (default, tenant-isolated data) / `global` (platform-wide)
    / `parent:<Entity>` ; **must be consistent with `tenantMode`** ;
  - **reset** — `none` / `yearly` / `monthly` / `daily`, aligned with a date token
    in the format (yearly ⇒ `{YY}`) ;
  - **gapless** — `true` (legal/accounting, no gaps) / `false` (gaps tolerated).

  Strategy shorthands still apply: `sequential` (default), `year-sequential`
  (invoices), `timestamp-daily` (high daily volume), `uuid-short` (tokens).
  Reference the `numbering` rule code (`BR-…`) in the entity's **Traçabilité**.
  When the pattern is **gapless + tenant-scoped**, also model the counter as a
  `technical` allocator entity (see `identify.md`) so the per-tenant sequence is
  explicit in the MCD. Lookups, junctions and audit logs get no code pattern.

## Quality checks (per entity)

- `tablePrefix` matches `^[a-z]{2,5}_$`, not reserved; `tenantMode` set.
- ≥1 attribute; every string has a length, every decimal precision/scale, every
  enum non-empty values.
- Computed attributes reference only same-entity PascalCase props (no
  cross-entity).
- No framework field listed. Then continue to relationships (`./relationships.md`).
