2018年2月28日 星期三

實驗心得:勞基法掃描器——如何把法條轉譯成自動化的判讀程式?以勞動基準法為例


《勞基法掃描器》歡迎頁

去年一整年都在逃避的專案,前些日子終於做到一個段落(淚)

勞基法掃描器》的運作方式很簡單,就是讓使用者回答一些關於工作狀況的問題後,生出一個報告頁面,告訴他有哪些地方違反勞基法。


原本因為要轉職 F2E,想說就拿這小專案來練習 React,還可以當作品集放履歷。沒想到實際規劃下去,才發現商業邏輯超複雜一點都不簡單,然後我 code 又寫得超爛,於是就進入一個產品規格、db schema、程式碼輪流重構的無限輪迴。更可怕的是寫到後面 Component 變超多超複雜,又不會寫測試(同時 spec 一直改也無法寫測試)去年下半年幾乎都處在改了東邊壞掉西邊的狀態,搞到最後自己都不太敢亂改,只好一直逃避去做別的事情,像是 替產品寫主題曲 之類的... orz

最後拯救這專案的是 LY 巨巨教我的 Redux。因為 Redux 全域 state 的需要,把原本火蟻窩一般有機成長的產品邏輯重新整理清楚,最後介面變得超簡潔;然後用 functional component 省掉一堆 setState 跟 props 的傳紙條遊戲,程式碼瘦身一半以上,維護起來輕鬆多了,再也不會改東牆壞西牆了(淚灑花)

目前以作品集來說,大概就差測試跟 DSL 的語法樹,可能等手上案子做完、要找工作的時候,再回來補寫。

前身:勞基法圖解——假日工時知多少


前年的時候,在 OCF 聽到 Lucien 他們聊勞基法,聽一聽覺得好像可以整理出什麼,就做了《假日工時知多少》系列圖解。結果 丟去 g0v 社團 release early 的時候發生一件意料之外的悲劇,就是相簿被大量轉發,但內容有超多錯誤 XD 後來只好把圖片移到其他相簿,避免錯誤的內容在分享文中被直接預覽,然後在原本的相簿描述中說明原委。

這是阿宅我第一次體會到論述內容與程式工具的不同。之前在 g0v 以 release early 方式發表的作品,都是不牽涉論述的程式工具,code 寫爛了頂多東西不會動、使用者關掉視窗,就算真的搞到人家電腦當掉,重開機也可以解決,並不會造成什麼無法逆轉的負面影響。但如果是論述性質的作品,一旦將錯誤的內容傳播出去,這些資訊會印在讀者腦袋裡,而讀者的腦袋無法用程式 undo,要覆寫先前的錯誤,只能用同樣力道把正確內容再次傳播給同一批受眾,換句話說,在論述的領域裡,要消除負面影響的成本是很高的,這也是為什麼許多嚴謹的學者會很保護 WIP 的內容,怕不成熟的想法被拿去引用。

這件事讓我體會到開源之道的侷限,也成為日後《ETCC / Bluty 圖表》的重要養分。其實開源協作之所以能運作良好,很多時候是因為軟體領域有特殊的環境條件,走出純軟體領域、失去這些特殊條件,很多額外的成本跟負面效應就會跑出來。詳情可以鎖定婷宇在今年 g0v summit 的投稿...(轉頭望 XD)

這段故事的結局是,緊急處理完被廣傳的《假日工時知多少》相簿後,重新研究勞基法,然後在電資工會巧克力的指導以及 g0v 勞基松眾人協力下,一起把勞基法中琳瑯滿目的工時規定整理成 這張 gsheet 表格

產品概念發想:法規的本質


處理上述悲劇的過程中,g0v 御用(?)律師 Isabel 隨口提到可以寫成程式,加上工時規定的表格整理好後,感覺好像可以看得到 db schema 的雛形,想說不如就來寫個產品好惹。那麼,一個把法規程式化的產品,到底該長成什麼樣子呢?這問題同時也是在問,對人類來說,法規這東西的本質是什麼呢?思考良久,得出這些結論:

簡單來說,法規這東西是在講人類「不可以做什麼」還有「要是做了不可以的事會怎樣」,這概念就像病毒定義檔是在講電腦裡「不該出現什麼」還有「要是出現了什麼該怎麼辦」。所以,如同掃毒軟體把「用病毒定義檔檢查電腦檔案」的動作自動化,《勞基法掃描器》要做的是把「用法規檢查勞動狀況」的動作自動化。因此《勞基法掃描器》的產品架構和運作邏輯可以用掃毒軟體當作參考的範本。

資料結構化:所有詞彙的全域 Unique ID


要做法律版的掃毒軟體,第一個碰到的問題就是掃描規則到底長啥樣子,畢竟病毒定義檔是結構化的資料,但法條不是。所以第一個任務,就是要把法條轉譯成結構化的掃描規則。要把法條轉成掃描規則,第一個碰到的問題就是有哪些掃描標的、該用什麼 unique ID 代表它們,於是又跟 g0v 勞基松的大家一起把勞基法的中英詞彙整理到這份 詞彙對照表 的「glossary」工作表中。

製作詞彙表的過程中,一度想偷懶用自動分詞程式幫忙抓出詞彙, 拿 html5 文字雲來亂搞 後聽作者 timdream 說才知道這種東西有專門的斷詞程式,像是 SegmentordeepSeg 或 jieba 都可以處理中文(三個都是用 python 寫的,到底 www)。嘗試使用斷詞程式的同時,又發現另一個問題,就是斷詞成功以後,從斷出來的詞裡面挑出掃描標的這件事,才是真正的大工程,而且好像還是沒有任何程式可以做到...

所以,最後這份詞彙表,完全是手工製作的喔哈哈哈(眼神死)

重點是,我最後用的 unique ID 不是直接採用法規裡的文字,而是另外設計整套命名規則,這是因為法規裡的詞彙描述的是概念,而同一個概念下常常會有多個具體的掃描標的。比方說,同一個員工的加班費在不同時段會有不同價碼,如果拿法規裡的用詞「延長工作時間之工資」當作 unique ID,顯然無法同時記錄所有時段的加班費。因此最後我的 unique ID 命名方式是把所有環境條件塞進去,變成「個案:延長工作時間:非突發:國定假日:工資(金額,每小時)」這種冗長到只有我自己看得懂的命名 XD ...

這命名規則絕對有改善空間,但由於我的主要目的還是在做 React 作品以利轉職,所以沒有在命名方面深究。如果有人要在正規專案裡做類似的事,建議同時找懂法律、懂勞工議題、懂資料、懂程式的人,一起討論出更好的命名系統。

設計掃描規則:啟動的條件、通過的條件


掃描標的的 unique ID 命名完後,想說事情就簡單了,只要把這些 unique ID 寫進掃描規則的判斷式就好,結果掃描規則的邏輯超複雜,又搞了超久 orz 總之,要描述「這個情況下套用那個規則」這樣的邏輯,需要兩組判斷式,第一組判斷「現在是這個情況嗎?」,第二組判斷「能通過那個規則嗎?」,而每一組判斷式裡面,又會有「同時符合以下條件」或「擇一符合以下條件」兩種狀況。

設計掃描規則結構的過程中,室友路過看到,笑說這很像是電商折扣系統的商業邏輯,還隨手秀給我看,然後 Poga 在臉書上看到後也留言講了一模一樣的話... 好窩,我知道自己想破頭得出的結論是別人早就做到爛的東西惹,菜鳥臭了嗎 =3=

條件判斷式:Domain Specific Language


掃描規則的結構確定後,再來就是一條一條地輸入條件判斷式。原本想說這是單純苦力活,只要按照條文無腦輸入,不管是大於小於等於,還是要加減乘除,這些邏輯判斷都是任何程式語言內建的東西。寫了幾條後發現一個問題,就是這些判斷式現在是 string 的狀態,要直接把判斷式字串轉成程式的話,一來 eval() 有安全性問題,二來判斷式裡面有許多前面講的「個案:延長工作時間:非突發:國定假日:工資(金額,每小時)」這種 value 是使用者輸入的東西,需要處理沒資料或資料型別不符之類的例外狀況,沒辦法直接 eval() ,也就是說,我必須自己 parse 這些判斷式字串。

這時候,室友又路過看到,笑說我這是在寫 DSL。我想說貧僧不過是 parse 個字串,跟 DSL 有啥鳥毛關係,結果寫著寫著,聽 Simon 講解起 string -> tokens -> syntax tree,才漸漸發現,我真的好像是在寫 DSL ...

我只是想做個可以放上履歷的 React 作業啊教練!!! QAQ

總之後來 DSL 的問題暫時用偷懶方式解掉,之後得另外找時間研究演算法跟資料結構,把正規的作法補上去。跟技術問題相比,另一個更大的問題是切判斷式,因為法條文字的語意往往無法直接按照字面上的意思去解讀,而是要跨越不同條文合併在一起看,比方說第三十條第一項「勞工正常工作時間,每日不得超過八小時,每週不得超過四十小時」看起來是很單純的判斷式,但同一條第二項「得將其二週內二日之正常工作時數,分配於其他工作日」馬上翻案,然後隔壁三十之一條「四週內正常工作時數分配於其他工作日之時數,每日不得超過二小時」又是另一個故事,因此呆呆地逐條轉譯是行不通的。把法條轉譯成判斷式的人,必須對整部法規的思考邏輯有完整概念,不然很容易搞錯意思又自己為看懂,就像當初那個製作《假日工時知多少》的我一樣(淚)

這幾年在不同軟體專案中,一直重複遇到這種超越資訊領域的 domain knowledge 問題,不禁覺得這年頭真是個強烈需要異業合作的跨領域時代,在這方面 g0v 的勞基法專案群是很幸運的,因為電資工會一直像月亮一樣照耀著我們 XD 剛開始鑽研法條的時候真的超崩潰,好在巧克力一直不厭其煩地回答問題,最後直到去聽他們 關於工時規定的講座 才對勞基法的整體架構有基本的概念,非常推薦對勞基法感到雲霧的人去看這份講座共筆。讓我們一起感恩電資工會、讚嘆電資工會~~~

題外話,就在我摸索掃描規則的同一時間,小朱以無法理解的光速完成了《勞基法函式庫》/OoO/ 但一來我還是得練習 React,二來我希望掃描規則的定義檔將來可以讓工程師以外的人編輯,因此需要把它寫成一般人看得懂的資料、跟程式碼分離,所以就沒有考慮跟小朱的函式庫對接,一切整合性的問題等我搞清楚自己要幹嘛以後再說 www

程式開發:React、React Router、Redux、棄用的 Firebase


程式技術沒啥好嘴的,就菜鳥第一次寫 React,一邊看教學影片一邊做作業一邊求 google 問卜一邊塗塗改改,會卡關的地方大多是打錯字之類的超蠢問題,不然就是 stackoverflow 上被問到爛的超級初心者問題。

這中間比較大的轉折是,做到第二版也就是 Redux 版的時候,發現除了我自己以外根本沒人會去編輯那些判斷式,所以放棄自幹判斷式編輯介面,改成用 google spreadsheet 當編輯後台,再用 csv 轉 json 手動把 gsheet 的資料 commit 到 repo 上面去。也因為砍掉編輯介面的關係,少了一大塊登入跟權限控管的需求,變成只需要記錄使用者輸入的資料而已,因此 firebase 也拿掉了,使用者輸入的資料就很偷懶地存在 local storage 裡面。這個決定大概讓整個 app 的技術複雜度下降一半以上吧,畢竟控權限就是麻煩,編輯介面就是難做阿 QQ

產品的最後一哩:接上第一線需求


