ACPT28PC ;IHS/VEN/TOAD - ACPT*2.08*1 postinit step 4.1 ; 04/21/2008 11:32
;;2.08;CPT FILES;**1**;DEC 17, 2007
;
; This subroutine of the post-init for ACPT*2.08*1 loads the HCPCS 2008
; codes from the American Medical Association's text file acpt2008.01h
; and installs them in the RPMS CPT file (81). It does not do all of the
; actions needed to install those codes, only the initial cleanup of the
; file and raw loading of the codes. See routine ACPT28PA for the
; complete algorithm and overview.
;
QUIT ; This routine should not be called at the top or anywhere
; else. It is only to be called at LOADCODE by ACPT28PA as part of the
; post-init for ACPT*2.08*1.
;
; 2008 04 16 Rick Marshall created this routine because ACPTPOST (now
; ACPT28PA) expanded beyond the 10,000 character routine portability
; limit when it was refactored for ACPT*2.08*1. All of the code in this
; routine was previously in ACPT28PA. In addition to the refactoring,
; this patch fixes two bugs: 1) undeleted codes are properly
; reactivated, and (2) when the AMA shortened its long description, old
; nodes left over from the previous, longer description were left; this
; routine now kills the description before setting the new nodes.
;
; 2008 04 17-20 Rick Marshall did invasive surgery on the algorithm,
; which was full of so many holes I cannot even summarize them
; effectively here. In short, the code had been patched so many times
; the logic was frayed and failed to handle any but the most typical
; cases. Just about every unexpected case resulted in incorrect
; behavior. A corresponding change was made to ACTIV8^ACPT28PE (used to
; be ACT^ACPTSINF). Fixed bug in nonportable handling of Description
; field.
;
; The original code in ACPTPOST upon which this was based was written by
; IHS/ASDST/DMJ and Shonda Render (SDR).
;
;
LOADCODE ; load HCPCS codes from AMA HCPCS Description file
;
; private: called only by step 4 of routine ACPT28PA
;
; input: ACPTPTH = path to directory where file is stored
;
N POP D Q:POP
. D OPEN^%ZISH("CPTHFILE",ACPTPTH,"acpt2008.01h","R") ; open read-only
. U IO(0) ; use terminal
. I POP D MES^XPDUTL($$T("MSG+1")) ; Could not open HCPCS file.
. E D MES^XPDUTL($$T("MSG+2")) ; Reading HCPCS Codes file.
;
N ACPTCNT ; count entries to print a dot for every 100
F ACPTCNT=1:1 D Q:$$STATUS^%ZISH ; loop until end of file
. ;
. N ACPTLINE ; each line extracted from the file
. U IO R ACPTLINE Q:$$STATUS^%ZISH
. ;
. N ACPTCODE ; HCPCS code
. S ACPTCODE=$E(ACPTLINE,1,5) ; first 5 chars contain the HCPCS code
. Q:ACPTCODE'?1U4N ; skip any non-HCPCS codes found (should be none)
. ;
. N ACPTACT ; action code, if any
. S ACPTACT=$E(ACPTLINE,6) ; action code
. ;
. N ACPTIEN ; IEN of entry in CPT file
. S ACPTIEN=$O(^ICPT("B",ACPTCODE,0)) ; find the code's record number
. I '$D(^ICPT("B",ACPTCODE)) D ; if there isn't one, create it
. . S ACPTIEN=$A($E(ACPTCODE))_$E(ACPTCODE,2,5) ; CPT range three
. . S ^ICPT(ACPTIEN,0)=ACPTCODE ; CPT Code field (.01)
. . S ^ICPT("B",ACPTCODE,ACPTIEN)="" ; index of CPT Codes
. . S $P(^ICPT(ACPTIEN,0),U,6)=ACPTYR ; Date Added (7) to 3080000
. ;
. N ACPTNODE S ACPTNODE=$G(^ICPT(ACPTIEN,0)) ; get record's header node
. ;
. ; Q:ACPTACT="" ; no action code (unchanged code, so do nothing)
. ; TOAD: I am not convinced the action codes are always correct.
. ; If a code has no action code but has actually been changed, this
. ; line will prevent the change from being applied. Someone should
. ; write some code to check out the validity of these action codes.
. ; This version of the patch will be conservative and not trust the
. ; data at the sites to be completely in synch with AMA; this patch
. ; will run more slowly, but subsequent HCPCS updates may then
. ; reinstate this line for improved efficiency knowing the data's in
. ; synch to begin with.
. ;
. N ACPTSHRT ; Short Name (2)
. S ACPTSHRT=$$CLEAN($E(ACPTLINE,7,41),1) ; clean up the Short Name
. I ACPTSHRT'="" S $P(ACPTNODE,U,2)=ACPTSHRT ; update it
. ;
. I ACPTACT="A" D ; handle new codes specially:
. . S $P(ACPTNODE,U,4)=1 ; Inactive Flag (5) is true till step 6
. . S $P(ACPTNODE,U,6)=ACPTYR ; use special Date Added (7) flag
. E D ; for all other codes:
. . S $P(ACPTNODE,U,4)="" ; Inactive Flag is cleared
. . I $P(ACPTNODE,U,6)="" S $P(ACPTNODE,U,6)=3080101 ; set Date Added
. ;
. S $P(ACPTNODE,U,7)="" ; clear Date Deleted field (8)
. ;
. S ^ICPT(ACPTIEN,0)=ACPTNODE ; update header node
. ;
. N ACPTDESC ; Description field (50)
. S ACPTDESC=$$CLEAN($E(ACPTLINE,42,1097)) ; clean up the Description
. D TEXT(.ACPTDESC) ; convert string to WP array
. K ^ICPT(ACPTIEN,"D") ; clean out old Description (50)
. M ^ICPT(ACPTIEN,"D")=ACPTDESC ; copy array to field, incl. header
. ;
. N ACPTEDT ; last Effective Date
. S ACPTEDT=$O(^ICPT(ACPTIEN,60,"B",9999999),-1) ; find the last
. N ACPTEIEN S ACPTEIEN=$O(^ICPT(ACPTIEN,60,"B",+ACPTEDT,0)) ; its IEN
. ;
. I ACPTEDT=3080101,ACPTEIEN D ; if there is one for this install date
. . Q:$P($G(^ICPT(ACPTIEN,60,ACPTEIEN,0)),U,2) ; if active, we're fine
. . ; otherwise, we need to activate it:
. . N DA S DA=+ACPTEIEN ; IEN of last Effective Date
. . S DA(1)=ACPTIEN ; IEN of its parent CPT
. . N DIE S DIE="^ICPT("_DA(1)_",60," ; Effective Date (60/81.02)
. . N DR S DR=".02////1" ; set Status field to ACTIVE
. . N DIDEL,DTOUT ; other parameters for DIE
. . D ^DIE ; Fileman Data Edit call
. ;
. E D ; if not, then we need one
. . N DA S DA(1)=ACPTIEN ; into subfile under new entry
. . N DIC S DIC="^ICPT("_DA(1)_",60," ; Effective Date (60/81.02)
. . S DIC(0)="L" ; LAYGO
. . S DIC("P")=$P(^DD(81,60,0),U,2) ; subfile # & specifier codes
. . N X S X="01/01/2008" ; new entry for 1/1/2008
. . S DIC("DR")=".02////1" ; with Status = 1 (active)
. . N DLAYGO,Y,DTOUT,DUOUT ; other parameters
. . D ^DIC ; Fileman LAYGO lookup
. ;
. U IO(0) W:'(ACPTCNT#100) "."
;
D ^%ZISC ; close the file
;
QUIT ; end of LOADCODE
;
;
T(TAG) QUIT $P($T(@TAG),";;",2)
;
;
MSG ; messages to display
;;Could not open HCPCS file.
;;Reading HCPCS Codes file.
;
;
CLEAN(ACPTDESC,ACPTUP) ; clean up description field
;
; private, called only by LOADCODE above
;
; 1) strip out control characters
I ACPTDESC?.E1C.E D CLEAN^ACPT28P1(.ACPTDESC)
;
; 2) trim extra spaces
N ACPTCLN S ACPTCLN=""
N ACPTPIEC F ACPTPIEC=1:1:$L(ACPTDESC," ") D ; traverse words
. N ACPTWORD S ACPTWORD=$P(ACPTDESC," ",ACPTPIEC) ; grab each word
. Q:ACPTWORD="" ; skip empty words (multiple spaces together)
. S ACPTCLN=ACPTCLN_" "_ACPTWORD ; reassemble words with 1 space between
S $E(ACPTCLN)="" ; remove extraneous leading space
;
; 3) optionally, convert to upper case
I $G(ACPTUP) S ACPTDESC=$$UP^XLFSTR(ACPTCLN)
;
QUIT ACPTCLN ; end of CLEAN
;
;
TEXT(ACPTDESC) ; convert Description text to Word-Processing data type
;
; private, called only by LOADCODE above
;
; input: .ACPTDESC = passed by reference, starts out as long string,
; ends as Fileman WP-format array complete with header
;
N ACPTSTRN S ACPTSTRN=ACPTDESC ; copy string out
K ACPTDESC ; clear what will now become a WP array
N ACPTCNT S ACPTCNT=0 ; count WP lines for header
;
F Q:ACPTSTRN="" D ; loop until ACPTSTRN is fully transformed
. ;
. N ACPTBRK S ACPTBRK=0 ; character position to break at
. ;
. D ; find the character position to break at
. . N ACPTRY ; break position to try
. . S ACPTRY=$L(ACPTSTRN) ; how long is the string?
. . I ACPTRY<81 S ACPTBRK=ACPTRY Q ; if 1 full line or less, we're done
. . ;
. . F ACPTRY=80:-1:2 D Q:ACPTBRK
. . . I $E(ACPTSTRN,ACPTRY+1)=" " D Q ; can break on a space
. . . . S $E(ACPTSTRN,ACPTRY+1)="" ; remove the space
. . . . S ACPTBRK=ACPTRY ; and let's break here
. . . ;
. . . I "&_+-*/<=>}])|:;,.?!"[$E(ACPTSTRN,ACPTRY) D Q ; on delimiter?
. . . . S ACPTBRK=ACPTRY ; so let's break here
. . ;
. . Q:ACPTBRK ; if we found a good spot to break, we're done
. . ;
. . S ACPTBRK=80 ; otherwise, hard-break on 80 (weird content)
. ;
. S ACPTCNT=ACPTCNT+1 ; one more line
. S ACPTDESC(ACPTCNT,0)=$E(ACPTSTRN,1,ACPTBRK) ; copy line into array
. S $E(ACPTSTRN,1,ACPTBRK)="" ; & remove it from the string
;
S ACPTDESC(0)="^81.01A^"_ACPTCNT_U_ACPTCNT_U_DT ; set WP header
;
QUIT ; end of TEXT
;
;
; end of routine ACPT28PC
ACPT28PC ;IHS/VEN/TOAD - ACPT*2.08*1 postinit step 4.1 ; 04/21/2008 11:32
+1 ;;2.08;CPT FILES;**1**;DEC 17, 2007
+2 ;
+3 ; This subroutine of the post-init for ACPT*2.08*1 loads the HCPCS 2008
+4 ; codes from the American Medical Association's text file acpt2008.01h
+5 ; and installs them in the RPMS CPT file (81). It does not do all of the
+6 ; actions needed to install those codes, only the initial cleanup of the
+7 ; file and raw loading of the codes. See routine ACPT28PA for the
+8 ; complete algorithm and overview.
+9 ;
+10 ; This routine should not be called at the top or anywhere
QUIT
+11 ; else. It is only to be called at LOADCODE by ACPT28PA as part of the
+12 ; post-init for ACPT*2.08*1.
+13 ;
+14 ; 2008 04 16 Rick Marshall created this routine because ACPTPOST (now
+15 ; ACPT28PA) expanded beyond the 10,000 character routine portability
+16 ; limit when it was refactored for ACPT*2.08*1. All of the code in this
+17 ; routine was previously in ACPT28PA. In addition to the refactoring,
+18 ; this patch fixes two bugs: 1) undeleted codes are properly
+19 ; reactivated, and (2) when the AMA shortened its long description, old
+20 ; nodes left over from the previous, longer description were left; this
+21 ; routine now kills the description before setting the new nodes.
+22 ;
+23 ; 2008 04 17-20 Rick Marshall did invasive surgery on the algorithm,
+24 ; which was full of so many holes I cannot even summarize them
+25 ; effectively here. In short, the code had been patched so many times
+26 ; the logic was frayed and failed to handle any but the most typical
+27 ; cases. Just about every unexpected case resulted in incorrect
+28 ; behavior. A corresponding change was made to ACTIV8^ACPT28PE (used to
+29 ; be ACT^ACPTSINF). Fixed bug in nonportable handling of Description
+30 ; field.
+31 ;
+32 ; The original code in ACPTPOST upon which this was based was written by
+33 ; IHS/ASDST/DMJ and Shonda Render (SDR).
+34 ;
+35 ;
LOADCODE ; load HCPCS codes from AMA HCPCS Description file
+1 ;
+2 ; private: called only by step 4 of routine ACPT28PA
+3 ;
+4 ; input: ACPTPTH = path to directory where file is stored
+5 ;
+6 NEW POP
Begin DoDot:1
+7 ; open read-only
DO OPEN^%ZISH("CPTHFILE",ACPTPTH,"acpt2008.01h","R")
+8 ; use terminal
USE IO(0)
+9 ; Could not open HCPCS file.
IF POP
DO MES^XPDUTL($$T("MSG+1"))
+10 ; Reading HCPCS Codes file.
IF '$TEST
DO MES^XPDUTL($$T("MSG+2"))
End DoDot:1
IF POP
QUIT
+11 ;
+12 ; count entries to print a dot for every 100
NEW ACPTCNT
+13 ; loop until end of file
FOR ACPTCNT=1:1
Begin DoDot:1
+14 ;
+15 ; each line extracted from the file
NEW ACPTLINE
+16 USE IO
READ ACPTLINE
IF $$STATUS^%ZISH
QUIT
+17 ;
+18 ; HCPCS code
NEW ACPTCODE
+19 ; first 5 chars contain the HCPCS code
SET ACPTCODE=$EXTRACT(ACPTLINE,1,5)
+20 ; skip any non-HCPCS codes found (should be none)
IF ACPTCODE'?1U4N
QUIT
+21 ;
+22 ; action code, if any
NEW ACPTACT
+23 ; action code
SET ACPTACT=$EXTRACT(ACPTLINE,6)
+24 ;
+25 ; IEN of entry in CPT file
NEW ACPTIEN
+26 ; find the code's record number
SET ACPTIEN=$ORDER(^ICPT("B",ACPTCODE,0))
+27 ; if there isn't one, create it
IF '$DATA(^ICPT("B",ACPTCODE))
Begin DoDot:2
+28 ; CPT range three
SET ACPTIEN=$ASCII($EXTRACT(ACPTCODE))_$EXTRACT(ACPTCODE,2,5)
+29 ; CPT Code field (.01)
SET ^ICPT(ACPTIEN,0)=ACPTCODE
+30 ; index of CPT Codes
SET ^ICPT("B",ACPTCODE,ACPTIEN)=""
+31 ; Date Added (7) to 3080000
SET $PIECE(^ICPT(ACPTIEN,0),U,6)=ACPTYR
End DoDot:2
+32 ;
+33 ; get record's header node
NEW ACPTNODE
SET ACPTNODE=$GET(^ICPT(ACPTIEN,0))
+34 ;
+35 ; Q:ACPTACT="" ; no action code (unchanged code, so do nothing)
+36 ; TOAD: I am not convinced the action codes are always correct.
+37 ; If a code has no action code but has actually been changed, this
+38 ; line will prevent the change from being applied. Someone should
+39 ; write some code to check out the validity of these action codes.
+40 ; This version of the patch will be conservative and not trust the
+41 ; data at the sites to be completely in synch with AMA; this patch
+42 ; will run more slowly, but subsequent HCPCS updates may then
+43 ; reinstate this line for improved efficiency knowing the data's in
+44 ; synch to begin with.
+45 ;
+46 ; Short Name (2)
NEW ACPTSHRT
+47 ; clean up the Short Name
SET ACPTSHRT=$$CLEAN($EXTRACT(ACPTLINE,7,41),1)
+48 ; update it
IF ACPTSHRT'=""
SET $PIECE(ACPTNODE,U,2)=ACPTSHRT
+49 ;
+50 ; handle new codes specially:
IF ACPTACT="A"
Begin DoDot:2
+51 ; Inactive Flag (5) is true till step 6
SET $PIECE(ACPTNODE,U,4)=1
+52 ; use special Date Added (7) flag
SET $PIECE(ACPTNODE,U,6)=ACPTYR
End DoDot:2
+53 ; for all other codes:
IF '$TEST
Begin DoDot:2
+54 ; Inactive Flag is cleared
SET $PIECE(ACPTNODE,U,4)=""
+55 ; set Date Added
IF $PIECE(ACPTNODE,U,6)=""
SET $PIECE(ACPTNODE,U,6)=3080101
End DoDot:2
+56 ;
+57 ; clear Date Deleted field (8)
SET $PIECE(ACPTNODE,U,7)=""
+58 ;
+59 ; update header node
SET ^ICPT(ACPTIEN,0)=ACPTNODE
+60 ;
+61 ; Description field (50)
NEW ACPTDESC
+62 ; clean up the Description
SET ACPTDESC=$$CLEAN($EXTRACT(ACPTLINE,42,1097))
+63 ; convert string to WP array
DO TEXT(.ACPTDESC)
+64 ; clean out old Description (50)
KILL ^ICPT(ACPTIEN,"D")
+65 ; copy array to field, incl. header
MERGE ^ICPT(ACPTIEN,"D")=ACPTDESC
+66 ;
+67 ; last Effective Date
NEW ACPTEDT
+68 ; find the last
SET ACPTEDT=$ORDER(^ICPT(ACPTIEN,60,"B",9999999),-1)
+69 ; its IEN
NEW ACPTEIEN
SET ACPTEIEN=$ORDER(^ICPT(ACPTIEN,60,"B",+ACPTEDT,0))
+70 ;
+71 ; if there is one for this install date
IF ACPTEDT=3080101
IF ACPTEIEN
Begin DoDot:2
+72 ; if active, we're fine
IF $PIECE($GET(^ICPT(ACPTIEN,60,ACPTEIEN,0)),U,2)
QUIT
+73 ; otherwise, we need to activate it:
+74 ; IEN of last Effective Date
NEW DA
SET DA=+ACPTEIEN
+75 ; IEN of its parent CPT
SET DA(1)=ACPTIEN
+76 ; Effective Date (60/81.02)
NEW DIE
SET DIE="^ICPT("_DA(1)_",60,"
+77 ; set Status field to ACTIVE
NEW DR
SET DR=".02////1"
+78 ; other parameters for DIE
NEW DIDEL,DTOUT
+79 ; Fileman Data Edit call
DO ^DIE
End DoDot:2
+80 ;
+81 ; if not, then we need one
IF '$TEST
Begin DoDot:2
+82 ; into subfile under new entry
NEW DA
SET DA(1)=ACPTIEN
+83 ; Effective Date (60/81.02)
NEW DIC
SET DIC="^ICPT("_DA(1)_",60,"
+84 ; LAYGO
SET DIC(0)="L"
+85 ; subfile # & specifier codes
SET DIC("P")=$PIECE(^DD(81,60,0),U,2)
+86 ; new entry for 1/1/2008
NEW X
SET X="01/01/2008"
+87 ; with Status = 1 (active)
SET DIC("DR")=".02////1"
+88 ; other parameters
NEW DLAYGO,Y,DTOUT,DUOUT
+89 ; Fileman LAYGO lookup
DO ^DIC
End DoDot:2
+90 ;
+91 USE IO(0)
IF '(ACPTCNT#100)
WRITE "."
End DoDot:1
IF $$STATUS^%ZISH
QUIT
+92 ;
+93 ; close the file
DO ^%ZISC
+94 ;
+95 ; end of LOADCODE
QUIT
+96 ;
+97 ;
T(TAG) QUIT $PIECE($TEXT(@TAG),";;",2)
+1 ;
+2 ;
MSG ; messages to display
+1 ;;Could not open HCPCS file.
+2 ;;Reading HCPCS Codes file.
+3 ;
+4 ;
CLEAN(ACPTDESC,ACPTUP) ; clean up description field
+1 ;
+2 ; private, called only by LOADCODE above
+3 ;
+4 ; 1) strip out control characters
+5 IF ACPTDESC?.E1C.E
DO CLEAN^ACPT28P1(.ACPTDESC)
+6 ;
+7 ; 2) trim extra spaces
+8 NEW ACPTCLN
SET ACPTCLN=""
+9 ; traverse words
NEW ACPTPIEC
FOR ACPTPIEC=1:1:$LENGTH(ACPTDESC," ")
Begin DoDot:1
+10 ; grab each word
NEW ACPTWORD
SET ACPTWORD=$PIECE(ACPTDESC," ",ACPTPIEC)
+11 ; skip empty words (multiple spaces together)
IF ACPTWORD=""
QUIT
+12 ; reassemble words with 1 space between
SET ACPTCLN=ACPTCLN_" "_ACPTWORD
End DoDot:1
+13 ; remove extraneous leading space
SET $EXTRACT(ACPTCLN)=""
+14 ;
+15 ; 3) optionally, convert to upper case
+16 IF $GET(ACPTUP)
SET ACPTDESC=$$UP^XLFSTR(ACPTCLN)
+17 ;
+18 ; end of CLEAN
QUIT ACPTCLN
+19 ;
+20 ;
TEXT(ACPTDESC) ; convert Description text to Word-Processing data type
+1 ;
+2 ; private, called only by LOADCODE above
+3 ;
+4 ; input: .ACPTDESC = passed by reference, starts out as long string,
+5 ; ends as Fileman WP-format array complete with header
+6 ;
+7 ; copy string out
NEW ACPTSTRN
SET ACPTSTRN=ACPTDESC
+8 ; clear what will now become a WP array
KILL ACPTDESC
+9 ; count WP lines for header
NEW ACPTCNT
SET ACPTCNT=0
+10 ;
+11 ; loop until ACPTSTRN is fully transformed
FOR
IF ACPTSTRN=""
QUIT
Begin DoDot:1
+12 ;
+13 ; character position to break at
NEW ACPTBRK
SET ACPTBRK=0
+14 ;
+15 ; find the character position to break at
Begin DoDot:2
+16 ; break position to try
NEW ACPTRY
+17 ; how long is the string?
SET ACPTRY=$LENGTH(ACPTSTRN)
+18 ; if 1 full line or less, we're done
IF ACPTRY<81
SET ACPTBRK=ACPTRY
QUIT
+19 ;
+20 FOR ACPTRY=80:-1:2
Begin DoDot:3
+21 ; can break on a space
IF $EXTRACT(ACPTSTRN,ACPTRY+1)=" "
Begin DoDot:4
+22 ; remove the space
SET $EXTRACT(ACPTSTRN,ACPTRY+1)=""
+23 ; and let's break here
SET ACPTBRK=ACPTRY
End DoDot:4
QUIT
+24 ;
+25 ; on delimiter?
IF "&_+-*/<=>}])|:;,.?!"[$EXTRACT(ACPTSTRN,ACPTRY)
Begin DoDot:4
+26 ; so let's break here
SET ACPTBRK=ACPTRY
End DoDot:4
QUIT
End DoDot:3
IF ACPTBRK
QUIT
+27 ;
+28 ; if we found a good spot to break, we're done
IF ACPTBRK
QUIT
+29 ;
+30 ; otherwise, hard-break on 80 (weird content)
SET ACPTBRK=80
End DoDot:2
+31 ;
+32 ; one more line
SET ACPTCNT=ACPTCNT+1
+33 ; copy line into array
SET ACPTDESC(ACPTCNT,0)=$EXTRACT(ACPTSTRN,1,ACPTBRK)
+34 ; & remove it from the string
SET $EXTRACT(ACPTSTRN,1,ACPTBRK)=""
End DoDot:1
+35 ;
+36 ; set WP header
SET ACPTDESC(0)="^81.01A^"_ACPTCNT_U_ACPTCNT_U_DT
+37 ;
+38 ; end of TEXT
QUIT
+39 ;
+40 ;
+41 ; end of routine ACPT28PC