@medicus.ai/medicus-report-pdf-generator 1.3.21 → 1.3.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
- "title": "HealthRiskAssessment",
2
+ "title": "wellbeingReport",
3
3
  "logo": "data:image/png;base64,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",
4
4
  "body_icon": "https://i.ibb.co/2hfhHM3/body-section.png",
5
5
  "lifestyle_icon": "https://i.ibb.co/ZNkkF12/lifestyle-section.png",
@@ -98,7 +98,8 @@ function getDummyData() {
98
98
 
99
99
  // Section labels of the reference design, keyed by cerbo's stable symbol. Keeps
100
100
  // the table wording stable even though the metadata element names differ
101
- // (e.g. "IMMUNE SYSTEM", "LIFESTYLE").
101
+ // (e.g. "IMMUNE SYSTEM", "LIFESTYLE"). These are the English fallbacks; the
102
+ // translated label comes from the locale key "bigIntegralSystem_<symbol>".
102
103
  var SYSTEM_LABELS = {
103
104
  "stomach-and-gi-tract": "STOMACH and GI (Gastrointestinal) Tract",
104
105
  "small-intestine": "Small Intestine",
@@ -112,12 +113,53 @@ var SYSTEM_LABELS = {
112
113
  "lifestyle": "Lifestyle",
113
114
  };
114
115
 
116
+ // English fallbacks for every static label of the section, keyed by locale key.
117
+ var STATIC_LABELS = {
118
+ bigIntegralTitle: "Key to the Big Integral Questionnaire",
119
+ bigIntegralPatientName: "Client/Patient Full Name",
120
+ bigIntegralAge: "Age",
121
+ bigIntegralBmi: "BMI",
122
+ bigIntegralSystemScores: "SYSTEM SCORES",
123
+ bigIntegralSystem: "SYSTEM",
124
+ bigIntegralHigh: "High",
125
+ bigIntegralQuestions: "Questions",
126
+ bigIntegralResult: "Result",
127
+ bigIntegralMaximum: "Maximum",
128
+ bigIntegralHormonalFemale: "Hormonal Health Female",
129
+ bigIntegralHormonalMale: "Hormonal Health Male",
130
+ };
131
+
132
+ // Placeholder rows (getDummyData) carry only the English label; map it back to
133
+ // the symbol so the preview can be translated too.
134
+ var SYMBOL_BY_LABEL = Object.keys(SYSTEM_LABELS).reduce(function (acc, symbol) {
135
+ acc[SYSTEM_LABELS[symbol]] = symbol;
136
+ return acc;
137
+ }, {});
138
+
139
+ // localeService.t() returns the entry's `.message`, which is undefined for a key
140
+ // that is missing from the locale file (i18n then stores the key as a bare
141
+ // string). Fall back to the English label in that case so a missing translation
142
+ // can never print "undefined" or the raw key.
143
+ function makeTranslator(localeService) {
144
+ return function (key, fallback) {
145
+ var value;
146
+ try {
147
+ value = localeService && typeof localeService.t === "function" ? localeService.t(key) : undefined;
148
+ } catch (e) {
149
+ value = undefined;
150
+ }
151
+ return typeof value === "string" && value.trim() && value !== key ? value : fallback;
152
+ };
153
+ }
154
+
115
155
  function buildRowsFromDoctorScores(doctorScores, meta) {
116
156
  var groups = (doctorScores && doctorScores.groups) || [];
117
157
  if (!groups.length) return null;
118
158
 
119
159
  var rows = groups.map(function (group) {
120
160
  return {
161
+ key: "system:" + group.symbol,
162
+ symbol: group.symbol,
121
163
  system: SYSTEM_LABELS[group.symbol] || group.name || group.symbol,
122
164
  high:
123
165
  group.highThreshold === null || group.highThreshold === undefined
@@ -132,29 +174,44 @@ function buildRowsFromDoctorScores(doctorScores, meta) {
132
174
 
133
175
  // Hormonal Health is never scored (no maximum in the spreadsheet either); the
134
176
  // rows exist so the doctor can see the questions were asked.
135
- rows.push({
136
- system: "Hormonal Health Female",
137
- high: null,
138
- questions: null,
139
- result: 0,
140
- maximum: null,
141
- });
142
- rows.push({
143
- system: "Hormonal Health Male",
177
+ rows.push(hormonalRow("female"));
178
+ rows.push(hormonalRow("male"));
179
+
180
+ return { rows: rows, meta: meta || {} };
181
+ }
182
+
183
+ function hormonalRow(sex) {
184
+ return {
185
+ key: "hormonal:" + sex,
186
+ symbol: null,
187
+ system: sex === "female" ? STATIC_LABELS.bigIntegralHormonalFemale : STATIC_LABELS.bigIntegralHormonalMale,
144
188
  high: null,
145
189
  questions: null,
146
190
  result: 0,
147
191
  maximum: null,
148
- });
192
+ };
193
+ }
149
194
 
150
- return { rows: rows, meta: meta || {} };
195
+ // Gives placeholder rows the same identity fields as real rows.
196
+ function normalizeRow(row) {
197
+ if (row.key) return row;
198
+ if (row.system === STATIC_LABELS.bigIntegralHormonalFemale) return hormonalRow("female");
199
+ if (row.system === STATIC_LABELS.bigIntegralHormonalMale) return hormonalRow("male");
200
+ var symbol = SYMBOL_BY_LABEL[row.system] || null;
201
+ return Object.assign({}, row, { key: "system:" + (symbol || row.system), symbol: symbol });
202
+ }
203
+
204
+ function translateSystem(row, tr) {
205
+ if (row.key === "hormonal:female") return tr("bigIntegralHormonalFemale", row.system);
206
+ if (row.key === "hormonal:male") return tr("bigIntegralHormonalMale", row.system);
207
+ return row.symbol ? tr("bigIntegralSystem_" + row.symbol, row.system) : row.system;
151
208
  }
152
209
 
153
210
  // ---------------------------------------------------------------------------
154
211
  // Helpers
155
212
  // ---------------------------------------------------------------------------
156
213
  function fmt(val, decimals) {
157
- if (val === null || val === undefined) return "\u2014"; // em dash
214
+ if (val === null || val === undefined) return "—"; // em dash
158
215
  if (typeof val === "number") {
159
216
  const rounded =
160
217
  Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals);
@@ -163,6 +220,20 @@ function fmt(val, decimals) {
163
220
  return String(val);
164
221
  }
165
222
 
223
+ function escapeHtml(value) {
224
+ return String(value === null || value === undefined ? "" : value)
225
+ .replace(/&/g, "&amp;")
226
+ .replace(/</g, "&lt;")
227
+ .replace(/>/g, "&gt;")
228
+ .replace(/"/g, "&quot;");
229
+ }
230
+
231
+ // Numbers, thresholds (">8") and "/3" must keep left-to-right order inside an
232
+ // Arabic (RTL) table, otherwise they render mirrored ("8<", "3/").
233
+ function ltr(content) {
234
+ return `<span dir="ltr" style="direction:ltr;unicode-bidi:isolate;">${content}</span>`;
235
+ }
236
+
166
237
  // ---------------------------------------------------------------------------
167
238
  // Single render — faithful to the reference image, Maison Sante colours.
168
239
  //
@@ -178,28 +249,38 @@ function fmt(val, decimals) {
178
249
  // ├────────────┼──────┼─────────┼────────┼───────┼──┼───┤
179
250
  // │ ...rows... │
180
251
  // └──────────────────────────────────────────────────────┘
252
+ // In Arabic the whole block is laid out right-to-left (dir="rtl"): the SYSTEM
253
+ // column moves to the right edge and every label comes from the locale files.
181
254
  // ---------------------------------------------------------------------------
182
- function renderTable(rows, meta, cfg) {
255
+ function renderTable(rows, meta, cfg, tr, isRtl) {
183
256
  const c = cfg.colors;
184
- const title = cfg.title || "Key to the Big Integral Questionnaire";
257
+ const title = tr("bigIntegralTitle", cfg.title || STATIC_LABELS.bigIntegralTitle);
258
+ const dir = isRtl ? "rtl" : "ltr";
259
+ const start = isRtl ? "right" : "left";
260
+ // Letter-spacing breaks the joining of Arabic letters, so drop it in RTL.
261
+ const titleSpacing = isRtl ? "0" : "0.3px";
262
+ const bandSpacing = isRtl ? "0" : "0.8px";
263
+ const fontFamily = isRtl ? "'Cairo','Open Sans',sans-serif" : "'Open Sans',sans-serif";
264
+
265
+ // Padding with the "start" side first, mirrored for RTL.
266
+ const pad = (top, startPx, bottom, endPx) =>
267
+ isRtl
268
+ ? `padding:${top}px ${startPx}px ${bottom}px ${endPx}px;`
269
+ : `padding:${top}px ${endPx}px ${bottom}px ${startPx}px;`;
185
270
 
186
271
  // ── patient info bar ──────────────────────────────────────────────────
272
+ const infoCell = (label, value, startPx, endPx) => `
273
+ <td style="${pad(7, startPx, 7, endPx)}font-size:9px;color:#1a1a1a;white-space:nowrap;vertical-align:middle;text-align:${start};">
274
+ <span style="color:${c.headerBg};font-weight:700;">${label}</span>
275
+ &nbsp;&nbsp;&nbsp;<bdi>${escapeHtml(value)}</bdi>
276
+ </td>`;
187
277
  const infoBar = `
188
278
  <div style="background:#ffffff;border-bottom:1px solid ${c.rowBorder};">
189
- <table style="border-collapse:collapse;">
279
+ <table dir="${dir}" style="border-collapse:collapse;">
190
280
  <tr>
191
- <td style="padding:7px 40px 7px 14px;font-size:9px;color:#1a1a1a;white-space:nowrap;vertical-align:middle;">
192
- <span style="color:${c.headerBg};font-weight:700;">Client/Patient Full Name</span>
193
- &nbsp;&nbsp;&nbsp;${meta.patientName}
194
- </td>
195
- <td style="padding:7px 40px 7px 0;font-size:9px;color:#1a1a1a;white-space:nowrap;vertical-align:middle;">
196
- <span style="color:${c.headerBg};font-weight:700;">Age</span>
197
- &nbsp;&nbsp;&nbsp;${meta.age}
198
- </td>
199
- <td style="padding:7px 0 7px 0;font-size:9px;color:#1a1a1a;white-space:nowrap;vertical-align:middle;">
200
- <span style="color:${c.headerBg};font-weight:700;">BMI</span>
201
- &nbsp;&nbsp;&nbsp;${meta.bmi}
202
- </td>
281
+ ${infoCell(tr("bigIntegralPatientName", STATIC_LABELS.bigIntegralPatientName), meta.patientName, 14, 40)}
282
+ ${infoCell(tr("bigIntegralAge", STATIC_LABELS.bigIntegralAge), meta.age, 0, 40)}
283
+ ${infoCell(tr("bigIntegralBmi", STATIC_LABELS.bigIntegralBmi), meta.bmi, 0, 0)}
203
284
  </tr>
204
285
  </table>
205
286
  </div>`;
@@ -213,13 +294,14 @@ function renderTable(rows, meta, cfg) {
213
294
  color: #ffffff;
214
295
  font-size: 8.5px;
215
296
  font-weight: 700;
216
- letter-spacing: 0.8px;
297
+ letter-spacing: ${bandSpacing};
217
298
  text-transform: uppercase;
218
- ">SYSTEM SCORES</div>`;
299
+ text-align: ${start};
300
+ ">${tr("bigIntegralSystemScores", STATIC_LABELS.bigIntegralSystemScores)}</div>`;
219
301
 
220
302
  // ── column widths ──────────────────────────────────────────────────────
221
303
  const COL = {
222
- system: "width:45%;text-align:left;",
304
+ system: `width:45%;text-align:${start};`,
223
305
  high: "width:10%;text-align:center;",
224
306
  questions: "width:12%;text-align:center;",
225
307
  result: "width:10%;text-align:center;",
@@ -235,12 +317,12 @@ function renderTable(rows, meta, cfg) {
235
317
 
236
318
  const tableHeader = `
237
319
  <tr>
238
- ${th("SYSTEM", "system")}
239
- ${th("High", "high")}
240
- ${th("Questions", "questions")}
241
- ${th("Result", "result")}
242
- ${th("Maximum", "maximum")}
243
- ${th("/3", "perThree")}
320
+ ${th(tr("bigIntegralSystem", STATIC_LABELS.bigIntegralSystem), "system")}
321
+ ${th(tr("bigIntegralHigh", STATIC_LABELS.bigIntegralHigh), "high")}
322
+ ${th(tr("bigIntegralQuestions", STATIC_LABELS.bigIntegralQuestions), "questions")}
323
+ ${th(tr("bigIntegralResult", STATIC_LABELS.bigIntegralResult), "result")}
324
+ ${th(tr("bigIntegralMaximum", STATIC_LABELS.bigIntegralMaximum), "maximum")}
325
+ ${th(ltr("/3"), "perThree")}
244
326
  </tr>`;
245
327
 
246
328
  // ── data rows ─────────────────────────────────────────────────────────
@@ -254,19 +336,19 @@ function renderTable(rows, meta, cfg) {
254
336
  const hasMaximum = row.maximum !== null && row.maximum !== undefined;
255
337
 
256
338
  const highDisplay = hasThreshold
257
- ? `<span style="color:${c.elevatedText};font-weight:600;">${row.high}</span>`
258
- : "\u2014";
339
+ ? `<span style="color:${c.elevatedText};font-weight:600;">${ltr(escapeHtml(row.high))}</span>`
340
+ : "—";
259
341
 
260
- const questionsDisplay = hasQuantity ? String(row.questions) : "\u2014";
261
- const resultDisplay = String(row.result);
262
- const maximumDisplay = hasMaximum ? fmt(row.maximum, 0) : "\u2014";
263
- const perThreeDisplay = hasMaximum ? fmt(row.maximum / 3, 2) : "\u2014";
342
+ const questionsDisplay = hasQuantity ? ltr(String(row.questions)) : "—";
343
+ const resultDisplay = ltr(String(row.result));
344
+ const maximumDisplay = hasMaximum ? ltr(fmt(row.maximum, 0)) : "—";
345
+ const perThreeDisplay = hasMaximum ? ltr(fmt(row.maximum / 3, 2)) : "—";
264
346
 
265
347
  const rowBg = i % 2 === 0 ? "#ffffff" : "rgba(15, 32, 98, 0.03)";
266
348
 
267
349
  return `
268
350
  <tr style="background:${rowBg};">
269
- ${TD(`<span style="color:#1a1a1a;">${row.system}</span>`, "system")}
351
+ ${TD(`<span style="color:#1a1a1a;">${escapeHtml(translateSystem(row, tr))}</span>`, "system")}
270
352
  ${TD(highDisplay, "high")}
271
353
  ${TD(questionsDisplay, "questions")}
272
354
  ${TD(resultDisplay, "result", `font-weight:600;color:#444;`)}
@@ -281,9 +363,9 @@ function renderTable(rows, meta, cfg) {
281
363
  // empty divs with only `height` get collapsed by wkhtmltopdf. Padding
282
364
  // always renders.
283
365
  return `
284
- <div style="padding-top:25px;font-family:'Open Sans',sans-serif;">
366
+ <div dir="${dir}" style="padding-top:25px;font-family:${fontFamily};direction:${dir};text-align:${start};">
285
367
  <div class="biq-wrapper" style="
286
- font-family: 'Open Sans', sans-serif;
368
+ font-family: ${fontFamily};
287
369
  page-break-inside: avoid;
288
370
  border-radius: 4px;
289
371
  overflow: hidden;
@@ -297,13 +379,14 @@ function renderTable(rows, meta, cfg) {
297
379
  padding: 9px 14px;
298
380
  font-size: 11px;
299
381
  font-weight: 700;
300
- letter-spacing: 0.3px;
382
+ letter-spacing: ${titleSpacing};
383
+ text-align: ${start};
301
384
  ">${title}</div>
302
385
 
303
386
  ${infoBar}
304
387
  ${subheaderBand}
305
388
 
306
- <table style="width:100%;border-collapse:collapse;table-layout:fixed;">
389
+ <table dir="${dir}" style="width:100%;border-collapse:collapse;table-layout:fixed;">
307
390
  <thead>${tableHeader}</thead>
308
391
  <tbody>${dataRows}</tbody>
309
392
  </table>
@@ -318,11 +401,16 @@ function renderTable(rows, meta, cfg) {
318
401
  * Render the Big Integral Questionnaire section for Maison Sante doctor PDFs.
319
402
  *
320
403
  * @param {object} opts
321
- * @param {object} opts.clientConfig - The full maisonsante.json config object
322
- * @param {object} opts.localeService - localeService with .t(key) method (unused for now; strings are hardcoded to match the reference design)
323
- * @returns {string} HTML string to inject into #big-integral-section
404
+ * @param {object} opts.clientConfig - The full maisonsante.json config object
405
+ * @param {object} opts.doctorScores - The `doctorScores` payload from cerbo
406
+ * @param {object} opts.meta - { patientName, age, bmi }
407
+ * @param {object} opts.localeService - localeService (already set to the report language) with .t(key)
408
+ * @param {boolean} opts.isRtl - true for Arabic reports
409
+ * @param {boolean} opts.usePlaceholder - render the design placeholder when doctorScores is absent
410
+ * (preview script only; real PDFs never show dummy data)
411
+ * @returns {string} HTML string to inject into #big-integral-section ('' when there is nothing to show)
324
412
  */
325
- function renderBigIntegralQuestionnaire({ clientConfig, doctorScores, meta }) {
413
+ function renderBigIntegralQuestionnaire({ clientConfig, doctorScores, meta, localeService, isRtl = false, usePlaceholder = false }) {
326
414
  const cfg = (clientConfig && clientConfig.big_integral_questionnaire) || {};
327
415
 
328
416
  const colors = Object.assign(
@@ -338,30 +426,28 @@ function renderBigIntegralQuestionnaire({ clientConfig, doctorScores, meta }) {
338
426
 
339
427
  const resolvedCfg = Object.assign({}, cfg, { colors });
340
428
 
341
- // Real payload when cerbo sent one; the dummy table is the design placeholder
342
- // and is only reached when doctorScores is absent (e.g. an unfinished quiz).
429
+ // Cerbo only sends doctorScores for a FINISHED quiz. Without it there is no real
430
+ // data: the section is omitted rather than filled with the placeholder patient
431
+ // ("Final Test 3", age 37, BMI 23.67) that getDummyData() carries for the preview.
343
432
  const real = buildRowsFromDoctorScores(doctorScores, meta);
344
- if (!real) {
345
- console.log("[BigIntegral] no doctorScores in payload — rendering placeholder table");
346
- } else {
433
+ if (!real && !usePlaceholder) {
434
+ console.log("[BigIntegral] no doctorScores in payload — section omitted");
435
+ return "";
436
+ }
437
+ if (real) {
347
438
  console.log("[BigIntegral] rendering " + real.rows.length + " rows from doctorScores");
348
439
  }
349
440
  const source = real || getDummyData();
350
- const allRows = source.rows;
441
+ const allRows = source.rows.map(normalizeRow);
351
442
  const resolvedMeta = source.meta;
352
443
 
353
444
  const rows = allRows.filter((row) => {
354
- if (
355
- row.system === "Hormonal Health Female" &&
356
- cfg.showHormonalFemale === false
357
- )
358
- return false;
359
- if (row.system === "Hormonal Health Male" && cfg.showHormonalMale === false)
360
- return false;
445
+ if (row.key === "hormonal:female" && cfg.showHormonalFemale === false) return false;
446
+ if (row.key === "hormonal:male" && cfg.showHormonalMale === false) return false;
361
447
  return true;
362
448
  });
363
449
 
364
- return renderTable(rows, resolvedMeta, resolvedCfg);
450
+ return renderTable(rows, resolvedMeta, resolvedCfg, makeTranslator(localeService), !!isRtl);
365
451
  }
366
452
 
367
453
  module.exports = { renderBigIntegralQuestionnaire };
@@ -13,6 +13,39 @@ const { cli } = require("webpack");
13
13
 
14
14
  var localeService = new LocaleService(i18n);
15
15
 
16
+ /**
17
+ * Translate `key` for the current report language, falling back to `fallback`.
18
+ * localeService.t() returns undefined for a key missing from the locale file
19
+ * (i18n stores it as a bare string without `.message`), which would otherwise be
20
+ * printed as "undefined".
21
+ */
22
+ const tOr = (key, fallback) => {
23
+ let value;
24
+ try {
25
+ value = localeService.t(key);
26
+ } catch (e) {
27
+ value = undefined;
28
+ }
29
+ return typeof value === 'string' && value.trim() && value !== key ? value : fallback;
30
+ };
31
+
32
+ /** Score statements are null for a score that has not been calculated yet. */
33
+ const textOr = (value, fallback = '') => (value === null || value === undefined ? fallback : value);
34
+
35
+ /**
36
+ * Metrics for a score that is not available yet (null in the payload, e.g. a
37
+ * wellbeing quiz still in progress). parseFloat(null) is NaN, which the gauge
38
+ * used to print as "NaNaN"; show an em dash with the needle at the start instead.
39
+ */
40
+ const missingScoreMetrics = (client = 'default') => ({
41
+ score: null,
42
+ amount: 214,
43
+ color: client === 'nasco' ? config.second_level_color : config.first_level_color,
44
+ rotateAmount: 0,
45
+ scoreInteger: '\u2014',
46
+ scoreDecimal: ''
47
+ });
48
+
16
49
 
17
50
  let OUT_FILE = 'output/nasco-pdf.html';
18
51
  let Pdf_file = '';
@@ -280,6 +313,9 @@ const wrapLTR = (text) => {
280
313
  */
281
314
  const calculateScoreMetrics = (score, client = 'default') => {
282
315
  const scoreValue = parseFloat(score);
316
+ if (!Number.isFinite(scoreValue)) {
317
+ return missingScoreMetrics(client);
318
+ }
283
319
 
284
320
  // Determine amount based on score range
285
321
  let amount;
@@ -413,6 +449,9 @@ const calculateExtendedScoreMetrics = (score, scoreType, client = 'default') =>
413
449
  */
414
450
  const calculatePhysicalScoreMetrics = (score, client = 'default') => {
415
451
  const scoreValue = parseFloat(score);
452
+ if (!Number.isFinite(scoreValue)) {
453
+ return missingScoreMetrics(client);
454
+ }
416
455
 
417
456
  let amount;
418
457
  if (scoreValue > 40 && scoreValue < 70) {
@@ -475,14 +514,14 @@ const renderFirstSection = (template, calculationData, physicalMetrics, psycholo
475
514
  return template
476
515
  .replace("{{physicalScore}}", physicalMetrics.scoreInteger)
477
516
  .replace("{{physicalScoreDecimalPart}}", physicalMetrics.scoreDecimal)
478
- .replace("{{physicalScoreStatement}}", calculationData.physicalScoreStatement)
517
+ .replace("{{physicalScoreStatement}}", textOr(calculationData.physicalScoreStatement))
479
518
  .replace("{{PhysicalWord}}", localeService.t('physical'))
480
519
  .replace(/{{rotatePhysicalAmount}}/gi, physicalMetrics.rotateAmount)
481
520
  .replace(/{{physicalColor}}/gi, physicalMetrics.color)
482
521
  .replace("{{psychologicalScore}}", psychologicalMetrics.scoreInteger)
483
522
  .replace("{{psychologicalScoreDecimalPart}}", psychologicalMetrics.scoreDecimal)
484
523
  .replace("{{psychologicalWord}}", localeService.t('psychological'))
485
- .replace("{{psychologicalScoreStatement}}", calculationData.psychologicalScoreStatement)
524
+ .replace("{{psychologicalScoreStatement}}", textOr(calculationData.psychologicalScoreStatement))
486
525
  .replace(/{{rotatePsychologicalAmount}}/gi, psychologicalMetrics.rotateAmount)
487
526
  .replace(/{{fifthLevelColor}}/g, config.fifth_level_color)
488
527
  .replace(/{{thirdLevelColor}}/g, config.third_level_color)
@@ -507,19 +546,19 @@ const renderExtendedFirstSection = (template, calculationData, metrics, client)
507
546
  return template
508
547
  .replace("{{depressionRiskScore}}", metrics.depression.scoreInteger)
509
548
  .replace("{{depressionRiskScoreDecimalPart}}", metrics.depression.scoreDecimal)
510
- .replace("{{depressionRiskScoreStatement}}", calculationData.depressionRiskScoreStatement)
549
+ .replace("{{depressionRiskScoreStatement}}", textOr(calculationData.depressionRiskScoreStatement))
511
550
  .replace("{{DepressionRiskWord}}", localeService.t('depressionRisk'))
512
551
  .replace(/{{rotateDepressionRiskAmount}}/gi, metrics.depression.rotateAmount)
513
552
  .replace(/{{depressionRiskColor}}/gi, metrics.depression.color)
514
553
  .replace("{{anxietyRiskScore}}", metrics.anxiety.scoreInteger)
515
554
  .replace("{{anxietyRiskScoreDecimalPart}}", metrics.anxiety.scoreDecimal)
516
- .replace("{{anxietyRiskScoreStatement}}", calculationData.anxietyRiskScoreStatement)
555
+ .replace("{{anxietyRiskScoreStatement}}", textOr(calculationData.anxietyRiskScoreStatement))
517
556
  .replace("{{AnxietyRiskWord}}", localeService.t('anxietyRisk'))
518
557
  .replace(/{{rotateAnxietyRiskAmount}}/gi, metrics.anxiety.rotateAmount)
519
558
  .replace(/{{anxietyRiskColor}}/gi, metrics.anxiety.color)
520
559
  .replace("{{physicalScore}}", metrics.physical.scoreInteger)
521
560
  .replace("{{physicalScoreDecimalPart}}", metrics.physical.scoreDecimal)
522
- .replace("{{physicalScoreStatement}}", calculationData.physicalScoreStatement)
561
+ .replace("{{physicalScoreStatement}}", textOr(calculationData.physicalScoreStatement))
523
562
  .replace("{{PhysicalWord}}", localeService.t('physicalScore'))
524
563
  .replace(/{{rotatePhysicalAmount}}/gi, metrics.physical.rotateAmount)
525
564
  .replace(/{{physicalColor}}/gi, metrics.physical.color)
@@ -531,7 +570,7 @@ const renderExtendedFirstSection = (template, calculationData, metrics, client)
531
570
  .replace("{{psychologicalScore}}", metrics.psychological.scoreInteger)
532
571
  .replace("{{psychologicalScoreDecimalPart}}", metrics.psychological.scoreDecimal)
533
572
  .replace("{{psychologicalWord}}", localeService.t('psychological'))
534
- .replace("{{psychologicalScoreStatement}}", calculationData.psychologicalScoreStatement)
573
+ .replace("{{psychologicalScoreStatement}}", textOr(calculationData.psychologicalScoreStatement))
535
574
  .replace(/{{rotatePsychologicalAmount}}/gi, metrics.psychological.rotateAmount)
536
575
  .replace(/{{fifthLevelColor}}/g, config.fifth_level_color)
537
576
  .replace(/{{thirdLevelColor}}/g, config.third_level_color)
@@ -999,7 +1038,7 @@ const renderDoctorDetails = (doctorData, startQuizAnswersOnNewPage = false) => {
999
1038
  : columnsHTML;
1000
1039
 
1001
1040
  return `
1002
- <p class="resume-title">Quiz Answers</p>
1041
+ <p class="resume-title">${tOr('quizAnswers', 'Quiz Answers')}</p>
1003
1042
  ${bodyHTML}
1004
1043
  `;
1005
1044
  };
@@ -1415,10 +1454,12 @@ let generateHTMLWellbeingReportWithSmartReport = async (data, isDebugging, clien
1415
1454
  ? profileInfo.find(function (item) { return item && /bmi/i.test(String(item.title || '')); })
1416
1455
  : null;
1417
1456
  console.log('[BigIntegral] maisonsante doctor PDF — doctorScores '
1418
- + (empty(data.doctorScores) ? 'ABSENT (placeholder table)' : 'present, groups=' + (data.doctorScores.groups || []).length));
1457
+ + (empty(data.doctorScores) ? 'ABSENT (section omitted)' : 'present, groups=' + (data.doctorScores.groups || []).length));
1419
1458
  const integralHtml = renderBigIntegralQuestionnaire({
1420
1459
  clientConfig: rawClientConfig,
1421
1460
  doctorScores: data.doctorScores,
1461
+ localeService,
1462
+ isRtl,
1422
1463
  meta: {
1423
1464
  patientName: wellbeingData.name || '',
1424
1465
  age: wellbeingData.age === undefined || wellbeingData.age === null ? '' : String(wellbeingData.age),
@@ -1788,15 +1829,69 @@ const cleanStyledHeader = (html, profileInfo, language = 'ar', clientName = 'med
1788
1829
  `;
1789
1830
  };
1790
1831
 
1832
+ /**
1833
+ * Translate `key` in an explicit locale. The footer is built after the HTML, so it
1834
+ * must not rely on whatever locale the shared localeService was last switched to.
1835
+ */
1836
+ const tForLocale = (locale, key, fallback) => {
1837
+ let entry;
1838
+ try {
1839
+ entry = i18n.__({ phrase: key, locale });
1840
+ } catch (e) {
1841
+ entry = undefined;
1842
+ }
1843
+ const value = entry && typeof entry === 'object' ? entry.message : undefined;
1844
+ return typeof value === 'string' && value.trim() && value !== key ? value : fallback;
1845
+ };
1846
+
1847
+ /**
1848
+ * Maison Sante footer for Arabic reports: Arabic labels, Arabic address, laid out
1849
+ * right-to-left (page number on the right, contact block on the left). Latin
1850
+ * values (e-mail address, page numbers) are isolated so they keep their order.
1851
+ * Cairo is installed in the PDF service image (Dockerfile), Open Sans is the fallback.
1852
+ * @param {string} language - report locale, e.g. 'ar-SA'
1853
+ * @returns {string} Footer template HTML
1854
+ */
1855
+ const getMaisonSanteArabicPdfFooterTemplate = (language) => {
1856
+ const font = "font-family: 'Cairo', 'Open Sans', sans-serif;";
1857
+ const isolate = "direction: ltr; unicode-bidi: isolate; font-family: 'Open Sans', sans-serif; font-feature-settings: 'tnum';";
1858
+ const page = tForLocale(language, 'Page', 'صفحة');
1859
+ const of = tForLocale(language, 'pageOf', 'من');
1860
+ const addressLabel = tForLocale(language, 'footerAddressLabel', 'العنوان');
1861
+ const address = tForLocale(language, 'maisonSanteFooterAddress', 'ميان مول، أم سقيم 2، دبي، الإمارات العربية المتحدة');
1862
+ const emailLabel = tForLocale(language, 'footerEmailLabel', 'البريد الإلكتروني');
1863
+ return `
1864
+ <div dir="rtl" style="width: 100%; font-size:7.3px; color: ${config.colors.footerText}; padding: 0 20px; box-sizing: border-box; display: flex; justify-content: space-between; align-items: center; direction: rtl; ${font}">
1865
+ <div style="display: flex; align-items: center; ${font}">
1866
+ <span style="${font}">${page}
1867
+ <span class="pageNumber" style="${isolate}"></span>
1868
+ ${of}
1869
+ <span class="totalPages" style="${isolate}"></span>
1870
+ </span>
1871
+ </div>
1872
+ <div style="display: flex; flex-direction: column; align-items: flex-end; text-align: left; ${font}">
1873
+ <div style="${font}">${addressLabel}: ${address}</div>
1874
+ <div style="${font}">${emailLabel}: <span style="${isolate}">info@maisonsante.ae</span></div>
1875
+ </div>
1876
+ </div>
1877
+ `;
1878
+ };
1879
+
1791
1880
  /**
1792
1881
  * Get PDF footer template HTML (client-specific contact info)
1793
1882
  * @param {string} [client='mediclinic'] - Client identifier: 'mediclinic' | 'pha' | other
1794
1883
  * @param {array} [selectedLabs=[]] - Selected divisions/labs (e.g. ['wp'], ['labcorp'])
1795
1884
  * @param {object} [footerFromData] - Footer data from input (e.g. { address: "123 Main St..." }) for PHA lab footers
1885
+ * @param {string} [language='en'] - Report locale; Maison Sante Arabic reports get a translated RTL footer
1796
1886
  * @returns {string} Footer template HTML
1797
1887
  */
1798
- const getPdfFooterTemplate = (client = 'mediclinic', selectedLabs = [], footerFromData = null) => {
1888
+ const getPdfFooterTemplate = (client = 'mediclinic', selectedLabs = [], footerFromData = null, language = 'en') => {
1799
1889
  const clientKey = (client || 'mediclinic').toLocaleLowerCase();
1890
+ // Maison Sante Arabic reports get a translated, right-to-left footer. Every other
1891
+ // client/language keeps the footer below exactly as it was.
1892
+ if (clientKey === 'maisonsante' && /^ar/i.test(String(language || ''))) {
1893
+ return getMaisonSanteArabicPdfFooterTemplate(language);
1894
+ }
1800
1895
  const isPhaWithWp = clientKey === 'pha' && selectedLabs?.some(lab => lab?.toLowerCase() === 'wp');
1801
1896
  const isPhaWithLabcorp = clientKey === 'pha' && selectedLabs?.some(lab => lab?.toLowerCase() === 'labcorp');
1802
1897
  const footerAddress =
@@ -1909,7 +2004,7 @@ const setupExtendedPageContent = async (page, contentHtml, filePath) => {
1909
2004
  const generatePDFReport = async (data, hideWellbeingUI = false) => {
1910
2005
  try {
1911
2006
  const contentHtml = data.html;
1912
- const footerTemplate = getPdfFooterTemplate(data.client, data.selectedLabs, data.footer);
2007
+ const footerTemplate = getPdfFooterTemplate(data.client, data.selectedLabs, data.footer, data.language);
1913
2008
 
1914
2009
  // Launch browser and setup page
1915
2010
  const browser = await launchExtendedBrowser();
@@ -146,5 +146,109 @@
146
146
  "referenceTitle": {
147
147
  "message": "الرقم المرجعي",
148
148
  "description": "التسمية الظاهرة فوق الأطباء الذين وقعوا على هذا التقرير"
149
+ },
150
+ "bigIntegralTitle": {
151
+ "message": "مفتاح الاستبيان التكاملي الشامل",
152
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
153
+ },
154
+ "bigIntegralPatientName": {
155
+ "message": "الاسم الكامل للعميل/المريض",
156
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
157
+ },
158
+ "bigIntegralAge": {
159
+ "message": "العمر",
160
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
161
+ },
162
+ "bigIntegralBmi": {
163
+ "message": "منسب كتلة الجسم (BMI)",
164
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
165
+ },
166
+ "bigIntegralSystemScores": {
167
+ "message": "نتائج الأجهزة",
168
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
169
+ },
170
+ "bigIntegralSystem": {
171
+ "message": "الجهاز",
172
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
173
+ },
174
+ "bigIntegralHigh": {
175
+ "message": "مرتفع",
176
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
177
+ },
178
+ "bigIntegralQuestions": {
179
+ "message": "الأسئلة",
180
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
181
+ },
182
+ "bigIntegralResult": {
183
+ "message": "النتيجة",
184
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
185
+ },
186
+ "bigIntegralMaximum": {
187
+ "message": "الحد الأقصى",
188
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
189
+ },
190
+ "bigIntegralHormonalFemale": {
191
+ "message": "الصحة الهرمونية (إناث)",
192
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
193
+ },
194
+ "bigIntegralHormonalMale": {
195
+ "message": "الصحة الهرمونية (ذكور)",
196
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
197
+ },
198
+ "bigIntegralSystem_stomach-and-gi-tract": {
199
+ "message": "المعدة والجهاز الهضمي",
200
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
201
+ },
202
+ "bigIntegralSystem_small-intestine": {
203
+ "message": "الأمعاء الدقيقة",
204
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
205
+ },
206
+ "bigIntegralSystem_infections-invasions-of-the-gi": {
207
+ "message": "العدوى/الغزو في الجهاز الهضمي",
208
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
209
+ },
210
+ "bigIntegralSystem_large-intestine": {
211
+ "message": "الأمعاء الغليظة",
212
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
213
+ },
214
+ "bigIntegralSystem_liver-and-gallbladder": {
215
+ "message": "الكبد والمرارة",
216
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
217
+ },
218
+ "bigIntegralSystem_essential-nutrients": {
219
+ "message": "العناصر الغذائية الأساسية",
220
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
221
+ },
222
+ "bigIntegralSystem_carbohydrate-metabolism": {
223
+ "message": "استقلاب الكربوهيدرات",
224
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
225
+ },
226
+ "bigIntegralSystem_immune-system": {
227
+ "message": "الجهاز المناعي",
228
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
229
+ },
230
+ "bigIntegralSystem_lifestyle": {
231
+ "message": "نمط الحياة",
232
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
233
+ },
234
+ "quizAnswers": {
235
+ "message": "إجابات الاستبيان",
236
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
237
+ },
238
+ "pageOf": {
239
+ "message": "من",
240
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
241
+ },
242
+ "footerAddressLabel": {
243
+ "message": "العنوان",
244
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
245
+ },
246
+ "footerEmailLabel": {
247
+ "message": "البريد الإلكتروني",
248
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
249
+ },
250
+ "maisonSanteFooterAddress": {
251
+ "message": "ميان مول، أم سقيم 2، دبي، الإمارات العربية المتحدة",
252
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
149
253
  }
150
- }
254
+ }
@@ -254,5 +254,109 @@
254
254
  "PsychologicalWellbeingText": {
255
255
  "message": "الصحة النفسية",
256
256
  "description": "التسمية المعروضة فوق الأطباء الذين وقعوا على هذا التقرير."
257
+ },
258
+ "bigIntegralTitle": {
259
+ "message": "مفتاح الاستبيان التكاملي الشامل",
260
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
261
+ },
262
+ "bigIntegralPatientName": {
263
+ "message": "الاسم الكامل للعميل/المريض",
264
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
265
+ },
266
+ "bigIntegralAge": {
267
+ "message": "العمر",
268
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
269
+ },
270
+ "bigIntegralBmi": {
271
+ "message": "منسب كتلة الجسم (BMI)",
272
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
273
+ },
274
+ "bigIntegralSystemScores": {
275
+ "message": "نتائج الأجهزة",
276
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
277
+ },
278
+ "bigIntegralSystem": {
279
+ "message": "الجهاز",
280
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
281
+ },
282
+ "bigIntegralHigh": {
283
+ "message": "مرتفع",
284
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
285
+ },
286
+ "bigIntegralQuestions": {
287
+ "message": "الأسئلة",
288
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
289
+ },
290
+ "bigIntegralResult": {
291
+ "message": "النتيجة",
292
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
293
+ },
294
+ "bigIntegralMaximum": {
295
+ "message": "الحد الأقصى",
296
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
297
+ },
298
+ "bigIntegralHormonalFemale": {
299
+ "message": "الصحة الهرمونية (إناث)",
300
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
301
+ },
302
+ "bigIntegralHormonalMale": {
303
+ "message": "الصحة الهرمونية (ذكور)",
304
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
305
+ },
306
+ "bigIntegralSystem_stomach-and-gi-tract": {
307
+ "message": "المعدة والجهاز الهضمي",
308
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
309
+ },
310
+ "bigIntegralSystem_small-intestine": {
311
+ "message": "الأمعاء الدقيقة",
312
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
313
+ },
314
+ "bigIntegralSystem_infections-invasions-of-the-gi": {
315
+ "message": "العدوى/الغزو في الجهاز الهضمي",
316
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
317
+ },
318
+ "bigIntegralSystem_large-intestine": {
319
+ "message": "الأمعاء الغليظة",
320
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
321
+ },
322
+ "bigIntegralSystem_liver-and-gallbladder": {
323
+ "message": "الكبد والمرارة",
324
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
325
+ },
326
+ "bigIntegralSystem_essential-nutrients": {
327
+ "message": "العناصر الغذائية الأساسية",
328
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
329
+ },
330
+ "bigIntegralSystem_carbohydrate-metabolism": {
331
+ "message": "استقلاب الكربوهيدرات",
332
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
333
+ },
334
+ "bigIntegralSystem_immune-system": {
335
+ "message": "الجهاز المناعي",
336
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
337
+ },
338
+ "bigIntegralSystem_lifestyle": {
339
+ "message": "نمط الحياة",
340
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
341
+ },
342
+ "quizAnswers": {
343
+ "message": "إجابات الاستبيان",
344
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
345
+ },
346
+ "pageOf": {
347
+ "message": "من",
348
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
349
+ },
350
+ "footerAddressLabel": {
351
+ "message": "العنوان",
352
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
353
+ },
354
+ "footerEmailLabel": {
355
+ "message": "البريد الإلكتروني",
356
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
357
+ },
358
+ "maisonSanteFooterAddress": {
359
+ "message": "ميان مول، أم سقيم 2، دبي، الإمارات العربية المتحدة",
360
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
257
361
  }
258
- }
362
+ }
@@ -407,18 +407,114 @@
407
407
  "message": "Psychological Wellbeing",
408
408
  "description": "The label displayed above the doctors who signed this report."
409
409
  },
410
- "bigIntegralSystem": "bigIntegralSystem",
411
- "bigIntegralHigh": "bigIntegralHigh",
412
- "bigIntegralQuestions": "bigIntegralQuestions",
413
- "bigIntegralResult": "bigIntegralResult",
414
- "bigIntegralMaximum": "bigIntegralMaximum",
410
+ "bigIntegralSystem": {
411
+ "message": "SYSTEM",
412
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
413
+ },
414
+ "bigIntegralHigh": {
415
+ "message": "High",
416
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
417
+ },
418
+ "bigIntegralQuestions": {
419
+ "message": "Questions",
420
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
421
+ },
422
+ "bigIntegralResult": {
423
+ "message": "Result",
424
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
425
+ },
426
+ "bigIntegralMaximum": {
427
+ "message": "Maximum",
428
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
429
+ },
415
430
  "bigIntegralComments": "bigIntegralComments",
416
431
  "bigIntegralElevated": "bigIntegralElevated",
417
432
  "bigIntegralVariantClinical": "bigIntegralVariantClinical",
418
433
  "bigIntegralDateOfCompletion": "bigIntegralDateOfCompletion",
419
- "bigIntegralPatientName": "bigIntegralPatientName",
420
- "bigIntegralAge": "bigIntegralAge",
421
- "bigIntegralBmi": "bigIntegralBmi",
434
+ "bigIntegralPatientName": {
435
+ "message": "Client/Patient Full Name",
436
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
437
+ },
438
+ "bigIntegralAge": {
439
+ "message": "Age",
440
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
441
+ },
442
+ "bigIntegralBmi": {
443
+ "message": "BMI",
444
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
445
+ },
422
446
  "bigIntegralVariantScorecard": "bigIntegralVariantScorecard",
423
- "bigIntegralVariantHybrid": "bigIntegralVariantHybrid"
424
- }
447
+ "bigIntegralVariantHybrid": "bigIntegralVariantHybrid",
448
+ "bigIntegralTitle": {
449
+ "message": "Key to the Big Integral Questionnaire",
450
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
451
+ },
452
+ "bigIntegralSystemScores": {
453
+ "message": "SYSTEM SCORES",
454
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
455
+ },
456
+ "bigIntegralHormonalFemale": {
457
+ "message": "Hormonal Health Female",
458
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
459
+ },
460
+ "bigIntegralHormonalMale": {
461
+ "message": "Hormonal Health Male",
462
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
463
+ },
464
+ "bigIntegralSystem_stomach-and-gi-tract": {
465
+ "message": "STOMACH and GI (Gastrointestinal) Tract",
466
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
467
+ },
468
+ "bigIntegralSystem_small-intestine": {
469
+ "message": "Small Intestine",
470
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
471
+ },
472
+ "bigIntegralSystem_infections-invasions-of-the-gi": {
473
+ "message": "Infections/Invasions of the GI (Gastrointestinal Tract)",
474
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
475
+ },
476
+ "bigIntegralSystem_large-intestine": {
477
+ "message": "Large Intestine",
478
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
479
+ },
480
+ "bigIntegralSystem_liver-and-gallbladder": {
481
+ "message": "Liver and Gallbladder",
482
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
483
+ },
484
+ "bigIntegralSystem_essential-nutrients": {
485
+ "message": "Essential Nutrients",
486
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
487
+ },
488
+ "bigIntegralSystem_carbohydrate-metabolism": {
489
+ "message": "Carbohydrate Metabolism",
490
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
491
+ },
492
+ "bigIntegralSystem_immune-system": {
493
+ "message": "Immune System",
494
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
495
+ },
496
+ "bigIntegralSystem_lifestyle": {
497
+ "message": "Lifestyle",
498
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
499
+ },
500
+ "quizAnswers": {
501
+ "message": "Quiz Answers",
502
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
503
+ },
504
+ "pageOf": {
505
+ "message": "of",
506
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
507
+ },
508
+ "footerAddressLabel": {
509
+ "message": "Address",
510
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
511
+ },
512
+ "footerEmailLabel": {
513
+ "message": "Email",
514
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
515
+ },
516
+ "maisonSanteFooterAddress": {
517
+ "message": "Meyan Mall, Umm Suqeim 2, Dubai, United Arab Emirates",
518
+ "description": "Maison Sante PDF (Big Integral Questionnaire / Quiz Answers / footer)"
519
+ }
520
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medicus.ai/medicus-report-pdf-generator",
3
- "version": "1.3.21",
3
+ "version": "1.3.23",
4
4
  "description": "Nasco corporate report - latest update in 12/10/2023 - Fix HRC for bionext",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -15,7 +15,7 @@ const clientConfig = JSON.parse(
15
15
  fs.readFileSync(path.resolve(__dirname, 'config/maisonsante.json'), 'utf8')
16
16
  );
17
17
 
18
- const sectionHtml = renderBigIntegralQuestionnaire({ clientConfig });
18
+ const sectionHtml = renderBigIntegralQuestionnaire({ clientConfig, usePlaceholder: true });
19
19
 
20
20
  const fullHtml = `<!DOCTYPE html>
21
21
  <html lang="en">
@@ -45,6 +45,21 @@
45
45
  .score-main td.biom-range {
46
46
  text-align: right !important;
47
47
  }
48
+
49
+ /* Quiz Answers (doctor PDF): questions/answers that only exist in English had
50
+ their punctuation displaced ("?Do you have ..."). Same per-line direction. */
51
+ .score-main .doctor-details .question-title,
52
+ .score-main .doctor-details .details-value {
53
+ unicode-bidi: plaintext !important;
54
+ text-align: right !important;
55
+ }
56
+
57
+ /* Wellbeing gauges: the "/100" under the score rendered as "100/" in RTL. */
58
+ .score-main .speedometer-value-down,
59
+ .score-main .speedometer-value-up {
60
+ direction: ltr !important;
61
+ unicode-bidi: isolate !important;
62
+ }
48
63
  </style>
49
64
  <script>
50
65
  var IS_ARABIC = true;