sassoftware / saspy

A Python interface module to the SAS System. It works with Linux, Windows, and Mainframe SAS as well as with SAS in Viya.
https://sassoftware.github.io/saspy
Other
367 stars 149 forks source link

The SASResults object only returns 1 table when there should be multiple #527

Closed mrmorgan17 closed 1 year ago

mrmorgan17 commented 1 year ago

Describe the bug My setup:

<module 'saspy' from '/usr/local/lib/python3.10/site-packages/saspy/__init__.py'>
/usr/local/lib/python3.10/site-packages/saspy/sascfg_personal.py
['/usr/local/lib/python3.10/site-packages/saspy/sascfg_personal.py']

To Reproduce Here is the saspy code I ran

sasfit = stat.glm(
  data = 'snapbean',
  cls = 'S V',
  model = 'x1 x2 x3 x4 = S V S*V',
  manova = 'H = S V S*V / PRINTE PRINTH MSTAT=exact'
)

Here is the equivalent SAS code

proc glm data=snapbean;
  class S V;
  model x1 x2 x3 x4 = S V S*V;
  manova H = S V S*V / PRINTE PRINTH MSTAT=exact;
run;

According the the PROC GLM documentation, the MANOVA statement should produce a MULTSTAT and a CHARSTRUCT table for S, V, and S*V in the output and this happens when I run the procedure in base SAS

The saspy code runs just fine as well as I am able to reference the MULTSTAT and CHARSTRUCT table from my sasfit object in the following way: sasfit.MULTSTAT and sasfit.CHARSTRUCT.

The problem is that sasfit.MULTSTAT and sasfit.CHARSTRUCT only give me the MULTSTAT and CHARSTRUCT tables for the S variable. I am wondering how I could see the other 2 MULTSTAT and CHARSTRUCT tables that should've been created with my sasfit object.

SAS Screenshots MULTSTAT and CHARSTRUCT tables for S

Screenshot 2023-02-08 at 4 05 36 PM

MULTSTAT and CHARSTRUCT tables for V

Screenshot 2023-02-08 at 4 06 31 PM

MULTSTAT and CHARSTRUCT tables for S*V

Screenshot 2023-02-08 at 4 07 14 PM

saspy Screenshots MULTSTAT table for S

Screenshot 2023-02-08 at 4 08 43 PM

CHARSTRUCT table for S

Screenshot 2023-02-08 at 4 09 51 PM

Configuration information. Please provide the configuration you're trying to use (your sascfg_personal.py file) as well as what client system you are on and what kind of SAS deployment you're trying to connect to and where it's deployed (local to the client or remote). MacOS Ventura 13.2 Python 3.10.10 saspy 4.6.0

Connecting to a remote SAS On Demand for Academics Session

sascfg_personal:

SAS_config_names=['oda']
oda = {'java' : '/usr/bin/java',
#US Home Region 1
'iomhost' : ['odaws01-usw2.oda.sas.com','odaws02-usw2.oda.sas.com','odaws03-usw2.oda.sas.com','odaws04-usw2.oda.sas.com'],
#US Home Region 2
#'iomhost' : ['odaws01-usw2-2.oda.sas.com','odaws02-usw2-2.oda.sas.com'],
#European Home Region 1
#'iomhost' : ['odaws01-euw1.oda.sas.com','odaws02-euw1.oda.sas.com'],
#Asia Pacific Home Region 1
#'iomhost' : ['odaws01-apse1.oda.sas.com','odaws02-apse1.oda.sas.com'],
#Asia Pacific Home Region 2
#'iomhost' : ['odaws01-apse1-2.oda.sas.com','odaws02-apse1-2.oda.sas.com'],
'iomport' : 8591,
'authkey' : 'oda',
'encoding' : 'utf-8'
}

Additional context I probably just don't know how to reference the other MULTSTAT and CHARSTRUCT components of my sasfit object

mrmorgan17 commented 1 year ago

SNAPBEAN.csv

Here's the dataset

tomweber-sas commented 1 year ago

Hey, I'm on vacation till middle of next week. Can you issue the following after you run the glm method:

# sasfit = sas.glm ( ... )   your method call
print(sas.lastlog())
dir(sasfit)

