読み合わせ上手なMacBookにする(sayEB)

会社には支払がつきもので、その多くが紙の請求書として通知される。支払は、その請求書に記載された情報を元に実行することになる。電話料金のように振込依頼書が添付する場合は、そのまま銀行に持ち込んで支払できるので便利だ。しかし、多くの場合、自分で請求書の金額と支払先情報を確認して、銀行指定の振込依頼書を作成することになる。

ところで、会社の規模がある程度以上になると、振込依頼書を手書きして振込することはほとんど無く、全銀フォーマットのデータとして送信してしまう方法が主流になってくるのだと思う。

ところが、紙の請求書を見て、パソコンに入力するのは人間であり、人間は必ず間違える。だから、誰かが入力したデータをそのまま利用するのは非常に危険だ。必ず、入力者以外の誰かがチェックする必要がある。

チェック方法は、原始的かもしれないが、一人が読み上げて、もう一人が元データを黙読しながらそれを聴く、いわゆる「読み合わせ」と呼ばれる方法でよく行う。自分の環境では、労力と確実性の妥協点として、現状ではベストかもしれない。

  • 別人が同じデータを2回入力して整合性をチェックする、より信頼性の高い方法もあるが、入力する手間が2倍になってしまうのが難点。
  • 元データと入力したデータを並べて、視線を頻繁に移動して、目視のみでチェックと言う方法もあるが、経験上、チェック漏れが多くなる。

それにしても今時の会社は、徹底的に効率を追及する...。人は極限まで減らして、それでも尚、より素早く、正確な処理を求める。間違えは許されない...。読み合わせと言えども、二人の労力が必要で、件数が増えればそれに比例してチェックの時間も長くなる。

そこで、saykanaに活躍してもらうことを考える。パソコンに全銀フォーマットのデータを読み上げてもらって、一人で読み合わせチェックできることを目指してみる。今回は、AppleScriptベースで作ってみた。

半角カタカナ

全銀フォーマットには半角カタカナが登場する。今まで、漢字や英単語ばかりに気を取られていて盲点になっていたが、saykanjiは半角カタカナが読めないことに気付いた。半角カタカナを含んだ文字列を渡すと、発音しないばかりでなく、不正終了してしまうのだ。半角カタカナは全角カタカナに変換してから、saykanaに渡す必要がある。

利用可能な文字

  • 全銀フォーマットとして利用が許されるのは、0-9A-Zア-ン\「」,.( )-/のみ。
  • 以下、つい、うっかり使いたくなってしまうけど、使えない文字。
  • a-z英小文字、ァィゥェォッャュョ小さなカナ文字、半角カタカナの長音記号、_アンダーバー、なかぐろ、
  • 読み上げ前に全銀フォーマット規格外の文字が含まれていないかチェックすることにしてみた。


記号の読み方

saykanaが読んでくれる記号は「.」を「てん」と発音してくれるだけ。それ以外の記号は読まずに無視される。だから、記号は仮名読みに変換してから渡す必要がある。
それから、ちょっとややこしいのが「-」で、使われる状況によって読み方が変わる。

  • マイナス:-100「まいなす いちぜろぜろ」と読み上げることを期待
  • ハイフン:so-netえすおー はいふん えぬいーてぃー」と読み上げることを期待
  • 長音記号:デ-タ「でーた」と読み上げることを期待

「,」も状況によって読み方が変わる可能性がある。

  • 数字の桁区切りとして利用される時は、若干の間を空けるだけにしてもらいたいが、
  • 桁区切り以外の利用では「かんま」と、読み上げてもらうことを期待する。

略記法

  • カ=株式会社、ユ=有限会社、...など
  • 利用する時は( )で区切って以下のように使う。
  • カ)xxxx、xxxx(カ、xxxx(カ)ooシテン
  • 読む時は以下のように発音してもらいたい、
  • かぶしきがいしゃxxxx、xxxxかぶしきがいしゃ、xxxxかぶしきがいしゃooしてん
  • 預金種は、1=普通預金、2=当座預金、9=その他
  • ふつうよきん、とうざよきん、などと発音してもらいたい。

読み上げの一時停止

  • 表計算ソフトで振込データを作成すると、おそらく以下のような書式になると思う。
銀行No. 銀行名 支店No. 支店名 口座種 口座No. 口座名義 金額
1 ミズホ 1 ホンテン 1 1234567 カ)ザリガニ 1000000
  • 最初は1行を連続して読み上げ、改行で一時停止するようにしていたが、
  • 1行を一気に読み上げてしまうと、確認している人間の方が間に合わないことになってしまうことも...。*1
  • そこで、空白セルを挿入すれば、そこで一時停止するようにしてみた。
