Парсим X12 «на коліні»

При створенні програми, активно взаємодіє зі сторонніми сервісами і системами, часто потрібно забезпечити обмін інформацією з ними, односторонній або двосторонній

При цьому найчастіше сторонній сервіс надає єдиний формат і структури даних для такої взаємодії.

Одним з таких форматів електронного документообігу є EDI ANSI ASC X12, досить докладний опис якого наведено за посиланням.

Під катом наведено простий алгоритм парсера X12 та код на Clojure, що реалізує парсер і приклад обробки распарсенных даних.

Трохи про формат

Цитуючи вищенаведену посилання: Стандарт електронного обміну документами ANSI ASC X12 (American National Standards Institute Accredited Standards Committee X12) був розроблений у 70-х роках, коли був важливий малий розмір електронного документа (для модемів зі швидкостями 300-1200 мм. біт в секунду) і кожен байт повинен був нести максимум інформації. Таким чином, від «читаності» електронного документа відмовилися на користь «щільності інформації».
Тому ви не побачите в ньому ніяких человекочитаемых красот, як XML. І хоча стандарт дозволяє створювати документи досить складної ієрархічної структури, з наявністю блоків і так званих loop-ів, тим не менше навіть закриваючі теги для всіх блоків (крім ISA/GS/ST) не передбачені. За посиланням до ката бажаючі можуть в деталях ознайомитися зі структурою і описом формату, далі ми будемо стосуватися тільки необхідних речей.

Кожен тип документа має свою структуру шаблону, в якому зазначається зміст і призначення окремих полів і сегментів, їх типи і можливі значення, а також перелік обов’язкових і опціональних сегментів і блоків. Підтримується версіонування шаблонів, інформація про тип і номер версії передається у відповідних полях документа. Передбачається, що саме з використанням шаблону конкретного типу документа повинен проводитися його розбір і валідація.

Нижче представлений приклад документа, що містить декілька транзакцій типу 835 (документ типу claim response), на якому буде продемонстровано парсинг і подальша обробка даних.

Приклад X12

