# HTML Data Mapping Reference

> **Used by:** `/business-analyse-html` skill (step-02-build-data)
> **Purpose:** Exact mapping from module JSON files to HTML FEATURE_DATA and EMBEDDED_ARTIFACTS objects

---

## FEATURE_DATA Mapping

Build a JSON object following this **exact mapping** from index.json to the HTML data model:

```javascript
{
  metadata: {
    applicationName: master.metadata.application,       // e.g. "RH"
    applicationId: master.id,                           // e.g. "FEAT-001"
    version: master.version,                            // e.g. "1.0"
    createdAt: master.metadata.createdAt,
    lastModified: master.metadata.updatedAt,
    analysisMode: master.metadata.analysisMode || "interactive"  // always "interactive" in JSON format
  },
  cadrage: {
    // CONTEXT SECTION — lean format (merged from problem/asIs/toBe)
    // NOTE: 01-data-init.js has backward compat for old problem/current/vision format
    context: {
      problem: master.cadrage.problem || "",             // flat string from index.json
      trigger: master.cadrage.trigger || "",              // flat string from index.json
      currentSituation: master.cadrage.asIs || "",        // flat string from index.json
      desiredSituation: master.cadrage.toBe || "",        // flat string from index.json
      painPoints: (master.cadrage.stakeholders || [])
        .flatMap(s => s.painPoints || []).join("\n"),     // aggregate all painPoints
      acceptanceCriteria: (master.cadrage.acceptanceCriteria || [])
        .map(ac => ac.criterion).join("\n")               // AC → newline-separated string
    },
    stakeholders: master.cadrage.stakeholders.map(s => ({
      role: s.role,
      function: s.function || "",
      tasks: s.tasks || [],
      frequency: mapFrequency(s.frequency),             // "Quotidien"→"daily", etc.
      access: mapAccess(s.involvement),                 // "decision-maker"→"admin", "end-user"→"contributor"
      frustrations: (s.painPoints || []).join("\n")
    })),
    scope: {
      inscope: (master.cadrage.globalScope.inScope || [])
        .map(item => ({ name: item, description: "" })),      // string[] → {name,description}[]
      outofscope: (master.cadrage.globalScope.outOfScope || [])
        .map(item => ({ name: item, description: "" }))
    },
    criteria: (master.cadrage.acceptanceCriteria || []).map(ac => ({
      text: ac.criterion,
      validated: ac.validated || false
    }))
  },
  // Multi-app support: applications[] from navigation.json or index.json
  applications: (collected_data.navigation?.applications || master.applications || []).map(app => ({
    code: app.code,
    name: app.name || app.code,
    icon: app.icon || ""
  })),
  modules: master.modules.map(m => ({
    code: m.code,
    name: m.name || m.code,                             // display name (MANDATORY, separate from code)
    applicationCode: m.applicationCode || "",            // for multi-app sidebar grouping
    description: m.description || "",
    featureType: m.featureType || "data-centric",
    entities: m.entities || [],
    // NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
    anticipatedSections: (m.anticipatedSections || []).map(s =>
      typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
    ),   // ALWAYS [{code, name, description, resources[]}]
    dependencies: m.dependencies || [],
    dependents: m.dependents || [],
    estimatedComplexity: m.estimatedComplexity || "medium",
    status: m.status || "handed-off"
  })),
  dependencies: (master.dependencyGraph?.edges || []).map(e => ({
    from: e.from,
    to: e.to,
    description: e.description || ""
  })),
  moduleSpecs: {
    // FOR EACH module: read module index.json, then map:
    // [moduleCode]: { useCases, businessRules, entities, permissions, notes, mockupNotes }
  },
  consolidation: {
    interactions: (master.consolidation?.crossModuleInteractions || []).map(i => ({
      from: i.fromModule,
      to: i.toModule,
      description: i.description || ""
    })),
    e2eFlows: (master.consolidation?.e2eFlows || []).map(f => ({
      name: f.name,
      steps: (f.steps || []).map(s => ({ module: s.module, action: s.action })),
      actors: (f.steps || []).map(s => s.permission).join(", ")
    }))
  },
  handoff: master.handoff || {}
}
```

### Module Specs Mapping (FLAT-FILE ARCHITECTURE)

For EACH module in `master.modules[]`:

1. Use flat file data from `collected_data.modules[moduleCode]` (loaded in step-01)
2. Data sources: `entities.json`, `rules.json`, `usecases.json`, `permissions.json`, `screens.json`
3. Do NOT read from `moduleIndex.specification` or `moduleIndex.analysis` — these don't exist in flat-file format