See what code was actually generated and run, in the log; see if there were error or something and see what it did. Then see what objects are actually in the results (sasfit variable). I'm not really sure what the UI you're using is or if it renders things or anything really. So I can't tell much about if there's something wrong or not.

Did it generate the same code as what you expect? Without seeing what results it created and the log to see what really happened, I don't have anything to go on.

mrmorgan17 commented 1 year ago

print(sas.lastlog()):

62                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

976        ;*';*";*/;
977        data _null_; e = exist("'snapbean'n");
978        v = exist("'snapbean'n", 'VIEW');
979         if e or v then e = 1;
980        te='TABLE_EXISTS='; put te e;run;
TABLE_EXISTS= 1
981        
982        
983        ;*';*";*/;
984        

63                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

985        
986        %put E3969440A681A2408885998500000029;
E3969440A681A2408885998500000029
987        

64                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

988        ;*';*";*/;
989        data _null_; e = exist("user.'snapbean'n");
990        v = exist("user.'snapbean'n", 'VIEW');
991         if e or v then e = 1;
992        te='TABLE_EXISTS='; put te e;run;
TABLE_EXISTS= 0
993        
994        
995        ;*';*";*/;
996        
65                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

997        
998        %put E3969440A681A2408885998500000030;
E3969440A681A2408885998500000030
999        

66                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1000       ;*';*";*/;
1001       data _null_; e = exist("WORK.'snapbean'n");
1002       v = exist("WORK.'snapbean'n", 'VIEW');
1003        if e or v then e = 1;
1004       te='TABLE_EXISTS='; put te e;run;
TABLE_EXISTS= 1
1005       
1006       
1007       ;*';*";*/;
1008       

67                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1009       
1010       %put E3969440A681A2408885998500000031;
E3969440A681A2408885998500000031
1011       

68                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1012       ;*';*";*/;
1013       %macro proccall(d);
1014       proc glm data=WORK.'snapbean'n  plots=all  ;
1015       class S V;
1016       model x1 x2 x3 x4 = S V S*V;
1017       manova H = S V S*V / PRINTE PRINTH MSTAT=exact;
1018       run; quit; %mend;
1019       %mangobj(glm0004,glm,'snapbean'n);
1035       
1036       ;*';*";*/;
1037       

69                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1038       
1039       %put E3969440A681A2408885998500000032;
E3969440A681A2408885998500000032
1040       

70                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1041       ;*';*";*/;
1042       
1043               data _null_;
1044                  set glm0004._glm0004filelist(where=(length(method)>1)) end=last;
1045                  if _n_=1 then put "METHLIST=";
1046                  put %upcase("meth=") method %upcase("methEND=");
1047                  if  last then put "METHLISTEND=";
1048                  run;
METHLIST=
METH=CHARSTRUCT METHEND=
METH=CLASSLEVELS METHEND=
METH=DIAGNOSTICSPANEL METHEND=
METH=ERRORSSCP METHEND=
METH=FITSTATISTICS METHEND=
METH=HYPOTHESISSSCP METHEND=
METH=INTPLOT METHEND=
METH=MODELANOVA METHEND=
METH=MULTSTAT METHEND=
METH=NOBS METHEND=
METH=OVERALLANOVA METHEND=
METH=PARTIALCORR METHEND=
METH=GLM0004 METHEND=
METH=_GLM0004PROPERTIES METHEND=
METHLISTEND=
1049       
1050       
1051       ;*';*";*/;
1052       

71                                                         The SAS System                      Monday, February 13, 2023 09:01:00 PM

1053       
1054       %put E3969440A681A2408885998500000033;
E3969440A681A2408885998500000033
1055    

dir(sasfit):

['CHARSTRUCT', 'CLASSLEVELS', 'DIAGNOSTICSPANEL', 'ERRORSSCP', 'FITSTATISTICS', 'HYPOTHESISSSCP', 'INTPLOT', 'LOG', 'MODELANOVA', 'MULTSTAT', 'NOBS', 'OVERALLANOVA', 'PARTIALCORR']

I'm using RStudio for saspy.

Overall I don't think there are any errors. I just cannot access the objects that I want to. For example, in the screenshots I posted, the sasfit.CHARSTRSUCT is exactly what I expected. The problem is that in SAS, 3 CHARSTRUCT tables were output, one for each variable (S, V, S*V) and in saspy I cannot reference the other 2 CHARSTRUCT tables that should be in sasfit. I can only reference the first one which was with S. The CHARSTRUCT tables associated with V and S*V cannot be found.