ISA*00* *00* *ZZ*EMEDNYBAT *ZZ*ETIN *140305*0929*^*00501*111111123*0*P*:~
GS*HP*EMED*ETIN*20140301*09304100*111111123*X*005010X221A1~
ST*835*35681~
BPR*I*810.8*C*CHK************20140331~
TRN*1*12345*1512345678~
REF*EV*XYZ CLEARINGHOUSE~
N1*PR*DENTAL OF ABC~
N3*225 MAIN STREET~
N4*CENTERVILLE*PA*17111~
PER*BL*JANE DOE*TE*9005555555~
N1*PE*BAN DDS LLC*FI*999994703~
LX*1~
CLP*7722337*1*226*132**12*119932404007801~
NM1*QC*1*DOE*SANDY****MI*SJD11112~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*132~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D0220*25*14~
DTM*472*20140324~
CAS*CO*131*11~
AMT*B6*14~
SVC*AD:D0230*22*10~
DTM*472*20140324~
CAS*CO*131*12~
AMT*B6*10~
SVC*AD:D0274*60*34~
DTM*472*20140324~
CAS*CO*131*26~
AMT*B6*34~
SVC*AD:D1110*73*49~
DTM*472*20140324~
CAS*CO*131*24~
AMT*B6*49~
LX*2~
CLP*7722337*1*119*74**12*119932404007801~
NM1*QC*1*DOE*SALLY****MI*SJD11111~
NM1*IL*1*DOE*JOHN****MI*123456~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*74~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D1110*73*49~
DTM*472*20140324~
CAS*CO*131*24~
AMT*B6*49~
LX*3~
CLP*7722337*1*226*108*24*12*119932404007801~
NM1*QC*1*SMITH*SALLY****MI*SJD11113~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*132~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D0220*25*0~
DTM*472*20140324~
CAS*PR*3*14~
CAS*CO*131*11~
AMT*B6*14~
SVC*AD:D0230*22*0~
DTM*472*20140324~
CAS*PR*3*10~
CAS*CO*131*12~
AMT*B6*10~
SVC*AD:D0274*60*34~
DTM*472*20140324~
CAS*CO*131*26~
AMT*B6*34~
SVC*AD:D1110*73*49~
DTM*472*20140324~
CAS*CO*131*24~
AMT*B6*49~
LX*4~
CLP*7722337*1*1145*14*902*12*119932404007801~
NM1*QC*1*SMITH*SAM****MI*SJD11116~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*14~
SVC*AD:D0220*25*14~
DTM*472*20140324~
CAS*CO*131*11~
AMT*B6*14~
SVC*AD:D2790*940*0~
DTM*472*20140324~
CAS*PR*3*756~
CAS*CO*131*184~
SVC*AD:D2950*180*0~
DTM*472*20140324~
CAS*PR*3*146~
CAS*CO*131*34~
LX*5~
CLP*7722337*1*348*16.8*44.2*12*119932404007801~
NM1*QC*1*JONES*SAM****MI*SJD11122~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*28~
SVC*AD:D4342*125*0~
DTM*472*20140313~
CAS*CO*131*125~
SVC*AD:D4381*43*0~
DTM*472*20140313~
CAS*PR*3*33~
CAS*CO*131*10~
SVC*AD:D2950*180*16.8~
DTM*472*20140313~
CAS*PR*3*11.2~
CAS*CO*131*152~
AMT*B6*28~
LX*6~
CLP*7722337*1*226*132**12*119932404007801~
NM1*QC*1*JONES*SALLY****MI*SJD11133~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*132~
SVC*AD:D0120*46*25~
DTM*472*20140321~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D0220*25*14~
DTM*472*20140321~
CAS*CO*131*11~
AMT*B6*14~
SVC*AD:D0230*22*10~
DTM*472*20140321~
CAS*CO*131*12~
AMT*B6*10~
SVC*AD:D0274*60*34~
DTM*472*20140321~
CAS*CO*131*26~
AMT*B6*34~
SVC*AD:D1110*73*49~
DTM*472*20140321~
CAS*CO*131*24~
AMT*B6*49~
LX*7~
CLP*7722337*1*179*108**12*119932404007801~
NM1*QC*1*DOE*SAM****MI*SJD99999~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*108~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D0274*60*34~
DTM*472*20140324~
CAS*CO*131*26~
AMT*B6*34~
SVC*AD:D1110*73*49~
DTM*472*20140324~
CAS*CO*131*24~
AMT*B6*49~
LX*8~
CLP*7722337*1*129*82**12*119932404007801~
NM1*QC*1*DOE*SUE****MI*SJD88888~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*82~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D1120*54*37~
DTM*472*20140324~
CAS*CO*131*17~
AMT*B6*37~
SVC*AD:D1208*29*20~
DTM*472*20140324~
CAS*CO*131*9~
AMT*B6*20~
LX*9~
CLP*7722337*1*221*144**12*119932404007801~
NM1*QC*1*DOE*DONNA****MI*SJD77777~
NM1*82*1*BAN*ERIN****XX*1811901945~
AMT*AU*144~
SVC*AD:D0120*46*25~
DTM*472*20140324~
CAS*CO*131*21~
AMT*B6*25~
SVC*AD:D0330*92*62~
DTM*472*20140324~
CAS*CO*131*30~
AMT*B6*62~
SVC*AD:D1120*54*37~
DTM*472*20140324~
CAS*CO*131*17~
AMT*B6*37~
SVC*AD:D1208*29*20~
DTM*472*20140324~
CAS*CO*131*9~
AMT*B6*20~
SE*190*35681~
GE*1*111111123~
IEA*1*111111123~