銀行No. 銀行名 支店No. 支店名 口座種 口座No. 空白 口座名義 空白 金額
1 ミズホ 1 ホンテン 1 1234567 カ)ザリガニ 1000000
  • 上記のように空白列を挿入すれば、1行を読み終わるまでに3回、一時停止することになる。
  • 聴き逃した時のために、繰り返し再生もに対応してみた。


数値の区切り

  • saykanaはデフォルトで数値を4桁ごとに区切って発音してくれる。例:12345678 →「いちにーさんしー、ごーろくななはち」
  • しかし、金額を表示する時は3桁区切りにすることが慣例。(何故?)
  • セルの書式設定で桁区切りにしておけば、12,345,678 のように表示され、saykanaは「,」で間をあけて発音してくれる。
  • ところが、全銀フォーマットとしては数値を桁区切りすることは許していない。
  • だから、12345678 のまま読み取って、自分で12,345,678に変換してsaykanaに渡すことにした。
  • 一方、金額でない数字は、saykanaデフォルトの4桁区切りの方が聴き取り易いのだ。
  • よって、空白セルで区切られた単独セルで数値のみの範囲は金額と見なし、3桁区切り読み。
  • 複数のセルが連続した場合は、デフォルトの4桁区切り読み、とした。
  • もちろん、セルが書式設定されていれば、そこで指定された桁区切りで読み上げる。

コード


(*
このスクリプトは、表計算ソフトに入力された全銀データの読み合わせを行います。 このスクリプトを動かすために必要なもの ◆SayKana SayKana(Mac版 日本語音声合成ソフト)が/usr/local/bin/以下にインストールされている必要があります。 http://www.a-quest.com/aquestalk/saykana/
AppleScriptライブラリ 依存するAppleScriptライブラリを以下のページからダウンロードしておく必要があります。 http://github.com/zarigani/AppleScript-bebe-s-Library/tree/master
そして、以下のファイルをユーザースクリプトフォルダ(~/Library/Scripts)にインストールしてください。 _lib.scpt _gui.scpt *)
property LIB : load script file ((path to scripts folder as text) & "_lib.scpt")
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")

--文字列中の置き換えで利用
property KIGO : "/()"
property YOMI : {"、すらっしゅ、", "、かっこ、", "、かっことじ、"}

--セル単位の置き換えで利用
property ZENGIN_RYAKUGO : {{"1", "ふつうよきん"}, {"2", "とうざよきん"}, {"9", "その"}, {"", "かぶしきがいしゃ"}, {"", "ゆうげんがいしゃ"}, {"ザイ", "ざいだんほーじん"}, {"シャ", "しゃだんほうじん"}, {"ソ", "そうごがいしゃ"}, {"エイ", "えいぎょうしょ"}, {"シユツ", "しゅっちょうしょ"}}
property is_ZENGIN_reading : true

GUI's init()
GUI's shortcut("", "command-c")

LIB's message("SayEB", "読み取りを開始しました...")
set a_list to LIB's do_ruby_jcode_u("'" & (the clipboard as string) & "'" & ".gsub(/\\t+[\\n\\r]/,\"\\r\")") --右側の余分な空白セルを削除
set a_list to LIB's every_split(a_list, {"\n", "\r"}) --改行でリストに変換
--set a_list to LIB's reject_item(a_list, "")--空文字リストを削除する
zengin_check(a_list)
set is_ZENGIN_reading to reading_select() = "全銀データとして読む"

set i to 0
repeat with a_line in a_list
set i to i + 1
say_line(a_line, i)
end repeat

LIB's message("SayEB", "完了しました。")



--1行を空白セルで区切った範囲リストに変換
--次の空白セルまで読んで、次を読む指示待ち
on say_line(a_line, num)
set ranges to LIB's every_split(a_line, {tab & tab})
set complete to ""
repeat with a_range in ranges
repeat
say_range(a_range)
set complete to complete & "\"" & LIB's every_replace(a_range, {tab}, {" "}) & "\"" & return
if one_stop(complete, num) = "次を読む[return]" then exit repeat
end repeat
end repeat
end say_line