tomweber-sas commented 1 year ago

Thanks @mrmorgan17 I just got back and I will look into this today and post back what I find. Thanks, Tom

tomweber-sas commented 1 year ago

I'm trying to reproduce what you have, since I have no idea what proc glm does or requires, so I want to use your exact case. The csv file you attached has no metadata; column names in the first row, so I can't correlate it with the statements in the proc; don't know what the column names need to be. Can you attach one with the metadata, or just post what the column names should be and I can add them manually?

Thanks, Tom

mrmorgan17 commented 1 year ago

The file snapbean.csv gives data from a two-way (fixed-effects MANOVA) on snap beans showing the results of four variables: $x_1$ = yield earliness, $x_2$ = specific leaf area (SLA) earliness, $x_3$ = total yield, and $x_4$ = average SLA. The factors are sowing date (S) and variety (V). The columns of the file are: sowing-date, variety, replicate, $x_1$, $x_2$, $x_3$, and $x_4$.

columns = ("S", "V", "replicate", "x1", "x2", "x3", "x4")

tomweber-sas commented 1 year ago

Awesome, thanks, I was able to import that data and reproduce what you're seeing. And, all of the output is created and there. The issue is that the way this was written, it doesn't take into account multiple output tables for these situations. The analytic methods generate the proc code and then use an ODS document to store the results, so that you can access them after the proc finishes. This is compared to just submitting the proc and having all of the output written to the LST as HTML5 output, which you could see (if it renders in your UI) but couldn't access as actual data after. The ODS document has these multiple tables in it, but in a hierarchy that the current implementation doesn't account for, thus you only can access the first one.

So, I'm going to look into enhancing this to be able access all of the output, but that's gonna take a little time, as this is the one part of SASPy I didn't write, and I've never worked with the ODS document, so I'll need to play around with it to understand what's the best way to support this. I believe I can get it working as expected, just need to research and prototype!

Thanks, Tom

tomweber-sas commented 1 year ago

I've enhanced the code to iterate over the ODS document to find duplicate table names, and rename each subsequent one with the variable it's tagged to. It adds some time to the processing, but it gets all of the results back and accessible. I pushed this to a new branch; multstat. I still need to do more validation and regression testing, and some performance testing too; see if I can optimize anything.

Can you pull that branch and try any/all of your tests to see if it works as expected for you? I'll continue to assess it as well.

here's the results for your case:

>>> sasfit = stat.glm(
...   data = 'snapbean',
...   cls = 'S V',
...   model = 'x1 x2 x3 x4 = S V S*V',
...   manova = 'H = S V S*V / PRINTE PRINTH MSTAT=exact'
... )
>>> for item in dir(sasfit):
...    print(item)
...
CHARSTRUCT
CHARSTRUCT_S_V
CHARSTRUCT_V
CLASSLEVELS
DIAGNOSTICSPANEL
DIAGNOSTICSPANEL_X2
DIAGNOSTICSPANEL_X3
DIAGNOSTICSPANEL_X4
ERRORSSCP
FITSTATISTICS
FITSTATISTICS_X2
FITSTATISTICS_X3
FITSTATISTICS_X4
HYPOTHESISSSCP
HYPOTHESISSSCP_S_V
HYPOTHESISSSCP_V
INTPLOT
INTPLOT_X2
INTPLOT_X3
INTPLOT_X4
LOG
MODELANOVA
MODELANOVA_X2
MODELANOVA_X3
MODELANOVA_X4
MULTSTAT
MULTSTAT_S_V
MULTSTAT_V
NOBS
OVERALLANOVA
OVERALLANOVA_X2
OVERALLANOVA_X3
OVERALLANOVA_X4
PARTIALCORR
>>> 

Thanks, Tom

tomweber-sas commented 1 year ago

I've tweaked this (not pushed yet) to rename the first occurrence so it all matches up more consistently. Finding some issues after doing that and it's a breaking change too; since the list above still have the same list as before, but with extras, while what I have now no longer had the original name in the list (all occurrences changed). I still like this better, but I hate breaking changes; so I'm still thinking on this. I also have some other issues to look into with this still, though I think I'll be able to address them too.

Here's the list with what I'm working with currently. What are your thoughts on the difference between the list above?

CHARSTRUCT_S
CHARSTRUCT_S_V
CHARSTRUCT_V
CLASSLEVELS
DIAGNOSTICSPANEL_X1
DIAGNOSTICSPANEL_X2
DIAGNOSTICSPANEL_X3
DIAGNOSTICSPANEL_X4
ERRORSSCP
FITSTATISTICS_X1
FITSTATISTICS_X2
FITSTATISTICS_X3
FITSTATISTICS_X4
HYPOTHESISSSCP_S
HYPOTHESISSSCP_S_V
HYPOTHESISSSCP_V
INTPLOT_X1
INTPLOT_X2
INTPLOT_X3
INTPLOT_X4
LOG
MODELANOVA_X1
MODELANOVA_X2
MODELANOVA_X3
MODELANOVA_X4
MULTSTAT_S
MULTSTAT_S_V
MULTSTAT_V
NOBS
OVERALLANOVA_X1
OVERALLANOVA_X2
OVERALLANOVA_X3
OVERALLANOVA_X4
PARTIALCORR
mrmorgan17 commented 1 year ago

I like that the list has included S and X1 where necessary in the table names. I'd prefer the second list to the first one.

tomweber-sas commented 1 year ago

Me too! it's consistent that way (I consider it's been wrong since day 1, so breaking change isn't so bad). I also optimized some of the code it it performs better now too (again, not pushed yet).

But I'm running into what I believe is a bug in the proc document code in viya 4. That's what I've been trying to figure out all night. The naming changed in viya 4 for the paths in the itemstore where all of the proc results are stored, compared to SAS 9. Now half of the ways I need to try and access this fail with syntax errors for the path in the itemstore. Some things work right, but not enough for my needs. So, that's a problem. I'll need to spend more time tomorrow trying to document it all and see if anyone knows what's changed. What version of SAS are you using?

mrmorgan17 commented 1 year ago

Whatever SAS OnDemand for Academics uses

tomweber-sas commented 1 year ago

Gotcha, so that's SAS 9.4 M7. But given the change in the ODS Document path syntax in viya 4, which broke my code (it shouldn't, and I could try to get someone to fix that but that wouldn't happen any time soon), I've had to completely rework how I am trying to fix this. Good news it that I have done that and I'm currently working through regressions.

I found another problematic issue in that the format of the paths in the ODS document, for each output, don't follow the same pattern across all procs. In your glm case, the paths in the item store had the last directory (that each same named member was in) being different (S, V, S_V, X1[2,3,4]) while in other procs, ets for instance, only the first level of the 3 or 4 level directory structure was different. So my original algorithm got different names for your case, but got the same duplicate names in the ets case. So, I modified it to deal with that issue too. I think it's pretty solid now and will return all of the results for each proc in the SASResults object now. Of course, this will be a breaking change, for cases where there were missing results - if there weren't, then the names remain the same. But I 'm finding there are plenty of cases were all of the results were never being returned.

Anyway, wanted to provide an update. I'm hoping to be able to push this code out to the multstat branch for you to try today, if I don't find any other problems.

Tom

tomweber-sas commented 1 year ago

Ok, I pushed this implementation to the multstat branch. It seem to be working for all cases I know of. It also performs much better then the first implementation, so that really good too!

Git it a try and see what you think!

BTW, the names for your glm case now have a number (1) on the new names, but that's part of the adjustment to handle the other cases where the only character in the whole path that varied was 1/2/3/..., so that's part of the suffix now. You can still identify each table/plot by name though, so it is finally correct (giving you all the results) and easy to identify what each one is.

Here's 2 example of results:

Your GLM proc:

CHARSTRUCT
CHARSTRUCT_S1
CHARSTRUCT_V1
CLASSLEVELS
DIAGNOSTICSPANEL_X11
DIAGNOSTICSPANEL_X21
DIAGNOSTICSPANEL_X31
DIAGNOSTICSPANEL_X41
ERRORSSCP
FITSTATISTICS_X11
FITSTATISTICS_X21
FITSTATISTICS_X31
FITSTATISTICS_X41
HYPOTHESISSSCP
HYPOTHESISSSCP_S1
HYPOTHESISSSCP_V1
INTPLOT_X11
INTPLOT_X21
INTPLOT_X31
INTPLOT_X41
LOG
MODELANOVA_X11
MODELANOVA_X21
MODELANOVA_X31
MODELANOVA_X41
MULTSTAT
MULTSTAT_S1
MULTSTAT_V1
NOBS
OVERALLANOVA_X11
OVERALLANOVA_X21
OVERALLANOVA_X31
OVERALLANOVA_X41
PARTIALCORR

An ETS proc:

DATASET
ERRORACFNORMPLOT_VARIABLE1
ERRORACFNORMPLOT_VARIABLE2
ERRORACFPLOT_VARIABLE1
ERRORACFPLOT_VARIABLE2
ERRORHISTOGRAM_VARIABLE1
ERRORHISTOGRAM_VARIABLE2
ERRORIACFNORMPLOT_VARIABLE1
ERRORIACFNORMPLOT_VARIABLE2
ERRORIACFPLOT_VARIABLE1
ERRORIACFPLOT_VARIABLE2
ERRORPACFNORMPLOT_VARIABLE1
ERRORPACFNORMPLOT_VARIABLE2
ERRORPACFPLOT_VARIABLE1
ERRORPACFPLOT_VARIABLE2
ERRORPERIODOGRAM_VARIABLE1
ERRORPERIODOGRAM_VARIABLE2
ERRORPLOT_VARIABLE1
ERRORPLOT_VARIABLE2
ERRORSPECTRALDENSITYPL_VARIABLE1
ERRORSPECTRALDENSITYPL_VARIABLE2
ERRORWHITENOISELOGPROB_VARIABLE1
ERRORWHITENOISELOGPROB_VARIABLE2
ERRORWHITENOISEPROBPLO_VARIABLE1
ERRORWHITENOISEPROBPLO_VARIABLE2
FORECASTSONLYPLOT_VARIABLE1
FORECASTSONLYPLOT_VARIABLE2
FORECASTSPLOT_VARIABLE1
FORECASTSPLOT_VARIABLE2
LEVELSTATEPLOT
LOG
MODELFORECASTSPLOT_VARIABLE1
MODELFORECASTSPLOT_VARIABLE2
MODELPLOT
VARIABLE
tomweber-sas commented 1 year ago

If I looked closer I'd have seen I was still had one glitch that wasn't accounted for with the new code. Pushed fix now, and here's the right output:

dir(sasfit)
['CHARSTRUCT_S1',
 'CHARSTRUCT_S_V1',
 'CHARSTRUCT_V1',
 'CLASSLEVELS',
 'DIAGNOSTICSPANEL_X11',
 'DIAGNOSTICSPANEL_X21',
 'DIAGNOSTICSPANEL_X31',
 'DIAGNOSTICSPANEL_X41',
 'ERRORSSCP',
 'FITSTATISTICS_X11',
 'FITSTATISTICS_X21',
 'FITSTATISTICS_X31',
 'FITSTATISTICS_X41',
 'HYPOTHESISSSCP_S1',
 'HYPOTHESISSSCP_S_V1',
 'HYPOTHESISSSCP_V1',
 'INTPLOT_X11',
 'INTPLOT_X21',
 'INTPLOT_X31',
 'INTPLOT_X41',
 'LOG',
 'MODELANOVA_X11',
 'MODELANOVA_X21',
 'MODELANOVA_X31',
 'MODELANOVA_X41',
 'MULTSTAT_S1',
 'MULTSTAT_S_V1',
 'MULTSTAT_V1',
 'NOBS',
 'OVERALLANOVA_X11',
 'OVERALLANOVA_X21',
 'OVERALLANOVA_X31',
 'OVERALLANOVA_X41',
 'PARTIALCORR']
tomweber-sas commented 1 year ago

I've found one more pattern, different than any others that I still need to account for. So, expect one more push. Just FYI. Shouldn't make a different to your glm case, but could for other cases.

tomweber-sas commented 1 year ago

I pushed the latest this morning, been on a call since then so didn't get this posted then. I still need to go and manually verify everything all over again to make sure this handles every case I can see and some contrived ones to cover other potential cases I don't happen to have test cases for. Then rebench all the ones that changed and doc and ...