Детальний опис призначення кожного блоку та сегмента можна дізнатися при аналізі загальної структури 12 і структури шаблону даного типу документа. Але базова концепція загальна для всіх типів — вміст посилки складається з сегментів, розділених символом ~ (у тексті прикладу кожен сегмент виведений з нового рядка, для зручності читання). У свою чергу, кожен сегмент може містити довільне число полів, розділених символом *.

Такі угоди дозволяють нам легко отримати лінійну структуру документа списку сегментів з переліком їх полів. Однак, це недостатньо для відновлення ієрархічної структури блоків документа. Для цього, як я вже згадував, передбачається використовувати схему, яка для більшості типів документів являє собою досить об’ємний файл. Але, оскільки ми не будемо розглядати задачу валідації документа, а обмежимося тільки парсингом, то для наших цілей цілком підійде наступний алгоритм — для кожного сегмента, який зустрівся при послідовному розборі лінійного списку сегментів документа, нам необхідно одержати відповідь на єдине питання: чи формує даний сегмент новий вкладений блок, є закінченням поточного блоку (і одночасно початком наступного), або ж (в інших випадках) є внутрішнім сегментом поточного блоку.

Парсер

Нижче продемонстровано код на Clojure, здійснює парсинг лінійної структури сегментів в ієрархічну структуру блоків. Для задання структури ієрархії використовується декларативний підхід — найпростіша структура даних loops, в якій для перерахованих сегментів окремо визначаються переліки сегментів, що утворюють вкладені блоки, і закінчують поточний блок. Зрозуміло, ця структура залежить від типу документа, вона, власне, й задає його ієрархію. Але наведена нижче функція парсинга є універсальною, і буде працювати на будь-яких коректно заданих таким чином шаблонах структур, звичайно за умови відповідності типу разбираемого документа вибраного шаблону.

;; Parse 835 x12 string to structure hierarchical