--1範囲をtabと()で区切ったリストに変換
--口座種と略語を変換した後、「、」で区切ったテキストに戻す
on say_range(a_range)
if is_ZENGIN_reading then
set kugiri to "()"
else
set kugiri to ""
end if
set word_list to LIB's every_split(a_range, tab & kugiri)
repeat with a_word in word_list
if is_ZENGIN_reading then
LIB's look_up(ZENGIN_RYAKUGO, a_word's contents) as text --口座種と略語を変換
if result ≠ "" then set a_word's contents to result
end if
end repeat
say_text(LIB's join(word_list, ""))
end say_range

--saykanaで発声する
--マイナス、長音記号、ハイフンの使い分けをする
--半角カナを全角カナに変換する
on say_text(str)
LIB's every_replace(str, {tab, "\"", "'"}, {"", "", ""})
LIB's do_ruby_jcode_u("('" & result & "'" & ".gsub(/(\\d)(?=(?:\\d\\d\\d)+(?!\\d))/, '\\1,')" & " if /^-?\\d+$/ =~ " & "'" & result & "')" & "|| '" & result & "'") --数値を3桁区切りにする
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/([^0-9]),([^0-9])|([0-9]),([^0-9])|([^0-9]),([0-9])/,'\\1\\3\\5んま\\2\\4\\6')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/(^|\\s)[-]([0-9])/,'\\1まいなす\\2')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/([-])[-]([-])/,'\\1\\2')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/([^\\^\\s-])[-]([^-])/,'\\1いふん\\2')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/(^|\\s)[-]([^0-9-])/,'\\1いふん\\2')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/ /,'、ぜんかくすぺーす、')")
LIB's do_ruby_jcode_u("'" & result & "'" & ".gsub(/ (.)/,'\\1’')")
LIB's every_replace(result, KIGO, YOMI)
set voice to LIB's kana_han2zen(result)
do shell script "/usr/local/bin/saykana " & quoted form of voice & "> /dev/null 2>&1 &" --デーモンで実行、戻り値は無視する必要あり
end say_text

--次を読む指示待ち
on one_stop(msg, num)
activate
"■■■ No." & num & " ■■■" & return & msg
--display dialog result buttons {"キャンセル", "もう一度繰り返す", "次を読む[return]"} default button 3
display alert result buttons {"キャンセル", "もう一度繰り返す", "次を読む[return]"} default button 3 cancel button 1
result's button returned
end one_stop

--全銀フォーマット対応文字かどうかのチェック
--対応文字は以下の通り
-- 数字の0から9 英大文字のA-Z 半角カタカナ大文字と濁点と半濁点のア-ン゙゚
-- ピリオド. 括弧() マイナスハイフン- スラッシュ/ 半角スペース
on zengin_check(a_list)
set i to 0
set err_list to {}
repeat with a_row in a_list
set i to i + 1
if not (LIB's reg("/^[\\s0-9A-Z-ン゙゚.()\\-\\/]+$/", a_row)) then
set err_list to err_list & (("■■" & i & ". " & a_row) as text)
end if
end repeat
if err_list's number > 0 then
beep
activate
"■■■全銀フォーマット規格外の文字を含んでいます。\n\n" & LIB's every_replace(LIB's join(err_list, "\n"), "\t", " ")
display alert result buttons {"キャンセル", "続ける"} default button 1 cancel button 1 as warning if result's button returned = "キャンセル" then
LIB's message("SayEB", "読み上げを中止しました。")
error number -128 --スクリプトを中止する
end if
end if
end zengin_check

--全銀フォーマットとして読むかどうか選択する
on reading_select()
activate
"準備が整いました。\nreturnキーを押すごとに、1件ずつ読み上げます。"
--display dialog result buttons {"キャンセル", "そのまま読む", "全銀データとして読む"} default button 3
display alert result buttons {"キャンセル", "そのまま読む", "全銀データとして読む"} default button 3 cancel button 1
result's button returned
end reading_select


これでMacBookが読み上げてくれる!それにしても、数字とかな、一部の記号しか読み上げないのに何と手間のかかること。人間の「読む」という行為の裏にある、無意識な処理のレベルの高さを思い知らされた。そういえば、ちょっと前にもこんなことが話題になって、気付かないほど自然に読めてしまう自分に驚いたのであった。

*1:人間が作業するのであれば、相手を気遣い絶妙のタイミングで読み上げてくれるのだが...