AHK: Remove Duplicates While Parsing Text Into Array - arrays

I work at a doctors office doing the billing, and I've been writing code to streamline the billing process. I must include all diagnoses in the billing software, so I copy the whole chart, and parse it into an array by newlines looking for the prefix ICD-10, and if two codes are on the same line, it separates those too (via a comma). Before that, it removes a part (if it exists) of the chart that includes canceled procedures so the canceled procedures aren't charged. Sometimes, multiple of the same diagnosis is included in the chart for the purpose of ordering a procedure (it's automatic), and I need to only add each diagnosis to the array once.
[...]
SendInput, ^a
ClipBoard :=
SendInput, ^c
ClipWait
BlockInput, MouseMoveOff
lString := ClipBoard
Sleep, 200
IfInString, lString, Canceled Orders
{
lStringLeft := SubStr(lString, 1, InStr(lString, "Canceled Orders")-1)
Sleep, 20
lStringRight := SubStr(lString, InStr(lString, "Allergies as of"))
Sleep, 20
lString :=
Sleep, 20
lString := lStringLeft
Sleep, 20
lString .= lStringRight
Sleep, 20
}
DxArr := []
numDx := 0
Loop, Parse, lString, `n
If InStr(A_LoopField, "ICD-10")
Loop, Parse, A_LoopField, `,
DxArr[++numDx] := Trim(SubStr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")
[...]
The ideal output for
Essential Hypertension
ICD-9-CM: 401.0
ICD-10-CM: I10
Essential Hypertension with Chronic Kidney Disease, Stage 3
ICD-9-CM: 585.3, 401.0
ICD-10-CM: N18.3, I10
is
I10 N18.3
I've been at this with several different solutions I found on the internet, but so far, they've just made a mess rather than actually fixing any problem. Any help is appreciated!

Use a hash to remove duplicates. Use your code as the key and a dummy for the value. I use "true" below as the dummy value. Duplicate records have the same key so they replace the previous key-value pair.
Output the hash's keys after you are done parsing the input.
DxHash := {}
Loop, Parse, lString, `n
If InStr(A_LoopField, "ICD-10")
Loop, Parse, A_LoopField, `,
DxHash[Trim(SubStr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")] := true
for diagnosis,dummy in DxHash
send %diagnosis%{SPACE}

Related

COBOL85: How to find number of rows in an array dynamically

In my program I keep filling the following array with data obtained from a database table then inspect it to find certain words:
01 PRODUCTS-TABLE.
03 PRODUCT-LINE PIC X(40) OCCURS 50 TIMES.
sometimes it occurs 6 times, sometimes more than 6 times.
I'd like to find the number of lines in the array every time I write data to it , how can I do that ?
I tried this but it based on a fixed length:
INSPECT-PROCESS.
MOVE 0 TO TALLY-1.
INSPECT PRODUCTS-TABLE TALLYING TALLY-1 FOR ALL "PRODUCT"
IF TALLY-1 > 0
MOVE SER-NUMBER TO HITS-SN-OUTPUT
MOVE FILLER-SYM TO FILLER-O
MOVE PRODUCT-LINE(1) TO HITS-PR-OUTPUT
WRITE HITS-REC
PERFORM WRITE-REPORT VARYING CNT1 FROM 2 BY 1 UNTIL CNT1 = 11.
WRITE-REPORT.
MOVE " " TO HITS-SN-OUTPUT
MOVE PRODUCT-LINE(CNT1) TO HITS-TX-OUTPUT
WRITE HITS-REC.
In the first output line it writes the SN and the first product-line then in the following lines it writes all remaining product-line and blank out SN.
Something like:
12345678 first product-line
Second product-line
etc
It’s working, however, it only stops when CNT1 is 11, how can I feed the procedure with a variable CNT1 based on how many lines are actually in PRODUCTS-TABLE each time?
I solved the problem by adding an array line counter (LINE-COUNTER-1) to count (ADD 1 TO LINE-COUNTER-1) how many times I add data to the array and stop writing the report when "WRITE-COUNTER = LINE-COUNTER-1"
INSPECT-PROCESS.
MOVE 0 TO TALLY-1
INSPECT PRODUCTS-TABLE TALLYING TALLY-1 FOR ALL "PRODUCT"
IF TALLY-1 > 0
MOVE HOLD-SER-NUM TO HITS-SN-OUTPUT
MOVE FILLER-SYM TO FILLER-O
MOVE PRODUCT-LINE(1) TO HITS-PR-OUTPUT
WRITE HITS-REC
PERFORM WRITE-REPORT VARYING WRITE-COUNTER FROM 2 BY 1
UNTIL WRITE-COUNTER = LINE-COUNTER-1.

Autohotkey variable expressions for pseudo-arrays - Convert String to Number?

This question was answered at the bottom of this post.
I have looked at 6 different web pages from the AHK forums asking this question, and another one on SO here:
String to Number using autohotkey
...but none of them are working for me. I'm just trying to subtract a number from a string that has been grabbed from the StringSplit function. This is my code:
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice := %prices32% -= 0.10
MsgBox, Your SLPrice is %SLPrice%.
I receive the error "The following variable name contains an illegal character" on the line "SLPrice := %prices32% -= 0.10", so then I try:
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice = %prices32% - 0.10
MsgBox, Your SLPrice is %SLPrice%.
...to which I get the output:
Your SLPrice is 7.450 - 0.10
So it just displays the formula as a text string, it doesn't actually do the calculation.
Thoughts? Thanks!
UPDATE
To continue working out this solution, here is my full code up to the part I'm having an issue with, along with screenshots of what's happening:
; Get the latest window text to parse values from it
WinGetText, Window1Text, ahk_class WindowsForms10.Window.8.app.0.f96fc5_r9_ad1
MsgBox, The text is: %Window1Text% ; Displays the window get text values
Sleep, 5
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice := prices32 - 0.10
MsgBox, Your SLPrice is %SLPrice%.
ANSWER
Thanks to the contributor below, we discovered that there was a "." from the first MsgBox messing up the SLPrice variable, so we updated the SLPrice variable to read:
SLPrice := SubStr(Trim(prices32), 1, 5) - 0.10 ; to pull the left 5 characters
Thanks!
You are on the right track. But, per my comment, note := implies expressions including variable expressions (hence no surrounding %'s):
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
; Note, the 32 line also includes non printing characters
; so must be trimmed and then we take the left 5 characters
SLPrice := SubStr(Trim(prices32), 1, 5) - 0.10
MsgBox, Your SLPrice is %SLPrice%.
Should do it . . .
And note, using something := %myvariable% implies reading the contents of a variable named myvariable and using those contents as the variable name. So if myvariable is "test", you are really saying something := test (where something ends up being equal to the contents of the test variable).
Hth,
EDIT per below, here is a working example (BUT PER LATTER COMMENT, SEE BELOW, TOO):
Window1Text =
(
25
26
27
28
)
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices2%. ; using only 2nd line (26)
; Assign Stop Loss variable
SLPrice := prices2 - 0.10 ; again, changed to 2nd line
MsgBox, Your SLPrice is %SLPrice%. ; 25.900000
clipboard := SLPrice
HTH,
FURTHER EDIT: Because this is really cool and illustrates the several concepts as to how they relate to pseudo array variable expressions:
Window1Text =
(
25
26
27
28
)
; Assign entry price variable.
StringSplit, prices, Window1Text, `n ; (prices0 is the number of entries)
InputBox, num,, % "Pick an array number from 1 to " prices0 ; get the array number
; note the variable expression includes the num variable contents
MsgBox, % "Your entry price is " Trim(prices%num%) "." ; depends on array number
; Assign Stop Loss variable
SLPrice := Trim(prices%num%) - 0.10 ; uses the array number value
MsgBox, Your SLPrice is %SLPrice%. ; so depends on the array number
clipboard := SLPrice
Right?
But note, these testers work easily. The real life example from the OP is copied text and the line 32 contains non-printing characters dealt with by Trim(x) and taking only the first few characters from Left with SubStr(x,1,5).

AHK: Copying a block of text into an array, modifying, and transferring to another array

Alright so the question is hard to word. I've googled this several times, but usually it turns out I'm not googling the right phrases, and the answer is readily available. I do work as a medical biller for a doctors office, and I have to include diagnosis codes for the office visits. In the Electronic Medical Record program, there's a diagnosis list. The first line is the doctors description, I don't care about that. The second line is an ICD-9 code. Those are old, I don't care about those either. The third line (every third line) contains the ICD-10 code. That's what I need. What I'd like to be able to do is grab the whole list, dump it into an array delimited by new lines, and get rid of every element that doesn't contain a specific string. Then, dump all the kept elements into another array (or the same one, but not separated by 3 like they would be after the removals) and remove that prefixed string that I kept elements based on. After that, I need to click a specific spot (I know how to do this), add exactly four of the arrays elements as text (can't figure this out), hit enter, and keep adding and hitting enter until I've entered all of the array. I will post what I've tried cobble together from google searches if anyone wants to see that mess. But a general explanation on how to do this would also be appreciated. Thanks.
first of all, the stuff I'd be copying would look like this (actual example)
Lumbar stenosis - Primary
ICD-9-CM: 724.02
ICD-10-CM: M48.06
Spondylolisthesis of lumbar region
ICD-9-CM: 738.4
ICD-10-CM: M43.16
Lumbar degenerative disc disease
ICD-9-CM: 722.52
ICD-10-CM: M51.36
Chronic bilateral low back pain with bilateral sciatica
ICD-9-CM: 724.2, 724.3, 338.29
ICD-10-CM: M54.42, M54.41, G89.29
Naturally the list would be much longer.
The string I'd look for to keep the lines would be "ICD-10-CM: ", just so you guys know. I DID try using it as a delimiter, in quotes, but got some really quite weird results. It would have made this problem slightly easier to solve had that worked as the delimiter.
Arrays:={}
RealArray:={}
^j::
sendinput, ^c
sleep 20
bigone:=ClipBoard
sleep 2000
;StringReplace, bigone, newbigone, `n, "DLMTR", All
;Arrays:= StrSplit(newbigone, "DLMTR")
StringSplit, Arrays, bigone, `n
k=4
j=1
loop
{
if (k<Arrays.Max_Index)
{
RealArray%j%=Arrays%k%
j++
k++
k++
k++
}
else
return
}
return
^L::
a=0
loop
{
if (a<RealArray.Max_Index)
{
send RealArray%a%
a++
sendinput, {Space}
if(mod(a,5)==0)
sendinput, {enter}
}
else
return
}
Program
^j collects codes containing "ICD-10", ^k pastes the codes formatted 5 per line
^j::copyit()
^l::pasteit()
copyit()
{
sendinput, ^c
sleep 20
bigone := ClipBoard
sleep 100
global matches
matches := []
numMatches := 0
Loop parse, bigone, `n
Loop parse, A_LoopField, `,
if InStr(A_LoopField, "ICD-10")
matches[++numMatches] := trim( substr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")
}
pasteit()
{
global matches
for index, element in matches
{
Send %element%{SPACE}
if mod(index,5) == 0
Send {ENTER}
}
}
Input:
Recurrent major depressive disorder, in partial remission
ICD-9-CM: 296.35
ICD-10-CM: F33.1
ICD-10-CM: F33.2
ICD-9-CM: 296.35
ICD-10-CM: F33.3
ICD-10-CM: F33.4
ICD-9-CM: 296.35
ICD-10-CM: F33.5, ICD-10-CM: X432.6, ICD-10-CM: Y232.6
ICD-10-CM: F33.6
ICD-9-CM: 296.35
Output:
F33.1 F33.2 F33.3 F33.4 F33.5
X432.6 Y232.6 F33.6
Without knowing how the underlying program that you are automating works, I can't tell you when to sleep or send extra ENTERs.
Maybe you can query the state of the screen to determine what to do next (e.g., send codes, extra ENTERs, wait).
I identify the screen state by searching for a small image that uniquely identifies the state the program is in. I make the images using Alt+PrintScrn to capture the entire screen and then use pbrush.exe to crop out small a unique identifying image.
; Search screen for image stored in "images/name.png"
; return true if found, false otherwise
find( name )
{
fname := "images\" . name . ".png"
ImageSearch x, y, 0,0,A_ScreenWidth, A_ScreenHeight, *32 %fname%
return ( ErrorLevel = 0 and x >= 0 and y >= 0 )
}
; Wait for image stored in "images/name.png" to appear on the screen
wait_for( name )
{
tooltip Waiting for %name%, 100,0,1
while !find(name)
sleep 100
}
; business/domain logic bot
automate_screen()
{
if ( find( "logon" ))
do_stuff_to_logon()
else if ( find( "payroll_history" ))
do_some_other_stuff()
else if ( find( "payroll_screen1" ))
{
sendplay Type this on screen1{enter}01{enter}
wait_for( "payroll_screen2" )
sendplay Type this on screen2{enter}
}
}
main()
{
loop
{
automate_screen()
sleep 250
}
}

AHK: Comparing Two Txt Files

So I'm doing billing for a doctors office, and we like to triple check that the flu shots we give are actually billed out. Right now, my AHK checks their entire chart for keys like the ICD-10 Code Z23, then checks for "Influenza". If it finds both of these, it adds the patients Medical Record Number to the age appropriate text file, either 65+ or Under 65. There are also paper sheets with 10 slots we record the MRNs by hand on, to double check. I want to create a script that will compare three files, a file of mixed MRNs (both 65+ and <65), and if the MRN is found in the mixed file, it should remove that line. (I've done a bunch of googling on the subject, but everyone is recommending using a library, something that would only slow down my program.) If the line isn't found, it should be left in the master file for my review. I cannot figure out how to remove lines, let alone a specific line. So far, to check the files against each other, I have this. In lieu of removing lines, I'm appending the unbilled numbers to a fourth text file, but ultimately, removing would be better.
FILENUM = 1
FILENU = 1
^L::
Loop
{
FileReadLine, MRN, E:\AHK & AHKS\flushot.txt, %LINENUM%
If ErrorLevel
{
break
}
Else
{
Loop
{
FileReadLine, MRNc, E:\AHK & AHKS\Billed65+Flushots.txt, LINENU
StringTrimRight, MRNc, 11
If (MRN != MRNc)
{
LINENU++
Match = 0
}
If (MRN == MRNc)
Match = 1
If ErrorLevel and Match = 0
{
FileAppend, MRN, E:\AHK & AHKS\Unbilledtester.txt
LINENU = 1
}
If (Match = 1)
GoTo Lab2
}
Lab2:
Loop
{
FileReadLine, MRNc, E:\AHK & AHKS\BilledAdultFlushots.txt, LINENU
StringTrimRight, MRNc, 11
If (MRN != MRNc)
{
LINENU++
Match = 0
}
If (MRN == MRNc)
Match = 1
If ErrorLevel and Match = 0
FileAppend, MRN, E:\AHK & AHKS\Unbilledtester.txt
If (Match = 1)
GoTo Lab2
}
LINENUM++
}
}
UPDATE!!!!!!!
Alright, so I grabbed your code, and I've been trying to run a reverse check. You check the associative arrays of the Adult and Senior flushot text files for the MRNS in flushot.txt, then if not found they're added to unbilled. Below are several of my attempts to reverse that, and check for the MRNs in Adult and Senior in flushot.txt, and if not found, to add it to a file called notrecorded.txt so I know what people got flushots and the MA who didn't record it (by Primary Care Provider).
FlushotsBilled()
{
Root_Dir := "E:\AHK & AHKS"
fName_Input := "flushot.txt"
fName_Backup := "flushot_old.txt"
fName_Output := "unbilled.txt"
fName_Billed_Adult := "billedAdultFlushots.txt"
fName_Billed_Senior := "billed65+Flushots.txt"
fName_Billed_NR := "notrecorded.txt"
fName_Mixed := "mixrecord.txt"
SetWorkingDir %Root_Dir%
FileDelete, %fName_Output%
AdultFlu := {}
SeniorFlu := {}
GivenFluA := {}
GivenFluB := {}
Mixed := {}
Loop, Read, %fName_Mixed%
Mixed[A_LoopReadLine] := True
Loop, Read, %fName_Billed_Adult%
{
;Loop, Parse, A_LoopReadLine, `t
; AdultFlu[A_LoopField] := True
AdultFlu[A_LoopReadLine] := True
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
}
Loop, Read, %fName_Billed_Senior%
{
;Loop, Parse, A_LoopReadLine, `t
; SeniorFlu[A_LoopField] := True
SeniorFlu[A_LoopReadLine] := True
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
}
Loop, Read, %fName_Input% ;check flushot.txt for
If !AdultFlu[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))] && !SeniorFlu[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))]
;If !AdultFlu[A_LoopReadLine] && !SeniorFlu[A_LoopReadLine] ;flushot is checking the associated arrays
FileAppend, %A_LoopReadLine%`n, %fName_Output% ;if not found, stick it into unbilled.
Loop, Read, %fName_Input%
GivenFluA[A_LoopReadLine] := True
/* Loop, Read, %fName_Billed_Senior%
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
Loop, Read, %fName_Billed_Adult%
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
Loop, Read, %fName_Mixed%
Mixed[A_LoopReadLine] := True
*/
Loop, Read, %fName_Mixed%
If !GivenFluA[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))] ;a_Loopreadline is a line in mixed. it is looking through flushot.txt for that line, if it's there, it's givenflua's index
Loop, Read, %fName_Billed_NR%
If !GivenFluA[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n
/*
Loop, Read, %fName_Input% ;read flushot
If Mixed[A_LoopReadLine] Contains A_LoopReadLine ;check to see if each line in flushot is in mixed
GivenFluA[Mixed[A_LoopReadLine]] := True ;Mixed[A_LoopReadLine]
Loop, Read, %fName_Billed_NR%
If !GivenFluA[A_LoopReadLine]
FileAppend GivenFluA[%A_LoopReadLine%], %fName_Billed_NR%
*/
;if readline is in mixed, but not in flushot, check to see if it's in notrecorded, if not, append it
/*
Loop, Read, %fName_Mixed% ;open up the mixed record
IfNotInString, A_LoopReadLine, GivenFluA[A_LoopReadLine] ;see if inside each line, we can find the lines from flushot.txt, if not
;GivenFluB[GivenFluA[A_LoopReadLine]] := True ;if not we give the line from inside flushot to another associative array
GivenFluB[A_LoopReadLine] := True ;lets try this, put the value
Loop, Read, %fName_Mixed%
IfInString, A_LoopReadLine, GivenFluA[A_LoopReadLine]
GivenFluB[A_LoopReadLine]
Loop, Read, %fName_Billed_NR% ;open up not recorded
IfNotInString, A_LoopReadLine, GivenFluB[A_LoopReadLine] ;see if inside each line we can find that line from
FileAppend, %A_LoopReadLine%`n, %fName_Billed_NR%
*/
}
I think you were on the right track by creating an output file rather than deleting lines. Text files are usually treated as streams, not fixed-record-length data files. Deleting lines is error prone and not efficient; you'd have to compact the file somehow.
How big are your files? If there are only a few thousand patients, I would just load everything into associative arrays so we can use keyed lookups.
Program
^L:: medical_run()
medical_run()
{
root_dir := "E:\AHK & AHKS"
fname_input := "flushot.txt"
fname_backup := "flushot_old.txt"
fname_temp_output := "Unbilledtester.txt"
fname_billed_adult := "billedAdultFlushots.txt"
fname_billed_senior := "billed65+Flushots.txt"
SetWorkingDir %root_dir%
FileDelete %fname_output%
adults := {}
seniors := {}
unmatched := {}
loop READ, %fname_billed_adult%
adults[A_LoopReadLine] := true
loop READ, %fname_billed_senior%
seniors[A_LoopReadLine] := true
loop READ, %fname_input%
if !adults[A_LoopReadLine] && !seniors[A_LoopReadLine]
unmatched[A_LoopReadLine] := true
for code,dummy in unmatched
FileAppend %code%`n, %fname_output%
FileMove %fname_input%, %fname_backup%
FileMove %fname_temp_output%, %fname_input%
}
billedAdultFlushots.txt
101
102
103
104
billed65+Flushots.txt
205
206
207
208
flushot.txt BEFORE
301
101
103
302
205
301
207
103
303
301
flushot.txt AFTER
301
302
303

AutoHotkey's Loop (read file contents) issues related to "+" symbol

Referring to Loop (read file contents), a quite strange thing happens every time I use a code like this one to run a script:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
Send, %temp%
Send, +{Enter}
}
}
Return
MyFile.txt is a simple text file where sometimes the "plus" symbol (+) is used together with normal letters and numbers.
Despite of this, however, what I see if I run the hotkey on an empty text file, either a Notepad or Microsoft Word blank sheet, is that every + is replaced by an underscore (_), an exclamation mark (!) or a question mark (?). I've seen an occurrence with a dollar symbol ($) replacement, too.
I tried to debug it printing on screen a message box with
MsgBox, %temp%
before sending text and it shows the original content of MyFile.txt perfectly.
Thus the issue should be on Send rather than on file reading.
The content of my file is something like this (repeated for about 20 rows more):
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
120001267381 ~ TEXT 0 10/20/18 VARIABLE word text -> numbers: 17,000 x 108.99 | 109.26 x 15,000 /// number = +5.500% some text
+---------------------------------
What can be the cause of this?
Found the answer: due to the fact that + symbols read from my file are sent like pressing the Shift key, the output is amended by the pressing of such a key instead of sending the original symbol present in file.
In order to send the original content of my file without triggering special hotkeys, I have to use SendRaw instead of Send, like in this example:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
SendRaw, %temp%
Send, +{Enter}
}
}
Return
Here's an updated version that pastes using CTRL-V instead of Send to "retype" rows of data:
^+k::
{
Gosub, MySub
}
Return
MySub:
{
Send, +{Enter}
Loop, read, C:\MyFile.txt
{
temp = %A_LoopReadLine%
Clipboard = %temp% ; Write to clipboard
Send, ^v+{enter} ; Paste from clipboard
Sleep 10
; Short delay so it doesn't try to paste again before the clipboard has changed
; This check can get a lot more complex, but just increase it if 10 doesn't work
}
}
Return

Resources