A forensic comparison of the Water Supplies Department (WSDSCU) and the ICAC (icaccu30) legacy Classic ASP systems. The question: were they written by the same author? 對水務署(WSDSCU)與廉政公署(icaccu30)兩套 Classic ASP 舊系統的鑑識比對。問題是:它們是否出自同一位作者之手? 对水务署(WSDSCU)与廉政公署(icaccu30)两套 Classic ASP 旧系统的取证比对。问题是:它们是否出自同一位作者之手?
For Each Field in Request.Form TheString = Field & "= Request.Form(""" & Field & """)" Execute(TheString) Next
The smoking gun — this rare, dangerous auto-variable idiom appears in 48 WSDSCU files and 53 ICAC files. It is no ASP textbook pattern; it is one person's personal habit. 鐵證——這個罕見而危險的自動變數寫法,出現在水務署 48 個檔案、廉政公署 53 個檔案中。它不是任何 ASP 教程的通用寫法,而是同一個人的個人習慣。 铁证——这个罕见而危险的自动变量写法,出现在水务署 48 个文件、廉政公署 53 个文件中。它不是任何 ASP 教程的通用写法,而是同一个人的个人习惯。
These are not two systems that merely look alike. They are the same base codebase, by the same author — one credit-union management product, customized for two clients and then maintained apart. 這並不是兩套「碰巧相似」的系統,而是同一位作者、基於同一套底層程式碼做出來的兩個客戶版本——同一套儲蓄互助社管理軟體,分別定制給兩個客戶後各自維護。 这并不是两套「碰巧相似」的系统,而是同一位作者、基于同一套底层代码做出来的两个客户版本——同一套储蓄互助社管理软件,分别定制给两个客户后各自维护。
Each item below is, on its own, hard to explain by coincidence. Together they are conclusive. 下列每一項單獨來看都難以用「巧合」解釋;合在一起則是鐵證。 下列每一项单独来看都难以用「巧合」解释;合在一起则是铁证。
Both roots share 8 identically-named files (conn.asp, CheckUserStatus.asp, illegal.asp, logout.asp, timeout.asp, index.asp, addUserLog.asp, main.css). The anti-framing referer check is word-for-word identical, comments and all.
兩邊根目錄共有 8 個同名檔案(conn.asp、CheckUserStatus.asp、illegal.asp、logout.asp、timeout.asp、index.asp、addUserLog.asp、main.css)。其中防盜鏈 referer 校驗邏輯連註解都一字不差。
两边根目录共有 8 个同名文件(conn.asp、CheckUserStatus.asp、illegal.asp、logout.asp、timeout.asp、index.asp、addUserLog.asp、main.css)。其中防盗链 referer 校验逻辑连注释都一字不差。
http_referer = lcase(Request.ServerVariables("HTTP_REFERER")) http_host = lcase(Request.ServerVariables("HTTP_Host")) If http_referer = "" then response.redirect "../illegal.asp" elseif instr(1,http_referer,"http://" & http_host) <> 1 then response.redirect "../illegal.asp" end if
The author dynamically rewrites every form field into a VBScript variable and Executes it — an unusual anti-pattern that is also a security hole. It appears across 48 / 53 files in the two systems. This single trait nearly closes the case.
作者把每個表單欄位動態拼成 VBScript 變數再 Execute 執行——這是一種罕見的反模式,同時也是安全漏洞。它在兩套系統中分別出現於 48 / 53 個檔案。單憑這一條幾乎就能定案。
作者把每个表单字段动态拼成 VBScript 变量再 Execute 执行——这是一种罕见的反模式,同时也是安全漏洞。它在两套系统中分别出现于 48 / 53 个文件。单凭这一条几乎就能定案。
For Each Field in Request.Form TheString = Field & "= Request.Form(""" & Field & """)" Execute(TheString) Next
Both conn.asp files open the connection the same way and carry the same idiosyncratic FMonthStart = 8 (August fiscal-year start, a Hong Kong tell) and an identical month array.
兩邊 conn.asp 以相同方式開啟連線,並帶有同樣特殊的 FMonthStart = 8(以 8 月為財政年度起點,香港特徵)與一模一樣的月份陣列。
两边 conn.asp 以相同方式打开连接,并带有同样特殊的 FMonthStart = 8(以 8 月为财年起点,香港特征)与一模一样的月份数组。
Set conn = Server.CreateObject("adodb.connection") FMonthStart = 8 ArrMonth = Array("Jan","Feb","Mar","Apr","May","Jun", _ "Jul","Aug","Sep","Oct","Nov","Dec")
Both footers print the same product tagline and the same Ver X.XX.YYYYMM version format — two builds of one commercial product.
兩邊頁腳印著相同的產品標語與相同的 Ver X.XX.YYYYMM 版本格式——同一商業產品的兩個版本。
两边页脚印着相同的产品标语与相同的 Ver X.XX.YYYYMM 版本格式——同一商业产品的两个版本。
' WSDSCU → "Credit Union Ver 1.01.200604" ' ICAC → "Credur Union Ver 2.00.200804" → "VER3.0i" ' shared tagline, both systems: "… Credit Union — Membership, Accounting, Savings and Loans Software"
The loan-payment PMT() function and the fiscal-year helpers in init.asp are character-for-character identical — same formula, same edge cases. Even the adversarial reviewer confirmed this one.
還款額 PMT() 函式與 init.asp 中的財政年度輔助函式逐字元相同——公式相同、邊界處理相同。連「唱反調」的覆核員都確認了這一條。
还款额 PMT() 函数与 init.asp 中的财年辅助函数逐字符相同——公式相同、边界处理相同。连「唱反调」的复核员都确认了这一条。
function PMT(ByVal Rate, nPer, PV, FV, iType) PMT = (-PV*(1+Rate)^nPer - FV)*Rate _ /((1+Rate*iType)*((1+Rate)^nPer-1)) end function Function GetFiscalYear(ByVal x) ' FMonthStart drives this
Table and column names are shared (loginUser, userLog, loanApp, memmaster, Share; memno, memname, membday…). The birthday-list report runs the exact same SQL, including its DISTINCT sub-query.
表名與欄位名共用(loginUser、userLog、loanApp、memmaster、Share;memno、memname、membday…)。生日名單報表跑的是完全相同的 SQL,連 DISTINCT 子查詢都一樣。
表名与字段名共用(loginUser、userLog、loanApp、memmaster、Share;memno、memname、membday…)。生日名单报表跑的是完全相同的 SQL,连 DISTINCT 子查询都一样。
select m.memno, m.memname, m.memcname, CONVERT(varchar, m.membday, 107) as membday from memmaster m, (SELECT DISTINCT memNo FROM Share WHERE amount > 0) s where m.memNo = s.memno
| Metric指標指标 | WSDSCU | ICAC |
|---|---|---|
en/ business filesen/ 業務檔案en/ 业务文件 | 130 | 292 |
| Shared root files共有根目錄檔案共有根目录文件 | 8 shared | |
Shared en/ filenames共有 en/ 檔名共有 en/ 文件名 | 22 shared | |
| Idiom寫法写法 | WSDSCU | ICAC |
|---|---|---|
| Execute(TheString) | 48 | 53 |
| For Each Field… | 45 | 30 |
| month(date()) -4 | ✓ | ✓ |
| deleted=-1 | ✓ | ✓ |
| CheckUserStatus inc. | ✓ | ✓ |
Scores shown are conservative: every dimension was re-checked by a skeptic agent instructed to argue "unrelated". The lower bars (reports, UI) reflect genuine divergence after years of separate maintenance — not weaker provenance. 此處分數偏保守:每個維度都由一個被要求論證「無關」的覆核員重新挑刺。較低的條目(報表、UI)反映的是多年獨立維護後的真實分化,而非血緣較弱。 此处分数偏保守:每个维度都由一个被要求论证「无关」的复核员重新挑刺。较低的条目(报表、UI)反映的是多年独立维护后的真实分化,而非血缘较弱。
Same origin does not mean identical today. After years of independent upkeep, the two forks diverged in six clear ways. 同源不代表今天完全一樣。經過多年獨立維護,兩個分支在六個方面明顯分化。 同源不代表今天完全一样。经过多年独立维护,两个分支在六个方面明显分化。
17 gl_* account constants and a glTxListPrint*.asp report family — general-ledger accounting that WSDSCU has none of.
17 個 gl_* 科目常數與 glTxListPrint*.asp 報表系列——水務署完全沒有的總帳會計層。
17 个 gl_* 科目常量与 glTxListPrint*.asp 报表系列——水务署完全没有的总账会计层。
ICAC keeps separate cn/ + en/ trees; WSDSCU has only en/ with Chinese mixed inline.
廉署分為 cn/ 與 en/ 兩套目錄;水務署只有 en/,中文混排在檔案內。
廉署分为 cn/ 与 en/ 两套目录;水务署只有 en/,中文混排在文件内。
Maintained into 2021 with PDF/TXT export and IE10 hints; ICAC pages mostly sit in the IE5/IE8 era. 維護到 2021 年,新增 PDF/TXT 匯出與 IE10 提示;廉署頁面多停留在 IE5/IE8 時代。 维护到 2021 年,新增 PDF/TXT 导出与 IE10 提示;廉署页面多停留在 IE5/IE8 时代。
The LoanCode/ShareCode case functions share structure but diverged: Chinese codes vs English mnemonics.
LoanCode/ShareCode 的 case 函式結構相同,但內容分化:中文碼 vs 英文助記碼。
LoanCode/ShareCode 的 case 函数结构相同,但内容分化:中文码 vs 英文助记码。
activateMenu() (document.all) vs a timer-based mopen()/mclose() — the same menu, re-implemented.
activateMenu()(document.all)vs 定時器式 mopen()/mclose()——同一套選單,重新實作。
activateMenu()(document.all)vs 定时器式 mopen()/mclose()——同一套菜单,重新实现。
Different DB targets, session timeout 1200 vs 30, and charset/locale settings. 資料庫目標不同、會話逾時 1200 vs 30、字元集/地區設定不同。 数据库目标不同、会话超时 1200 vs 30、字符集/区域设置不同。
Neither is cleanly the parent of the other. Both descend from one developer's "Credit Union" management product; each footer's version marks the base release it was forked from. 兩者並非簡單的「誰是誰的父版本」。它們都源自同一位開發者的「儲蓄互助社」管理產品;各自頁腳的版本號標示其 fork 時所基於的母產品版本。 两者并非简单的「谁是谁的父版本」。它们都源自同一位开发者的「储蓄互助社」管理产品;各自页脚的版本号标示其 fork 时所基于的母产品版本。
Water Supplies Department branch · forked from the ~2006 base, then actively maintained through 2021 (PDF/TXT reports, IE10). 水務署分支 · 基於約 2006 年母版,之後一直積極維護至 2021 年(PDF/TXT 報表、IE10)。 水务署分支 · 基于约 2006 年母版,之后一直积极维护至 2021 年(PDF/TXT 报表、IE10)。
ICAC branch · built on the ~2008 (v2) base and evolved toward v3, adding the rich GL accounting layer and a bilingual cn/ tree.
廉政公署分支 · 基於約 2008(v2)母版並向 v3 演進,新增完整總帳會計層與雙語 cn/ 目錄。
廉政公署分支 · 基于约 2008(v2)母版并向 v3 演进,新增完整总账会计层与双语 cn/ 目录。
Because they share a codebase, both inherit the same high-risk patterns — worth noting for the migration. 因為同源,兩邊都帶著相同的高危寫法——遷移時值得注意。 因为同源,两边都带着相同的高危写法——迁移时值得注意。
SQL is built everywhere as "… where username ='" & username & "'"到處用 "… where username ='" & username & "'" 拼接 SQL。到处用 "… where username ='" & username & "'" 拼接 SQL。
Execute() of user inputExecute() 動態執行使用者輸入Execute() 动态执行用户输入The Execute(TheString) auto-variable idiom runs attacker-influenced strings — variable / code injection risk.Execute(TheString) 自動變數寫法會執行受攻擊者影響的字串——存在變數/程式碼注入風險。Execute(TheString) 自动变量写法会执行受攻击者影响的字符串——存在变量/代码注入风险。
WSDSCU conn.asp ships a plaintext sa password (Pwd=Mountext666!). Rotate it and purge from the repo.水務署 conn.asp 明文寫了 sa 密碼(Pwd=Mountext666!)。請輪換並從版本庫清除。水务署 conn.asp 明文写了 sa 密码(Pwd=Mountext666!)。请轮换并从代码库清除。
Static analysis (grep / diff across both source trees) plus a structured 8-dimension comparison run by 18 parallel agents. Every finding was re-checked by an adversarial skeptic agent told to argue the systems are unrelated; the scores shown are the conservative post-skeptic numbers. No code was executed — provenance was judged from shared idioms, schema, templates and version strings. 靜態分析(對兩套原始碼樹做 grep / diff)加上由 18 個並行代理執行的結構化八維度比對。每項發現都由一個被要求論證系統「無關」的覆核代理重新檢驗;所示分數為保守的覆核後數值。全程不執行程式碼——血緣判斷依據共用的寫法、結構、模板與版本字串。 静态分析(对两套源码树做 grep / diff)加上由 18 个并行代理执行的结构化八维度比对。每项发现都由一个被要求论证系统「无关」的复核代理重新检验;所示分数为保守的复核后数值。全程不执行代码——血缘判断依据共用的写法、结构、模板与版本字符串。
Bottom line: one author, one codebase, two clients — WSD and ICAC. Highly similar; same origin, beyond reasonable doubt. 一句話總結:同一位作者、同一套底層程式碼、兩個客戶——水務署與廉政公署。高度相似;同源無疑。 一句话总结:同一位作者、同一套底层代码、两个客户——水务署与廉政公署。高度相似;同源无疑。
Because the two old systems are the same base, much of the new WSD system ports to ICAC. The new system is also further along than a glance suggests — a real backend already exists. The remaining difficulty is concentrated in three gaps. 因為兩套舊系統同源,新水務署系統有很大一部分可直接搬到廉署。而且新系統比乍看更成熟——後端是真實存在的。剩下的難度集中在三道缺口。 因为两套旧系统同源,新水务署系统有很大一部分可直接搬到廉署。而且新系统比乍看更成熟——后端是真实存在的。剩下的难度集中在三道缺口。
web/urls.pyweb/urls.py API 路由web/urls.py API 路由
The finance app already models the whole WSDSCU domain (loan / share / autopay / dividend / bankruptcy / IVA / month-end). The 13-line server/index.js is just a Cloudflare placeholder — the real backend is the Django project under Mutual-Aid-Societ-App-Demo/backend.
finance 應用已經把整套 WSDSCU 業務建模(貸款/股金/自動轉賬/股息/破產/IVA/月結)。那 13 行的 server/index.js 只是 Cloudflare 佔位——真正的後端是 Mutual-Aid-Societ-App-Demo/backend 下的 Django 專案。
finance 应用已经把整套 WSDSCU 业务建模(贷款/股金/自动转账/股息/破产/IVA/月结)。那 13 行的 server/index.js 只是 Cloudflare 占位——真正的后端是 Mutual-Aid-Societ-App-Demo/backend 下的 Django 项目。
| Work package工作包工作包 | Solo (p-mo)傳統(人月)传统(人月) | + AI (p-mo)+AI(人月)+AI(人月) | Risk風險风险 |
|---|---|---|---|
| Bilingual i18n中英雙語 i18n中英双语 i18n | 3–3.5 | 0.5–1 | Med中中 |
| Transaction-code dialect config交易碼方言配置交易码方言配置 | 1–1.5 | 0.4–0.6 | Med-High中高中高 |
| Multi-tenant config多租戶配置化多租户配置化 | 1–1.5 | 0.5–0.8 | Med中中 |
| GLI group-life insuranceGLI 團體保險GLI 团体保险 | 1.5 | 0.5–0.8 | Med中中 |
| GL general ledgerGL 總帳子系統GL 总账子系统 | 4–6 | 1.5–2.5 | High高高 |
| Data migration + tie-out數據遷移+對賬数据迁移+对账 | 2–3.5 | 1.5–2.5 | High高高 |
| Extra report variants額外報表變體额外报表变体 | 1 | 0.3–0.5 | Low低低 |
| Testing / UAT / parallel-run測試/UAT/並行運行测试/UAT/并行运行 | 1.5–2 | 1–1.5 | High高高 |
| Total合計合计 | 15–22 | ~6–10 | — |
Claude Code compresses the writing to a few months — but it cannot compress proving the books are correct. The bottleneck shifts to GL posting-rule review and a financial parallel-run, not to typing code. Claude Code 把「寫代碼」壓到幾個月——但壓不動「證明賬是對的」。瓶頸轉移到 GL 過賬規則核對與財務並行試運行,而不是敲代碼。 Claude Code 把「写代码」压到几个月——但压不动「证明账是对的」。瓶颈转移到 GL 过账规则核对与财务并行试运行,而不是敲代码。
"Monthly account statistics" (which both old systems and the new system have) sums the member sub-ledgers. The general ledger (which only ICAC has) is a separate double-entry book: every transaction posts balanced debit/credit entries that must foot to a trial balance. Below is mock data so you can see exactly what that means. 「每月帳統計」(兩套舊系統與新系統都有)是把社員子帳求和。總帳(只有廉署有)是另一本複式帳:每筆交易都過借貸平衡的分錄,並必須匯總出試算平衡。下面用 Mock 數據讓你直觀看懂。 「每月账统计」(两套旧系统与新系统都有)是把社员子账求和。总账(只有廉署有)是另一本复式账:每笔交易都过借贷平衡的分录,并必须汇总出试算平衡。下面用 Mock 数据让你直观看懂。
| Monthly statistics (have)每月帳統計(已有)每月账统计(已有) | GL ledger (missing)GL 總帳(缺失)GL 总账(缺失) | |
|---|---|---|
| Data source數據來源数据来源 | SUM the share/loan sub-ledgers對 share/loan 子帳求和对 share/loan 子账求和 | A separate voucher table glTx獨立憑證表 glTx独立凭证表 glTx |
| Core action核心動作核心动作 | Totals & counts by code按代碼求和、計數按代码求和、计数 | Balanced debit/credit posting借貸平衡過賬借贷平衡过账 |
| Chart of accounts會計科目表会计科目表 | ✗ none✗ 沒有✗ 没有 | ✓ 14 gl_ accounts✓ 14 個 gl_ 科目✓ 14 个 gl_ 科目 |
| Debit = Credit?借=貸?借=贷? | ✗ N/A✗ 不涉及✗ 不涉及 | ✓ must balance✓ 必須對平✓ 必须对平 |
| Trial balance / P&L試算平衡/損益试算平衡/损益 | ✗ none✗ 沒有✗ 没有 | ✓ glTxList-Bal/WBal✓ glTxList-Bal/WBal✓ glTxList-Bal/WBal |
| For whom給誰看给谁看 | Management (business figures)管理層(經營數字)管理层(经营数字) | Auditors (do the books foot?)核數師(賬平不平)核数师(账平不平) |
gl_ accounts)
MOCK 1 · 會計科目表(廉署 14 個 gl_ 科目)
MOCK 1 · 会计科目表(廉署 14 个 gl_ 科目)
| Code代碼代码 | Account科目科目 | Type類型类型 |
|---|---|---|
| 101 | Loans Receivable貸款應收贷款应收 | Asset資產资产 |
| 104 | Bank銀行存款银行存款 | Asset資產资产 |
| 110 | Investments投資投资 | Asset資產资产 |
| 204 | Interest Rebate利息回扣利息回扣 | Liability負債负债 |
| 209 | Subsidy補貼补贴 | Liability負債负债 |
| 302 | Loan Insurance貸款保險贷款保险 | Liability負債负债 |
| 306 | Insurance保險保险 | Liability負債负债 |
| 310 | Members' Shares社員股金社员股金 | Equity權益权益 |
| 313 | Profit & Loss損益损益 | P&L損益损益 |
| 315 | Group Life Insurance團體人壽保險团体人寿保险 | Liability負債负债 |
| 401 | Interest Income利息收入利息收入 | Income收入收入 |
| 405 | Investment Income投資收益投资收益 | Income收入收入 |
| 409 | Other Income其他收入其他收入 | Income收入收入 |
| 410 | Insurance Surplus保險盈餘保险盈余 | Income收入收入 |
| 104 | Bank銀行存款银行存款 | Dr 1,000.00 |
| 101 | Loans Receivable貸款應收贷款应收 | Cr 900.00 |
| 401 | Interest Income利息收入利息收入 | Cr 100.00 |
| 104 | Bank銀行存款银行存款 | Dr 500.00 |
| 310 | Members' Shares社員股金社员股金 | Cr 500.00 |
| Account科目科目 | Dr | Cr |
|---|---|---|
| 101 Loans Receivable貸款應收贷款应收 | 12,480,000.00 | — |
| 104 Bank銀行存款银行存款 | 8,650,000.00 | — |
| 110 Investments投資投资 | 5,000,000.00 | — |
| 204 Interest Rebate利息回扣利息回扣 | — | 120,000.00 |
| 209 Subsidy補貼补贴 | — | 80,000.00 |
| 310 Members' Shares社員股金社员股金 | — | 24,300,000.00 |
| 313 Profit & Loss損益损益 | — | 270,000.00 |
| 401 Interest Income利息收入利息收入 | — | 1,150,000.00 |
| 405 Investment Income投資收益投资收益 | — | 210,000.00 |
| Total合計合计 | 26,130,000.00 | 26,130,000.00 |
| Transaction交易交易 | Dr | Cr |
|---|---|---|
| New loan disbursed新貸款發放新贷款发放 | 101 Loans Recv.貸款應收贷款应收 | 104 Bank銀行银行 |
| Cash repay · principal現金還款·本金现金还款·本金 | 104 Bank銀行银行 | 101 Loans Recv.貸款應收贷款应收 |
| Cash repay · interest現金還款·利息现金还款·利息 | 104 Bank銀行银行 | 401 Interest Inc.利息收入利息收入 |
| Share deposit存入股金存入股金 | 104 Bank銀行银行 | 310 Shares股金股金 |
| Dividend paid派發股息派发股息 | 313 P&L損益损益 | 310 Shares股金股金 |
| Bad-debt write-off壞賬核銷坏账核销 | 313 P&L損益损益 | 101 Loans Recv.貸款應收贷款应收 |
| GLI premium deduction團體保險扣費团体保险扣费 | 310 Shares股金股金 | 315 Group Life Ins.團體人壽团体人寿 |
⚠ Illustrative mappings — the real debit/credit rules must be confirmed against ICAC's actual code (its gl_ constants and posting files). Getting a direction wrong silently mis-posts money.
⚠ 此為示意對照——真實借貸規則須按 ICAC 實際代碼(其 gl_ 常量與過賬檔案)核對。方向錯了會靜默錯賬。
⚠ 此为示意对照——真实借贷规则须按 ICAC 实际代码(其 gl_ 常量与过账文件)核对。方向错了会静默错账。
{
"voucher_no": "2406-0087",
"date": "2024-06-15", "fiscal_year": 2024,
"source": "loan_repayment", // posted from the sub-ledger
"member_no": "M00231",
"entries": [
{ "account": "104", "name": "Bank", "dr": 1000.00, "cr": 0 },
{ "account": "101", "name": "Loans Receivable", "dr": 0, "cr": 900.00 },
{ "account": "401", "name": "Interest Income", "dr": 0, "cr": 100.00 }
],
"total_dr": 1000.00, "total_cr": 1000.00,
"balanced": true // must be true before posting
}
In the new backend this would be a GLVoucher + GLEntry model pair, written atomically with the sub-ledger row inside one DB transaction via a shared post_to_gl() hook on the existing finance methods.
在新後端裡,這會是一對 GLVoucher + GLEntry 模型,透過掛在現有 finance 方法上的 post_to_gl() 鉤子,與子帳行在同一個資料庫事務中原子寫入。
在新后端里,这会是一对 GLVoucher + GLEntry 模型,通过挂在现有 finance 方法上的 post_to_gl() 钩子,与子账行在同一个数据库事务中原子写入。
~13 ICAC screens: members join / quit / adjust a group-life plan; each premium deduction posts to the GL too. A mock enrolment record: 廉署約 13 個畫面:社員加入/退出/調整團體人壽計劃;每次扣保費也要過 GL。一條投保 Mock 記錄: 廉署约 13 个画面:社员加入/退出/调整团体人寿计划;每次扣保费也要过 GL。一条投保 Mock 记录:
{ "member_no": "M00231", "plan": "GLI-A", "sum_assured": 200000,
"annual_premium": 480.00, "subsidy": 120.00, "net_premium": 360.00,
"status": "active", "joined": "2019-08-01" }
// each deduction posts: Dr 310 Shares / Cr 315 Group Life Ins.
One sentence: rebuilding the ICAC system on our new platform is technically feasible and the risk is manageable. Because we already have a working foundation and AI-assisted development, this is a project measured in months — not the year or two people might assume. 一句話:把廉政公署的舊系統改造到我們的新平台上,技術上完全可行、風險可控。因為我們已有可運作的基礎、又有 AI 輔助開發,這是一個以「幾個月」計、而非外界以為的「一兩年」的專案。 一句话:把廉政公署的旧系统改造到我们的新平台上,技术上完全可行、风险可控。因为我们已有可运作的基础、又有 AI 辅助开发,这是一个以「几个月」计、而非外界以为的「一两年」的项目。
We compared the Independent Commission Against Corruption (ICAC) old system with the Water Supplies Department (WSD) old system we are already modernising, and we assessed how hard it would be to adapt our new system for ICAC. 我們把廉政公署的舊系統,與我們正在改造的水務署舊系統做了對比,並評估了把現有新系統改造給廉政公署使用的難度。 我们把廉政公署的旧系统,与我们正在改造的水务署旧系统做了对比,并评估了把现有新系统改造给廉政公署使用的难度。
ICAC's old system and WSD's old system are essentially two versions built by the same person on the same foundation. That means most of the new system we are building for WSD can be reused for ICAC, instead of starting from scratch. This is the biggest piece of good news — it saves money and time. 廉政公署的舊系統,和水務署的舊系統,基本上是同一個人、用同一套底子做出來的兩個版本。這意味著我們為水務署做的新系統,大部分可以直接沿用到廉政公署,不必從零開始。這是最大的好消息——省錢、省時間。 廉政公署的旧系统,和水务署的旧系统,基本上是同一个人、用同一套底子做出来的两个版本。这意味着我们为水务署做的新系统,大部分可以直接沿用到廉政公署,不必从零开始。这是最大的好消息——省钱、省时间。
The core of our new WSD system — member management, loans, shares, automatic transfers and the day-to-day business — is already built and working. It is not an empty shell. ICAC can sit directly on top of this. 我們給水務署做的新系統,核心部分(社員管理、貸款、股金、自動轉賬等日常業務)已經建好並能運作,不是空架子。廉政公署可以直接建在它之上。 我们给水务署做的新系统,核心部分(社员管理、贷款、股金、自动转账等日常业务)已经建好并能运转,不是空架子。廉政公署可以直接建在它之上。
ICAC additionally keeps a complete set of accounting books — recording the whole organisation's income and spending, and producing the financial statements an auditor checks. Neither WSD nor our new system has this, so it must be built from scratch. It is the largest part of this project and the part that most needs professional oversight. On top of it we also need a bilingual Chinese/English interface, and we must move the old historical data into the new system and check every figure ties up exactly. 廉政公署額外有一整套完整的會計帳——記錄整個機構的收支,並能產生給核數師查核的財務報表。這是水務署和我們新系統都沒有的,必須從頭建。它是本次改造裡最大、也最需要專業把關的一塊。此外還要做中英文雙語介面,並把舊系統的歷史資料搬到新系統、逐筆核對帳目分毫不差。 廉政公署额外有一整套完整的会计账——记录整个机构的收支,并能生成给核数师查核的财务报表。这是水务署和我们新系统都没有的,必须从头建。它是本次改造里最大、也最需要专业把关的一块。此外还要做中英文双语界面,并把旧系统的历史数据搬到新系统、逐笔核对账目分毫不差。
Because we develop with AI assistance, writing the software is far faster than the traditional pace. But why does going live still take longer than the build? Because an accounting system must run alongside the old system for several months and be reconciled month by month with zero discrepancy before we can switch over — that waiting and checking cannot be sped up by AI; it is a necessary safety step. This is nowhere near the year or two an outside estimate would assume. 因為我們有 AI 輔助開發,寫程式的速度比傳統快很多。但為什麼上線還是比開發久?因為帳務系統必須與舊系統並行運作數個月、逐月核對完全無誤,才能正式切換——這段等待與核對靠 AI 也壓縮不了,是穩妥起見的必要步驟。這遠不是外界傳統估算的一兩年。 因为我们有 AI 辅助开发,写程序的速度比传统快很多。但为什么上线还是比开发久?因为账务系统必须与旧系统并行运行几个月、逐月核对完全无误,才能正式切换——这段等待与核对靠 AI 也压缩不了,是稳妥起见的必要步骤。这远不是外界传统估算的一两年。
The money and the books cannot be wrong — not by a single cent. The accounting and the data transfer must be exact. ICAC in particular is held to very high oversight and audit standards, so any error in the books is a serious matter. The real challenge of this project is therefore not "writing it fast", but "getting the books right and making them stand up to an audit". 錢和帳,一分都不能錯。會計帳目與資料搬遷必須分毫不差。尤其廉政公署受極高的監管與審計標準約束,帳目出一點差錯都是大事。因此這個專案真正的難點不在「寫得快」,而在「帳要對、要經得起審計」。 钱和账,一分都不能错。会计账目与数据搬迁必须分毫不差。尤其廉政公署受极高的监管与审计标准约束,账目出一点差错都是大事。因此这个项目真正的难点不在「写得快」,而在「账要对、要经得起审计」。
(1) Run both WSD and ICAC on one system, configured per organisation, so we never end up maintaining two systems that drift apart. (2) Confirm two things early: whether ICAC definitely requires an English interface (it directly affects the workload), and get access to the real old data for checking as soon as possible. (3) Put our strongest people on the accounting books and the data reconciliation — that is where the project is won or lost. (1)讓水務署與廉政公署共用一套系統、按機構分別配置,避免日後維護兩套各自走樣。(2)儘早確認兩件事:廉政公署是否一定要英文介面(直接影響工作量),並儘快取得舊系統的真實資料以供核對。(3)把最強的人力放在「會計帳目」與「資料核對」這兩件事上——成敗在此。 (1)让水务署与廉政公署共用一套系统、按机构分别配置,避免日后维护两套各自走样。(2)尽早确认两件事:廉政公署是否一定要英文界面(直接影响工作量),并尽快取得旧系统的真实数据以供核对。(3)把最强的人力放在「会计账目」与「数据核对」这两件事上——成败在此。