# FEATURE_DATA & EMBEDDED_ARTIFACTS Build Reference

> Reference for `/business-analyse-html` skill (step-02-build-data) — complete mapping specification for the interactive HTML.
> **Canonical field mapping reference:** See also `data-mapping.md` for the authoritative field name mapping between module JSON files and HTML data objects.

## FEATURE_DATA Object

```javascript
const FEATURE_DATA = {
  metadata: {
    applicationName: master.application_name,
    applicationId: master.feature_id,
    version: master.version,
    createdAt: ISO_TIMESTAMP,
    analysisMode: master.metadata.analysisMode || "interactive" // always "interactive" — analysis is always interactive
  },
  cadrage: {
    // CONTEXT SECTION — merged from problem/asIs/toBe (lean cadrage)
    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 || []),
      acceptanceCriteria: (master.cadrage.acceptanceCriteria || []).map(c => c.criterion)
    },
    scope: {
      // CONVERT index.json keys to HTML keys:
      // inScope → inscope, outOfScope → outofscope
      // CRITICAL: Preserve per-item `module` assignment from coverageMatrix (NOT modules[0])
      inscope: (master.cadrage.globalScope?.inScope || master.cadrage.coverageMatrix?.filter(i => i.category === "inScope") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
      outofscope: (master.cadrage.globalScope?.outOfScope || master.cadrage.coverageMatrix?.filter(i => i.category === "outOfScope") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null })
    },
    stakeholders: (master.cadrage.stakeholders || []).map(s => ({
      role: s.role,
      function: s.function,
      tasks: s.tasks || [],
      frequency: s.frequency || "",
      access: s.involvement || "",
      frustrations: (s.painPoints || []).join("\n")
    }))
  },
  modules: [
    // FOR EACH module in master.modules[]:
    {
      code: module.code,                              // PascalCase key (e.g., "GestionTemps")
      name: module.name || module.code,               // Display name (e.g., "Gestion du temps") — MANDATORY
      description: module.description || "",
      featureType: module.featureType || "data-centric",
      estimatedComplexity: module.estimatedComplexity || "medium",
      entities: module.entities || [],
      // NORMALIZE: source may have strings (coverageMatrix) or objects (module schema)
      anticipatedSections: (module.anticipatedSections || []).map(s =>
        typeof s === 'string' ? { code: s, name: s, description: '', resources: [] } : s
      ),   // ALWAYS [{code, name, description, resources[]}]
      dependencies: module.dependencies || [],
      dependents: module.dependents || []
    }
  ],
  moduleSpecs: {
    // CRITICAL: Must have ONE entry per module with ALL module data
    // FLAT-FILE: Data comes from collected_data.modules[moduleCode] (flat files), NOT module index.json
    "{moduleCode}": {
      // const mod = collected_data.modules[moduleCode];
      // NORMALIZE: handle both key conventions (useCases/usecases, rules/businessRules)
      useCases: (mod.usecases?.useCases || mod.usecases?.usecases || []).map(uc => ({
        // Handle both "name"/"title"/"id" and "primaryActor"/"actor" conventions
        // Handle steps as strings (mainScenario) or objects ({step, action})
      })),
      businessRules: (mod.rules?.rules || mod.rules?.businessRules || []).map(br => ({
        // Handle both "name"/"id" and "statement"/"description" conventions
      })),
      // ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
      entities: (mod.entities?.entities || []).map(ent => ({
        name: ent.name,
        description: ent.description || "",
        attributes: (ent.attributes || []).length > 0
          ? ent.attributes.map(a => ({ name: a.name, type: a.type || "string", required: a.required || false, description: a.description || "" }))
          : (ent.fields || []).map(f => ({ name: f.name, type: f.type || "string", required: f.required || false, description: f.description || "" })),
        relationships: (ent.relationships || []).length > 0
          ? ent.relationships.map(r =>
              typeof r === 'string' ? r : `${r.target} (${r.type}) - ${r.description || ""}`)
          : (ent.fields || []).filter(f => f.foreignKey)
              .map(f => `${f.foreignKey.table} (N:1) - ${f.description || f.name}`)
      })),
      permissions: mod.permissions?.matrix || mod.permissions?.permissions || [],
      apiEndpoints: mod.usecases?.apiEndpoints || []
    }
  },
  consolidation: {
    integrations: master.consolidation.crossModuleInteractions || [],
    sharedEntities: master.consolidation.sharedEntities || [],
    sequenceDiagrams: master.consolidation.e2eFlows || []
  },
  handoff: {
    complexity: master.handoff.complexity,
    implementationStrategy: master.handoff.implementationStrategy,
    moduleOrder: master.handoff.moduleOrder,
    filesToCreate: master.handoff.filesToCreate,
    brToCodeMapping: master.handoff.brToCodeMapping,
    apiEndpointSummary: master.handoff.apiEndpointSummary
  }
};
```

### Build Process

1. Extract metadata from master index.json
2. Extract cadrage from master cadrage.json (CONVERT scope keys: inScope→inscope, outOfScope→outofscope)
3. Extract stakeholders from master cadrage.stakeholders
4. Iterate ALL modules and populate moduleSpecs (THIS IS CRITICAL — empty moduleSpecs = BUG)
5. For EACH module, read FLAT FILES from `collected_data.modules[moduleCode]`:
   - `entities.json` → entities
   - `rules.json` → businessRules
   - `usecases.json` → useCases
   - `permissions.json` → permissions
   - `screens.json` → wireframes
6. Extract consolidation data (integrations, shared entities, E2E flows)
7. Extract handoff section (complexity, strategy, module order, file counts)

## EMBEDDED_ARTIFACTS Object

> **CRITICAL:** The wireframes object is keyed by moduleCode (NOT a flat array).
> Field names MUST be renamed from module JSON files: `mockupFormat` → `format`, `mockup` → `content`.
> The HTML renderer reads `wf.format` and `wf.content` — using module JSON file names will produce EMPTY mockups.

```javascript
const EMBEDDED_ARTIFACTS = {
  wireframes: {
    // PER-MODULE keyed object (NOT a flat array)
    // TWO SOURCE FORMATS: screens.json may use screens[] (Format A) or sections[] (Format B)
    // const mod = collected_data.modules[moduleCode];
    // Collect from ALL sources:
    //   Source A: mod.screens.screens[] with mockup/mockupFormat (flat format)
    //   Source B1: mod.screens.sections[].wireframe (section-level wireframe)
    //   Source B2: mod.screens.sections[].resources[].wireframe (resource-level wireframe)
    //   Source C: auto-generated text for screens/sections without wireframe
    [moduleCode]: rawWireframes.map(wf => ({
      screen: wf.screen || wf.name || wf.title || wf.id || "",
      section: wf.section || "",
      format: wf.mockupFormat || wf.format || "ascii",    // RENAME: mockupFormat → format
      content: wf.mockup || wf.ascii || wf.content || "", // SAFETY NET: mockup/ascii/content → content
      svgContent: null,                              // Populated by SVG generation step
      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 || []
    }))
  },
  e2eFlows: (master.consolidation?.e2eFlows || []).map(flow => ({
    name: flow.name,
    diagram: flow.steps.map(s => `${s.action}(${s.module})`).join(" ──→ "),
    steps: flow.steps || [],
    actors: [...new Set(flow.steps.map(s => s.permission?.split(".")[0]).filter(Boolean))].join(", "),
    modules: [...new Set(flow.steps.map(s => s.module))].join(" → ")
  })),
  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 || ""
    }))
  }
};
```

### Artifact Gathering

1. For EACH module: read `screens.json` from `collected_data.modules[moduleCode].screens`. **Detect format:** if `screens[]` exists → Format A (flat). If `sections[]` exists → Format B (nested). Collect wireframes from all sources (A: screen.mockup, B1: section.wireframe, B2: section.resources[].wireframe, C: auto-text). **Rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`). Store under `wireframes[moduleCode]`
2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
3. Read master's `dependencyGraph` and build nodes/edges
4. Serialize as JSON with 2-space indentation
5. **SVG Wireframe Enrichment** (after wireframe extraction): For EACH wireframe with `content` (ASCII) and `svgContent === null`, generate SVG using parallel Task(sonnet) agents. See [wireframe-svg-style-guide.md](wireframe-svg-style-guide.md) for the complete prompt template and orchestration process. If generation fails for any wireframe → leave `svgContent` as null (graceful fallback to ASCII).

## Placeholder Replacement

1. Serialize FEATURE_DATA as JSON (2-space indent)
2. Serialize EMBEDDED_ARTIFACTS as JSON (2-space indent)
3. Replace `{{FEATURE_DATA}}` with serialized FEATURE_DATA
4. Replace `{{EMBEDDED_ARTIFACTS}}` with serialized EMBEDDED_ARTIFACTS
5. Replace `{{APPLICATION_NAME}}`, `{{APPLICATION_ID}}`, `{{VERSION}}`, `{{CREATED_AT}}`
6. Verify: grep for `{{` to confirm all placeholders replaced