> **TWO INPUT FORMATS:** The `/business-analyse` skill may produce JSON in two schema conventions.
> You MUST normalize both to the same output format. The mapping below shows both variants.

```javascript
const mod = collected_data.modules[moduleCode];

// NORMALIZE: usecases.json key may be "useCases" (camelCase) or "usecases" (lowercase)
const rawUCs = mod.usecases?.useCases || mod.usecases?.usecases || [];
// NORMALIZE: rules.json key may be "rules" or "businessRules"
const rawBRs = mod.rules?.rules || mod.rules?.businessRules || [];

moduleSpecs[moduleCode] = {
  useCases: rawUCs.map(uc => ({
    name: uc.name || uc.title || uc.id || "",            // Format A: "name", Format B: "title" or "id"
    sectionCode: uc.sectionCode || "",
    actor: uc.primaryActor || uc.actor || "",             // Format A: "primaryActor", Format B: "actor"
    description: uc.description || "",                    // Optional description field
    // SAFETY NET: steps may be string[] or object[] ({step, action})
    steps: (uc.mainScenario || uc.steps || []).map(s =>
      typeof s === 'string' ? s : (s.action || s.description || "")
    ).join("\n"),
    alternative: (uc.alternativeScenarios || uc.alternativeFlows || []).map(a =>
      (a.name || a.trigger || "") + ": " + (a.steps || a.actions || []).map(s =>
        typeof s === 'string' ? s : (s.action || s.description || "")
      ).join(", ")
    ).join("\n"),
    preconditions: uc.preconditions || [],                // Array of precondition objects or strings
    postconditions: uc.postconditions || [],              // Array of postcondition objects or strings
    errorScenarios: uc.errorScenarios || []               // Array of error scenario objects
  })),
  businessRules: rawBRs.map(br => ({
    name: br.name || br.id || "",
    sectionCode: br.sectionCode || "",
    category: br.category || "",
    statement: br.statement || br.description || "",
    example: (br.examples || []).map(e =>
      typeof e === 'string' ? e : ((e.input || "") + " → " + (e.expected || ""))
    ).join("; ")
  })),
  entities: (mod.entities?.entities || []).map(ent => ({
    name: ent.name,
    description: ent.description || "",
    attributes: (ent.attributes || ent.fields || []).map(a => ({
      name: a.name,
      type: a.type || "string",
      required: a.required || false,
      description: a.description || ""
    })),
    relationships: (ent.relationships || []).map(r =>
      typeof r === 'string' ? r : (r.target || r.to || "") + " (" + (r.type || r.cardinality || "") + ") - " + (r.description || "")
    )
  })),
  permissions: buildPermissionKeys(mod.permissions),    // see below
  notes: "",
  mockupNotes: ""  // Deprecated: wireframes now embedded separately in EMBEDDED_ARTIFACTS
}
```

### Permission Keys Function

The HTML uses `"Role|Action"` format (e.g. `"RH Admin|Consulter"`):

