{
  "_about": "Example mapping for to-dimensionlabs.mjs: a Lumen Coffee help-desk export, one row per message. Keys that start with _ are ignored. Docs: https://docs.dimensionlabs.io/transform/toolkit",

  "input": {
    "format": "auto",
    "_format": "auto picks from the extension: .csv, .tsv, .jsonl, .json, .xlsx",
    "sheet": "Messages",
    "headerRow": 1,
    "skipRowsAfterHeader": 0,
    "_skipRowsAfterHeader": "survey tools often add a row of question text under the column names: set 1 to skip it (Qualtrics adds two: set 2)"
  },

  "shape": "messages",
  "_shape": "messages = one row per message; transcript = one row per conversation with the whole transcript in a cell; survey = one row per respondent with one column per question",

  "columns": {
    "text": "body",
    "userId": "author_id",
    "sessionId": "ticket_id",
    "conversationId": "ticket_id",
    "timestamp": "created_at",
    "_columns": "source column names; use a dot path such as sender.id for nested JSON. Never use an email, phone number or name as userId or sessionId. Optional: intent and intentConfidence, for an intent-based chatbot"
  },

  "userIdForReplies": "customer",
  "_userIdForReplies": "customer (default) gives the agent's and bot's replies the end user's userId, so both sides of a conversation carry the customer's id; own keeps each row's userId",

  "incoming": {
    "column": "author_type",
    "true": ["customer", "end-user"],
    "false": ["agent", "bot", "system"],
    "_incoming": "values are matched case-insensitively; rows with any other value are dropped and counted. Use \"incoming\": true when every row is from the customer"
  },

  "timestamp": {
    "format": "auto",
    "timeZone": "America/Los_Angeles",
    "dayFirst": false,
    "_timestamp": "auto reads Unix ms, Unix seconds, ISO 8601 and Excel serial dates. timeZone applies to times written without a zone, such as 1/3/2026 9:12 AM"
  },

  "clean": {
    "flattenNewlines": true,
    "stripHtml": true,
    "replaceColonsWith": "-"
  },

  "metadata": {
    "keep": "unmapped",
    "drop": ["customer_email", "customer_phone"],
    "rename": { "store_name": "store" },
    "_metadata": "keep: unmapped (every column not used above), none, or a list of column names. Drop personal data you don't need"
  },

  "constants": { "channel": "email" },

  "skip": [
    { "column": "author_type", "values": ["system"] }
  ],
  "_skip": "rows whose column holds one of these values are dropped and counted, for example automatic ticket-closed notes",

  "dedupe": true,
  "spaceMessagesMs": 1000,

  "transcript": {
    "_used_when": "shape is transcript",
    "column": "transcript",
    "speakerPattern": "^(Agent|Customer):\\s*",
    "incomingSpeakers": ["Customer"],
    "outgoingSpeakers": ["Agent"],
    "secondsApart": 1,
    "userId": "session"
  },

  "survey": {
    "_used_when": "shape is survey",
    "questionsFromRow": 2,
    "_questionsFromRow": "the file row (counting from 1) that holds the question wording; it must be one of the rows skipped by input.skipRowsAfterHeader. A question's own text, if set, wins",
    "questions": [
      { "column": "why_score", "text": "Why did you give that score?" },
      { "column": "improve", "text": "What one thing should we improve?" }
    ]
  },

  "output": {
    "format": "csv",
    "path": "out/lumen-tickets",
    "chunkRows": 50000,
    "_output": "csv or jsonl. chunkRows 0 writes one file; otherwise files of about that many rows, never splitting a session"
  }
}
