railwise-ai 1.2.32 → 1.2.34
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/agent-pack/README.md +16 -0
- package/agent-pack/assets/agent/chief_manager.md +69 -0
- package/agent-pack/assets/agent/commercial_specialist.md +47 -0
- package/agent-pack/assets/agent/data_analyst.md +54 -0
- package/agent-pack/assets/agent/docs.md +38 -0
- package/agent-pack/assets/agent/duplicate-pr.md +25 -0
- package/agent-pack/assets/agent/qa_inspector.md +58 -0
- package/agent-pack/assets/agent/qa_reviewer.md +57 -0
- package/agent-pack/assets/agent/solution_architect.md +53 -0
- package/agent-pack/assets/agent/technical_writer.md +61 -0
- package/agent-pack/assets/agent/translator.md +34 -0
- package/agent-pack/assets/agent/triage.md +139 -0
- package/agent-pack/assets/command/ai-deps.md +24 -0
- package/agent-pack/assets/command/bid-intel.md +29 -0
- package/agent-pack/assets/command/bid-prepare.md +24 -0
- package/agent-pack/assets/command/commit.md +37 -0
- package/agent-pack/assets/command/daily-report.md +26 -0
- package/agent-pack/assets/command/data-check.md +22 -0
- package/agent-pack/assets/command/emergency-response.md +66 -0
- package/agent-pack/assets/command/issues.md +23 -0
- package/agent-pack/assets/command/learn.md +42 -0
- package/agent-pack/assets/command/monthly-report.md +35 -0
- package/agent-pack/assets/command/payment-reminder.md +27 -0
- package/agent-pack/assets/command/plan-draft.md +28 -0
- package/agent-pack/assets/command/review-response.md +23 -0
- package/agent-pack/assets/command/rmslop.md +15 -0
- package/agent-pack/assets/command/safety-check.md +20 -0
- package/agent-pack/assets/command/spellcheck.md +5 -0
- package/agent-pack/assets/command/trend-analysis.md +38 -0
- package/agent-pack/assets/command/weekly-report.md +28 -0
- package/agent-pack/assets/lib/os_api.ts +100 -0
- package/agent-pack/assets/skill/adjustment-report/SKILL.md +88 -0
- package/agent-pack/assets/skill/approval-flow-intelligence/SKILL.md +123 -0
- package/agent-pack/assets/skill/bidding-knowledge/SKILL.md +98 -0
- package/agent-pack/assets/skill/bun-file-io/SKILL.md +42 -0
- package/agent-pack/assets/skill/business-finance/SKILL.md +91 -0
- package/agent-pack/assets/skill/business-operations-analytics/SKILL.md +120 -0
- package/agent-pack/assets/skill/cad-bim-review/SKILL.md +84 -0
- package/agent-pack/assets/skill/canvas-design/SKILL.md +141 -0
- package/agent-pack/assets/skill/construction-monitoring/SKILL.md +89 -0
- package/agent-pack/assets/skill/customer-portal-brief/SKILL.md +121 -0
- package/agent-pack/assets/skill/data-analysis/SKILL.md +79 -0
- package/agent-pack/assets/skill/di-bao-monitoring/SKILL.md +680 -0
- package/agent-pack/assets/skill/di-bao-monitoring/agents/openai.yaml +4 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/crossing-static-level-input-template.csv +2 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/crossing-total-station-input-template.csv +2 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/crossing-total-station-report-template.md +59 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/daily-report-template.md +90 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/data-input-template.csv +2 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/initial-value-report-template.md +92 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/monthly-report-template.md +103 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/plan-template.md +673 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/point-alias-map-template.json +25 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/project-brief-template.md +59 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/project-summary-template.md +154 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/warning-notice-template.md +58 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets/weekly-report-template.md +49 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets//345/256/214/346/225/264/346/212/245/350/241/250/346/250/241/346/235/277.xlsx +0 -0
- package/agent-pack/assets/skill/di-bao-monitoring/assets//345/277/253/346/212/245/346/250/241/346/235/277.xlsx +0 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/crossing-static-level-report.md +115 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/crossing-total-station-report.md +206 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/data-processing.md +170 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/elevated-line.md +319 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/initial-and-summary-workflow.md +222 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/key-difficulties.md +216 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/monitoring-catalog.md +80 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/phase0-intake.md +169 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/phase1-drafting.md +185 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/phase2-internal-review.md +166 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/report-workflow.md +181 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/review-checklist.md +140 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/review-response-template.md +87 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/scoping-and-pricing.md +214 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/source-analysis.md +137 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/technical-standards.md +188 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/toc-template-A.md +142 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/toc-template-B.md +100 -0
- package/agent-pack/assets/skill/di-bao-monitoring/references/warning-workflow.md +63 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/build_crossing_total_station_xlsx.py +2438 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/evaluate_alarms.py +215 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/fetch_adjusted_total_station.py +946 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/fetch_shhh_static_level.py +556 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/run_crossing_4h_report.sh +141 -0
- package/agent-pack/assets/skill/di-bao-monitoring/scripts/summarize_crossing_total_station.py +303 -0
- package/agent-pack/assets/skill/docx-generation/SKILL.md +141 -0
- package/agent-pack/assets/skill/excel-operations/SKILL.md +121 -0
- package/agent-pack/assets/skill/frontend-design/SKILL.md +151 -0
- package/agent-pack/assets/skill/humanizer/SKILL.md +90 -0
- package/agent-pack/assets/skill/monitoring-design/SKILL.md +100 -0
- package/agent-pack/assets/skill/operational-monitoring/SKILL.md +135 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/checklists/archive-self-check.md +54 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/schemas/convergence-result-table.csv +5 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/schemas/horizontal-result-table.csv +3 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/schemas/settlement-result-table.csv +7 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/scripts/init-archive-tree.sh +65 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/control-network-report.md +102 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/daily-log.md +47 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/i-angle-check.md +38 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/monitoring-scheme.md +234 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/period-report.md +95 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/point-acceptance-record.md +51 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/point-installation-record.md +38 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/summary-report.md +89 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/warning-bulletin.md +61 -0
- package/agent-pack/assets/skill/operational-monitoring/assets/templates/weekly-monthly-report.md +46 -0
- package/agent-pack/assets/skill/operational-monitoring/references/archive-and-delivery.md +146 -0
- package/agent-pack/assets/skill/operational-monitoring/references/baseline-network.md +131 -0
- package/agent-pack/assets/skill/operational-monitoring/references/convergence-monitoring.md +134 -0
- package/agent-pack/assets/skill/operational-monitoring/references/data-processing.md +178 -0
- package/agent-pack/assets/skill/operational-monitoring/references/horizontal-displacement.md +128 -0
- package/agent-pack/assets/skill/operational-monitoring/references/monitoring-points.md +132 -0
- package/agent-pack/assets/skill/operational-monitoring/references/monitoring-scheme.md +178 -0
- package/agent-pack/assets/skill/operational-monitoring/references/period-report.md +108 -0
- package/agent-pack/assets/skill/operational-monitoring/references/regulations-and-frequency.md +127 -0
- package/agent-pack/assets/skill/operational-monitoring/references/settlement-monitoring.md +116 -0
- package/agent-pack/assets/skill/operational-monitoring/references/summary-report.md +128 -0
- package/agent-pack/assets/skill/operational-monitoring/references/warning-and-disposal.md +118 -0
- package/agent-pack/assets/skill/ops-monitoring/SKILL.md +93 -0
- package/agent-pack/assets/skill/railwise-knowledge-curation/SKILL.md +92 -0
- package/agent-pack/assets/skill/report-dibao/SKILL.md +60 -0
- package/agent-pack/assets/skill/report-writing/SKILL.md +103 -0
- package/agent-pack/assets/skill/resource-dispatch-intelligence/SKILL.md +125 -0
- package/agent-pack/assets/skill/standard-reference/SKILL.md +101 -0
- package/agent-pack/assets/skill/weekly-work-intelligence/SKILL.md +123 -0
- package/agent-pack/assets/template/adjustment-trend.json +53 -0
- package/agent-pack/assets/template/bid-technical-proposal.json +42 -0
- package/agent-pack/assets/template/compliance-review.json +35 -0
- package/agent-pack/assets/template/daily-monitor-report.json +34 -0
- package/agent-pack/assets/template/field-data-qa.json +47 -0
- package/agent-pack/assets/template/monitoring-plan.json +28 -0
- package/agent-pack/assets/template/monthly-monitor-report.json +47 -0
- package/agent-pack/assets/template/project-ppt.json +47 -0
- package/agent-pack/assets/template/review-response.json +28 -0
- package/agent-pack/assets/template/schema.json +72 -0
- package/agent-pack/assets/template/weekly-monitor-report.json +33 -0
- package/agent-pack/assets/theme/mytheme.json +223 -0
- package/agent-pack/assets/tool/angle_convert.ts +151 -0
- package/agent-pack/assets/tool/axial_force.ts +186 -0
- package/agent-pack/assets/tool/chart_generator.ts +185 -0
- package/agent-pack/assets/tool/control_network.ts +413 -0
- package/agent-pack/assets/tool/coord_transform.ts +333 -0
- package/agent-pack/assets/tool/cpiii_adjustment.ts +446 -0
- package/agent-pack/assets/tool/cross_section.ts +356 -0
- package/agent-pack/assets/tool/deformation_rate.ts +234 -0
- package/agent-pack/assets/tool/distance_calculator.ts +239 -0
- package/agent-pack/assets/tool/excel_export.ts +494 -0
- package/agent-pack/assets/tool/format_parser.ts +181 -0
- package/agent-pack/assets/tool/github-pr-search.ts +57 -0
- package/agent-pack/assets/tool/github-pr-search.txt +10 -0
- package/agent-pack/assets/tool/github-triage.ts +113 -0
- package/agent-pack/assets/tool/github-triage.txt +6 -0
- package/agent-pack/assets/tool/inclinometer.ts +248 -0
- package/agent-pack/assets/tool/monitoring_csv.ts +120 -0
- package/agent-pack/assets/tool/os_contract_query.ts +63 -0
- package/agent-pack/assets/tool/os_dibao_project_files.ts +75 -0
- package/agent-pack/assets/tool/os_file_download_ref.ts +48 -0
- package/agent-pack/assets/tool/os_file_preview.ts +46 -0
- package/agent-pack/assets/tool/os_gbrain_think.ts +31 -0
- package/agent-pack/assets/tool/os_ppt_generate.ts +37 -0
- package/agent-pack/assets/tool/os_project_context.ts +24 -0
- package/agent-pack/assets/tool/os_report_publish.ts +28 -0
- package/agent-pack/assets/tool/os_wiki_ingest.ts +38 -0
- package/agent-pack/assets/tool/os_wiki_query.ts +26 -0
- package/agent-pack/assets/tool/pile_stakeout.ts +299 -0
- package/agent-pack/assets/tool/report_export.ts +279 -0
- package/agent-pack/assets/tool/shield_guidance.ts +311 -0
- package/agent-pack/assets/tool/standard_query.ts +391 -0
- package/agent-pack/assets/tool/survey_calculator.ts +415 -0
- package/agent-pack/assets/tool/water_level.ts +209 -0
- package/agent-pack/bin/install.js +183 -0
- package/agent-pack/package.json +695 -0
- package/package.json +14 -8
- package/postinstall.mjs +53 -22
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/// <reference path="../env.d.ts" />
|
|
2
|
+
import { tool } from "nb-railwise/tool"
|
|
3
|
+
import { deflateRawSync } from "node:zlib"
|
|
4
|
+
|
|
5
|
+
// XLSX = ZIP of XML files (Office Open XML SpreadsheetML)
|
|
6
|
+
// Reuse the minimal ZIP builder from report_export.ts
|
|
7
|
+
|
|
8
|
+
function crc32(buf: Uint8Array) {
|
|
9
|
+
let c = 0xffffffff
|
|
10
|
+
for (let i = 0; i < buf.length; i++) {
|
|
11
|
+
c ^= buf[i]!
|
|
12
|
+
for (let j = 0; j < 8; j++) c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0)
|
|
13
|
+
}
|
|
14
|
+
return (c ^ 0xffffffff) >>> 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function zip(files: Array<{ name: string; data: Uint8Array }>) {
|
|
18
|
+
const entries: Array<{
|
|
19
|
+
name: Uint8Array
|
|
20
|
+
compressed: Uint8Array
|
|
21
|
+
crc: number
|
|
22
|
+
size: number
|
|
23
|
+
csize: number
|
|
24
|
+
offset: number
|
|
25
|
+
}> = []
|
|
26
|
+
const parts: Uint8Array[] = []
|
|
27
|
+
let offset = 0
|
|
28
|
+
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
const nameBytes = new TextEncoder().encode(f.name)
|
|
31
|
+
const crc = crc32(f.data)
|
|
32
|
+
const compressed = new Uint8Array(deflateRawSync(f.data))
|
|
33
|
+
const header = new Uint8Array(30 + nameBytes.length)
|
|
34
|
+
const v = new DataView(header.buffer)
|
|
35
|
+
v.setUint32(0, 0x04034b50, true)
|
|
36
|
+
v.setUint16(4, 20, true)
|
|
37
|
+
v.setUint16(8, 8, true)
|
|
38
|
+
v.setUint32(14, crc, true)
|
|
39
|
+
v.setUint32(18, compressed.length, true)
|
|
40
|
+
v.setUint32(22, f.data.length, true)
|
|
41
|
+
v.setUint16(26, nameBytes.length, true)
|
|
42
|
+
header.set(nameBytes, 30)
|
|
43
|
+
|
|
44
|
+
entries.push({ name: nameBytes, compressed, crc, size: f.data.length, csize: compressed.length, offset })
|
|
45
|
+
parts.push(header, compressed)
|
|
46
|
+
offset += header.length + compressed.length
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const cdStart = offset
|
|
50
|
+
for (const e of entries) {
|
|
51
|
+
const cd = new Uint8Array(46 + e.name.length)
|
|
52
|
+
const v = new DataView(cd.buffer)
|
|
53
|
+
v.setUint32(0, 0x02014b50, true)
|
|
54
|
+
v.setUint16(4, 20, true)
|
|
55
|
+
v.setUint16(6, 20, true)
|
|
56
|
+
v.setUint16(10, 8, true)
|
|
57
|
+
v.setUint32(16, e.crc, true)
|
|
58
|
+
v.setUint32(20, e.csize, true)
|
|
59
|
+
v.setUint32(24, e.size, true)
|
|
60
|
+
v.setUint16(28, e.name.length, true)
|
|
61
|
+
v.setUint32(42, e.offset, true)
|
|
62
|
+
cd.set(e.name, 46)
|
|
63
|
+
parts.push(cd)
|
|
64
|
+
offset += cd.length
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const eocd = new Uint8Array(22)
|
|
68
|
+
const ev = new DataView(eocd.buffer)
|
|
69
|
+
ev.setUint32(0, 0x06054b50, true)
|
|
70
|
+
ev.setUint16(8, entries.length, true)
|
|
71
|
+
ev.setUint16(10, entries.length, true)
|
|
72
|
+
ev.setUint32(12, offset - cdStart, true)
|
|
73
|
+
ev.setUint32(16, cdStart, true)
|
|
74
|
+
parts.push(eocd)
|
|
75
|
+
|
|
76
|
+
let total = 0
|
|
77
|
+
for (const p of parts) total += p.length
|
|
78
|
+
const result = new Uint8Array(total)
|
|
79
|
+
let pos = 0
|
|
80
|
+
for (const p of parts) {
|
|
81
|
+
result.set(p, pos)
|
|
82
|
+
pos += p.length
|
|
83
|
+
}
|
|
84
|
+
return result
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function esc(s: string) {
|
|
88
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Excel column letter: 0->A, 1->B, ..., 25->Z, 26->AA
|
|
92
|
+
function colLetter(idx: number): string {
|
|
93
|
+
let s = ""
|
|
94
|
+
let n = idx
|
|
95
|
+
while (n >= 0) {
|
|
96
|
+
s = String.fromCharCode(65 + (n % 26)) + s
|
|
97
|
+
n = Math.floor(n / 26) - 1
|
|
98
|
+
}
|
|
99
|
+
return s
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type CellValue = string | number | boolean | null | undefined
|
|
103
|
+
type SheetData = {
|
|
104
|
+
name: string
|
|
105
|
+
headers: string[]
|
|
106
|
+
rows: CellValue[][]
|
|
107
|
+
columnWidths?: number[]
|
|
108
|
+
freezeRow?: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildSharedStrings(sheets: SheetData[]): { xml: string; lookup: Map<string, number> } {
|
|
112
|
+
const lookup = new Map<string, number>()
|
|
113
|
+
let idx = 0
|
|
114
|
+
|
|
115
|
+
for (const sheet of sheets) {
|
|
116
|
+
for (const h of sheet.headers) {
|
|
117
|
+
if (!lookup.has(h)) lookup.set(h, idx++)
|
|
118
|
+
}
|
|
119
|
+
for (const row of sheet.rows) {
|
|
120
|
+
for (const cell of row) {
|
|
121
|
+
if (typeof cell === "string" && !lookup.has(cell)) lookup.set(cell, idx++)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const items = Array.from(lookup.entries())
|
|
127
|
+
.sort((a, b) => a[1] - b[1])
|
|
128
|
+
.map(([s]) => `<si><t>${esc(s)}</t></si>`)
|
|
129
|
+
.join("")
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
xml: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
133
|
+
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${lookup.size}" uniqueCount="${lookup.size}">${items}</sst>`,
|
|
134
|
+
lookup,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildSheet(sheet: SheetData, strings: Map<string, number>): string {
|
|
139
|
+
const cols = sheet.headers.length
|
|
140
|
+
const lastCol = colLetter(cols - 1)
|
|
141
|
+
const lastRow = sheet.rows.length + 1
|
|
142
|
+
|
|
143
|
+
// Column widths
|
|
144
|
+
let colsXml = ""
|
|
145
|
+
if (sheet.columnWidths && sheet.columnWidths.length > 0) {
|
|
146
|
+
const colDefs = sheet.columnWidths
|
|
147
|
+
.map((w, i) => `<col min="${i + 1}" max="${i + 1}" width="${w}" customWidth="1"/>`)
|
|
148
|
+
.join("")
|
|
149
|
+
colsXml = `<cols>${colDefs}</cols>`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Header row
|
|
153
|
+
const headerCells = sheet.headers
|
|
154
|
+
.map((h, c) => {
|
|
155
|
+
const ref = `${colLetter(c)}1`
|
|
156
|
+
const si = strings.get(h) ?? 0
|
|
157
|
+
return `<c r="${ref}" t="s" s="1"><v>${si}</v></c>`
|
|
158
|
+
})
|
|
159
|
+
.join("")
|
|
160
|
+
const headerRow = `<row r="1">${headerCells}</row>`
|
|
161
|
+
|
|
162
|
+
// Data rows
|
|
163
|
+
const dataRows = sheet.rows
|
|
164
|
+
.map((row, ri) => {
|
|
165
|
+
const r = ri + 2
|
|
166
|
+
const cells = row
|
|
167
|
+
.map((cell, ci) => {
|
|
168
|
+
const ref = `${colLetter(ci)}${r}`
|
|
169
|
+
if (cell === null || cell === undefined) return `<c r="${ref}"/>`
|
|
170
|
+
if (typeof cell === "number") return `<c r="${ref}" s="2"><v>${cell}</v></c>`
|
|
171
|
+
if (typeof cell === "boolean") return `<c r="${ref}"><v>${cell ? 1 : 0}</v></c>`
|
|
172
|
+
const si = strings.get(cell) ?? 0
|
|
173
|
+
return `<c r="${ref}" t="s"><v>${si}</v></c>`
|
|
174
|
+
})
|
|
175
|
+
.join("")
|
|
176
|
+
return `<row r="${r}">${cells}</row>`
|
|
177
|
+
})
|
|
178
|
+
.join("\n")
|
|
179
|
+
|
|
180
|
+
// Freeze pane (freeze header row by default)
|
|
181
|
+
const freezeRow = sheet.freezeRow ?? 1
|
|
182
|
+
const pane =
|
|
183
|
+
freezeRow > 0
|
|
184
|
+
? `<pane ySplit="${freezeRow}" topLeftCell="A${freezeRow + 1}" activePane="bottomLeft" state="frozen"/>`
|
|
185
|
+
: ""
|
|
186
|
+
|
|
187
|
+
// AutoFilter on header row
|
|
188
|
+
const autoFilter = `<autoFilter ref="A1:${lastCol}${lastRow}"/>`
|
|
189
|
+
|
|
190
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
191
|
+
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
192
|
+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
193
|
+
${colsXml}
|
|
194
|
+
<sheetViews><sheetView tabSelected="1" workbookViewId="0">${pane}</sheetView></sheetViews>
|
|
195
|
+
<sheetData>
|
|
196
|
+
${headerRow}
|
|
197
|
+
${dataRows}
|
|
198
|
+
</sheetData>
|
|
199
|
+
${autoFilter}
|
|
200
|
+
</worksheet>`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function buildStyles(): string {
|
|
204
|
+
// s="0" = default, s="1" = header (bold, light blue bg), s="2" = number (2 decimals)
|
|
205
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
206
|
+
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
|
207
|
+
<numFmts count="1">
|
|
208
|
+
<numFmt numFmtId="164" formatCode="0.000"/>
|
|
209
|
+
</numFmts>
|
|
210
|
+
<fonts count="2">
|
|
211
|
+
<font><sz val="11"/><name val="等线"/></font>
|
|
212
|
+
<font><b/><sz val="11"/><name val="等线"/></font>
|
|
213
|
+
</fonts>
|
|
214
|
+
<fills count="3">
|
|
215
|
+
<fill><patternFill patternType="none"/></fill>
|
|
216
|
+
<fill><patternFill patternType="gray125"/></fill>
|
|
217
|
+
<fill><patternFill patternType="solid"><fgColor rgb="FFD9E2F3"/></patternFill></fill>
|
|
218
|
+
</fills>
|
|
219
|
+
<borders count="2">
|
|
220
|
+
<border/>
|
|
221
|
+
<border>
|
|
222
|
+
<left style="thin"><color auto="1"/></left>
|
|
223
|
+
<right style="thin"><color auto="1"/></right>
|
|
224
|
+
<top style="thin"><color auto="1"/></top>
|
|
225
|
+
<bottom style="thin"><color auto="1"/></bottom>
|
|
226
|
+
</border>
|
|
227
|
+
</borders>
|
|
228
|
+
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
|
|
229
|
+
<cellXfs count="3">
|
|
230
|
+
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
|
|
231
|
+
<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1">
|
|
232
|
+
<alignment horizontal="center" vertical="center" wrapText="1"/>
|
|
233
|
+
</xf>
|
|
234
|
+
<xf numFmtId="164" fontId="0" fillId="0" borderId="1" applyNumberFormat="1" applyBorder="1"/>
|
|
235
|
+
</cellXfs>
|
|
236
|
+
</styleSheet>`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildXlsx(sheets: SheetData[]): Uint8Array {
|
|
240
|
+
const { xml: sst, lookup } = buildSharedStrings(sheets)
|
|
241
|
+
|
|
242
|
+
const sheetXmls = sheets.map((s) => buildSheet(s, lookup))
|
|
243
|
+
|
|
244
|
+
const sheetRefs = sheets
|
|
245
|
+
.map((s, i) => `<sheet name="${esc(s.name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`)
|
|
246
|
+
.join("")
|
|
247
|
+
|
|
248
|
+
const workbook = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
249
|
+
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
250
|
+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
251
|
+
<sheets>${sheetRefs}</sheets>
|
|
252
|
+
</workbook>`
|
|
253
|
+
|
|
254
|
+
const sheetRelEntries = sheets
|
|
255
|
+
.map(
|
|
256
|
+
(_, i) =>
|
|
257
|
+
`<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`,
|
|
258
|
+
)
|
|
259
|
+
.join("")
|
|
260
|
+
|
|
261
|
+
const wbRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
262
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
263
|
+
${sheetRelEntries}
|
|
264
|
+
<Relationship Id="rId${sheets.length + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
265
|
+
<Relationship Id="rId${sheets.length + 2}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
|
|
266
|
+
</Relationships>`
|
|
267
|
+
|
|
268
|
+
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
269
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
270
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
|
271
|
+
</Relationships>`
|
|
272
|
+
|
|
273
|
+
const sheetOverrides = sheets
|
|
274
|
+
.map(
|
|
275
|
+
(_, i) =>
|
|
276
|
+
`<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`,
|
|
277
|
+
)
|
|
278
|
+
.join("\n ")
|
|
279
|
+
|
|
280
|
+
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
281
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
282
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
283
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
284
|
+
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
285
|
+
${sheetOverrides}
|
|
286
|
+
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
|
287
|
+
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
|
|
288
|
+
</Types>`
|
|
289
|
+
|
|
290
|
+
const enc = (s: string) => new TextEncoder().encode(s)
|
|
291
|
+
|
|
292
|
+
const files: Array<{ name: string; data: Uint8Array }> = [
|
|
293
|
+
{ name: "[Content_Types].xml", data: enc(contentTypes) },
|
|
294
|
+
{ name: "_rels/.rels", data: enc(rels) },
|
|
295
|
+
{ name: "xl/workbook.xml", data: enc(workbook) },
|
|
296
|
+
{ name: "xl/_rels/workbook.xml.rels", data: enc(wbRels) },
|
|
297
|
+
{ name: "xl/styles.xml", data: enc(buildStyles()) },
|
|
298
|
+
{ name: "xl/sharedStrings.xml", data: enc(sst) },
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < sheetXmls.length; i++) {
|
|
302
|
+
files.push({ name: `xl/worksheets/sheet${i + 1}.xml`, data: enc(sheetXmls[i]!) })
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return zip(files)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const excel_export = tool({
|
|
309
|
+
description:
|
|
310
|
+
"将监测数据导出为 .xlsx(Excel)文件。支持多 Sheet、表头冻结、自动筛选、数值格式化。用于导出基坑监测日报/周报数据表、变形汇总表、轴力统计表等。data_analyst 或 technical_writer 需要输出 Excel 报表时必须调用此工具。",
|
|
311
|
+
args: {
|
|
312
|
+
sheets: tool.schema
|
|
313
|
+
.array(
|
|
314
|
+
tool.schema.object({
|
|
315
|
+
name: tool.schema.string().describe("Sheet 名称,如 '沉降监测' '深层位移' '轴力统计'"),
|
|
316
|
+
headers: tool.schema.array(tool.schema.string()).min(1).describe("列标题"),
|
|
317
|
+
rows: tool.schema
|
|
318
|
+
.array(
|
|
319
|
+
tool.schema.array(
|
|
320
|
+
tool.schema.union([
|
|
321
|
+
tool.schema.string(),
|
|
322
|
+
tool.schema.number(),
|
|
323
|
+
tool.schema.boolean(),
|
|
324
|
+
tool.schema.null(),
|
|
325
|
+
]),
|
|
326
|
+
),
|
|
327
|
+
)
|
|
328
|
+
.describe("数据行,每行元素数量与 headers 一致"),
|
|
329
|
+
columnWidths: tool.schema
|
|
330
|
+
.array(tool.schema.number().positive())
|
|
331
|
+
.optional()
|
|
332
|
+
.describe("各列宽度(字符数),不传则自动计算"),
|
|
333
|
+
freezeRow: tool.schema.number().int().default(1).describe("冻结前N行,默认1(冻结表头)"),
|
|
334
|
+
}),
|
|
335
|
+
)
|
|
336
|
+
.min(1)
|
|
337
|
+
.describe("工作表列表,支持多个 Sheet"),
|
|
338
|
+
title: tool.schema.string().default("监测数据").describe("文件名(不含扩展名)"),
|
|
339
|
+
outputPath: tool.schema.string().optional().describe("输出路径,默认 ./[title].xlsx"),
|
|
340
|
+
},
|
|
341
|
+
async execute(args) {
|
|
342
|
+
const sheetsData: SheetData[] = args.sheets.map((s) => {
|
|
343
|
+
const widths =
|
|
344
|
+
s.columnWidths ??
|
|
345
|
+
s.headers.map((h, i) => {
|
|
346
|
+
const headerLen = [...h].length + 4
|
|
347
|
+
const maxDataLen = s.rows.reduce((max, row) => {
|
|
348
|
+
const cell = row[i]
|
|
349
|
+
const len = cell === null || cell === undefined ? 0 : String(cell).length
|
|
350
|
+
return Math.max(max, len)
|
|
351
|
+
}, 0)
|
|
352
|
+
return Math.min(Math.max(headerLen, maxDataLen + 2), 50)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
name: s.name,
|
|
357
|
+
headers: s.headers,
|
|
358
|
+
rows: s.rows,
|
|
359
|
+
columnWidths: widths,
|
|
360
|
+
freezeRow: s.freezeRow,
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const xlsxBytes = buildXlsx(sheetsData)
|
|
365
|
+
const dest = args.outputPath ?? `./${args.title.replace(/[/\\:*?"<>|]/g, "_")}.xlsx`
|
|
366
|
+
await Bun.write(dest, xlsxBytes)
|
|
367
|
+
|
|
368
|
+
const totalRows = args.sheets.reduce((s, sh) => s + sh.rows.length, 0)
|
|
369
|
+
const totalCols = args.sheets.reduce((s, sh) => s + sh.headers.length, 0)
|
|
370
|
+
|
|
371
|
+
return JSON.stringify({
|
|
372
|
+
output_path: dest,
|
|
373
|
+
file_size_kb: Number((xlsxBytes.length / 1024).toFixed(1)),
|
|
374
|
+
format: "xlsx (Office Open XML SpreadsheetML)",
|
|
375
|
+
sheets: args.sheets.map((s) => ({
|
|
376
|
+
name: s.name,
|
|
377
|
+
columns: s.headers.length,
|
|
378
|
+
rows: s.rows.length,
|
|
379
|
+
})),
|
|
380
|
+
total_rows: totalRows,
|
|
381
|
+
total_columns: totalCols,
|
|
382
|
+
message: `✅ Excel 报表已导出:${dest}(${(xlsxBytes.length / 1024).toFixed(1)} KB),${args.sheets.length}个工作表,共 ${totalRows} 行数据`,
|
|
383
|
+
})
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
export const monitoring_table_export = tool({
|
|
388
|
+
description:
|
|
389
|
+
"快速导出标准格式的监测数据汇总表。直接输入测点数据,自动生成带预警标识的规范化 Excel 报表。适用于沉降、位移、轴力、水位等各类监测项目的数据报表导出。",
|
|
390
|
+
args: {
|
|
391
|
+
projectName: tool.schema.string().describe("项目名称"),
|
|
392
|
+
monitoringType: tool.schema
|
|
393
|
+
.enum(["settlement", "displacement", "axial_force", "water_level", "inclinometer", "convergence"])
|
|
394
|
+
.describe("监测类型"),
|
|
395
|
+
date: tool.schema.string().describe("报表日期 YYYY-MM-DD"),
|
|
396
|
+
points: tool.schema
|
|
397
|
+
.array(
|
|
398
|
+
tool.schema.object({
|
|
399
|
+
id: tool.schema.string().describe("测点编号"),
|
|
400
|
+
section: tool.schema.string().optional().describe("所属断面"),
|
|
401
|
+
initialValue: tool.schema.number().optional().describe("初始值"),
|
|
402
|
+
previousValue: tool.schema.number().optional().describe("上期值"),
|
|
403
|
+
currentValue: tool.schema.number().describe("本期值"),
|
|
404
|
+
cumulativeChange: tool.schema.number().describe("累计变化量"),
|
|
405
|
+
periodChange: tool.schema.number().optional().describe("本期变化量"),
|
|
406
|
+
rate: tool.schema.number().optional().describe("变化速率(/d)"),
|
|
407
|
+
}),
|
|
408
|
+
)
|
|
409
|
+
.min(1)
|
|
410
|
+
.describe("各测点数据"),
|
|
411
|
+
alertThreshold: tool.schema.number().positive().optional().describe("报警控制值"),
|
|
412
|
+
unit: tool.schema.string().default("mm").describe("单位"),
|
|
413
|
+
outputPath: tool.schema.string().optional().describe("输出路径"),
|
|
414
|
+
},
|
|
415
|
+
async execute(args) {
|
|
416
|
+
const typeLabels: Record<string, string> = {
|
|
417
|
+
settlement: "沉降监测",
|
|
418
|
+
displacement: "水平位移",
|
|
419
|
+
axial_force: "轴力监测",
|
|
420
|
+
water_level: "水位监测",
|
|
421
|
+
inclinometer: "深层水平位移",
|
|
422
|
+
convergence: "收敛监测",
|
|
423
|
+
}
|
|
424
|
+
const typeLabel = typeLabels[args.monitoringType] ?? args.monitoringType
|
|
425
|
+
|
|
426
|
+
const headers = [
|
|
427
|
+
"测点编号",
|
|
428
|
+
...(args.points.some((p) => p.section) ? ["所属断面"] : []),
|
|
429
|
+
...(args.points.some((p) => p.initialValue !== undefined) ? [`初始值(${args.unit})`] : []),
|
|
430
|
+
...(args.points.some((p) => p.previousValue !== undefined) ? [`上期值(${args.unit})`] : []),
|
|
431
|
+
`本期值(${args.unit})`,
|
|
432
|
+
`累计变化量(${args.unit})`,
|
|
433
|
+
...(args.points.some((p) => p.periodChange !== undefined) ? [`本期变化量(${args.unit})`] : []),
|
|
434
|
+
...(args.points.some((p) => p.rate !== undefined) ? [`变化速率(${args.unit}/d)`] : []),
|
|
435
|
+
...(args.alertThreshold ? [`控制值(${args.unit})`, "占控制值(%)", "预警状态"] : []),
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
const rows: CellValue[][] = args.points.map((p) => {
|
|
439
|
+
const ratio = args.alertThreshold ? Math.abs(p.cumulativeChange) / args.alertThreshold : 0
|
|
440
|
+
let alertStatus = ""
|
|
441
|
+
if (args.alertThreshold) {
|
|
442
|
+
if (ratio >= 1.0) alertStatus = "超限"
|
|
443
|
+
else if (ratio >= 0.85) alertStatus = "橙色预警"
|
|
444
|
+
else if (ratio >= 0.7) alertStatus = "黄色预警"
|
|
445
|
+
else alertStatus = "正常"
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return [
|
|
449
|
+
p.id,
|
|
450
|
+
...(args.points.some((pt) => pt.section) ? [p.section ?? ""] : []),
|
|
451
|
+
...(args.points.some((pt) => pt.initialValue !== undefined) ? [p.initialValue ?? null] : []),
|
|
452
|
+
...(args.points.some((pt) => pt.previousValue !== undefined) ? [p.previousValue ?? null] : []),
|
|
453
|
+
p.currentValue,
|
|
454
|
+
p.cumulativeChange,
|
|
455
|
+
...(args.points.some((pt) => pt.periodChange !== undefined) ? [p.periodChange ?? null] : []),
|
|
456
|
+
...(args.points.some((pt) => pt.rate !== undefined) ? [p.rate ?? null] : []),
|
|
457
|
+
...(args.alertThreshold ? [args.alertThreshold, Number((ratio * 100).toFixed(1)), alertStatus] : []),
|
|
458
|
+
]
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
// Summary row
|
|
462
|
+
const cumulativeValues = args.points.map((p) => Math.abs(p.cumulativeChange))
|
|
463
|
+
const maxIdx = cumulativeValues.indexOf(Math.max(...cumulativeValues))
|
|
464
|
+
const avgCumulative = cumulativeValues.reduce((s, v) => s + v, 0) / cumulativeValues.length
|
|
465
|
+
const alertCount = args.alertThreshold
|
|
466
|
+
? args.points.filter((p) => Math.abs(p.cumulativeChange) >= args.alertThreshold! * 0.7).length
|
|
467
|
+
: 0
|
|
468
|
+
|
|
469
|
+
const sheetData: SheetData = {
|
|
470
|
+
name: typeLabel,
|
|
471
|
+
headers,
|
|
472
|
+
rows,
|
|
473
|
+
freezeRow: 1,
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const xlsxBytes = buildXlsx([sheetData])
|
|
477
|
+
const filename = `${args.projectName}_${typeLabel}_${args.date}`
|
|
478
|
+
const dest = args.outputPath ?? `./${filename.replace(/[/\\:*?"<>|]/g, "_")}.xlsx`
|
|
479
|
+
await Bun.write(dest, xlsxBytes)
|
|
480
|
+
|
|
481
|
+
return JSON.stringify({
|
|
482
|
+
output_path: dest,
|
|
483
|
+
file_size_kb: Number((xlsxBytes.length / 1024).toFixed(1)),
|
|
484
|
+
project: args.projectName,
|
|
485
|
+
type: typeLabel,
|
|
486
|
+
date: args.date,
|
|
487
|
+
point_count: args.points.length,
|
|
488
|
+
max_point: { id: args.points[maxIdx]!.id, value: args.points[maxIdx]!.cumulativeChange },
|
|
489
|
+
avg_cumulative: Number(avgCumulative.toFixed(3)),
|
|
490
|
+
alert_count: alertCount,
|
|
491
|
+
message: `✅ ${typeLabel}报表已导出:${dest},${args.points.length}个测点,最大变化 ${args.points[maxIdx]!.id}(${args.points[maxIdx]!.cumulativeChange}${args.unit})${alertCount > 0 ? `,${alertCount}个测点预警` : ""}`,
|
|
492
|
+
})
|
|
493
|
+
},
|
|
494
|
+
})
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/// <reference path="../env.d.ts" />
|
|
2
|
+
import { tool } from "nb-railwise/tool"
|
|
3
|
+
import path from "path"
|
|
4
|
+
|
|
5
|
+
const FIELDS: Record<number, string> = {
|
|
6
|
+
11: "point_id",
|
|
7
|
+
21: "hz_angle_deg",
|
|
8
|
+
22: "v_angle_deg",
|
|
9
|
+
31: "slope_dist_m",
|
|
10
|
+
32: "horiz_dist_m",
|
|
11
|
+
33: "height_diff_m",
|
|
12
|
+
81: "easting_m",
|
|
13
|
+
82: "northing_m",
|
|
14
|
+
83: "elevation_m",
|
|
15
|
+
87: "reflector_height_m",
|
|
16
|
+
88: "instrument_height_m",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ANGLES = new Set([21, 22])
|
|
20
|
+
const METRIC = new Set([31, 32, 33, 81, 82, 83, 87, 88])
|
|
21
|
+
|
|
22
|
+
const PATTERNS: [RegExp, string][] = [
|
|
23
|
+
[/^(point|id|name|pt|nr|编号|测点|点号)$/i, "point_id"],
|
|
24
|
+
[/^(hz|h_angle|horizontal_angle|水平角|ha|hz_angle)$/i, "hz_angle_deg"],
|
|
25
|
+
[/^(v_angle|vertical|zenith|天顶角|竖直角|va)$/i, "v_angle_deg"],
|
|
26
|
+
[/^(slope|sd|slope_dist|斜距|slope_distance)$/i, "slope_dist_m"],
|
|
27
|
+
[/^(hd|horiz_dist|horizontal_dist|平距|水平距)$/i, "horiz_dist_m"],
|
|
28
|
+
[/^(dh|height_diff|高差)$/i, "height_diff_m"],
|
|
29
|
+
[/^(east|easting|e_coord|东坐标)$/i, "easting_m"],
|
|
30
|
+
[/^(north|northing|n_coord|北坐标)$/i, "northing_m"],
|
|
31
|
+
[/^(elev|elevation|height|h_coord|高程|高度)$/i, "elevation_m"],
|
|
32
|
+
[/^(rh|reflector|target_height|棱镜高|觇标高)$/i, "reflector_height_m"],
|
|
33
|
+
[/^(ih|instrument_height|仪器高)$/i, "instrument_height_m"],
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
function dms(data: string, wide: boolean) {
|
|
37
|
+
const deg = parseInt(data.slice(0, 3), 10)
|
|
38
|
+
const min = parseInt(data.slice(3, 5), 10)
|
|
39
|
+
const sec = wide
|
|
40
|
+
? parseInt(data.slice(5, 7), 10) + parseInt(data.slice(7), 10) / Math.pow(10, data.length - 7)
|
|
41
|
+
: parseInt(data.slice(5, 7), 10) + parseInt(data.slice(7), 10) / 10
|
|
42
|
+
return deg + min / 60 + sec / 3600
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function word(raw: string, wide: boolean) {
|
|
46
|
+
const clean = raw.replace(/^[*+]+/, "")
|
|
47
|
+
const m = clean.match(/^(\d{2})([^+-]+)([+-])(.+)$/)
|
|
48
|
+
if (!m) return null
|
|
49
|
+
|
|
50
|
+
const wi = parseInt(m[1]!, 10)
|
|
51
|
+
const name = FIELDS[wi]
|
|
52
|
+
if (!name) return null
|
|
53
|
+
|
|
54
|
+
if (wi === 11) return { name, value: m[4]!.replace(/^[0\s]+/, "") || "0" }
|
|
55
|
+
|
|
56
|
+
const sign = m[3] === "-" ? -1 : 1
|
|
57
|
+
if (ANGLES.has(wi)) return { name, value: Number((sign * dms(m[4]!, wide)).toFixed(6)) }
|
|
58
|
+
if (METRIC.has(wi)) return { name, value: Number(((sign * parseInt(m[4]!, 10)) / (wide ? 10000 : 1000)).toFixed(4)) }
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function gsi(content: string, wide: boolean) {
|
|
63
|
+
const lines = content
|
|
64
|
+
.split("\n")
|
|
65
|
+
.map((l) => l.trim())
|
|
66
|
+
.filter((l) => l.length > 0)
|
|
67
|
+
if (lines.length === 0) return null
|
|
68
|
+
|
|
69
|
+
const starred = lines.some((l) => l.startsWith("*"))
|
|
70
|
+
const groups = starred
|
|
71
|
+
? lines.reduce<string[][]>((acc, line) => {
|
|
72
|
+
if (line.startsWith("*")) {
|
|
73
|
+
acc.push([line])
|
|
74
|
+
return acc
|
|
75
|
+
}
|
|
76
|
+
const last = acc[acc.length - 1]
|
|
77
|
+
if (last) last.push(line)
|
|
78
|
+
return acc
|
|
79
|
+
}, [])
|
|
80
|
+
: lines.map((l) => [l])
|
|
81
|
+
|
|
82
|
+
const records = groups
|
|
83
|
+
.map((group) =>
|
|
84
|
+
group
|
|
85
|
+
.flatMap((line) => line.split(/\s+/))
|
|
86
|
+
.reduce<Record<string, string | number>>((obs, w) => {
|
|
87
|
+
const parsed = word(w, wide)
|
|
88
|
+
if (parsed) obs[parsed.name] = parsed.value
|
|
89
|
+
return obs
|
|
90
|
+
}, {}),
|
|
91
|
+
)
|
|
92
|
+
.filter((obs) => Object.keys(obs).length > 0)
|
|
93
|
+
|
|
94
|
+
if (records.length === 0) return null
|
|
95
|
+
return { format: wide ? "gsi-16" : "gsi-8", records }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function dat(content: string) {
|
|
99
|
+
const lines = content
|
|
100
|
+
.split("\n")
|
|
101
|
+
.map((l) => l.trim())
|
|
102
|
+
.filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith("!"))
|
|
103
|
+
if (lines.length < 2) return null
|
|
104
|
+
|
|
105
|
+
const first = lines[0]!
|
|
106
|
+
const delim = first.includes("\t") ? /\t+/ : first.includes(",") ? /,\s*/ : first.includes(";") ? /;\s*/ : /\s+/
|
|
107
|
+
const cells = first.split(delim).map((c) => c.trim())
|
|
108
|
+
if (!cells.some((c) => /[a-zA-Z\u4e00-\u9fff]/.test(c))) return null
|
|
109
|
+
|
|
110
|
+
const mapping = cells.map((h) => PATTERNS.find(([re]) => re.test(h.trim()))?.[1] ?? null)
|
|
111
|
+
if (!mapping.some(Boolean)) return null
|
|
112
|
+
|
|
113
|
+
const records = lines
|
|
114
|
+
.slice(1)
|
|
115
|
+
.map((line) =>
|
|
116
|
+
line
|
|
117
|
+
.split(delim)
|
|
118
|
+
.map((c) => c.trim())
|
|
119
|
+
.reduce<Record<string, string | number>>((obs, val, i) => {
|
|
120
|
+
const col = mapping[i]
|
|
121
|
+
if (!col || !val) return obs
|
|
122
|
+
if (col === "point_id") {
|
|
123
|
+
obs[col] = val
|
|
124
|
+
return obs
|
|
125
|
+
}
|
|
126
|
+
const num = parseFloat(val)
|
|
127
|
+
if (!isNaN(num)) obs[col] = num
|
|
128
|
+
return obs
|
|
129
|
+
}, {}),
|
|
130
|
+
)
|
|
131
|
+
.filter((obs) => Object.keys(obs).length > 0)
|
|
132
|
+
|
|
133
|
+
if (records.length === 0) return null
|
|
134
|
+
return { format: "dat", records }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function detect(content: string) {
|
|
138
|
+
const lines = content
|
|
139
|
+
.split("\n")
|
|
140
|
+
.map((l) => l.trim())
|
|
141
|
+
.filter((l) => l.length > 0)
|
|
142
|
+
if (lines.length === 0) return null
|
|
143
|
+
|
|
144
|
+
const probe = lines[0]!.replace(/^\*/, "").trim().split(/\s+/)[0] ?? ""
|
|
145
|
+
const m = probe.match(/^(\d{2})([^+-]+)([+-])(.+)$/)
|
|
146
|
+
if (m) return gsi(content, m[4]!.length > 8)
|
|
147
|
+
return dat(content)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default tool({
|
|
151
|
+
description:
|
|
152
|
+
"解析徕卡全站仪 GSI-8/GSI-16 格式及通用 DAT 文本格式的外业观测文件,将仪器原始数据转换为结构化 JSON,供平差计算和报告生成使用。",
|
|
153
|
+
args: {
|
|
154
|
+
filePath: tool.schema.string().describe("外业数据文件的绝对路径"),
|
|
155
|
+
format: tool.schema
|
|
156
|
+
.enum(["gsi-8", "gsi-16", "dat-auto"])
|
|
157
|
+
.describe("文件格式:gsi-8=Leica GSI-8, gsi-16=Leica GSI-16, dat-auto=自动检测(优先尝试 GSI,其次 DAT 表格)"),
|
|
158
|
+
},
|
|
159
|
+
async execute(args) {
|
|
160
|
+
const file = Bun.file(args.filePath)
|
|
161
|
+
const exists = await file.exists()
|
|
162
|
+
if (!exists) return JSON.stringify({ error: `文件不存在:${args.filePath}` })
|
|
163
|
+
|
|
164
|
+
const raw = await file.text()
|
|
165
|
+
if (raw.trim().length === 0) return JSON.stringify({ error: "文件内容为空" })
|
|
166
|
+
|
|
167
|
+
const result = args.format === "gsi-8" ? gsi(raw, false) : args.format === "gsi-16" ? gsi(raw, true) : detect(raw)
|
|
168
|
+
|
|
169
|
+
if (!result)
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
error: "无法识别文件格式,请确认为 GSI 或 DAT 表格格式,或手动指定 format 参数。",
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
return JSON.stringify({
|
|
175
|
+
format: result.format,
|
|
176
|
+
file: path.basename(args.filePath),
|
|
177
|
+
total_records: result.records.length,
|
|
178
|
+
records: result.records,
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
})
|