```javascript
// Input: permissions object from flat file (mod.permissions), NOT moduleFeature
// lang: optional language override ('fr' or 'en'), defaults to 'fr'
function buildPermissionKeys(permissionsData, lang = 'fr') {
  const keys = [];

  // Bilingual actionMap based on language parameter
  const actionMap = lang === 'en'
    ? {
        read: 'Read', create: 'Create', update: 'Update', delete: 'Delete',
        approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin',
        Read: 'Read', Create: 'Create', Update: 'Update', Delete: 'Delete',
        Approve: 'Approve', Export: 'Export', Validate: 'Validate', Admin: 'Admin',
        submit: 'Validate', import: 'Create', Submit: 'Validate', Import: 'Create'
      }
    : {
        read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer',
        approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer',
        Read: 'Consulter', Create: 'Créer', Update: 'Modifier', Delete: 'Supprimer',
        Approve: 'Valider', Export: 'Exporter', Validate: 'Valider', Admin: 'Administrer',
        submit: 'Valider', import: 'Créer', Submit: 'Valider', Import: 'Créer'
      };

  const matrix = permissionsData?.matrix || permissionsData?.permissionMatrix;

  // Format A: matrix is array of { role, permissions[] } (ba-009+ format)
  if (Array.isArray(matrix)) {
    matrix.forEach(entry => {
      const role = entry.role || "";
      (entry.permissions || []).forEach(perm => {
        const action = perm.split(".").pop();           // "Read" or "HumanResources.Employees.Read"
        if (action === "*") {
          Object.values(actionMap).forEach(uiAction => {
            const key = role + "|" + uiAction;
            if (!keys.includes(key)) keys.push(key);
          });
        } else {
          const uiAction = actionMap[action] || action;
          const key = role + "|" + uiAction;
          if (!keys.includes(key)) keys.push(key);
        }
      });
    });
    return keys;
  }

  // Format B: legacy matrix.roleAssignments[] format
  if (matrix && matrix.roleAssignments) {
    (matrix.roleAssignments || []).forEach(ra => {
      (ra.permissions || []).forEach(permPath => {
        const action = permPath.split(".").pop();
        if (action === "*") {
          Object.values(actionMap).forEach(uiAction => {
            const key = ra.role + "|" + uiAction;
            if (!keys.includes(key)) keys.push(key);
          });
        } else {
          const uiAction = actionMap[action] || action;
          const key = ra.role + "|" + uiAction;
          if (!keys.includes(key)) keys.push(key);
        }
      });
    });
    return keys;
  }

  // Format C: permissions.json from step-03 with top-level roles[]
  if (keys.length === 0 && permissionsData?.roles) {
    const actionMap_C = lang === 'en'
      ? { read: 'Read', create: 'Create', update: 'Update', delete: 'Delete', approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin' }
      : { read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer', approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer' };

    permissionsData.roles.forEach(entry => {
      const role = entry.role || entry.name || '';
      (entry.permissions || entry.actions || []).forEach(perm => {
        const action = typeof perm === 'string' ? perm.split('.').pop().toLowerCase() : (perm.action || '').toLowerCase();
        const uiAction = actionMap_C[action] || action;
        const key = role + '|' + uiAction;
        if (role && !keys.includes(key)) keys.push(key);
      });
      // Handle wildcard patterns like "HumanResources.*"
      if ((entry.permissionPattern || '').endsWith('*')) {
        Object.values(actionMap_C).forEach(a => {
          const key = (entry.role || entry.name) + '|' + a;
          if (!keys.includes(key)) keys.push(key);
        });
      }
    });
  }

  // Format D: permissionMatrix.roleAssignments[]
  if (keys.length === 0 && permissionsData?.permissionMatrix?.roleAssignments) {
    const actionMap_D = lang === 'en'
      ? { read: 'Read', create: 'Create', update: 'Update', delete: 'Delete', approve: 'Approve', export: 'Export', validate: 'Validate', admin: 'Admin' }
      : { read: 'Consulter', create: 'Créer', update: 'Modifier', delete: 'Supprimer', approve: 'Valider', export: 'Exporter', validate: 'Valider', admin: 'Administrer' };

    permissionsData.permissionMatrix.roleAssignments.forEach(entry => {
      const role = entry.role || '';
      (entry.permissions || []).forEach(perm => {
        if (typeof perm === 'string' && perm.endsWith('.*')) {
          // Wildcard: expand to all standard actions
          Object.values(actionMap_D).forEach(a => {
            const key = role + '|' + a;
            if (!keys.includes(key)) keys.push(key);
          });
        } else {
          const action = typeof perm === 'string' ? perm.split('.').pop().toLowerCase() : '';
          const uiAction = actionMap_D[action] || action;
          const key = role + '|' + uiAction;
          if (role && action && !keys.includes(key)) keys.push(key);
        }
      });
    });
  }

  return keys;
}
```

> **Language handling:** The function accepts an optional `lang` parameter ('fr' or 'en'). Defaults to 'fr' (French). For English labels: Read/Create/Update/Delete/Export/Validate. For French: Consulter/Créer/Modifier/Supprimer/Exporter/Valider.

### Frequency Mapping

```
"Quotidien" → "daily", "Hebdomadaire" → "weekly", "Mensuel" → "monthly"
Default: "daily"
```

### Access Mapping (involvement → access)

```
"decision-maker" → "admin", "end-user" with manager-like tasks → "manager"
"end-user" → "contributor", "observer" → "viewer"
Default: "contributor"
```

---

## EMBEDDED_ARTIFACTS Mapping

> **FIELD RENAME WARNING:** Module JSON files store wireframes with `mockupFormat` and `mockup` fields.
> The HTML renderer reads `format` and `content`. You MUST rename: `mockupFormat` → `format`, `mockup` → `content`.
> Failure to rename = empty mockup display in the browser.

Build a JSON object containing ALL visual artifacts from module JSON files (index.json):

```javascript
{
  wireframes: {
    // FOR EACH module: extract all wireframes from module index.json
    // [moduleCode]: [ {screen, format, content, elements, componentMapping, layout, description} ]
  },
  e2eFlows: [
    // Extract from master consolidation.e2eFlows[]
    // { name, diagram, steps, actors }
  ],
  dependencyGraph: {
    // Extract from master dependencyGraph
    // nodes: [ {id, label, type} ], edges: [ {from, to, description} ]
  }
}
```

