w-html2docx 1.0.8 → 1.0.10
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/README.md +11 -0
- package/dist/w-html2docx.umd.js +1 -1
- package/docs/WHtml2docx.mjs.html +1 -1
- package/docs/global.html +1 -1
- package/docs/index.html +1 -1
- package/package.json +3 -2
- package/src/tmp.docx +0 -0
- package/srcPython/htmlToDocx.py +38 -0
- package/test/WHtml2docx.test.mjs +150 -38
- package/test/ztmp.html +71 -24
- package/test/ztmpTrue.docx +0 -0
package/README.md
CHANGED
|
@@ -49,3 +49,14 @@ test()
|
|
|
49
49
|
console.log('catch', err)
|
|
50
50
|
})
|
|
51
51
|
```
|
|
52
|
+
|
|
53
|
+
### 外部提供docx模板要點
|
|
54
|
+
|
|
55
|
+
轉檔時以docx模板為基底產出文件,可透過`opt.fpInTemp`指定外部模板;未指定時依序使用`./src/tmp.docx`或`./node_modules/w-html2docx/src/tmp.docx`(套件內建模板)。
|
|
56
|
+
|
|
57
|
+
外部提供模板時須注意:
|
|
58
|
+
|
|
59
|
+
1. **內容附加於模板文末**:轉檔係將html內容(含格式)插入至模板既有內容之後再另存,故模板一般提供空白文件;模板內既有內容會保留於輸出文件開頭。
|
|
60
|
+
2. **頁面設定由模板決定**:輸出文件之紙張大小、邊界等頁面設定沿用模板;圖片縮放上限(滿版寬高)亦依模板版心(紙張大小扣除邊界)計算。
|
|
61
|
+
3. **模板settings會帶入輸出文件**:模板`word/settings.xml`內之文件層級設定,轉檔後會保留於輸出docx。
|
|
62
|
+
4. **建議帶入停用影像壓縮旗標**:模板`word/settings.xml`須含`<w:doNotAutoCompressPictures/>`,否則Word轉檔時會將寬圖(約超過1270px)自動降採樣至220ppi並轉存JPEG,導致圖內文字模糊。套件內建模板已含此旗標;外部模板可於Word開啟模板後,勾選[檔案 > 選項 > 進階 > 影像大小和品質 > 不壓縮檔案中的影像]再存檔,或直接編輯`word/settings.xml`加入該元素。
|
package/dist/w-html2docx.umd.js
CHANGED
package/docs/WHtml2docx.mjs.html
CHANGED
|
@@ -246,7 +246,7 @@ export default WHtml2docx
|
|
|
246
246
|
<br class="clear">
|
|
247
247
|
|
|
248
248
|
<footer>
|
|
249
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
249
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Jul 12 2026 14:01:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
250
250
|
</footer>
|
|
251
251
|
|
|
252
252
|
<script>prettyPrint();</script>
|
package/docs/global.html
CHANGED
|
@@ -593,7 +593,7 @@
|
|
|
593
593
|
<br class="clear">
|
|
594
594
|
|
|
595
595
|
<footer>
|
|
596
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
596
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Jul 12 2026 14:01:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
597
597
|
</footer>
|
|
598
598
|
|
|
599
599
|
<script>prettyPrint();</script>
|
package/docs/index.html
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Jul 12 2026 14:01:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-html2docx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"main": "dist/w-html2docx.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"lodash-es": "^4.18.1",
|
|
7
|
-
"wsemi": "^1.8.
|
|
7
|
+
"wsemi": "^1.8.64"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
+
"fflate": "^0.8.3",
|
|
10
11
|
"w-package-tools": "^1.1.10"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
package/src/tmp.docx
CHANGED
|
Binary file
|
package/srcPython/htmlToDocx.py
CHANGED
|
@@ -229,6 +229,44 @@ def htmlToDocx(fpInSrc, fpInTemp, fpOut, opt):
|
|
|
229
229
|
err=getError()
|
|
230
230
|
print(err)
|
|
231
231
|
|
|
232
|
+
#設定本文段落(p)與清單項目(li)之水平對齊, 已為置中者(如表格標題列, 圖名)不更動, 其餘改為左右對齊(兩端對齊), 使右側邊界齊平較為美觀
|
|
233
|
+
try:
|
|
234
|
+
|
|
235
|
+
WdAlignParagraphCenter = 1 #置中對齊
|
|
236
|
+
WdAlignParagraphJustify = 3 #左右對齊
|
|
237
|
+
WdOutlineLevelBodyText = 10 #本文層級, 標題(h1-h6)匯入後為大綱層級1-9
|
|
238
|
+
WdWithInTable = 12 #Information之參數, 判斷是否位於表格內
|
|
239
|
+
|
|
240
|
+
for p in docInTemp.Paragraphs:
|
|
241
|
+
try:
|
|
242
|
+
|
|
243
|
+
rng = p.Range
|
|
244
|
+
|
|
245
|
+
#跳過標題段落
|
|
246
|
+
if p.OutlineLevel != WdOutlineLevelBodyText:
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
#跳過表格內段落
|
|
250
|
+
if rng.Information(WdWithInTable):
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
#跳過圖片段落
|
|
254
|
+
if rng.InlineShapes.Count > 0:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
#跳過置中段落
|
|
258
|
+
if p.Alignment == WdAlignParagraphCenter:
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
p.Alignment = WdAlignParagraphJustify
|
|
262
|
+
|
|
263
|
+
except:
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
except:
|
|
267
|
+
err=getError()
|
|
268
|
+
print(err)
|
|
269
|
+
|
|
232
270
|
#移除零寬空格佔位字元(U+200B): 來源為w-md2html於換行標記空div內插入之佔位字元, 用以撐過Word匯入不被當成空段落丟棄
|
|
233
271
|
#於此移除後該段落成為真正的空段落(乾淨段落標記, 顯示編輯標記時亦無可見字元)
|
|
234
272
|
try:
|
package/test/WHtml2docx.test.mjs
CHANGED
|
@@ -1,38 +1,150 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import w from 'wsemi'
|
|
3
|
-
import assert from 'assert'
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import w from 'wsemi'
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import { unzipSync, strFromU8 } from 'fflate'
|
|
5
|
+
import WHtml2docx from '../src/WHtml2docx.mjs'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function isWindows() {
|
|
9
|
+
return process.platform === 'win32'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function getDocumentXml(fp) {
|
|
14
|
+
//docx為zip, 取出主文件word/document.xml
|
|
15
|
+
let u8 = fs.readFileSync(fp)
|
|
16
|
+
let files = unzipSync(new Uint8Array(u8))
|
|
17
|
+
return strFromU8(files['word/document.xml'])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function getParagraphs(xml) {
|
|
22
|
+
//切出全部段落<w:p>, 並標記其分類與水平對齊
|
|
23
|
+
|
|
24
|
+
//tbls, 各表格之區間
|
|
25
|
+
let tbls = []
|
|
26
|
+
let rTbl = /<w:tbl>[\s\S]*?<\/w:tbl>/g
|
|
27
|
+
let mt
|
|
28
|
+
while ((mt = rTbl.exec(xml)) !== null) {
|
|
29
|
+
tbls.push([mt.index, mt.index + mt[0].length])
|
|
30
|
+
}
|
|
31
|
+
let inTable = (i) => {
|
|
32
|
+
return tbls.some((t) => i >= t[0] && i < t[1])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//ps, 各段落
|
|
36
|
+
let ps = []
|
|
37
|
+
let rP = /<w:p\b[^>]*\/>|<w:p\b[^>]*>[\s\S]*?<\/w:p>/g
|
|
38
|
+
let mp
|
|
39
|
+
while ((mp = rP.exec(xml)) !== null) {
|
|
40
|
+
let s = mp[0]
|
|
41
|
+
let mj = s.match(/<w:jc w:val="([^"]+)"\s*\/>/)
|
|
42
|
+
ps.push({
|
|
43
|
+
align: mj ? mj[1] : '', //both為左右對齊, center為置中, 空字串為未設定
|
|
44
|
+
isHeading: /<w:outlineLvl\b/.test(s), //h1-h6匯入後帶有大綱層級
|
|
45
|
+
isList: /<w:numPr>/.test(s), //ul或ol之li
|
|
46
|
+
isImage: /<w:drawing\b|<w:pict\b/.test(s), //含圖片(w:drawing)或水平線hr(w:pict之v:rect)
|
|
47
|
+
inTable: inTable(mp.index), //位於表格內
|
|
48
|
+
text: [...s.matchAll(/<w:t[^>]*>([^<]*)<\/w:t>/g)].map((v) => v[1]).join(''),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ps
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
describe('WHtml2docx', function() {
|
|
57
|
+
|
|
58
|
+
//check
|
|
59
|
+
if (!isWindows()) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let fpIn = `./test/ztmp.html`
|
|
64
|
+
let fpOut = `./test/ztmp.docx`
|
|
65
|
+
let imgRatioWidthMax = 0.5
|
|
66
|
+
let fontFamilies = ['標楷體', 'Times New Roman']
|
|
67
|
+
let opt = {
|
|
68
|
+
imgRatioWidthMax,
|
|
69
|
+
fontFamilies,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//轉出docx後解析word/document.xml, 各測試針對其內容比對, 不使用檔案大小門檻
|
|
73
|
+
let xml = null
|
|
74
|
+
let ps = null
|
|
75
|
+
|
|
76
|
+
before(async function() {
|
|
77
|
+
this.timeout(60000)
|
|
78
|
+
await WHtml2docx(fpIn, fpOut, opt)
|
|
79
|
+
xml = getDocumentXml(fpOut)
|
|
80
|
+
ps = getParagraphs(xml)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
after(function() {
|
|
84
|
+
w.fsDeleteFile(fpOut)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('convert', function() {
|
|
88
|
+
assert.strict.deepEqual(true, ps.length > 0)
|
|
89
|
+
assert.strict.deepEqual(true, xml.includes('計畫報告')) //來源html之h1
|
|
90
|
+
assert.strict.deepEqual(true, xml.includes('資料流程自動化')) //來源html之li
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('本文段落與清單項目, 除已置中者外, 均為左右對齊', function() {
|
|
94
|
+
let rs = ps.filter((p) => !p.isHeading && !p.isImage && !p.inTable && p.align !== 'center')
|
|
95
|
+
assert.strict.deepEqual(true, rs.length > 0)
|
|
96
|
+
let rsErr = rs.filter((p) => p.align !== 'both')
|
|
97
|
+
assert.strict.deepEqual([], rsErr)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('清單項目(li)已納入左右對齊', function() {
|
|
101
|
+
let rs = ps.filter((p) => p.isList && !p.inTable && p.align !== 'center')
|
|
102
|
+
assert.strict.deepEqual(true, rs.length > 0)
|
|
103
|
+
let rsErr = rs.filter((p) => p.align !== 'both')
|
|
104
|
+
assert.strict.deepEqual([], rsErr)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('標題段落不套用左右對齊', function() {
|
|
108
|
+
let rs = ps.filter((p) => p.isHeading)
|
|
109
|
+
assert.strict.deepEqual(true, rs.length > 0)
|
|
110
|
+
let rsErr = rs.filter((p) => p.align === 'both')
|
|
111
|
+
assert.strict.deepEqual([], rsErr)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('表格內段落不套用左右對齊', function() {
|
|
115
|
+
let rs = ps.filter((p) => p.inTable)
|
|
116
|
+
assert.strict.deepEqual(true, rs.length > 0)
|
|
117
|
+
let rsErr = rs.filter((p) => p.align === 'both')
|
|
118
|
+
assert.strict.deepEqual([], rsErr)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('已強制更改字型', function() {
|
|
122
|
+
assert.strict.deepEqual(true, xml.includes(`w:eastAsia="${fontFamilies[0]}"`))
|
|
123
|
+
assert.strict.deepEqual(true, xml.includes(`w:ascii="${fontFamilies[1]}"`))
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('圖片寬度不超過版心寬度乘以imgRatioWidthMax', function() {
|
|
127
|
+
|
|
128
|
+
//twContent, 版心寬度, 單位twip
|
|
129
|
+
let mSz = xml.match(/<w:pgSz w:w="(\d+)"/)
|
|
130
|
+
let mMar = xml.match(/<w:pgMar[^>]*w:right="(\d+)"[^>]*w:left="(\d+)"/)
|
|
131
|
+
assert.strict.deepEqual(true, mSz !== null)
|
|
132
|
+
assert.strict.deepEqual(true, mMar !== null)
|
|
133
|
+
let twContent = parseInt(mSz[1]) - parseInt(mMar[1]) - parseInt(mMar[2])
|
|
134
|
+
|
|
135
|
+
//emuMax, 圖片寬度上限, 單位EMU, 1twip=635EMU
|
|
136
|
+
let emuMax = twContent * 635 * imgRatioWidthMax
|
|
137
|
+
|
|
138
|
+
//cxs, 各圖片寬度, 單位EMU
|
|
139
|
+
let cxs = [...xml.matchAll(/<wp:extent cx="(\d+)"/g)].map((v) => parseInt(v[1]))
|
|
140
|
+
assert.strict.deepEqual(true, cxs.length > 0)
|
|
141
|
+
|
|
142
|
+
let rsErr = cxs.filter((cx) => cx > emuMax)
|
|
143
|
+
assert.strict.deepEqual([], rsErr)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('已移除零寬空格佔位字元(U+200B)', function() {
|
|
147
|
+
assert.strict.deepEqual(false, xml.includes(String.fromCharCode(0x200B)))
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
})
|
package/test/ztmp.html
CHANGED
|
@@ -294,6 +294,7 @@
|
|
|
294
294
|
<hr>
|
|
295
295
|
|
|
296
296
|
<h2>四、成果內容</h2>
|
|
297
|
+
|
|
297
298
|
<h3>4.1 表格測試</h3>
|
|
298
299
|
<table>
|
|
299
300
|
<thead>
|
|
@@ -318,7 +319,38 @@
|
|
|
318
319
|
<td>80%</td>
|
|
319
320
|
<td>預計下期延伸</td>
|
|
320
321
|
</tr>
|
|
321
|
-
</tbody
|
|
322
|
+
</tbody>
|
|
323
|
+
</table>
|
|
324
|
+
|
|
325
|
+
<h3>4.2 表格測試(水平置中)</h3>
|
|
326
|
+
<div style="text-align:center;">
|
|
327
|
+
<table style="display:inline-table;">
|
|
328
|
+
<thead>
|
|
329
|
+
<tr>
|
|
330
|
+
<th>項目</th>
|
|
331
|
+
<th>完成度</th>
|
|
332
|
+
<th>備註</th>
|
|
333
|
+
</tr>
|
|
334
|
+
</thead>
|
|
335
|
+
<tbody><tr>
|
|
336
|
+
<td>自動化流程</td>
|
|
337
|
+
<td>100%</td>
|
|
338
|
+
<td>已完成</td>
|
|
339
|
+
</tr>
|
|
340
|
+
<tr>
|
|
341
|
+
<td>系統效能優化</td>
|
|
342
|
+
<td>95%</td>
|
|
343
|
+
<td>尚可持續改善</td>
|
|
344
|
+
</tr>
|
|
345
|
+
<tr>
|
|
346
|
+
<td>介面改善</td>
|
|
347
|
+
<td>80%</td>
|
|
348
|
+
<td>預計下期延伸</td>
|
|
349
|
+
</tr>
|
|
350
|
+
</tbody>
|
|
351
|
+
</table>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
322
354
|
<hr>
|
|
323
355
|
|
|
324
356
|
<h2>五、圖片、連結與註腳</h2>
|
|
@@ -399,31 +431,46 @@
|
|
|
399
431
|
|
|
400
432
|
<hr>
|
|
401
433
|
|
|
402
|
-
<h2
|
|
434
|
+
<h2>八、超長文字測試(換行與左右對齊)</h2>
|
|
403
435
|
|
|
404
|
-
<h3>8.1
|
|
405
|
-
<
|
|
406
|
-
<
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
<
|
|
410
|
-
|
|
411
|
-
<tr><td>A2</td><td>B2</td><td>C2</td></tr>
|
|
412
|
-
</tbody>
|
|
413
|
-
</table>
|
|
436
|
+
<h3>8.1 超長段落</h3>
|
|
437
|
+
<p>本節之各段落均刻意撰寫為超出單行寬度之長句,用以檢驗轉出Docx後之自動換行與左右對齊(兩端對齊)效果;於Word中,兩端對齊僅對「非末行」之文字生效,故段落須至少折行兩行以上,方能於視覺上觀察到右側邊界是否確實對齊齊平,此即本測試段落須刻意加長之原因。</p>
|
|
438
|
+
<p>資料處理流程之設計,須同時兼顧擴充性、可維護性與執行效能三者之平衡;當資料量由每日數萬筆成長至每日數百萬筆時,原本以單機批次處理為前提之架構,將面臨記憶體不足、處理時間過長以及失敗後難以續跑等問題,因此本計畫改以分散式串流處理為核心,並輔以檢查點(checkpoint)機制,使流程於任一節點失敗後,均可自最近之檢查點續行,而毋須自始重新執行。</p>
|
|
439
|
+
<p>就中英文混排之情形而言,兩端對齊之演算法會調整字元間距與詞間空白,以使各行右側邊界齊平;例如本段落即刻意混入 Extract-Transform-Load (ETL)、Continuous Integration / Continuous Delivery (CI/CD) 以及 Application Programming Interface (API) 等較長之英文詞彙與縮寫,藉以觀察Word於中英文交界處之斷行行為是否合理,以及英文詞彙是否遭到不當之切斷。</p>
|
|
440
|
+
<blockquote>
|
|
441
|
+
<p>引用區塊內之超長文字亦一併測試:引用段落於Word中雖具有額外之左側縮排,惟其本質仍為本文層級之段落,故理應同樣套用左右對齊;本句刻意加長,以確保其於Docx中至少折行兩行以上,俾利肉眼確認右側邊界是否齊平。</p>
|
|
442
|
+
</blockquote>
|
|
414
443
|
|
|
415
|
-
<h3>8.2
|
|
416
|
-
<
|
|
417
|
-
<
|
|
418
|
-
<
|
|
419
|
-
<
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
<
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
444
|
+
<h3>8.2 超長無序清單(ul li)</h3>
|
|
445
|
+
<ul>
|
|
446
|
+
<li style="margin:7px 0;">資料前處理:包含來源檔案之編碼偵測、分隔符號判別、欄位型別推斷、缺失值填補與離群值剔除等步驟;其中缺失值之填補策略,須依欄位語意分別採用平均值、中位數、前值延續或直接標記為無效等不同方式,不得一律以零值填補,否則將導致後續統計分析結果產生系統性偏誤。</li>
|
|
447
|
+
<li style="margin:7px 0;">格式轉換:須將來源之CSV、TSV、JSON、XML與Excel等多種異質格式,統一轉換為內部之欄式儲存格式(columnar format),以利後續之向量化運算與壓縮儲存;轉換過程中並須保留原始欄位名稱與單位資訊,俾利日後回溯查核。</li>
|
|
448
|
+
<li style="margin:7px 0;">短項目(對照組)</li>
|
|
449
|
+
<li style="margin:7px 0;">雜訊清理:針對感測器所回傳之時間序列資料,須先以移動平均或Savitzky-Golay濾波器進行平滑化處理,再行判別突波(spike)與漂移(drift)等異常型態;經判別為異常者,應予標記而非逕行刪除,以維持時間序列之等間距特性。</li>
|
|
450
|
+
</ul>
|
|
451
|
+
|
|
452
|
+
<h3>8.3 超長有序清單(ol li)</h3>
|
|
453
|
+
<ol>
|
|
454
|
+
<li style="margin:7px 0;">第一階段——需求訪談與現況盤點:應與各業務單位進行至少三輪之訪談,逐一釐清其現行作業流程、資料來源、交付週期與品質要求,並將訪談結果整理為需求規格書,經雙方確認後始得進入設計階段,避免需求於開發中期反覆變更而導致工期延宕。</li>
|
|
455
|
+
<li style="margin:7px 0;">第二階段——系統設計與雛形驗證:依前階段確認之需求規格,完成系統架構設計、資料庫綱要設計與介面設計,並以雛形(prototype)方式先行驗證關鍵技術風險,包含大量資料匯入之效能、第三方API之串接穩定性,以及視覺化平台於行動裝置之呈現效果。</li>
|
|
456
|
+
<li style="margin:7px 0;">第三階段(短項目對照組)</li>
|
|
457
|
+
</ol>
|
|
458
|
+
|
|
459
|
+
<h3>8.4 巢狀清單之超長項目</h3>
|
|
460
|
+
<ul>
|
|
461
|
+
<li style="margin:7px 0;"><p><strong>統計分析模組</strong>:本模組須提供描述性統計、相關性分析、假設檢定與迴歸分析等基本功能,並就各項分析結果自動產製圖表與文字摘要,俾利使用者無須具備統計專業背景亦能理解分析結論。</p>
|
|
462
|
+
<ul>
|
|
463
|
+
<li style="margin:7px 0;">描述性統計:包含平均數、中位數、眾數、標準差、偏態與峰態等指標之計算,並須支援分組計算,以利跨群體之比較分析;各指標之計算結果均須標註樣本數,避免因樣本過少而導致統計量失去代表性。</li>
|
|
464
|
+
<li style="margin:7px 0;">迴歸分析:支援線性迴歸、多元迴歸與邏輯斯迴歸等模型,並須輸出各係數之估計值、標準誤、t統計量與p值,另須提供殘差圖與共線性診斷(VIF)等模型檢核工具。</li>
|
|
465
|
+
</ul>
|
|
466
|
+
</li>
|
|
467
|
+
<li style="margin:7px 0;"><p><strong>時間序列模組</strong></p>
|
|
468
|
+
<ul>
|
|
469
|
+
<li style="margin:7px 0;">ARIMA:須自動進行定態性檢定(ADF test)與差分階數選擇,並以資訊準則(AIC/BIC)挑選最適之(p, d, q)參數組合,另須提供殘差之白噪音檢定,以確認模型已充分擷取序列之自我相關結構。</li>
|
|
470
|
+
<li style="margin:7px 0;">Prophet</li>
|
|
471
|
+
</ul>
|
|
472
|
+
</li>
|
|
473
|
+
</ul>
|
|
427
474
|
|
|
428
475
|
<hr>
|
|
429
476
|
|
package/test/ztmpTrue.docx
CHANGED
|
Binary file
|