# 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
    }))
  },
  modules: master.modules.map(m => ({
    code: m.code,
    name: m.name || m.code,                             // display name (MANDATORY, separate from code)
    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"
    // 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")
  })),
  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
function buildPermissionKeys(permissionsData) {
  const keys = [];
  const matrix = permissionsData?.matrix || permissionsData?.permissionMatrix;
  if (!matrix) return keys;
  const actionMap = { read: "Consulter", create: "Creer", update: "Modifier",
                      delete: "Supprimer", validate: "Valider", export: "Exporter",
                      submit: "Valider", import: "Creer" };
  (matrix.roleAssignments || []).forEach(ra => {
    (ra.permissions || []).forEach(permPath => {
      const action = permPath.split(".").pop();         // last segment = action
      if (action === "*") {
        // Wildcard: expand to all standard actions
        Object.values(actionMap).forEach(uiAction => {
          const key = ra.role + "|" + uiAction;
          if (!keys.includes(key)) keys.push(key);
        });
      } else {
        const uiAction = actionMap[action] || action;
        keys.push(ra.role + "|" + uiAction);
      }
    });
  });
  return keys;
}
```

> **Language handling:** The actionMap should use `metadata.language` to select the correct labels. For `fr`: Consulter/Créer/Modifier/Supprimer. For `en`: Read/Create/Update/Delete. Default to English if language is unknown.

### 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}`
