# Entity Name Canonicalization

> **Used by:** step-01-transform (section 2: file mapping) and step-02-export (POST-CHECK)
> **Purpose:** Convert BA entity names (potentially French, with spaces/apostrophes/accents) into valid C# identifiers for file paths.

---

## Why This Exists

Business analysts write entity names in the user's language (e.g., French: "Type d'absence", "Employé", "Congé maladie"). These names are used as-is in `entities.json`. However, C# file paths and class names **MUST** be valid identifiers:
- No spaces
- No apostrophes
- No accents (diacritics)
- PascalCase convention

Without canonicalization, the handoff generates paths like `src/Domain/Entities/Type d'absence.cs` → build failure (CS1001).

---

## Canonicalization Rules

### Step 1: Map to English PascalCase (PREFERRED)

If the BA provides an explicit `codeIdentifier` or `englishName` field on the entity, use it directly.

### Step 2: Strip Diacritics

Remove accents from characters:

| Input | Output |
|-------|--------|
| é, è, ê, ë | e |
| à, â, ä | a |
| ù, û, ü | u |
| ô, ö | o |
| î, ï | i |
| ç | c |

Example: `Employé` → `Employe`, `Congé` → `Conge`

### Step 3: Remove Apostrophes and Split on Spaces

Split the name on spaces, apostrophes, hyphens, and underscores:

| Input | Tokens |
|-------|--------|
| `Type d'absence` | `["Type", "d", "absence"]` |
| `Congé maladie` | `["Conge", "maladie"]` |
| `Mise à jour` | `["Mise", "a", "jour"]` |

### Step 4: Remove Articles and Prepositions (1-2 letter tokens)

Remove French articles and prepositions that don't contribute to the identifier:

**Remove:** `d`, `de`, `du`, `l`, `la`, `le`, `les`, `un`, `une`, `des`, `a`, `au`, `aux`, `en`

| Input Tokens | Filtered |
|-------------|----------|
| `["Type", "d", "absence"]` | `["Type", "absence"]` |
| `["Mise", "a", "jour"]` | `["Mise", "jour"]` |

### Step 5: PascalCase Assembly

Capitalize first letter of each remaining token, join without separator:

| Filtered Tokens | Result |
|----------------|--------|
| `["Type", "absence"]` | `TypeAbsence` |
| `["Conge", "maladie"]` | `CongeMaladie` |
| `["Mise", "jour"]` | `MiseJour` |
| `["Employe"]` | `Employe` |

---

## Canonicalization Function (Pseudocode)

```javascript
function canonicalizeEntityName(baName) {
  // Step 1: Use explicit code identifier if available
  // (handled by caller — check entity.codeIdentifier || entity.englishName first)

  // Step 2: Strip diacritics
  let name = baName.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

  // Step 3: Split on non-alphanumeric
  let tokens = name.split(/[\s'\-_]+/).filter(t => t.length > 0);

  // Step 4: Remove French articles/prepositions
  const stopWords = new Set(['d', 'de', 'du', 'l', 'la', 'le', 'les', 'un', 'une', 'des', 'a', 'au', 'aux', 'en']);
  tokens = tokens.filter(t => !stopWords.has(t.toLowerCase()));

  // Step 5: PascalCase
  return tokens.map(t => t.charAt(0).toUpperCase() + t.slice(1).toLowerCase()).join('');
}
```

---

## Validation: Is Valid C# Identifier?

After canonicalization, verify the result is a valid C# identifier:

```javascript
function isValidCSharpIdentifier(name) {
  // Must start with letter or underscore
  // Must contain only letters, digits, underscores
  // Must not be a C# keyword
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)
    && !CSHARP_KEYWORDS.has(name);
}

const CSHARP_KEYWORDS = new Set([
  'abstract', 'as', 'base', 'bool', 'break', 'byte', 'case', 'catch',
  'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default',
  'delegate', 'do', 'double', 'else', 'enum', 'event', 'explicit',
  'extern', 'false', 'finally', 'fixed', 'float', 'for', 'foreach',
  'goto', 'if', 'implicit', 'in', 'int', 'interface', 'internal',
  'is', 'lock', 'long', 'namespace', 'new', 'null', 'object',
  'operator', 'out', 'override', 'params', 'private', 'protected',
  'public', 'readonly', 'ref', 'return', 'sbyte', 'sealed', 'short',
  'sizeof', 'stackalloc', 'static', 'string', 'struct', 'switch',
  'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong',
  'unchecked', 'unsafe', 'ushort', 'using', 'virtual', 'void',
  'volatile', 'while'
]);
```

---

## Examples

| BA Entity Name (French) | Canonicalized | File Path |
|-------------------------|---------------|-----------|
| `Type d'absence` | `TypeAbsence` | `src/Domain/Entities/App/Module/TypeAbsence.cs` |
| `Employé` | `Employe` | `src/Domain/Entities/App/Module/Employe.cs` |
| `Congé maladie` | `CongeMaladie` | `src/Domain/Entities/App/Module/CongeMaladie.cs` |
| `Mise à jour` | `MiseJour` | `src/Domain/Entities/App/Module/MiseJour.cs` |
| `Département` | `Departement` | `src/Domain/Entities/App/Module/Departement.cs` |
| `Employee` | `Employee` | `src/Domain/Entities/App/Module/Employee.cs` (no change) |
| `AbsenceType` | `AbsenceType` | `src/Domain/Entities/App/Module/AbsenceType.cs` (no change) |

---

## Integration Points

1. **step-01-transform.md**: Apply `canonicalizeEntityName()` to ALL entity names BEFORE generating `filesToCreate` paths
2. **step-02-export.md**: POST-CHECK validates all paths in `filesToCreate` are valid C# identifiers
3. **entity-domain-mapping.md**: `{EntityName}` in path templates refers to the CANONICALIZED name
4. **handoff-file-templates.md**: All `{EntityName}` placeholders use canonicalized names

## Anti-Patterns

| Anti-Pattern | Risk | Correct Approach |
|-------------|------|-----------------|
| Translate to English semantically | `MiseJour` → `Update`? No — loses domain meaning | Only strip illegal chars, keep semantics |
| Remove all short words | `TypeAbsence` missing `Type`? | Only remove articles/prepositions, not nouns |
| Lowercase everything | `typeabsence` is not PascalCase | PascalCase each token |
| Skip validation | `Task` is a valid name but also a C# type | Warn on namespace conflicts, don't block |
