固定長電文と JSON の間の変換アダプター
目標
レイアウトファイルとコードマッピング表を読み、固定長の本文(EUC-KR)とJSONを相互に変換する変換器を作り、バイト長・コード・エンコーディング・小数のルールに違反する値を拒否させます。
なぜ重要なのか
変換層のミスは静かです。文字数で数えた長さは後ろのフィールドをすべてずらし、知らないコードを流すと内部システムが別の意味で読み、floatを通した金利は1足りなくなり、Pythonのeuc_krはttomと読むハングル1字を8バイトに変えて渡してしまいます。そこでルールをデータ(レイアウト・マッピング表)に置き、直せない値は直さずエラーコードで拒否します。
ステップ
- インターフェース仕様書
/opt/lab/fixtures/eaimw/transform/FXTR0001.mdを/root/eaimw/xform/FXTR0001.layoutに書き写してください。見出し行はname,length,type,key,map,scaleで、仕様書の順に12行です。nameは英語名、keyはJSONキー、mapはコードマッピングのドメイン(なければ空欄)、scaleはこの電文では空欄です。 /opt/lab/fixtures/eaimw/transform/CODES.mdを/root/eaimw/xform/codemap.csvに書き写してください。見出し行はdomain,external,internalで、使用中のコードだけを載せ、domain、externalの順に並べ替えます(LC_ALL=C sort)。/root/eaimw/xform/f2j.pyを作成してください。python3 f2j.py <레이아웃> <매핑표> <본문파일>(プレースホルダーはレイアウト、マッピング表、本文ファイルです)がJSONを1行出力し、0で終わります。Nは整数、ANとHは後ろの空白だけを除去し、本文はEUC-KRで厳密に読み、mapがあるフィールドはマッピング表で変換します。拒否するときは、標準エラーの1行目をエラーコードで始め、終了コード2にします。マッピング表にないコードはUNMAPPED、本文の長さの不一致・数字でない・EUC-KRで読めないものはBAD_VALUEです。/root/eaimw/xform/j2f.pyを作成してください。python3 j2f.py <레이아웃> <매핑표> <JSON파일>(プレースホルダーはレイアウト、マッピング表、JSONファイルです)が本文のバイトを標準出力へ出します。Nは右寄せで前を0埋め、ANとHは左寄せで後ろを空白埋め、長さはEUC-KRのバイトです。拒否(終了コード2)は、長さ・桁数の超過がOVERFLOW(切り詰めません)、マッピング表にない内部コードがUNMAPPED、キーがない・Nが0以上の整数でないものがBAD_VALUEです。/root/eaimw/xform/roundtrip.pyを作成してください。python3 roundtrip.py <레이아웃> <매핑표> <본문파일>...(プレースホルダーはレイアウト、マッピング表、本文ファイルです)がファイルごとに<파일이름>,<결과>(プレースホルダーはファイル名と結果です)を1行出力します。f2jが拒否したらREJECT、f2j→j2fの結果が元のバイトと同じならSAME、違えばDIFFです。そして、受信箱のFXTR0001-*.bodyすべて(名前順)の結果を、見出し行file,resultとともに/root/eaimw/xform/roundtrip.csvに保存してください。- インターフェース仕様書
/opt/lab/fixtures/eaimw/transform/DPRT0001.mdを/root/eaimw/xform/DPRT0001.layoutに書き写し(暗黙の小数点フィールドはscale 4)、f2jとj2fがscaleに対応するように直してください。f2jは"3.2500"のようにscaleの桁まで書いた文字列を出力し、j2fは文字列だけを受け取ってdecimalで計算します。小数の桁がscaleより多ければOVERFLOW(四捨五入は禁止)、文字列でなければ(JSON数値を含む)BAD_VALUEです。 - j2fが、KS X 1001完成形にない文字(例: ttomと読むハングル1字、絵文字)を
NOT_KSX1001で拒否するように直してください(終了コード2)。漢字・記号のようにEUC-KRで2バイトになる文字は、そのまま受け付けます。 - 受信箱
/opt/lab/fixtures/eaimw/transform/inbox/全体をf2jで変換してください。ファイル名の前半部分(FXTR0001・DPRT0001)がレイアウト名です。成功したら/root/eaimw/xform/out/<이름>.json(プレースホルダーは名前です。名前は拡張子.bodyを除いたものです)に出力し、拒否されたらJSONを残しません。結果は、見出し行file,result、ファイル名順、結果はOKまたはエラーコードで/root/eaimw/xform/summary.csvに書いてください。
参考
- バイトの扱い: 本文は
open(f, "rb").read()、フィールドはbody[pos:pos + 길이](プレースホルダーは長さです)、文字に変えるときは.decode("euc_kr")を使います。長さの検査はlen(값.encode("euc_kr"))(プレースホルダーは値です)です。 - エラー出力:
print("UNMAPPED ...", file=sys.stderr); sys.exit(2)。バイト出力:sys.stdout.buffer.write(본문)(プレースホルダーは本文です)。 - j2fは、f2jの
load_layout・load_codemapを取り込んで使えばよいです(from f2j import ...。同じディレクトリのファイルはそのままimportできます)。 - よくある間違い: 文字数で長さを数えてしまうこと、あふれた値を切って渡してしまうこと、知らないコードを元のまま渡してしまうこと、金利をfloatで掛けてしまうこと。
- 採点ツールは毎回、新しいレイアウト・マッピング表・値を作って引数で渡します。仕様書のフィールド名をコードに書いておくと合格できません。
- 共通ライブラリ
/opt/lab/fixtures/eaimw/lib/lhconv.pyはモジュール4から使います。このラボでは自分で作ります。
インターフェース仕様書をレイアウトファイルに書き写す
/opt/lab/fixtures/eaimw/transform/FXTR0001.mdの12フィールドを/root/eaimw/xform/FXTR0001.layout(name,length,type,key,map,scale)に書き写してください。
仕様書1節の表をそのまま書き写します。nameは英語名、keyはJSONキー(大文字・小文字もそのまま)、mapはコードマッピングの列です。長さをすべて足すと、仕様書に書かれた業務部の合計と一致するはずです。
コード定義書をマッピング表に書き写す
/opt/lab/fixtures/eaimw/transform/CODES.mdの使用中のコードを/root/eaimw/xform/codemap.csv(domain,external,internal)に書き写し、domain、externalの順に並べ替えてください。
ドメインはBANK・CHANNEL・KINDの3つです。状態が使用停止の行は載せません。そうすることで、そのコードを含む電文が変換で引っかかります。外部コード01の先頭の0は値の一部です(Excelで開くと消えてしまう、あの0です)。
固定長→JSON
/root/eaimw/xform/f2j.py <レイアウト> <マッピング表> <本文>がJSONを出力し、知らないコードはUNMAPPED、形式・エンコーディングのエラーはBAD_VALUEで拒否するようにしてください(終了コード2)。
open(ファイル, 'rb')で読み、レイアウトの長さだけバイトで切ります。フィールドごとにdecode('euc_kr')すると、半端なハングルやCP949専用の文字がUnicodeDecodeErrorとして表に出ます。Nはstripしたあとに数字か確認してintに、ANとHはrstrip(' ')だけを行います。マッピング表にないコードを元のまま渡さないでください。
JSON→固定長、バイトで数える
/root/eaimw/xform/j2f.py <レイアウト> <マッピング表> が本文のバイトを出力し、長さ超過はOVERFLOW、知らない内部コードはUNMAPPED、キーなし・整数でないものはBAD_VALUEで拒否するようにしてください。
Hフィールドの長さはlen(値)ではなくlen(値.encode('euc_kr'))です。あふれたら切って合わせず、拒否します。バイトで切るとハングルが半分に割れます。マッピングは、マッピング表を逆にした辞書(内部→外部)で探します。Nはrjust(長さ, '0')、それ以外はljust(長さ, b' ')です。
往復テスト
/root/eaimw/xform/roundtrip.pyが本文ごとにSAME・DIFF・REJECTを判定し、受信箱のFXTR0001サンプルすべての結果を/root/eaimw/xform/roundtrip.csvに保存するようにしてください。
f2jとj2fを関数としてimportして使います。f2jが拒否したらREJECT、復元したバイトが元と同じならSAMEです。DIFFが出たサンプルは変換器のバグかもしれませんが、相手が標準に違反しているのかもしれません。そのフィールドのバイトをod -cで見てください。
暗黙の小数点はdecimalで
/root/eaimw/xform/DPRT0001.layoutを作成し(scale 4)、f2jは"3.2500"のような文字列を、j2fは文字列の小数だけを受け取ってdecimalで計算するように直してください。
0032500をscale 4で読むと、Decimal(32500).scaleb(-4) = 3.2500です。戻すときはDecimal("3.25").scaleb(4) = 32500です。float("0.29") * 100は28.999999999999996なので、intに変えると28になります。小数の桁がscaleより多い場合は、四捨五入せずに拒否してください。
完成形にない文字を拒否する
j2fが、KS X 1001完成形にない文字(ttomと読むハングル1字、絵文字)をNOT_KSX1001で拒否し(終了コード2)、漢字・記号のような2バイトの文字は受け付けるように直してください。
まず、python3 -cでttomと読むハングル1字をeuc_krでエンコードしてみてください。エラーではなく8バイトが出ます。文字ごとにencode('euc_kr')の長さが2か確認すればよいです。エンコード自体が失敗する文字(絵文字)も、同じコードで拒否します。
受信箱全体を変換する
受信箱のすべての本文をf2jで変換し、成功したものは/root/eaimw/xform/out/<名前>.jsonに、結果一覧は/root/eaimw/xform/summary.csv(file,result)に残してください。
ファイル名の「-」より前の部分がレイアウト名です。終了コード2なら、標準エラーの1行目の最初の単語がエラーコードです。シェルのリダイレクトは失敗しても空ファイルを作ってしまうので、拒否されたサンプルのJSONファイルは削除してください。