### Wireframes Mapping (FLAT-FILE: from screens.json)

> **TWO SOURCE FORMATS:** screens.json may use `screens[]` (Format A) or `sections[]` (Format B).
> Wireframes may be at top level (Format A: `screen.mockup`) or nested (Format B: `section.wireframe`).

For EACH module in `master.modules[]`:

```javascript
const mod = collected_data.modules[moduleCode];
let rawWireframes = [];

// Source A: flat screens[] with mockup/mockupFormat at screen level
const flatScreens = mod.screens?.screens || [];
rawWireframes.push(...flatScreens.filter(s => s.mockup || s.mockupFormat));

// Source B: sections[] with wireframe at section or resource level
const sections = mod.screens?.sections || [];
for (const section of sections) {
  // B1: wireframe directly on section (section.wireframe)
  if (section.wireframe) {
    rawWireframes.push({
      ...section.wireframe,
      section: section.wireframe.section || section.id || section.code || "",
      screen: section.wireframe.screen || section.id || section.code || ""
    });
  }
  // B2: wireframe nested in section.resources[]
  for (const resource of (section.resources || [])) {
    if (resource.wireframe) {
      rawWireframes.push({
        ...resource.wireframe,
        section: resource.wireframe.section || section.id || section.code || "",
        screen: resource.wireframe.screen || `${section.id || section.code}-${resource.code}`
      });
    }
  }
}

// Map to HTML format (RENAME: mockupFormat → format, mockup → content)
EMBEDDED_ARTIFACTS.wireframes[moduleCode] = rawWireframes.map(wf => ({
    screen: wf.screen || wf.name || wf.id || "",
    section: wf.section || "",
    format: wf.mockupFormat || wf.format || "ascii",      // RENAME: mockupFormat → format
    content: wf.mockup || wf.ascii || wf.content || "",    // RENAME: mockup → content
    svgContent: null,
    description: wf.description || "",
    elements: wf.elements || [],
    actions: wf.actions || [],
    componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
      : typeof wf.componentMapping === 'object' && wf.componentMapping !== null
        ? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
        : [],
    layout: typeof wf.layout === 'object' ? wf.layout : null,
    permissionsRequired: wf.permissionsRequired || []
}));
```

### E2E Flows Mapping

From master consolidation:

```javascript
EMBEDDED_ARTIFACTS.e2eFlows = (master.consolidation?.e2eFlows || []).map(flow => ({
  name: flow.name,
  diagram: generateE2EDiagram(flow),                    // ASCII diagram from flow.steps[]
  steps: flow.steps || [],                              // [{ module, action, permission }]
  actors: Array.from(new Set((flow.steps || [])
    .map(s => s.permission?.split(".")[0])              // Extract role from permission path
    .filter(Boolean))).join(", "),
  modules: Array.from(new Set((flow.steps || [])
    .map(s => s.module))).join(" → ")
}));

function generateE2EDiagram(flow) {
  // Generate ASCII diagram from flow.steps[]
  // Example output:
  // "Customer ──[read]──→ Order ──[create]──→ Invoice ──[send]──→"
  // "  (Orders)           (Orders)            (Invoices)"
  // "  Contributor        Manager             System"

  const stepDiagrams = (flow.steps || []).map(s =>
    `${s.action}(${s.module})`
  ).join(" ──→ ");

  return stepDiagrams;
}
```

### Dependency Graph Mapping

From master dependencyGraph:

```javascript
EMBEDDED_ARTIFACTS.dependencyGraph = {
  nodes: (master.modules || []).map(m => ({
    id: m.code,
    label: m.code,
    type: m.featureType || "data-centric"
  })),
  edges: (master.dependencyGraph?.edges || []).map(e => ({
    from: e.from,
    to: e.to,
    description: e.description || ""
  }))
};
```

---

## Usage in step-05-deploy

1. Serialize the FEATURE_DATA object as JSON (with 2-space indentation for readability)
2. Serialize the EMBEDDED_ARTIFACTS object as JSON (with 2-space indentation)
3. Replace `{{FEATURE_DATA}}` with the serialized FEATURE_DATA JSON
4. Replace `{{EMBEDDED_ARTIFACTS}}` with the serialized EMBEDDED_ARTIFACTS JSON
5. Replace `{{APPLICATION_NAME}}` → `{application_name}` (still used in `<title>` and header)
6. Replace `{{APPLICATION_ID}}` → `{feature_id}` (still used in `APP_KEY`)
7. Replace `{{VERSION}}` → `{version}`
8. Replace `{{CREATED_AT}}` → `{ISO timestamp}`