這產品目前的運作方式,是讓使用者回答一些關於工作狀況的問題後,生出一個報告頁面,告訴他有哪些地方違反勞基法。實務應用上,也許可以讓公司 HR 或員工當作自我檢查的工具,或者用來當作檢舉前的快篩工具,替勞檢員省下一些資料收集的工夫。不過,上述使用情境單純是我自己的腦補,沒有任何 UX 上的驗證,目前唯一確定能達到的功效,大概就是用來滿足我的資料整理強迫症吧 XD

如果有人願意應用這個實驗性產品的概念,那麼下一步可以考慮實際去觀察 HR、個案、勞檢員等第一線的行為模式與需求,還有思考如何跟《勞基法計算機》《勞基法函式庫》《班表小幫手》《GoodJob》《qollie》等相關專案分工或對接,來滿足這些第一線的需求。

前面篇幅都在講法條自動化的事,其實整個開發過程中還有另一塊同樣吃重的東西,就是向使用者收集資料的心理測驗模組。雖然已經有許多工具像是《Twine》《PlotDB》可以達到類似的效果,但一來我不知道怎麼整合它們,二來我需要的故事線邏輯頗複雜,因此最後還是自己寫。結果這個心理測驗模組意外地實用,除了《勞基法掃描器》器以外,目前手上接案的工作以及排隊中的 side project《創用 CC 授權選擇器》都可以直接移植,算是好運撿到的經濟副產品 +Q+

小結:法條程式化的可能性


好ㄌ最後終於進入本篇主題,究竟法律條文有沒有可能變成程式、自動化地處理一些事情呢?不管是小朱的《勞基法函式庫》或者本魯的《勞基法掃描器》都證明這條路是有可能的。但如果要讓法律條文變成容易長期維護的程式,也就是要能方便地管理同一條文的不同版本、並且讓非資訊人也能協助維護的話,那可能就得考慮把條文內容寫成資料,像是掃毒軟體的病毒定義檔那樣。然後要把法律條文變成結構化資料,就會遇到前面說的變數命名、切判斷式等挑戰,會同時需要 domain knowledge 以及大量的工人智慧。

長遠來看,其實個人更希望的是從法條一開始的設計過程就可以導入程式思維。比方說七休一到底是可以連上六天還是連上十二天這種讓大家吵半天的東西,如果當初條文起草的時候,就有位工程師在旁邊試寫對應這條文的判斷式,那是不是就可以即時驗證條文的邏輯,這個自然語言模糊性所導致的 bug 就可以及早發現及早治療呢?

嘛,雖然聽起來有點天方夜譚,但我認真覺得這是一個可以考慮的方向。如果有其他法律跟資訊的跨界人認同的話,也許可以再拿其他部法律來做實證,如果有天證明了各種不同領域的法規做程式化都是可行的,那也許我們就能進一步找到一些流程上或者工具上的方式,減輕司法從業人員們明顯過重的工作負擔。

產品資訊


勞基法掃描器
Labor Laws Scanner

資料授權:CC0
源碼授權:MIT

上線網址
資料內容
程式碼
開發日誌
實驗心得
主題曲

對了,對這個小實驗有問題的話,歡迎到 OpenData Taiwan 臉書社團 留言,基本上讀者們的問題我應該都答不出來,但我相信社團裡有地縛靈可以回答... XD

後記


這篇心得文剛開始動筆時被別的事打斷,拖了一兩個月沒進度,原本想說大概會斷頭,結果這幾天開始寫另一個專案的 code,突然就文思泉湧,兩天內一氣呵成,傳說中的輾轉相逃法真的是太神奇了 lol

這篇會想這樣詳細寫,主要是因為前一個 open data 小實驗的心得文 得到很好的讀者回饋,有人消化吸收後應用在體制內的工作,並且很努力地——套句家華的話——撐出空間來做開創性、玩真的事情,讓我覺得自己做的東西終於對社會有貢獻,不是遊手好閒的啃老公族惹,超感動 QQ 為了從讀者回饋中得到人類社會的認可,我會繼續努力寫心得文的(握拳)

被啃老公族:妳在外面做的功德,可以考慮直接迴向給我嗎?
啃老公族:ᕕ ( ᐛ ) ᕗ