(def loops
 {"835" {:nested #{"ISA"}}
 "ISA" {:nested #{"GS"}}
 "GS" {:nested #{ST} :end #{"IEA"}}
 "ST" {:nested #{"N1" "LX"} :end #{"GE" "ST"}}
 "N1" {:end #{"SE" "LX" "N1"}}
 "LX" {:nested #{"CLP"} :end #{"SE" "LX"}}
 "CLP" {:nested #{"SVC"} :end #{"SE" "LX" "CLP"}}
 "SVC" {:end #{"SE" "LX" "CLP" "SVC"}}})

(defn parser-core [id ss acc]
 (let [seg-id (first (first ss))
 {:keys [nested end]} (loops id)]
 (if (or (empty? ss) (and (contains? end seg-id) (not (empty? acc))))
 [acc ss]
 (let [[v ss-] (if (contains? nested seg-id)
 (parser-core seg-id ss [])
 [(first ss) (rest ss)])]
 (recur id ss- (conj acc v))))))

(defn segments [s] (str/split (str/trim s) #"~"))
(defn elements [s] (str/split (str/trim s) #"*"))

(defn x12 [s] (first (parser-core "835" (mapv elements (segments (or s ""))) [])))

Під спойлером представлений

Читайте також  WPA3 міг би бути і безпечніше: думка експертів

Результат парсинга

[[["ISA"
"00"
 ""
"00"
 ""
"ZZ"
 "EMEDNYBAT "
"ZZ"
 "ETIN "
"140305"
"0929"
"^"
"00501"
"111111123"
"0"
"P"
":"]
 [["GS" "HP" "EMED" "ETIN" "20140301" "09304100" "111111123" "X" "005010X221A1"]
 [["ST" "835" "35681"]
 ["BPR" "I" "810.8" "C" "CHK" "" "" "" "" "" "" "" "" "" "" "" "20140331"]
 ["TRN" "1" "12345" "1512345678"]
 ["REF" "EV" "XYZ CLEARINGHOUSE"]
 [["N1" "PR" "OF DENTAL ABC"]
 ["N3" "225 MAIN STREET"]
 ["N4" "CENTERVILLE" "PA" "17111"]
 ["PER" "BL" "JANE DOE" "TE" "9005555555"]]
 [["N1" "PE" "BAN DDS LLC" FI "999994703"]]
 [["LX" "1"]
 [["CLP" "7722337" "1" "226" "132" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "DOE" "SANDY" "" "" "" "MI" "SJD11112"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "132"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D0220" "25" "14"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "11"]
 ["AMT" B6 "14"]]
 [["SVC" "AD:D0230" "22" "10"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "12"]
 ["AMT" B6 "10"]]
 [["SVC" "AD:D0274" "60" "34"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "26"]
 ["AMT" B6 "34"]]
 [["SVC" "AD:D1110" "73" "49"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "24"]
 ["AMT" B6 "49"]]]]
 [["LX" "2"]
 [["CLP" "7722337" "1" "119" "74" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "DOE" "SALLY" "" "" "" "MI" "SJD11111"]
 ["NM1" "IL" "1" "DOE" "JOHN" "" "" "" "MI" "123456"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "74"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D1110" "73" "49"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "24"]
 ["AMT" B6 "49"]]]]
 [["LX" "3"]
 [["CLP" "7722337" "1" "226" "108" "24" "12" "119932404007801"]
 ["NM1" "QC" "1" "SMITH" "SALLY" "" "" "" "MI" "SJD11113"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "132"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D0220" "25" "0"]
 ["DTM" "472" "20140324"]
 ["CAS" "PR" "3" "14"]
 ["CAS" "CO" "131" "11"]
 ["AMT" B6 "14"]]
 [["SVC" "AD:D0230" "22" "0"]
 ["DTM" "472" "20140324"]
 ["CAS" "PR" "3" "10"]
 ["CAS" "CO" "131" "12"]
 ["AMT" B6 "10"]]
 [["SVC" "AD:D0274" "60" "34"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "26"]
 ["AMT" B6 "34"]]
 [["SVC" "AD:D1110" "73" "49"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "24"]
 ["AMT" B6 "49"]]]]
 [["LX" "4"]
 [["CLP" "7722337" "1" "1145" "14" "902" "12" "119932404007801"]
 ["NM1" "QC" "1" "SMITH" "SAM" "" "" "" "MI" "SJD11116"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "14"]
 [["SVC" "AD:D0220" "25" "14"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "11"]
 ["AMT" B6 "14"]]
 [["SVC" "AD:D2790" "940" "0"]
 ["DTM" "472" "20140324"]
 ["CAS" "PR" "3" "756"]
 ["CAS" "CO" "131" "184"]]
 [["SVC" "AD:D2950" "180" "0"]
 ["DTM" "472" "20140324"]
 ["CAS" "PR" "3" "146"]
 ["CAS" "CO" "131" "34"]]]]
 [["LX" "5"]
 [["CLP" "7722337" "1" "348" "16.8" "44.2" "12" "119932404007801"]
 ["NM1" "QC" "1" "JONES" "SAM" "" "" "" "MI" "SJD11122"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "28"]
 [["SVC" "AD:D4342" "125" "0"]
 ["DTM" "472" "20140313"]
 ["CAS" "CO" "131" "125"]]
 [["SVC" "AD:D4381" "43" "0"]
 ["DTM" "472" "20140313"]
 ["CAS" "PR" "3" "33"]
 ["CAS" "CO" "131" "10"]]
 [["SVC" "AD:D2950" "180" "16.8"]
 ["DTM" "472" "20140313"]
 ["CAS" "PR" "3" "11.2"]
 ["CAS" "CO" "131" "152"]
 ["AMT" B6 "28"]]]]
 [["LX" "6"]
 [["CLP" "7722337" "1" "226" "132" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "JONES" "SALLY" "" "" "" "MI" "SJD11133"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "132"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140321"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D0220" "25" "14"]
 ["DTM" "472" "20140321"]
 ["CAS" "CO" "131" "11"]
 ["AMT" B6 "14"]]
 [["SVC" "AD:D0230" "22" "10"]
 ["DTM" "472" "20140321"]
 ["CAS" "CO" "131" "12"]
 ["AMT" B6 "10"]]
 [["SVC" "AD:D0274" "60" "34"]
 ["DTM" "472" "20140321"]
 ["CAS" "CO" "131" "26"]
 ["AMT" B6 "34"]]
 [["SVC" "AD:D1110" "73" "49"]
 ["DTM" "472" "20140321"]
 ["CAS" "CO" "131" "24"]
 ["AMT" B6 "49"]]]]
 [["LX" "7"]
 [["CLP" "7722337" "1" "179" "108" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "DOE" "SAM" "" "" "" "MI" "SJD99999"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "108"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D0274" "60" "34"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "26"]
 ["AMT" B6 "34"]]
 [["SVC" "AD:D1110" "73" "49"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "24"]
 ["AMT" B6 "49"]]]]
 [["LX" "8"]
 [["CLP" "7722337" "1" "129" "82" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "DOE" "SUE" "" "" "" "MI" "SJD88888"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "82"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D1120" "54" "37"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "17"]
 ["AMT" B6 "37"]]
 [["SVC" "AD:D1208" "29" "20"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "9"]
 ["AMT" B6 "20"]]]]
 [["LX" "9"]
 [["CLP" "7722337" "1" "221" "144" "" "12" "119932404007801"]
 ["NM1" "QC" "1" "DOE" "DONNA" "" "" "" "MI" "SJD77777"]
 ["NM1" "82" "1" "BAN" "ERIN" "" "" "" "XX" "1811901945"]
 ["AMT" "AU" "144"]
 [["SVC" "AD:D0120" "46" "25"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "21"]
 ["AMT" B6 "25"]]
 [["SVC" "AD:D0330" "92" "62"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "30"]
 ["AMT" B6 "62"]]
 [["SVC" "AD:D1120" "54" "37"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "17"]
 ["AMT" B6 "37"]]
 [["SVC" "AD:D1208" "29" "20"]
 ["DTM" "472" "20140324"]
 ["CAS" "CO" "131" "9"]
 ["AMT" B6 "20"]]]]
 ["SE" "190" "35681"]]
 ["GE" "1" "111111123"]]
 ["IEA" "1" "111111123"]]]

нашого вихідного прикладу — ієрархічна структура сегментів.

Читайте також  6 способів заховати дані в Android-додатку

Власне, на це завдання можна вважати вирішеною. Десяток рядків Clojure-коду забезпечують нам повнофункціональний парсер будь-яких X12 документів в ієрархічне AST. Але для повноти картини можна показати приклад обходу даного AST для виконання якої-небудь корисної завдання — наприклад, конструювання структур необхідного формату і запису цієї інформації в базу даних. Нижче представлений приклад коду, який обходить распарсенную структуру, і створює на її основі списку об’єктів. Пара універсальних функцій-помічники для зручного доступу до даних, як вони представлені в AST, і обхідник дерева, формує об’єкт з можливістю звернення при цьому до вихідним даним на будь-якому рівні ієрархії.

;; util helper for information extracting

(defn v-prefix? [v p]
(and
 (vector? v)
 (= p (if (vector? p) (subvec v 0 (min (count v) (count p))) (get v 0)))))

(defn items [v p & path] (filter #(v-prefix? (get in % (vec path)) p) v))
(defn item [v p & path] (first (apply items v p path)))

;; function test for extracting human-readable structure

(defn tst [x12-string]
 (for [isa (items (x12 x12-string) "ISA" 0)
 gs (items isa "GS" 0)
 st (items gs ST 0)
 lx (items st "LX" 0)
 clp (items lx "CLP" 0)]
 (let [bpr (item st "BPR")]
 {:message {:received (get in isa [0 9])
 :created (get in gs [0 4])}
 :transaction {:check (get (item st "TRN") 2)
 :payed (get bpr 16)
 :total (read-string (get bpr 2))}
 :insurer (get in (item st ["N1" "PR"] 0) [0 2])
 :organization (get in (item st ["N1" "PE"] 0) [0 2])
 :claim {:patient (if-let [x (item clp ["NM1" "QC"])]
 (str (get x 3) "" (get x 4)))
 :total (read-string (get in clp [0 4]))}
 :services (mapv
 (fn [svc]
 {:code (get in svc [0 1])
 :amount (read-string (get in svc [0 3]))
 :date (get (item svc ["DTM" "472"]) 2)})
 (items clp "SVC" 0))})))

Під спойлером представлений

Читайте також  Як ми придумували набори дитячої технічної творчості

Результат роботи функції

({:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "DOE SANDY", :total 132},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D0220", :amount 14, :date "20140324"}
 {:code "AD:D0230", :amount 10, :date "20140324"}
 {:code "AD:D0274", :amount 34, :date "20140324"}
 {:code "AD:D1110", :amount 49, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "DOE SALLY", :total 74},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D1110", :amount 49, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "SMITH SALLY", :total 108},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D0220", :amount 0, :date "20140324"}
 {:code "AD:D0230", :amount 0, :date "20140324"}
 {:code "AD:D0274", :amount 34, :date "20140324"}
 {:code "AD:D1110", :amount 49, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "SMITH SAM", :total 14},
:services
 [{:code "AD:D0220", :amount 14, :date "20140324"}
 {:code "AD:D2790", :amount 0, :date "20140324"}
 {:code "AD:D2950", :amount 0, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "JONES SAM", :total 16.8},
:services
 [{:code "AD:D4342", :amount 0, :date "20140313"}
 {:code "AD:D4381", :amount 0, :date "20140313"}
 {:code "AD:D2950", :amount 16.8, :date "20140313"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "JONES SALLY", :total 132},
:services
 [{:code "AD:D0120", :amount 25, :date "20140321"}
 {:code "AD:D0220", :amount 14, :date "20140321"}
 {:code "AD:D0230", :amount 10, :date "20140321"}
 {:code "AD:D0274", :amount 34, :date "20140321"}
 {:code "AD:D1110", :amount 49, :date "20140321"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "DOE SAM", :total 108},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D0274", :amount 34, :date "20140324"}
 {:code "AD:D1110", :amount 49, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "DOE SUE", :total 82},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D1120", :amount 37, :date "20140324"}
 {:code "AD:D1208", :amount 20, :date "20140324"}]}
 {:message {:received "140305", :created "20140301"},
 :transaction {:check "12345", :payed "20140331", :total 810.8},
 :insurer "DENTAL OF ABC",
 :organization "BAN DDS LLC",
 :claim {:patient "DOE DONNA", :total 144},
:services
 [{:code "AD:D0120", :amount 25, :date "20140324"}
 {:code "AD:D0330", :amount 62, :date "20140324"}
 {:code "AD:D1120", :amount 37, :date "20140324"}
 {:code "AD:D1208", :amount 20, :date "20140324"}]})

— можна подавати до столу писати в базу, візуалізувати на UI або використовувати як-небудь ще

Подібний код та алгоритм парсинга X12 документів використовується у моєму робочому проекті — зрозуміло, з купою додаткової функціональності. Приклади коду в статті — це мінімальний робочий прототип для демонстрації алгоритму і підходу. Сорі, що без абстрактних фабрик фабрик, комбінаторних парсерів, рекурсивних граматик та інших серйозних речей — усього 3 десятка рядків коду )

Бажаючі можуть погратися з даними парсером в будь-якому онлайн-репле, підтримує Clojure — ideone/replit/etc. Із залежностей потрібно підключити лише нэймспейс clojure.string, ну і можливо clojure.pprint для красивої друку результатів. Можна спробувати змінювати код тестової функції створення об’єкта, отримувати інші поля з распарсенной структури і т. п. Приклади X12 документів типу 835 (claim response) можна знайти в мережі.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *