@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.
- package/dist/agents/brain.d.ts +1 -0
- package/dist/agents/brain.d.ts.map +1 -1
- package/dist/agents/brain.js +39 -38
- package/dist/agents/brain.js.map +1 -1
- package/dist/agents/bridge-tools.js +15 -3
- package/dist/agents/bridge-tools.js.map +1 -1
- package/dist/agents/case-file.d.ts +52 -0
- package/dist/agents/case-file.d.ts.map +1 -0
- package/dist/agents/case-file.js +657 -0
- package/dist/agents/case-file.js.map +1 -0
- package/dist/agents/contracts.d.ts +232 -0
- package/dist/agents/contracts.d.ts.map +1 -0
- package/dist/agents/contracts.js +14 -0
- package/dist/agents/contracts.js.map +1 -0
- package/dist/agents/data-tools.js +11 -3
- package/dist/agents/data-tools.js.map +1 -1
- package/dist/agents/deliverable-tools.d.ts +2 -0
- package/dist/agents/deliverable-tools.d.ts.map +1 -0
- package/dist/agents/deliverable-tools.js +497 -0
- package/dist/agents/deliverable-tools.js.map +1 -0
- package/dist/agents/evidence.d.ts +21 -0
- package/dist/agents/evidence.d.ts.map +1 -0
- package/dist/agents/evidence.js +302 -0
- package/dist/agents/evidence.js.map +1 -0
- package/dist/agents/orchestrator.js +13 -13
- package/dist/agents/sandbox.d.ts.map +1 -1
- package/dist/agents/sandbox.js +84 -31
- package/dist/agents/sandbox.js.map +1 -1
- package/dist/agents/swarm.d.ts +2 -0
- package/dist/agents/swarm.d.ts.map +1 -1
- package/dist/agents/swarm.js +170 -149
- package/dist/agents/swarm.js.map +1 -1
- package/dist/bridge/index.js +87 -87
- package/dist/geo/index.d.ts +2 -1
- package/dist/geo/index.d.ts.map +1 -1
- package/dist/geo/index.js +1 -0
- package/dist/geo/index.js.map +1 -1
- package/dist/geo/pile-capacity.d.ts.map +1 -1
- package/dist/geo/pile-capacity.js +47 -8
- package/dist/geo/pile-capacity.js.map +1 -1
- package/dist/geo/seepage.d.ts +62 -0
- package/dist/geo/seepage.d.ts.map +1 -0
- package/dist/geo/seepage.js +130 -0
- package/dist/geo/seepage.js.map +1 -0
- package/dist/geo/slope-stability.d.ts.map +1 -1
- package/dist/geo/slope-stability.js +9 -6
- package/dist/geo/slope-stability.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/middleware/metering.js +14 -14
- package/dist/llm/providers/hosted-beta.d.ts.map +1 -1
- package/dist/llm/providers/hosted-beta.js +5 -2
- package/dist/llm/providers/hosted-beta.js.map +1 -1
- package/dist/llm/providers/zhipu.d.ts.map +1 -1
- package/dist/llm/providers/zhipu.js +3 -1
- package/dist/llm/providers/zhipu.js.map +1 -1
- package/dist/llm/util.d.ts +10 -0
- package/dist/llm/util.d.ts.map +1 -0
- package/dist/llm/util.js +54 -0
- package/dist/llm/util.js.map +1 -0
- package/dist/meta/metadata.json +4 -4
- package/dist/report/casefile.d.ts +207 -0
- package/dist/report/casefile.d.ts.map +1 -0
- package/dist/report/casefile.js +438 -0
- package/dist/report/casefile.js.map +1 -0
- package/dist/report/docx.d.ts +3 -0
- package/dist/report/docx.d.ts.map +1 -0
- package/dist/report/docx.js +178 -0
- package/dist/report/docx.js.map +1 -0
- package/dist/report/index.d.ts +3 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +23 -20
- package/dist/report/index.js.map +1 -1
- package/dist/report/pdf.d.ts +3 -0
- package/dist/report/pdf.d.ts.map +1 -0
- package/dist/report/pdf.js +195 -0
- package/dist/report/pdf.js.map +1 -0
- package/dist/vision/index.d.ts.map +1 -1
- package/dist/vision/index.js +163 -84
- package/dist/vision/index.js.map +1 -1
- package/package.json +58 -55
package/dist/report/index.d.ts
CHANGED
|
@@ -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"}
|
package/dist/report/index.js
CHANGED
|
@@ -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.`,
|
package/dist/report/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,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 @@
|
|
|
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":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vision/index.ts"],"names":[],"mappings":"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"}
|
package/dist/vision/index.js
CHANGED
|
@@ -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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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);
|