Here's the output for your case now. One extra number in there than before, but that's because other cases require that distinction, even if it doesn't happen to be required in the list of output for your case. Other cases would still be missing output w/out it.

CHARSTRUCT1_S1
CHARSTRUCT1_S_V1
CHARSTRUCT1_V1
CLASSLEVELS
DIAGNOSTICSPANEL1_X11
DIAGNOSTICSPANEL1_X21
DIAGNOSTICSPANEL1_X31
DIAGNOSTICSPANEL1_X41
ERRORSSCP
FITSTATISTICS1_X11
FITSTATISTICS1_X21
FITSTATISTICS1_X31
FITSTATISTICS1_X41
HYPOTHESISSSCP1_S1
HYPOTHESISSSCP1_S_V1
HYPOTHESISSSCP1_V1
INTPLOT1_X11
INTPLOT1_X21
INTPLOT1_X31
INTPLOT1_X41
LOG
MODELANOVA1_X21
MODELANOVA1_X31
MODELANOVA1_X41
MODELANOVA2_X21
MODELANOVA2_X31
MODELANOVA2_X41
MODELANOVA_1
MODELANOVA_2
MULTSTAT1_S1
MULTSTAT1_S_V1
MULTSTAT1_V1
NOBS
OVERALLANOVA1_X11
OVERALLANOVA1_X21
OVERALLANOVA1_X31
OVERALLANOVA1_X41
PARTIALCORR

Let me know if you can test it out with any/all of your cases.

Thanks, Tom

tomweber-sas commented 1 year ago

nope, still a tweak that need to be fixed. MODELANOVA_1 should be MODELANOVA1_X11 and MODELANOVA_2 should be MODELANOVA2_X21 :(

tomweber-sas commented 1 year ago

OK, that's it! Finally have this working with all of these inconsistent paths and multiple files names the same not only across different paths but even in the same path (cuz these paths aren't the real names; don't get me started). I've manually gone through every test case as well as some other's I made up just to push it past what I've happened to see.

I pushed this code out so you can finally test it. I need to re-bench test cases that have been benched with the incorrect results all this time., and do more cleanup, not to mention get ready to doc all of this since it's a breaking change ...

Here's your results finally named right along with all of the other obtuse cases:

['CHARSTRUCT1_S1',
 'CHARSTRUCT1_S_V1',
 'CHARSTRUCT1_V1',
 'CLASSLEVELS',
 'DIAGNOSTICSPANEL1_X11',
 'DIAGNOSTICSPANEL1_X21',
 'DIAGNOSTICSPANEL1_X31',
 'DIAGNOSTICSPANEL1_X41',
 'ERRORSSCP',
 'FITSTATISTICS1_X11',
 'FITSTATISTICS1_X21',
 'FITSTATISTICS1_X31',
 'FITSTATISTICS1_X41',
 'HYPOTHESISSSCP1_S1',
 'HYPOTHESISSSCP1_S_V1',
 'HYPOTHESISSSCP1_V1',
 'INTPLOT1_X11',
 'INTPLOT1_X21',
 'INTPLOT1_X31',
 'INTPLOT1_X41',
 'LOG',
 'MODELANOVA1_X11',
 'MODELANOVA1_X21',
 'MODELANOVA1_X31',
 'MODELANOVA1_X41',
 'MODELANOVA2_X11',
 'MODELANOVA2_X21',
 'MODELANOVA2_X31',
 'MODELANOVA2_X41',
 'MULTSTAT1_S1',
 'MULTSTAT1_S_V1',
 'MULTSTAT1_V1',
 'NOBS',
 'OVERALLANOVA1_X11',
 'OVERALLANOVA1_X21',
 'OVERALLANOVA1_X31',
 'OVERALLANOVA1_X41',
 'PARTIALCORR']

Finally let me know if you see anything strange with your code.

Thanks! Tom

tomweber-sas commented 1 year ago

Hey, I've gone ahead and merged all of this and built a new release with it, so it's not the current production release. If you can verify it works for you, we can close this one. Thanks, Tom

mrmorgan17 commented 1 year ago

It works great. Thank you so much for the thorough and timely work on this issue. It's fantastic.