@geotechcli/core 0.2.0 → 0.4.0

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.
Files changed (83) hide show
  1. package/dist/agents/brain.d.ts +1 -0
  2. package/dist/agents/brain.d.ts.map +1 -1
  3. package/dist/agents/brain.js +39 -38
  4. package/dist/agents/brain.js.map +1 -1
  5. package/dist/agents/bridge-tools.js +15 -3
  6. package/dist/agents/bridge-tools.js.map +1 -1
  7. package/dist/agents/case-file.d.ts +52 -0
  8. package/dist/agents/case-file.d.ts.map +1 -0
  9. package/dist/agents/case-file.js +657 -0
  10. package/dist/agents/case-file.js.map +1 -0
  11. package/dist/agents/contracts.d.ts +232 -0
  12. package/dist/agents/contracts.d.ts.map +1 -0
  13. package/dist/agents/contracts.js +14 -0
  14. package/dist/agents/contracts.js.map +1 -0
  15. package/dist/agents/data-tools.js +11 -3
  16. package/dist/agents/data-tools.js.map +1 -1
  17. package/dist/agents/deliverable-tools.d.ts +2 -0
  18. package/dist/agents/deliverable-tools.d.ts.map +1 -0
  19. package/dist/agents/deliverable-tools.js +497 -0
  20. package/dist/agents/deliverable-tools.js.map +1 -0
  21. package/dist/agents/evidence.d.ts +21 -0
  22. package/dist/agents/evidence.d.ts.map +1 -0
  23. package/dist/agents/evidence.js +302 -0
  24. package/dist/agents/evidence.js.map +1 -0
  25. package/dist/agents/orchestrator.js +13 -13
  26. package/dist/agents/sandbox.d.ts.map +1 -1
  27. package/dist/agents/sandbox.js +84 -31
  28. package/dist/agents/sandbox.js.map +1 -1
  29. package/dist/agents/swarm.d.ts +2 -0
  30. package/dist/agents/swarm.d.ts.map +1 -1
  31. package/dist/agents/swarm.js +170 -149
  32. package/dist/agents/swarm.js.map +1 -1
  33. package/dist/bridge/index.js +87 -87
  34. package/dist/geo/index.d.ts +2 -1
  35. package/dist/geo/index.d.ts.map +1 -1
  36. package/dist/geo/index.js +1 -0
  37. package/dist/geo/index.js.map +1 -1
  38. package/dist/geo/pile-capacity.d.ts.map +1 -1
  39. package/dist/geo/pile-capacity.js +47 -8
  40. package/dist/geo/pile-capacity.js.map +1 -1
  41. package/dist/geo/seepage.d.ts +62 -0
  42. package/dist/geo/seepage.d.ts.map +1 -0
  43. package/dist/geo/seepage.js +130 -0
  44. package/dist/geo/seepage.js.map +1 -0
  45. package/dist/geo/slope-stability.d.ts.map +1 -1
  46. package/dist/geo/slope-stability.js +9 -6
  47. package/dist/geo/slope-stability.js.map +1 -1
  48. package/dist/index.d.ts +5 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +6 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/llm/middleware/metering.js +14 -14
  53. package/dist/llm/providers/hosted-beta.d.ts.map +1 -1
  54. package/dist/llm/providers/hosted-beta.js +5 -2
  55. package/dist/llm/providers/hosted-beta.js.map +1 -1
  56. package/dist/llm/providers/zhipu.d.ts.map +1 -1
  57. package/dist/llm/providers/zhipu.js +3 -1
  58. package/dist/llm/providers/zhipu.js.map +1 -1
  59. package/dist/llm/util.d.ts +10 -0
  60. package/dist/llm/util.d.ts.map +1 -0
  61. package/dist/llm/util.js +54 -0
  62. package/dist/llm/util.js.map +1 -0
  63. package/dist/meta/metadata.json +4 -4
  64. package/dist/report/casefile.d.ts +207 -0
  65. package/dist/report/casefile.d.ts.map +1 -0
  66. package/dist/report/casefile.js +438 -0
  67. package/dist/report/casefile.js.map +1 -0
  68. package/dist/report/docx.d.ts +3 -0
  69. package/dist/report/docx.d.ts.map +1 -0
  70. package/dist/report/docx.js +178 -0
  71. package/dist/report/docx.js.map +1 -0
  72. package/dist/report/index.d.ts +3 -0
  73. package/dist/report/index.d.ts.map +1 -1
  74. package/dist/report/index.js +23 -20
  75. package/dist/report/index.js.map +1 -1
  76. package/dist/report/pdf.d.ts +3 -0
  77. package/dist/report/pdf.d.ts.map +1 -0
  78. package/dist/report/pdf.js +195 -0
  79. package/dist/report/pdf.js.map +1 -0
  80. package/dist/vision/index.d.ts.map +1 -1
  81. package/dist/vision/index.js +163 -84
  82. package/dist/vision/index.js.map +1 -1
  83. package/package.json +58 -55
@@ -1,4 +1,7 @@
1
1
  import type { LLMConfig } from '../llm/types.js';
2
+ export { renderReportAsPdf } from './pdf.js';
3
+ export { renderReportAsDocx } from './docx.js';
4
+ export { buildArtifactDrivenReport, generateReportFromCaseFile, type CaseFileGeneratedReport, type CaseFileReportInput, type GenerateStoredCaseFileReportOptions, } from './casefile.js';
2
5
  export interface ReportSection {
3
6
  title: string;
4
7
  content: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,OAAO,EAAE;IACP,IAAI,EAAE,UAAU,GAAG,oBAAoB,GAAG,eAAe,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CA8D1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,mCAAmC,GACzC,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,OAAO,EAAE;IACP,IAAI,EAAE,UAAU,GAAG,oBAAoB,GAAG,eAAe,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CA8D1B"}
@@ -1,26 +1,29 @@
1
1
  import { generateText } from '../llm/router.js';
2
+ export { renderReportAsPdf } from './pdf.js';
3
+ export { renderReportAsDocx } from './docx.js';
4
+ export { buildArtifactDrivenReport, generateReportFromCaseFile, } from './casefile.js';
2
5
  export async function generateReport(analysisData, options, config) {
3
6
  const dataStr = JSON.stringify(analysisData, null, 2);
4
- const prompt = `Generate a professional geotechnical engineering report based on this analysis data:
5
-
6
- ${dataStr}
7
-
8
- Report requirements:
9
- - Type: ${options.type}
10
- - Project: ${options.projectName ?? 'Unnamed Project'}
11
- - Location: ${options.location ?? 'Not specified'}
12
- - Language: ${options.language ?? 'English'}
13
- ${options.additionalNotes ? `- Notes: ${options.additionalNotes}` : ''}
14
-
15
- Structure the report in standard geotechnical format with these sections:
16
- 1. Executive Summary
17
- 2. Introduction & Scope
18
- 3. Site Description & Geology
19
- 4. Investigation / Analysis Methods
20
- 5. Results & Interpretation
21
- 6. Engineering Parameters
22
- 7. Conclusions & Recommendations
23
-
7
+ const prompt = `Generate a professional geotechnical engineering report based on this analysis data:
8
+
9
+ ${dataStr}
10
+
11
+ Report requirements:
12
+ - Type: ${options.type}
13
+ - Project: ${options.projectName ?? 'Unnamed Project'}
14
+ - Location: ${options.location ?? 'Not specified'}
15
+ - Language: ${options.language ?? 'English'}
16
+ ${options.additionalNotes ? `- Notes: ${options.additionalNotes}` : ''}
17
+
18
+ Structure the report in standard geotechnical format with these sections:
19
+ 1. Executive Summary
20
+ 2. Introduction & Scope
21
+ 3. Site Description & Geology
22
+ 4. Investigation / Analysis Methods
23
+ 5. Results & Interpretation
24
+ 6. Engineering Parameters
25
+ 7. Conclusions & Recommendations
26
+
24
27
  Output the report as clean markdown. Include tables where appropriate. Use proper geotechnical terminology and reference applicable standards.`;
25
28
  const response = await generateText(prompt, config, {
26
29
  systemPrompt: `You are a senior geotechnical engineer writing professional reports. Your reports are technically precise, well-structured, and suitable for submission to clients and regulatory bodies. Use standard formatting: numbered sections, tables for parameters, clear recommendations with supporting evidence.`,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAchD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,YAAqC,EACrC,OAOC,EACD,MAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAG;;EAEf,OAAO;;;UAGC,OAAO,CAAC,IAAI;aACT,OAAO,CAAC,WAAW,IAAI,iBAAiB;cACvC,OAAO,CAAC,QAAQ,IAAI,eAAe;cACnC,OAAO,CAAC,QAAQ,IAAI,SAAS;EACzC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;+IAWyE,CAAC;IAE9I,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE;QAClD,YAAY,EAAE,8SAA8S;QAC5T,WAAW,EAAE,GAAG;QAChB,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,+BAA+B;IAC/B,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,YAAY,GAAG,mBAAmB,CAAC;IACzC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,UAAU,CAAC;IAC3B,IAAI,KAA6B,CAAC;IAElC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE3B,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC1D,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QACD,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5C,CAAC;IAED,eAAe;IACf,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,aAAa,OAAO,CAAC,WAAW,IAAI,SAAS,EAAE;QACjI,QAAQ;QACR,YAAY,EAAE,QAAQ,CAAC,IAAI;QAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAI3B,MAAM,eAAe,CAAC;AAcvB,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,YAAqC,EACrC,OAOC,EACD,MAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAG;;EAEf,OAAO;;;UAGC,OAAO,CAAC,IAAI;aACT,OAAO,CAAC,WAAW,IAAI,iBAAiB;cACvC,OAAO,CAAC,QAAQ,IAAI,eAAe;cACnC,OAAO,CAAC,QAAQ,IAAI,SAAS;EACzC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;+IAWyE,CAAC;IAE9I,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE;QAClD,YAAY,EAAE,8SAA8S;QAC5T,WAAW,EAAE,GAAG;QAChB,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,+BAA+B;IAC/B,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,YAAY,GAAG,mBAAmB,CAAC;IACzC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,UAAU,CAAC;IAC3B,IAAI,KAA6B,CAAC;IAElC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE3B,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC1D,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QACD,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrB,SAAS,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5C,CAAC;IAED,eAAe;IACf,IAAI,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,aAAa,OAAO,CAAC,WAAW,IAAI,SAAS,EAAE;QACjI,QAAQ;QACR,YAAY,EAAE,QAAQ,CAAC,IAAI;QAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { GeneratedReport } from './index.js';
2
+ export declare function renderReportAsPdf(report: GeneratedReport): Promise<Buffer>;
3
+ //# sourceMappingURL=pdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../../src/report/pdf.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,YAAY,CAAC;AA4DjE,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgIhF"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * PDF report renderer using pdfkit (pure JS, no native deps).
3
+ * Converts a GeneratedReport (markdown sections) to a professional PDF buffer.
4
+ */
5
+ import { GEOTECHCLI_VERSION } from '../meta/index.js';
6
+ // Dynamic import to avoid top-level require errors when pdfkit is installed
7
+ async function getPDFKit() {
8
+ try {
9
+ // @ts-ignore — pdfkit may not have perfect ESM types
10
+ const { default: PDFDocument } = await import('pdfkit');
11
+ return PDFDocument;
12
+ }
13
+ catch {
14
+ throw new Error('pdfkit is required for PDF export. Install it: npm install pdfkit --workspace=@geotechcli/core');
15
+ }
16
+ }
17
+ const BRAND_CYAN = '#00b8c8';
18
+ const TEXT_DARK = '#1a1a2e';
19
+ const TEXT_GRAY = '#555577';
20
+ const LINE_GRAY = '#ccccdd';
21
+ const PAGE_MARGIN = 56;
22
+ const FONT_TITLE = 'Helvetica-Bold';
23
+ const FONT_BODY = 'Helvetica';
24
+ const FONT_BOLD = 'Helvetica-Bold';
25
+ /**
26
+ * Strip markdown syntax for plain text rendering in PDF.
27
+ */
28
+ function stripMarkdown(text) {
29
+ return text
30
+ .replace(/^#{1,6}\s+/gm, '') // headings
31
+ .replace(/\*\*(.+?)\*\*/g, '$1') // bold
32
+ .replace(/\*(.+?)\*/g, '$1') // italic
33
+ .replace(/`(.+?)`/g, '$1') // inline code
34
+ .replace(/^\s*[-*+]\s+/gm, '• ') // bullets
35
+ .replace(/^\s*\d+\.\s+/gm, '') // numbered lists
36
+ .replace(/\|[-:| ]+\|/g, '') // table separators
37
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // links
38
+ .trim();
39
+ }
40
+ /**
41
+ * Detect if a line is a markdown table row.
42
+ */
43
+ function isTableLine(line) {
44
+ return line.trim().startsWith('|');
45
+ }
46
+ /**
47
+ * Parse a simple markdown table into headers and rows.
48
+ */
49
+ function parseMarkdownTable(block) {
50
+ const dataLines = block.filter(l => !l.replace(/\|/g, '').replace(/-/g, '').trim().match(/^[-:| ]*$/));
51
+ if (dataLines.length < 2)
52
+ return null;
53
+ const parse = (line) => line.split('|').slice(1, -1).map(c => c.trim());
54
+ const headers = parse(dataLines[0]);
55
+ const rows = dataLines.slice(1).map(parse);
56
+ return { headers, rows };
57
+ }
58
+ export async function renderReportAsPdf(report) {
59
+ const PDFDocument = await getPDFKit();
60
+ return new Promise((resolve, reject) => {
61
+ const doc = new PDFDocument({ margin: PAGE_MARGIN, size: 'A4' });
62
+ const chunks = [];
63
+ doc.on('data', (chunk) => chunks.push(chunk));
64
+ doc.on('end', () => resolve(Buffer.concat(chunks)));
65
+ doc.on('error', reject);
66
+ const pageWidth = doc.page.width - PAGE_MARGIN * 2;
67
+ // -----------------------------------------------------------------------
68
+ // Title page
69
+ // -----------------------------------------------------------------------
70
+ doc.rect(0, 0, doc.page.width, 8).fill(BRAND_CYAN);
71
+ doc.moveDown(2);
72
+ doc.font(FONT_TITLE).fontSize(22).fillColor(TEXT_DARK)
73
+ .text(report.title, PAGE_MARGIN, 80, { width: pageWidth, align: 'center' });
74
+ doc.moveDown(0.5);
75
+ doc.font(FONT_BODY).fontSize(11).fillColor(TEXT_GRAY)
76
+ .text(`Generated by geotechCLI v${process.env.npm_package_version ?? GEOTECHCLI_VERSION}`, {
77
+ align: 'center',
78
+ });
79
+ doc.font(FONT_BODY).fontSize(10).fillColor(TEXT_GRAY)
80
+ .text(new Date().toISOString().split('T')[0], { align: 'center' });
81
+ doc.moveDown(2);
82
+ doc.moveTo(PAGE_MARGIN, doc.y).lineTo(doc.page.width - PAGE_MARGIN, doc.y)
83
+ .strokeColor(LINE_GRAY).lineWidth(1).stroke();
84
+ doc.moveDown(1.5);
85
+ // Table of contents (section names)
86
+ doc.font(FONT_BOLD).fontSize(12).fillColor(BRAND_CYAN).text('Contents', PAGE_MARGIN);
87
+ doc.moveDown(0.4);
88
+ for (const section of report.sections) {
89
+ doc.font(FONT_BODY).fontSize(10).fillColor(TEXT_DARK)
90
+ .text(`• ${section.title}`, PAGE_MARGIN + 12);
91
+ }
92
+ // -----------------------------------------------------------------------
93
+ // Content sections
94
+ // -----------------------------------------------------------------------
95
+ for (const section of report.sections) {
96
+ doc.addPage();
97
+ doc.rect(0, 0, doc.page.width, 5).fill(BRAND_CYAN);
98
+ doc.moveDown(1);
99
+ doc.font(FONT_TITLE).fontSize(14).fillColor(BRAND_CYAN)
100
+ .text(section.title, PAGE_MARGIN, 40, { width: pageWidth });
101
+ doc.moveDown(0.4);
102
+ doc.moveTo(PAGE_MARGIN, doc.y).lineTo(doc.page.width - PAGE_MARGIN, doc.y)
103
+ .strokeColor(BRAND_CYAN).lineWidth(1.5).stroke();
104
+ doc.moveDown(0.6);
105
+ // Render content line by line
106
+ const lines = section.content.split('\n');
107
+ let tableBuffer = [];
108
+ let inTable = false;
109
+ const flushTable = () => {
110
+ if (tableBuffer.length === 0)
111
+ return;
112
+ const parsed = parseMarkdownTable(tableBuffer);
113
+ if (parsed && doc.y < doc.page.height - 120) {
114
+ const colW = pageWidth / parsed.headers.length;
115
+ // Header row
116
+ let x = PAGE_MARGIN;
117
+ doc.font(FONT_BOLD).fontSize(8.5).fillColor(TEXT_DARK);
118
+ for (const h of parsed.headers) {
119
+ doc.text(h, x + 4, doc.y, { width: colW - 8, lineBreak: false });
120
+ x += colW;
121
+ }
122
+ doc.moveDown(0.3);
123
+ doc.moveTo(PAGE_MARGIN, doc.y).lineTo(doc.page.width - PAGE_MARGIN, doc.y)
124
+ .strokeColor(LINE_GRAY).lineWidth(0.5).stroke();
125
+ doc.moveDown(0.2);
126
+ // Data rows
127
+ doc.font(FONT_BODY).fontSize(8.5).fillColor(TEXT_DARK);
128
+ for (const row of parsed.rows) {
129
+ x = PAGE_MARGIN;
130
+ const rowY = doc.y;
131
+ for (const cell of row) {
132
+ doc.text(cell, x + 4, rowY, { width: colW - 8, lineBreak: false });
133
+ x += colW;
134
+ }
135
+ doc.moveDown(0.4);
136
+ }
137
+ doc.moveDown(0.4);
138
+ }
139
+ tableBuffer = [];
140
+ inTable = false;
141
+ };
142
+ for (const line of lines) {
143
+ if (isTableLine(line)) {
144
+ inTable = true;
145
+ tableBuffer.push(line);
146
+ continue;
147
+ }
148
+ if (inTable)
149
+ flushTable();
150
+ const stripped = strictRenderLine(line, doc, PAGE_MARGIN, pageWidth);
151
+ if (stripped !== null)
152
+ doc.moveDown(0.3);
153
+ }
154
+ if (inTable)
155
+ flushTable();
156
+ }
157
+ // -----------------------------------------------------------------------
158
+ // Footer on each page
159
+ // -----------------------------------------------------------------------
160
+ const range = doc.bufferedPageRange();
161
+ for (let i = range.start; i < range.start + range.count; i++) {
162
+ doc.switchToPage(i);
163
+ doc.font(FONT_BODY).fontSize(8).fillColor(TEXT_GRAY)
164
+ .text(`geotechCLI · ${report.title} · Page ${i + 1} of ${range.count}`, PAGE_MARGIN, doc.page.height - 36, { align: 'center', width: pageWidth });
165
+ }
166
+ doc.end();
167
+ });
168
+ }
169
+ /** Render a single non-table markdown line into the PDF. Returns null if empty line. */
170
+ function strictRenderLine(line, doc, margin, width) {
171
+ const trimmed = line.trim();
172
+ if (!trimmed)
173
+ return null;
174
+ if (/^#{1,2}\s/.test(trimmed)) {
175
+ const text = trimmed.replace(/^#{1,2}\s+/, '');
176
+ doc.font('Helvetica-Bold').fontSize(12).fillColor('#00b8c8').text(stripMarkdown(text), margin, undefined, { width });
177
+ return true;
178
+ }
179
+ if (/^#{3,6}\s/.test(trimmed)) {
180
+ const text = trimmed.replace(/^#{3,6}\s+/, '');
181
+ doc.font('Helvetica-Bold').fontSize(10.5).fillColor('#1a1a2e').text(stripMarkdown(text), margin, undefined, { width });
182
+ return true;
183
+ }
184
+ // Bullet
185
+ if (/^[-*+]\s/.test(trimmed)) {
186
+ const text = trimmed.replace(/^[-*+]\s+/, '');
187
+ doc.font('Helvetica').fontSize(10).fillColor('#1a1a2e')
188
+ .text('• ' + stripMarkdown(text), margin + 10, undefined, { width: width - 10 });
189
+ return true;
190
+ }
191
+ // Normal paragraph
192
+ doc.font('Helvetica').fontSize(10).fillColor('#1a1a2e').text(stripMarkdown(trimmed), margin, undefined, { width });
193
+ return true;
194
+ }
195
+ //# sourceMappingURL=pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../src/report/pdf.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vision/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vision/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,66 @@
1
1
  import { generateVision, generateText } from '../llm/router.js';
2
2
  import { classifyRMR89 } from '../geo/classification.js';
3
3
  import { clampConfidence, createParseSafety, deriveParseStatus, normalizeWarnings, parseJsonObject, readNumber, readString, } from './parse.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Vision retry helper — handles upstream empty-content failures
6
+ // ---------------------------------------------------------------------------
7
+ function isRecoverableVisionEmptyResponse(error) {
8
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
9
+ return (message.includes('returned no content') ||
10
+ message.includes('did not contain assistant text') ||
11
+ message.includes('no completion choices') ||
12
+ message.includes('empty completion'));
13
+ }
14
+ /**
15
+ * Execute a vision call with automatic retry on empty/null response.
16
+ * First attempt: strict JSON-only prompt at low temperature.
17
+ * Second attempt: softer plain-text prompt at higher temperature, same image.
18
+ * This handles hosted-beta vision models returning empty content on the first call.
19
+ */
20
+ async function visionWithRetry(imageBase64, mimeType, config, strictPrompt, softPrompt, systemPrompt, maxTokens) {
21
+ const start = Date.now();
22
+ // Attempt 1: strict JSON prompt
23
+ try {
24
+ const r1 = await generateVision(strictPrompt, imageBase64, mimeType, config, {
25
+ systemPrompt,
26
+ temperature: 0.1,
27
+ maxTokens,
28
+ });
29
+ if (r1.text && r1.text.trim().length > 10) {
30
+ return { text: r1.text, latencyMs: r1.latencyMs, usedFallback: false };
31
+ }
32
+ }
33
+ catch (error) {
34
+ if (!isRecoverableVisionEmptyResponse(error)) {
35
+ throw error;
36
+ }
37
+ }
38
+ // Attempt 2: softer plain text prompt
39
+ try {
40
+ const r2 = await generateVision(softPrompt, imageBase64, mimeType, config, {
41
+ systemPrompt: systemPrompt + ' Be concise but thorough. You must provide values even if approximate.',
42
+ temperature: 0.3,
43
+ maxTokens: maxTokens + 200,
44
+ });
45
+ if (r2.text && r2.text.trim().length > 0) {
46
+ return {
47
+ text: r2.text,
48
+ latencyMs: Date.now() - start,
49
+ usedFallback: true,
50
+ };
51
+ }
52
+ }
53
+ catch (error) {
54
+ if (!isRecoverableVisionEmptyResponse(error)) {
55
+ throw error;
56
+ }
57
+ }
58
+ return {
59
+ text: '',
60
+ latencyMs: Date.now() - start,
61
+ usedFallback: true,
62
+ };
63
+ }
4
64
  const VALID_JOINT_CONDITIONS = [
5
65
  'very_good',
6
66
  'good',
@@ -26,24 +86,31 @@ function combineWarnings(base, extra) {
26
86
  return [...new Set([...base, ...extra])];
27
87
  }
28
88
  export async function analyzeCoreBox(imageBase64, mimeType, config) {
29
- const prompt = `Analyze this rock core box image. You MUST respond with ONLY a JSON object (no markdown, no backticks, no explanation) with these exact fields:
30
- {
31
- "rqd": <number 0-100, Rock Quality Designation percentage>,
32
- "fractureSpacing": "<string: very close/close/moderate/wide/very wide>",
33
- "weatheringGrade": "<string: W1-Fresh/W2-Slightly/W3-Moderately/W4-Highly/W5-Completely/W6-Residual>",
34
- "rockType": "<string: identified lithology>",
35
- "coreRecovery": <number 0-100, percentage>,
36
- "discontinuities": "<string: description of joint surfaces, infilling, roughness>",
37
- "confidence": <number 0-100>,
38
- "warnings": ["<warning>", "<warning>"]
89
+ const strictPrompt = `Analyze this rock core box image. You MUST respond with ONLY a JSON object (no markdown, no backticks, no explanation) with these exact fields:
90
+ {
91
+ "rqd": <number 0-100, Rock Quality Designation percentage>,
92
+ "fractureSpacing": "<string: very close/close/moderate/wide/very wide>",
93
+ "weatheringGrade": "<string: W1-Fresh/W2-Slightly/W3-Moderately/W4-Highly/W5-Completely/W6-Residual>",
94
+ "rockType": "<string: identified lithology>",
95
+ "coreRecovery": <number 0-100, percentage>,
96
+ "discontinuities": "<string: description of joint surfaces, infilling, roughness>",
97
+ "confidence": <number 0-100>,
98
+ "warnings": ["<warning>", "<warning>"]
39
99
  }`;
40
- const response = await generateVision(prompt, imageBase64, mimeType, config, {
41
- systemPrompt: 'You are an expert engineering geologist performing core logging. Respond with JSON only.',
42
- temperature: 0.1,
43
- maxTokens: 900,
44
- });
100
+ const softPrompt = `Examine this rock core box image and describe:
101
+ 1. RQD (Rock Quality Designation) as a percentage 0-100
102
+ 2. Fracture spacing (very close / close / moderate / wide / very wide)
103
+ 3. Weathering grade (W1-Fresh through W6-Residual)
104
+ 4. Rock type / lithology
105
+ 5. Core recovery percentage
106
+ 6. Discontinuity description (surfaces, infilling, roughness)
107
+ Provide approximate values even if uncertain.`;
108
+ const response = await visionWithRetry(imageBase64, mimeType, config, strictPrompt, softPrompt, 'You are an expert engineering geologist performing core logging. Respond with JSON only.', 900);
45
109
  const parsed = parseJsonObject(response.text);
46
110
  const warnings = [...parsed.warnings];
111
+ if (response.usedFallback) {
112
+ warnings.push('Vision model used plain-text fallback — values extracted from narrative response.');
113
+ }
47
114
  const rqd = readNumber(parsed.value, 'rqd', warnings);
48
115
  const fractureSpacing = readString(parsed.value, 'fractureSpacing', warnings);
49
116
  const weatheringGrade = readString(parsed.value, 'weatheringGrade', warnings);
@@ -66,23 +133,29 @@ export async function analyzeCoreBox(imageBase64, mimeType, config) {
66
133
  };
67
134
  }
68
135
  export async function classifyRMRFromImage(imageBase64, mimeType, config) {
69
- const prompt = `Analyze this rock exposure / tunnel face / core box image for Rock Mass Rating input parameters. Respond with ONLY a JSON object (no markdown):
70
- {
71
- "estimatedUCS": <number in MPa, estimate from visual appearance>,
72
- "estimatedRQD": <number 0-100>,
73
- "estimatedSpacing": <number in meters, mean discontinuity spacing>,
74
- "jointCondition": "<very_good|good|fair|poor|very_poor>",
75
- "groundwaterCondition": "<dry|damp|wet|dripping|flowing>",
76
- "confidence": <number 0-100>,
77
- "warnings": ["<warning>", "<warning>"]
136
+ const strictPrompt = `Analyze this rock exposure / tunnel face / core box image for Rock Mass Rating input parameters. Respond with ONLY a JSON object (no markdown):
137
+ {
138
+ "estimatedUCS": <number in MPa, estimate from visual appearance>,
139
+ "estimatedRQD": <number 0-100>,
140
+ "estimatedSpacing": <number in meters, mean discontinuity spacing>,
141
+ "jointCondition": "<very_good|good|fair|poor|very_poor>",
142
+ "groundwaterCondition": "<dry|damp|wet|dripping|flowing>",
143
+ "confidence": <number 0-100>,
144
+ "warnings": ["<warning>", "<warning>"]
78
145
  }`;
79
- const response = await generateVision(prompt, imageBase64, mimeType, config, {
80
- systemPrompt: 'You are an expert rock mechanics engineer performing field classification. Respond with JSON only.',
81
- temperature: 0.1,
82
- maxTokens: 700,
83
- });
146
+ const softPrompt = `Look at this rock mass image and estimate:
147
+ 1. Uniaxial compressive strength UCS in MPa (from rock appearance)
148
+ 2. Rock Quality Designation RQD as % 0-100
149
+ 3. Mean discontinuity spacing in meters
150
+ 4. Joint condition: very_good, good, fair, poor, or very_poor
151
+ 5. Groundwater condition: dry, damp, wet, dripping, or flowing
152
+ Give approximate values based on what you observe.`;
153
+ const response = await visionWithRetry(imageBase64, mimeType, config, strictPrompt, softPrompt, 'You are an expert rock mechanics engineer performing field classification. Respond with JSON only.', 700);
84
154
  const parsed = parseJsonObject(response.text);
85
155
  const warnings = [...parsed.warnings];
156
+ if (response.usedFallback) {
157
+ warnings.push('Vision model used plain-text fallback — values extracted from narrative response.');
158
+ }
86
159
  const estimatedUCS = readNumber(parsed.value, 'estimatedUCS', warnings);
87
160
  const estimatedRQD = readNumber(parsed.value, 'estimatedRQD', warnings);
88
161
  const estimatedSpacing = readNumber(parsed.value, 'estimatedSpacing', warnings);
@@ -135,19 +208,19 @@ export async function classifyRMRFromImage(imageBase64, mimeType, config) {
135
208
  };
136
209
  }
137
210
  export async function classifySoilFromDescription(description, config) {
138
- const prompt = `Given this soil description: "${description}"
139
-
140
- Classify the soil and estimate engineering properties. Respond with ONLY a JSON object:
141
- {
142
- "uscsSymbol": "<USCS symbol e.g. CL, SM, GP>",
143
- "uscsName": "<full name e.g. Lean Clay, Silty Sand>",
144
- "estimatedFrictionAngle": <degrees>,
145
- "estimatedCohesion": <kPa>,
146
- "estimatedUnitWeight": <kN/m3>,
147
- "estimatedPermeability": "<e.g. 1e-7 m/s>",
148
- "engineeringNotes": "<brief note on behavior, compressibility, strength>",
149
- "confidence": <number 0-100>,
150
- "warnings": ["<warning>", "<warning>"]
211
+ const prompt = `Given this soil description: "${description}"
212
+
213
+ Classify the soil and estimate engineering properties. Respond with ONLY a JSON object:
214
+ {
215
+ "uscsSymbol": "<USCS symbol e.g. CL, SM, GP>",
216
+ "uscsName": "<full name e.g. Lean Clay, Silty Sand>",
217
+ "estimatedFrictionAngle": <degrees>,
218
+ "estimatedCohesion": <kPa>,
219
+ "estimatedUnitWeight": <kN/m3>,
220
+ "estimatedPermeability": "<e.g. 1e-7 m/s>",
221
+ "engineeringNotes": "<brief note on behavior, compressibility, strength>",
222
+ "confidence": <number 0-100>,
223
+ "warnings": ["<warning>", "<warning>"]
151
224
  }`;
152
225
  const response = await generateText(prompt, config, {
153
226
  systemPrompt: 'You are an expert geotechnical engineer. Classify soils based on verbal descriptions using USCS and estimate engineering properties. Respond with JSON only.',
@@ -183,31 +256,34 @@ Classify the soil and estimate engineering properties. Respond with ONLY a JSON
183
256
  };
184
257
  }
185
258
  export async function interpretBoreholeLog(imageBase64, mimeType, config, boreholeId) {
186
- const prompt = `Extract structured data from this borehole log image. Respond with ONLY a JSON object:
187
- {
188
- "boreholeId": "<ID if visible, or 'BH-unknown'>",
189
- "totalDepth": <number in meters>,
190
- "waterTableDepth": <number in meters or null>,
191
- "layers": [
192
- {
193
- "depthFrom": <m>,
194
- "depthTo": <m>,
195
- "description": "<soil/rock description>",
196
- "uscsSymbol": "<USCS if identifiable>",
197
- "sptN": <number or null>,
198
- "waterContent": <percent or null>,
199
- "notes": "<any additional notes>"
200
- }
201
- ],
202
- "summary": "<brief engineering summary of the borehole>",
203
- "confidence": <number 0-100>,
204
- "warnings": ["<warning>", "<warning>"]
259
+ const strictPrompt = `Extract structured data from this borehole log image. Respond with ONLY a JSON object:
260
+ {
261
+ "boreholeId": "<ID if visible, or 'BH-unknown'>",
262
+ "totalDepth": <number in meters>,
263
+ "waterTableDepth": <number in meters or null>,
264
+ "layers": [
265
+ {
266
+ "depthFrom": <m>,
267
+ "depthTo": <m>,
268
+ "description": "<soil/rock description>",
269
+ "uscsSymbol": "<USCS if identifiable>",
270
+ "sptN": <number or null>,
271
+ "waterContent": <percent or null>,
272
+ "notes": "<any additional notes>"
273
+ }
274
+ ],
275
+ "summary": "<brief engineering summary of the borehole>",
276
+ "confidence": <number 0-100>,
277
+ "warnings": ["<warning>", "<warning>"]
205
278
  }`;
206
- const response = await generateVision(prompt, imageBase64, mimeType, config, {
207
- systemPrompt: 'You are an expert geotechnical engineer extracting data from borehole log documents. Be precise with depths, descriptions, and test values. Respond with JSON only.',
208
- temperature: 0.1,
209
- maxTokens: 2200,
210
- });
279
+ const softPrompt = `Read this borehole log carefully and extract the key structured data:
280
+ 1. Borehole ID if visible
281
+ 2. Total depth in meters
282
+ 3. Water table depth if shown
283
+ 4. Each stratigraphic layer with depthFrom, depthTo, description, USCS symbol, SPT N, water content, and notes
284
+ 5. A brief engineering summary
285
+ Provide approximate values where necessary, but keep the structure complete.`;
286
+ const response = await visionWithRetry(imageBase64, mimeType, config, strictPrompt, softPrompt, 'You are an expert geotechnical engineer extracting data from borehole log documents. Be precise with depths, descriptions, and test values. Respond with JSON only.', 2200);
211
287
  const parsed = parseJsonObject(response.text);
212
288
  const warnings = [...parsed.warnings];
213
289
  const parsedLayers = Array.isArray(parsed.value?.layers)
@@ -252,28 +328,31 @@ export async function interpretBoreholeLog(imageBase64, mimeType, config, boreho
252
328
  };
253
329
  }
254
330
  export async function queryGBRDocument(question, documentBase64, mimeType, config) {
255
- const response = await generateVision(`Based on this Geotechnical Baseline Report, answer the following question:\n\n${question}\n\nProvide a concise, technically accurate answer with specific values and page/section references where possible.`, documentBase64, mimeType, config, {
256
- systemPrompt: 'You are an expert geotechnical engineer analyzing a Geotechnical Baseline Report (GBR). Provide precise, actionable answers referencing specific data from the document.',
257
- maxTokens: 2000,
258
- });
331
+ const response = await visionWithRetry(documentBase64, mimeType, config, `Based on this Geotechnical Baseline Report, answer the following question with a concise, technically accurate response and specific page/section references where possible:\n\n${question}`, `Read this Geotechnical Baseline Report and answer the question directly:\n\n${question}\n\nUse specific values, limits, assumptions, and references if they are visible in the document.`, 'You are an expert geotechnical engineer analyzing a Geotechnical Baseline Report (GBR). Provide precise, actionable answers referencing specific data from the document.', 2000);
332
+ if (!response.text.trim()) {
333
+ throw new Error('Hosted beta upstream returned no content. The document could not be interpreted. Try a smaller PNG or JPG export of the relevant pages.');
334
+ }
259
335
  return { answer: response.text, latencyMs: response.latencyMs };
260
336
  }
261
337
  export async function interpretSensorImage(imageBase64, mimeType, config) {
262
- const prompt = `Analyze this geotechnical sensor data image. Respond with ONLY a JSON object:
263
- {
264
- "sensorType": "<inclinometer|piezometer|extensometer|load_cell|settlement_plate|tiltmeter|other>",
265
- "measurements": "<key readings and values observed>",
266
- "interpretation": "<what the data shows about ground/structure behavior>",
267
- "evaluation": "<whether conditions are normal, warning, or critical>",
268
- "recommendations": "<recommended actions if any>",
269
- "confidence": <number 0-100>,
270
- "warnings": ["<warning>", "<warning>"]
338
+ const strictPrompt = `Analyze this geotechnical sensor data image. Respond with ONLY a JSON object:
339
+ {
340
+ "sensorType": "<inclinometer|piezometer|extensometer|load_cell|settlement_plate|tiltmeter|other>",
341
+ "measurements": "<key readings and values observed>",
342
+ "interpretation": "<what the data shows about ground/structure behavior>",
343
+ "evaluation": "<whether conditions are normal, warning, or critical>",
344
+ "recommendations": "<recommended actions if any>",
345
+ "confidence": <number 0-100>,
346
+ "warnings": ["<warning>", "<warning>"]
271
347
  }`;
272
- const response = await generateVision(prompt, imageBase64, mimeType, config, {
273
- systemPrompt: 'You are an expert geotechnical instrumentation engineer. Interpret sensor data precisely.',
274
- temperature: 0.1,
275
- maxTokens: 1100,
276
- });
348
+ const softPrompt = `Inspect this geotechnical instrumentation image and extract:
349
+ 1. Sensor type
350
+ 2. Key readings and visible values
351
+ 3. What the measurements indicate about ground or structural behavior
352
+ 4. Whether the condition appears normal, warning, or critical
353
+ 5. Recommended follow-up actions if any
354
+ If the display is partially unreadable, provide the best approximate interpretation from the visible chart or text.`;
355
+ const response = await visionWithRetry(imageBase64, mimeType, config, strictPrompt, softPrompt, 'You are an expert geotechnical instrumentation engineer. Interpret sensor data precisely.', 1100);
277
356
  const parsed = parseJsonObject(response.text);
278
357
  const warnings = [...parsed.warnings];
279
358
  const sensorType = readString(parsed.value, 'sensorType', warnings);