From edf28c5986a3c82802f15217081c775c96132587 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Sun, 5 Jan 2025 14:51:30 +0100 Subject: [PATCH 01/12] update --- .../fonts/OpenSans-BoldItalic.license | 96 +++ .../resources/fonts/OpenSans-BoldItalic.ttf | Bin 0 -> 136360 bytes .../resources/fonts/OpenSans-Italic.license | 96 +++ functions/resources/fonts/OpenSans-Italic.ttf | Bin 0 -> 136604 bytes .../healthSummary/generate+localizations.ts | 507 ++++++------ functions/src/healthSummary/generate.ts | 779 +++++------------- functions/src/healthSummary/pdfGenerator.ts | 318 +++++++ functions/src/models/healthSummaryData.ts | 133 ++- .../databaseHealthSummaryService.ts | 6 +- .../healthSummaryService.mock.ts | 6 +- .../src/tests/mocks/healthSummaryData.ts | 2 +- .../tests/resources/emptyHealthSummary.pdf | Bin 130 -> 130 bytes functions/src/tests/resources/mockChart.svg | 4 +- .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 14 files changed, 1140 insertions(+), 807 deletions(-) create mode 100644 functions/resources/fonts/OpenSans-BoldItalic.license create mode 100644 functions/resources/fonts/OpenSans-BoldItalic.ttf create mode 100644 functions/resources/fonts/OpenSans-Italic.license create mode 100644 functions/resources/fonts/OpenSans-Italic.ttf create mode 100644 functions/src/healthSummary/pdfGenerator.ts diff --git a/functions/resources/fonts/OpenSans-BoldItalic.license b/functions/resources/fonts/OpenSans-BoldItalic.license new file mode 100644 index 00000000..c9c42c60 --- /dev/null +++ b/functions/resources/fonts/OpenSans-BoldItalic.license @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) +# SPDX-License-Identifier: OFL-1.1-no-RFN + +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/functions/resources/fonts/OpenSans-BoldItalic.ttf b/functions/resources/fonts/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..855892833a465faca741e8c571fd5b49431989f5 GIT binary patch literal 136360 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 literal 0 HcmV?d00001 diff --git a/functions/resources/fonts/OpenSans-Italic.license b/functions/resources/fonts/OpenSans-Italic.license new file mode 100644 index 00000000..c9c42c60 --- /dev/null +++ b/functions/resources/fonts/OpenSans-Italic.license @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) +# SPDX-License-Identifier: OFL-1.1-no-RFN + +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/functions/resources/fonts/OpenSans-Italic.ttf b/functions/resources/fonts/OpenSans-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..29ff69386f7b74fe24f0cc85dea86771f5b7dc47 GIT binary patch literal 136604 zcmcG%2Yi&p_CGx3*-ZsP5s^T4lO-Xf?rwHNLQSWIo`fW{^a5$5(3?n&f(VF+h^T-N z5CVD;8zNV|iehiq1}K+n@7D_1|99q@-2_B`_r9Ocn`CCroH;XR&Y3ginP;8}p@fib z@HAo`k(QqE_=%@_5yB4=Lcbp|Vf^H|WdG-cEDRuI(A*J|vs0p;i+q9*YY#%YTo^w& z*1k3QrVv6H;-p}5R&v&ik5g^PdmQon;_AY>Gxs`t3Gqf;vlh>7G>>{=#Py(W1WiI& zU3vA#H|)$O#Jn8kZ7DBos3X@A3(}&HKcKv7LD@;;;g1PmczZ7z*htFI5Q}Pz+ z@Pph!|C~H~{o6nOGPBE&{}4ZKnf}k0PQ4+3^NZf!e<|eh2R?6kF9Qc7oc<$9_eM@a zdPqNtuz4ko6%^&oE3aroKr0&ytEh$?Ay9@9^vH|2&Ji)%hi*h$pgkpy(cYv)7OrXI zkz#<{aoQQzS+ZS(WE(p}>}-?HUiZ%t=1L;wK*YiYX2kgMBj}BUxGw8*d$+NhQTRO~ zO&e)AhQLfTV-Mm7xTkR1NYtOWU}G+{f$Q_Oo~}0FuLJxA_#dNAP<$#v;m98RZpH6z zvJAg_$U3r#JcRgkaxdDU8JLyi4sx2z0=$&mNbVwg$pZ2Kxs$9Xt3bDz+)Vb;cgPxQ zAW38mFrtJz$lb`Pm$L|T%_wU-*$FNWl7(b7xfK-mlNOX)N@`HvGEi(IWoY%~NL^31 zkR4{iL6DQ?bJX= zqE7QstJ}!!DDfV0(BMmY7*Fwqt^~G|KY&(Z;IIF5MohH+Fu0y@0fHT);0S7s+Sv zzrZggU*VUMZ}3aWclf2`M>>j7S!+(SX*S>~G#7q8ErDM~8{jw59q{j=mkDKkSzq}5 zSU>m~ECc>1h7#BqHV*y-HW_|4tB1dUErh?69fW^~9fAK4I|=_3kA{X~c`SSzhm5?E z*Lrj|htB~lG{^Z|z8vssz8e0`{1*5d_+9Yt<~!l<;=AGR<$K}p;|Jg$t!XaPhO~6k`c#+cmXrX~%_%NZb8@0-U2?r?ZL-U> zCi#GAb#l3BRg%lJa;Tr_rlCDdD~7vFHx4~uT0YceS~m2IY3VSRY00nyrp3cXn-&c@ zV_G=GWm+)UWtu-Y-865|4Aa~}2TVG+n5oD<+Ei$FnF``u zrkQbPOfzgQ({$Sz(=@BgG&Op&DL>j}%8PZGa$}d9ro^~RIZ3XVa#ME1a#L1>%QQJ+ zvuRSq4AaC&muW(j%QQafjA>k?%`|p^%aj@JGL7l)GL7!%GL7oD+%z)mjA=xe%ajr7 zGNnUH!-tMH4I7$cN=!&M#XD!1oK{QlGzW(lXU{R&yN3i%vxV49*5E)>Z1i|jOtje) z9qckiMIJCkMhrDY#F&Go4KQ~zg_~Ta{z0QnVg2Gwq5VQl{eoO3i!InR)jTLDeQJn+ zOakf~JUqyi-`&BiQq z$T7#gT$9GM`b?PGO4qiAP8NRB#Qav{+E$XCKQ;FVr5o~AtzS?2ri^LbI63zyMvuNJ zc}EyaotS$>;~Vl)8b~CGjErnRC_xcHRLAN*jcor#8Yn0N;Q-4RU=bxcQj&;N@gvC) z>g7t5?zs~@DIUZhiIKUN90D<$dFDScr}e-n)(7FY7?q8r4p1k|0S}YEQ7g64LRw6h z(M|MT+DgBo-%=L~WCK|qdxHIgy}&+XKX8rv^Dev_xA1V@%x~eF`4)aJ`tyGNIDdk_ zjGp?oMl^q|lQv$PpiR?eYAdwW+O67chVF(Z4JQnz4F52E9z=tN2dxU)8T4Jy4?(}0 z#+p7d`m2&OFFG#GGcXGcPnRGp{o@n>Uzun0K2WFdqyy1a}Ya8Eg&? z2@VU637!$cLX06@Lb`=q7t%Wj`^@#ZCg!y$B`5QR~iS4wAmO$cN^f3J#692|}DT$wh#P334!i`GeFuoEJZ-B(N z^ZOxj3nYG$za}N7khn7>o~TXN3bdQFwc18)lc5z7KMjdr4I)9qf|f(#T|wUm{U{|S z=I-X}&1NaF-JGZ-ZZt18FNegpm~S`V4~bi@l6b0;xa$=Xmq6l&ATjwp;`andob-F@ z?-`Kz5+tUs|F}MJonqg^&2v4?yj)Ma4wF`LKh_{Qu927n>uFEd&21~&Zfa|CO`}+e zQX^JCKa%e+zjFEI%V*j)UViEFi*1dUPhWcc@(XSA+8W!kFW=EN>hjh$ugkX+a`{)d zA1{|&{=TiQt+lPXt+I_>7<-}9Cvz`+dZFwS-wP!d`hBwHw|Ka%$ z&cAd1?enjEBIaiD$%Rj_iuuHOzWzMy@qCZ-Ugr&;v`_F zkBiTBKIeDVb@r39FP(kigHl3rbG;9G|K0nP_tV}dy`S3rz zK?~AMm@oUXIOgDu%*o<;lh&X$YPH%N);iM4_h<1X{86o|mZ@#kel_$oEH<1sb~UCL3ycem+l-%hE%Z9* z^|QClyV$$g`$6wte9S%$pJ_g8eRlc0=iAA5lJ6?tcl^5gmHVysd))7Wztw-L|5E?^ z{7?A5=KobEpH6|DhIG2E(-)mXJ1^_}QkPy`P6Y6Ph=B5dZ2)$u1ueEPV-|W5>eQyuaf_esp z2Mr1u8#FWMPE%)-*fLuQ364*6%ummzJI085C)VM(%NT4q@4EGsOVEqg4FSYEKaXZh0N z>gUyOPQRP_-QMqkevkHhvEPUNJ`2qctqNTddTZ#tp$~;V8~RS@=b=A^(Xj4eptpU$iOO z9-SUNF?xD*ZS;-No1=F}KN9^y^!w3YMZ03U#)QO7k9i=rQ|w)_55_(f`&#TLv0umj zY7MvMSZ7(cSdUuI+I(z7ZS!o~Y#-RZvHfEAx0~$_`$+pV`yBgf`v&_~`(gVd_NVOs zi0c(+jth?)7nc(^BW_LHt#J>={Vnc%+-Gs$IYv4rICeUYIG%C5<+$kh+3Dl#>s;sj zGCm}JN&G7VdkxGT_|U+!1OJ^cD4`_b^@R5l0~0F}A5Q#bkZn-;pc8|;49*{XeDI5d zKOFq^VAqhYL;4MA9I|@IYeW7yg&;Yqi2skGWyZcPmg|c^ao?I$2>gdi7_vaIhPrk zIWn^(b8hC^%X z{J3|=uNi;W_y@*6GydH8FDK9mLnqWt_{T*1#7&d5Nz*3%eX{rD+{yP%erWPDli!+r zF)JmjHmf=7;jCw~E@r1@&(FS?W6BwkQkR2Ce|#ed80P0_QARVbB3yw+{JTG&FeC6+`L=notXE{eCPaS z^PgMby&zyg$bz^984Ge36fCG%uyDc63(hW#TsUmugoVWm=Pq2gaL2;)i^3NTT9mnH z=At=^RxaAQXy>B6iymF{;-ZfheYcn|?y-Qd2uOQI&f+A(x#=aElXOqXSs3twB?6y?0jRxjZdyHt{A)GwiU14)a|C(H#OY! z;>w7X@hekSX00q+xp3u%mG`YYw(|7Kk5+!a%DAe}D*LL8Rr#xGR;^gIbydr%6RTcd z_35g%)xN8Ht`1*4X!Y3DGgmjPUbFhH)rVLAef2x5zgX>BbKRPVHAB{nTa&-0Y)$=| zrE4~>xo^#}HK*5nwC4M@#9J$Yuv4)ZoT`~JsbJPl8t}A?fTnh-L~bnuQ#P^+O%osrj|{|Hl5mZX45}6 z{j%A-*}ge#bI#_<&5Jj0*t~1=fz8J@Kfn3S%@;TSvPIkCzs0;Ie9Pc1satZk%-B-4 z<-nE`TmHS}n=L=zzUcNfw{PC+-1^Mcx3-?!`sE!n@0fkZ+&kXhmb7iswvuggw;jCG z_s*VohTIu*=b$@py|d-c%iB9|pTE6%`;P6qxBu&|9(VP>Yt3EH?C7y0Z^z~xpWHp* z?uNVXyZf`dFYmPNtl#szPa~3e!q5q z>izfKe`$B;-R9l#yEAv^?k?F~w|nvKwY#_M-nIL{?qj=8?S6Upd%G|0{(g7c9^XAZ z_k`?;*)wQQ#-2%gX6%`@XYrm@dp7L3W6!QV`}Z8(^Z1@;_MF-C_MWqQd+iP0J9O{V zy(N3A_b%J}z~0yP{`P?Vfte3H^T6eODf_1FTet7UeIM@oZGVsbG5hQGH}BuM|M>pb z_kXkhmloRM+tR(IPfKV^Ov}KQq?S=F6I=3H3R@~$=Cmwmxv^zk%WW-pw%pfppylC~ zCtIFtd8Osumh&y2xBPG*?m*swO$Rd-xx%Lt4{XGh3&$E^Td=E1+052+vGZ^Fi#O*C7Q{(#>m!f*JAm+NfYo9A48D zjQwG+AqqB--d-UJHj*GOO~GDdx3NIM-Xy{puV5b%ZtSdJUy@>YNx^<(m|=s0{mBr+ zBn5Y(Az~*)*0VEl@JkB5f%M_$bQp_afBvR|De>i}70gL5epJC)yDZr63*uW8-bi}# zr3&^UWRqHwF7)-_y<4pG2@@3g3x1*a`)ACgCh!!Cgoci&1a@iNS-a zl!bOBVe~x(cOzEXqTudi5Uo>i58#s&d>u)GoO<4#B!N7y;Oj{W_8oP6FYM(tD)of)TA)Se87upvz5egrK9mL)WHeqMkExQ>IA?etcY$Wxh5S(kV z4_M*}Cu2{vnpAq?V-YVwyb0JzfXe|@AzwYo1h$-1pqz0iqaNvnNO9*+1+E4;#SUwO zEaQ)u@p!5i>64LDP>n)aqKryXOrpsj>Hk>k|Fs0SwEt^9w^aXM^Z&2>+I9QaIsaFU z{CD;L-=w|LD*n2@?Q;FGbQE=E}1E*eX}1 zU%SqKUB5r$H3hw;1pTIhOhI3A_sG9$1<_+0(eno3?N%&$T`~G@CE|^MW8f;$17l>5 zE=T%!JY8p!ER{v&0_GKG$H%0JSLlB0e(%B(@D!pdtPVN8csb&u`s?IAdenMceGV$P^1vyoHS zl9`M^=$2Wuj<6fEq!JPpA%_|CVipp%CL|CsK_hBlmR3^_yl8O|p9LKY>uvyzu&GA) zLMy`3-InLBVe}Pa&D9ncha6V?#(DAy8`kHoD|>f4*=}pi<1#qG_nemp_ZMSY~ z7n&6mP0)5nt7~tUGO0l^TExnF=`*~pi2+HY@7#9B)-|hFt-NW)jmwuUU9x!5!Ugl^ z&24IIsGn0;TT@*%dsbydd0A;maZzEx%o)?CP0i2CosyHCHF@HM@#Ds3ju|~_Sww>2XY zc$uQdN60t`_2|f}l1I6dE#_7-p}D!_2;re9CMoa;m4G4j<~&G}XK5{pv;EkzS@n}b_vfhADIC*?w8v@oza*b;2c%RBCRx{si?1fwiQQjS>Y+KES!=-SEo zxnSCAUYnJBlu?#ifHzJ3kd}MgOj?s97B8L(QUp?vm_>w)p=g<-%v;h29#0~z%Vnx2 z5i(v3DI`uOdNc|9;>E|Ajth`h7>L@UeQJn9fA`4*JV-V$`>1LR9YtrOgL0b?An-9**ON)*Y)@9DMlm=Szj`Zl!T$g?%ARu*2 zb1DoSwk8eiNTD&TAhJ2PRhXPGG)urBm@N+-Rcy&90Eq?N27jY~D$X$%v=$Xa0yGC? zG$YwuTqvkX&m)Y79-&$&9Y%&hZ$|%CUrT99tDhyslQNtP*Herl#mkb?N_*;BPq(C- zdsQ|UTZ&+TNfUC*1IzLXQFd#RrLa}Aqy!$(ND6vaFA9;;kC1VZkZlZXaD3#1spzCa z8Rq8ZH1m-pEv&G(P{h-M(eIm8k|ixobXs?gbaQiSQeklcDAMyJadZ@_(41~5G?!rT zfVj}yWQ%yWnlHFy<>xm0msmxb0NeIG#BRuHs=*fjiWlK8i|fFw2w||aAIN% z2rVvyhi*|+U@6iOqIX`6E59012C9xY%V@z4l^rc=jj(yNCA|a`!WEXZa#&%oxdbnK zg~^Z!a@_b+A{5~vGNVmp;mrYqh24l4(q#-0{57|hUm30NL^F^KW6(llbX#fF!i44J z2Di=%Y^}-@_M(#%wk|KiTZ911AdB#%osB^1h=Nu_=!n+k#f6XqV-jo+h|xfpbBkch zC@`a-*==#iriFPpwbr2RI~0i_m1co83l&;uT|U8FkY_F^02Y%%aG<%>fY4l4D2z)C zyA!}4e-kkFBPeX1jJ!m2pukoy%m8JDrIug}?!e_qy~^eT_tCJB+*T6U+-zxXrKn

9!!Sn(}ZgVTcR3--vJ^v_?HBezxHelqqD`rj1D|!|D^!n;djiUTkfBEMV+UgyO zhHlkR2R+$Kh~q9(!l<;K2US9KbpZf_m6+$mpA@7(MYK?;9p%ZXQ3xPfKB*ABzR(hQ z+;wsS#>fK13M_eff<69Vb|Qx?x>+w+s0$@~UsDx|lg`{PwABw(LIfS5`@W&A_=8ZW zvq;kw2Npod`U*Zud#II&L32`XQgtE&4_zY~0-aR#yVAhcioD1Yy)dI5j51?H#mHSe zQLZ+oqBmHAy)ceJ0~pZEt&<}$iAdSj$Q)rXaWRAltE3qgk^vi10FI-z63a*`Jc6!e z8QIDZ^+1-Rj8Jb&f(U#p2}c<9!f1~%*b>mm9|L`JaY2bbQA5ilA#jLTDnTD;X#AmV z=Zc{zE7uUHiJpZX)jCgaQgkcmMG$z>=V2ri+R`=YEz+7ji9(Swv(PrWru64VdZWBQ z=4$r-@0h_9WNWti$cl*3D9l?_?9W+oy@5sx1{hqKF2-mfiUEp7pKfk0F2o9JdS}r? z{lmHf-wiS)LbOCB8+5Z6a!(N4Q3nVtqglB^d_Ne0G#)=B1VE2Z>!$68!~h8XGzK(P z@8d3)P@tmLRR{f|JZa}DS#}Ijn8-ITG7nIOa0Q^s5Ki?ZKh;b9uNw7L@p_y3T$yO` z6ecEmi>E{pJ3{@j7}f#};235OfVu|BrVE24h&K;9LcPKi0W2~AVh(C<_Os|yibV|n zkK$Avk&8=`htc^;MC%eXeRH#Sr)#HpUrpahW)Phpt=!2Iic(Z}@J(&?OBL%EG+?Wb z7>8oezDu4J<_4>f%ZU!tp+t$U*m08VC3Fu(BMUTMD+e<{=+;}A?9zI1S7Wnsmw<`T z)U!}VKr2OP2n!Z2P^eDYTq}A;ZDgLl8i3k6 zOb&9*SnyO9Vrs@XB*vY*URX;_602i80ayaeIB7jZe=M@-7B?AlfEJoNBrq`#&lSgA zpY;*r6;#78$-`weo6P}Tk<@JNh6jz-RiZ0uD$OEsn8l4@ib_bZDzdp*rxR;(f7U!^ zGL#^m*nATLeNiXR^Vhb>ze>jBl$wR{>Nu&e%nug2I8J6*7GSxMYH2l_r(saURDYa| z?UUEsj48F*BA%;q#Dflek%)8e);?l=Al5P-`o4Yetkx0Xj{+duao2%9;=!q%?~NY5 z^T1cspxMp%I4Qp-TVZl^s_^BX6!kd4Y;4ghqJ=5m&C{Cm@wga#oCFE>sy-s#xsMpe zWra423dxg3zU(J$7crz|WM;7^iWk&Zu?gk%rL&p2qO_=@(#^E@C}7B!W0Oak$%(P!GtFe~@JW-*`GR5-+@I z@WC5~9ysyclUz@F5%CTqkn|yaNf0q%$Kj9EUH>QQfd7fQ^M652J@xMNKT-St7u4-K z@%t~R+iQYz;(w{Z?$EcvQ|~{g)j%gSF6g!Yk)Hn-^mT-d9g}7z!zNEkG2@MsyLHHdF6A}pt!y31csca)_)$Dhj{Tv%AuNbf5yuCAlorQa<5<pOiKScWCs{*_kmOf5S0```^kMxCwP~s>3F4FHR zeShhrWsz!Hu5g^ktd-{QLZg;eBZ@eklBR(=QH#<%mk z_zs-@zn9;KQ?tAI9()7vg7_wY--+D42%h@M27EoTk=#Z;Cts4UaW?sT@&ow~&7-BX zk={*r(tFrZ_B4;-R=&Oi#{gpB$N8iDF`PZdHw4hZXmXaEC!dgid8klVEdw!%-YRTe zuYVadm4NrX2DEDs>d+1E$?@d@8P89`&*V?yD}*QbQ~0Lg3E)o1F!KuPLz4PINqrFh zD16JnvDZDGAC_SzI0-DiYEWDmc7A(k`Px*Rik_~`z{%(Wtxzk{iseb^iP|J>vX-S~ zYdP8!Ef;wiYK)WG+F&gWHNBz~r2L_WUaMqLip)>IBlqf3kTOIYstwbIYe`zNmZGJ? z9>l5bKd(!^R@|W!=oa52;Q>XKE_AEwm*MTXhBqvWrK5oRVOKp379^~eA?6Dmi7#N> zJ%^wbgud*c5h=U=kkSwN-Q`m<41D#n!catxPM|Dzr*%mNr|f(yFz&{4{@&zr@erwEZjm zRsI@s_WaAT$KWi%c)Yckh<7VFc%#|Q317VNXZW-D*5)7ldH#2#80kOg3-mO7i@r-g zy^2ztc0b9V<|oiIKnd$U$RFf~_+i+7dzol^oF!g^)A9JmhiG&r&X+Hy8$0wqpb_(2f6C8imMG})$kphNEOig9_Sx#h{j1s&pTVdyt+f) zZ{2igBX=p!DaaT6!_g{s)Fh3JM4cuPd|T(SnrWyFzI4%+;On!+`0`92fx_hoBrGcl zHO9##nM2IoRR4AmV~|JSD z7wSj-NLM+Zbdw`~AIv$$=!p#T24*bGA5-9`XgTncwQTrFS{D4_+GO~{v`O%XY7^lP z(I&tjtc{1ChB8FnBKRpm0Dh7-6aH{*2K-^#bofKHY4C?=Q{fNR@=?=1a;6Lv zd`WM?m)s!ul3s!@xnA(aSPH)6I>8t7FZhz~;LG_^z6{?hGJXtS5;FAQZG^EN{AHBg zsLe%7H)(@Vw{e)CtFhjR*Ah|3v6!!`5OZn?sB0$X@7bVi)CQu?V=$l3LYhO1N8Lwb zey>CVm9)@^IBa>Hn@R&O2V3(*hUt2u3%_zH#J**-@=0y`vh zKm+m6Py#eK2=&fJFX$&%ePT^83S;3IY5V#dHA&4;?iHbXGVSx4H$XpUC*%wW)U2HIjI+G;e~G81h*PCG-MhMwh@ z`wt9~p-B9BrIN_83Y?I(9V9vJqqg8{sNNYrU&&CL8G`*FLP06Bxre z$Cq9AgMOY?K_d`)!$re&hRcLo0rw!>G`Ipu&s=wlGT?TIkk2Of7@s56+G5vw)G0@M zm~1uVA`Brp{0)+0s6%`X$z_owhlRDB&=vvzHpwv_C0T}h;hrH|wKbp<;b!FXCjEIE zdDKu%cEk3bGQLjEYB8=YS|{?PmhAeHWxKv+FO&BXR%;u`MBdMp!J=uTwwe@c_mDw+ z7b!tlj5DXDY?l?(ztPPUHnQPRlYas7=Sb`9q1UFX^JBv315H?ZejM}+*k?pbhC9g=mM4PPT(+3+2#O z8SZuvZP3x7UWX8`5pB`Y$+ke7X}j7kNxci*U*(Kj&<1D=(GDG*-VW~eKpWgl@;&V! z+M=Tqc6K9ih_>kHL|ce9lcA7X@V+{P?9vwgB$PJ#5!x5>i?)~cin{(u=t&p$ENtpe zL)bd%ItVtOBSNWXVS7T~SA?!Z%-}jK@EHGdG$UxxKZK3SJ_0)xwkrCHuva~V9g6r6 z70;Am52X8wHUz)8KZFCxRD~NP`xx|lb!famwi~^pkr4?*+byOcLc`UMSLxt{}rx z3E2$O$fHtDF^)i=(uRc{!%n>-Tu*on*J*^kf=`>2QSXOS#9Sc9 ze=nERlX0MHr#2ci+mHuNPe?rp{dLrl=(9pcqD;4rW@4`BP5$GxpJaJG4A_S} zY8;7j8sU!O`-gWBZgc%jjs;WEk25ju+)wO6CNcgP?{}T!Yh7*7nH)>_ThQGE*IvWT z(DieGh3sNJ5aSBQEipd`8#IhZdtm(6P9k3u*#-O@ufBkvMg5;9%e4Mv8Q+gvK1QL8 zfvz_MFVQZ7w;cb(d?M!_G3K@XW{h&}HLd}zpg|p^or}2!_Qd;;LGC_qb?ELB{2h|u z^`P!B_t|m7#$vcd^xv+XG@pD5J6{a9h);Iy#Ix`_a4DRTw~Y9v!TT4ohdxej=GT*3 z*A4>tdt@dm0Hoa9-|fy*$a6&d!C+u7H+l?0~?wRvQfh8tb9 zwd7`PKkR-f-N4^tM*bT81@qokxF_KL4R=&L28yjpcgXPdFGHN>09OGR5t;=AZU%Dy zfIbH>SE*hgfZ*JmWs zYcGj39Kblg7xq^JdygQ?wRMHHzPGX(px~T2O8qMLlU`)r_Z3M}~I`juTbL=$qK{yAYNuL*uL1>%j$v7E4 zN5+XUMT`xie;SOS3B-3qLoxSEa_u(MBK{)cHz9l-VKT_*7#$v}7L=H=0glXT+yTupLJ5_6Rpn?$JE?CKDD=qCER7@K6@y($!A zlo%(_w{to4B*rK)PN03d^BY{hXwj}M{0)pJ829D4vJz`etUrt`m~);-c$j<(yV;^` zC!e8=ZC(i&Bdc9|y>_8J&cW6;p*@a~LPILr?I`Lx0ev0wfg#%Ugf`Z-*R6;D-$JEV zw|@WEP|hLxItc3)*DqJ=_rH$2`|tl1isuN?p6*cfcXz-4v(P=>{vo`F;62Hui*S=w z_$kJYPVMf}k94;yi{9e;7=2|2)?)~~&tQ7^Vv<0*qi*&ULJ+KX~ z4>5O~5#fL1w8K}@i+OmnJ7e8?~6X({JI z(i!uiKYgEcHfSV(wYeVQ#I=CUb)BJ)pl&z2-q(h^-sQtxk7*jYT{};@(ho^DmV;;B z^CSRnAw5B=`KM$XKka%F^YlZ4M*G0^PkPd|gHLq5smioodRFJ}I(r3w@b>UkJg>!1$N@Vu64Dln zH?>n?uMZThB51_hLhMPjZGbzVJM1VE;o4Rq{RG0_^|-{n40{iRyNR84y@!w?{1ssj zI1Aix#M=;d)|O+8+eu#5iZGv{R&ZiX5e8SnU%{GU7D)q~4qCB}_y%qwTqRr~+-kVJ zaF4*vg}Y6|TL*4{?VNR;=bxb62D{$ETJQ@kjLhV}z^+1IPhoN|WS-Uw&!4@(yO--f zaF4+4hIi*&qB)OdI{kh{1bACt|9Abv+Eo} zU%Jkf#}1LZ;P$X5U7xatu*Sk#O61dBqa3@X8r}}NPipA9shzNI+68a#yTU%M_0GqAb|V-&!xrp_iFeHXvHv^( zUzru#jSS3NfN$3Nx@g0X(XLw;G39{ zf7nr*h+XW-Bn$g);@wX!$-~#LQ^_>kRyTvpBn709YPh#=C0RwD#GQPf;+xqG_6{!Tu^9YH%u3;7Rrm471l;SRqS@Xq59zHKSSTig=d z=vRt2?5D{qgU&eiQW#kR=DtQeTb(Lc;`8Mo9zll2@D{#l%S7aT&q^Tsc@jX%% zz9HI$uV3ciyBzT)jrgi&F24Bs9N*P0#CxDcxCd}CcCGKlm0wHAa&n`5Tl^UIrNw6j zooN>uKrWLu+7H}#4O{9b9VC=FFrNiiOnnaUn3U<{$ zp{X>Drqc{MLcTQ`O~=qoIu?8K%R?%wg^w-ilI)^-t9nJ>qaW>(mz<<-Z zxT$d-olh6wZNMVzdoH0%$-8(5F5W@b+LG zZKgNVTj+Yaf!>P!(%Wzg)@Hhe-i~(*chGJ0PP(1mMR%xu)Lpm}>^^!w-aPD~d+7so zAKgz|=mC0=K1dJY?yOdN1n(stq7Re1=-=ou`Uoyudz3y#AE!^yC+P|L6y94rO;6Fk z(`V?j^f~OyJ}=)hzDQr9XYf|z75XZDjlNFbpl{;c8~vSL zqL*nKZZX0OD8?9P8Z$5>^TPKtKFpW-G4Z8JXV!%Uu&%i2s5{=aT*rE{>v0d*4Xiir zKkI`#6oZ(FnOQIkVHVt_7|Oy}e-@6nCl9d!EP@;(kC11u=E5y%D*kEKji*JaxsApP7}}m|eUXW=`xw#^au`1eVAK;R$gY8-iOQ?3OSEH;}}v1(SsYFQndgA*SOtdTXb zxojSrFK&Zmi`Zhe1UEu1W6Rl%Yz4cCtz@g%YPN>0W$RcoyP4g>*0T-lR<@Dd#x}9d zI5%=T+lrf_wy`_ec6Jxr!R}@|**$C*yO-U^?q|E%9=4Y~!1l5Itc4w52ib$TYw|E_ zWk;|x{}6ka{SCKFJ;IK&N7-ZSaojuiBs;;LVkg=gSudxkyBp2J;q&*N^k)9gj| z5}U21`<4C1erK21W!8opuL!4{an3bv zz-@3|+?)GwU+#yy@H+9%xE(Hlcg3w(-FXjw9q-An=e_t1yf+WzeRy9U#7*4HgLw$I z@P0g$hw=V6X)}OF@JJrTqj9Pw7Ux=Q+|J{;gFAUVAIKAUA|J#D<4n#_K8z3NNj#aS z@Km10(|HCT!AJ5@II%N^XY#Rp93Rgo@QHj9pNxCCvUv`l!gF~Z&*xM5G(Mfr;4^sv zFXTnMn3wQUUdGFLg?#%ln^*B_Uc+ngE^-d;@ND3Xya~5>&g1j>0=|$h;)`+5mwZpg zZ{#cZO}PJV74G*GcZIFPyK~&}i8m-4u6gR`PW47fob$Qos?$FY$hTACjnYB)`2pMl zdXzuJAI4tF-|#N|5q_MUBu}feJ>uK}&K$_I2dDVoJDgZ};p(#xc;Ed7f0Mt3x8CpY zclkg0d;ER=0soMH#Lx0`{5<~{Z^JLh_u>EMpSs_P^RM{V{2Tr)|Bippf8amz|L~vq z&-@quEB}rE&M)!Hyp6kXuOZc##x+edXhzLT^VWRu{?R}~Dp1_OCU0UhYr$HGX3_d-p<0;MUkld;Xc4$mHVQY&#%Qsc^;&Pt@D}ior*y9M zuId^mcjVi{sed?cRC0||B{kx$qIkcEH;s*26Yhqar_I+EXba`L?j_n%T*bLuyHQ)A z-K4G5R^d*^HMrw&oi;8zGZUZgc{SBkTCK@e6{h*r&MmEPD6Xw9)iEh{6~@UhE+tjP z6TOnF3ybS(YrG2e(3o6QUplwcSSSOpEYX8BlwH{9m9ChSDkkYV6P(foFW+?JD=aQ9t!eZv^?+XKigl?T z8q@U>OJ(3UqMf;4c{{9)5EUthCk3?Gb-``+IKPqYRDKohur{)&uwJWxXB^d7SyfVM ztdxP*C{^c5Rp(K9ohx<4j8e6$)I&CEG^?EDH@dxCzgg|DUYEFd6%O>DjbqBCHC2T* zC6&d-%);WP#!_RI3=~O<3R8`ly4Y1R&@zR1Rq%}C^jtL^ayguOE=Rm^oSv&jS5{4- zxHi1LwyvU-r`MG8(wcIw@k+c}CEj>lyjmG_8eh>=Q(jo#R9#ir)Yz%EW6U^7=Zxc} zsJ710sYG)QG)~ev*XzM#owh*+ev{i(;@8j)%QEb?WEG|uv-Hf3GSIR_12u{U%GMjG zNpGNRB|(#tAX}H9Nd|`O`pTMeLzD13Wp}K9r>2fEuWZ$jO?v1zrM+H$^E$wJ9pDA+ zurXhkaG?x*^U*ZQloxtHLse}}d4q4VsEbZk=mEWw()+`qJg1m38Ct`1Ru3H88PWT6gsR*5vo{8|yn4dp@@LrG~> zW1*KSOIwHpE)pB1NTM*qY$;NeE>cYsn_uZwtINadD{5sk#iiN}4N|B^2&EYFItWw@ zhk6)lg@*k_dG7jjxvGBts!+XoRHBUd%FGg!+#FV$3iUD1VbuqFht;9*`tabe>YdeL z9jNfSQ8}!MDz0}#hgBbh99Dg@a9Gn-dWIg_^y$W7Q+#cTuTAl_DZVzv*QWT|6knU- zYg2sH0N}7GzBa|zmZtbA{x-$muK3#(e>K)S?25l#@wY4fcE#VW_}dkKyW($Ga@!Su zyW($G{OyXrUGa}o{Noh=I3<6a;vc8@$0`15{CC7D{&9+boZ=s+_{S;!af*MO;vc8@ z$0_~}#owX$I~0G1;_p!W)wJMnDES?VzeDkNDE zfzK3oSMr(XhKzYS;Q|>5YqDEy&Q47=C2Ey~YbraUxh$PV^~l zXvA`&v9!botE$pU{11!@|HcZe1$4N;Z9!j!yW>sj{l7vRF=X z0dj&YuEmI@PdZ&Y!1ZFtD^(SiO=7nW)T?G4D4POt*%WrGRjL-DY*K`>NfF8>MX2&C z(X9Fq0=z26>Mln%De$sM5y~dbNY9W>n30hon--yNQnn1eN$qMq8keSFQgLZg zb+&Zf^byK(Z0UM^5$g2zj5x#iirV@bL#_0)rQalct)jMew$z?O&t!F2l|4I^Jvmi8 zUd7{;UB@fCj#qXauk1Qr*>$|?Pw~pGfy_gqWGsM{wa!oisGN5_@^lTDT;rJ;-8}UtIpy`QT$UB{}jbPRq;<% z{8N?usfvH9;-9Mcrz-xbihru&pQ`w$D*ma8f2xu{Rq;<%{8JVGG{s+aW{2v`jx@zT zP4Q1t{Pj+5b)+f&X^MZE;-9AYrz!qvihr8opQiYyDgNn-f4Y)CUCE!W_@^uW>56~4 zl0RMXPgnfY75{X_KV9)pSNziz|8&JaUGZ0gnj=H;&rtj`6#opxKSS})Q2a9#{|v=H zL-Eg0{4*4PeGs)e)FA4}Q2g~l*6LK_oKp>sPJJ*3Ugz&r>p`bJm;|q4hm7dq9t(5cUdwsgIJB9!_;DD{I-)(@f7 z4??LQgtC4JrG5}f{UDU}Ln!rwP}UEjY#)TOeGp3hAe8mPE#A^T5Gww9|3qBz*ZU{p ziof1J5m)^6{)xEaulG;H6@R^dBCh!B{S$G;U+-`gPCBNQ35m)l-{nM7N_fLdMe!YJpuH@JIC*n$e_avam8QnpNK2|djGVg>-`g<;;;8l#1((Ne-`gPrGLGDBChnW_fN!?{`LNe zxYEDgKM_~@*ZZd}UGJX=mA>`%NGF-<&aWqWvIe-$icSq`c4uNIk-e}8JBgx>g36YmKML3~^d`mnQFbMSQe_AeyNjL3 zxRgX|oHYRs5204;HE_18aPilm6F5beioxsZ@~*&LP1m7351lN}8~a}|_-5JW2JCJi zu8XX?)dp6AG(p9&2)-gN(uG(Z)AhJp^)qW5s|)Mps?(OPuTGK&>Z1!nhb-3?ml7ks zti}xldfegk#MP#&jN?{tFMV#HV3#?dnH8zn7C!P3Yi95AZg zI3=RD3D8uvMxT zB@7H>TgOPUo&_m(n?1g>*aDLK9TM#$q7v#VperixFPah?9iGAv8Q3w>&c^CUwaOIP z53FiYX4fCm(Ohy6N=|Xc@v@m^2Dq7((y#IpJg~7WtVuO#s%(PxCXM&R)&9A=iMpsw zZBJZnBYWa%H_{z9mdh#zblB;Z2*2|7w(t?V-V$ppi^1pjc5hTx0=}}=?O>HVxzY{j z7{9WrT5L=Tq}~}E&Nx{ET!$bfbi~O3m=7-fq>O z2N0^kM1LMYT#mm8<#>-!4qXW4(1lPRx}0j==~U}Zr&@P9)w{ClYTfBn>rSUycRJO&)2Y^-PPOiIs&%JR ztvj7+-RV^8PNzOZXQb%+QV6BI2&KFT<@|z&j_WS5h}j=eiY)0pT7T;k)rQEAynn-`%Z|fa`pWt#8vtFeiP!V zd`F6pXR`D0%!0Bz##!w?p1Dq8WbMV6tm1N2AjV@A*C)0(wOWl+tJOHQI*qg0{n1Z~ zN~>z;x#u-&oZ1?UQ(J>^YHKjgy#4Y_vsz5KvqpHj(I3N(^ zSCx6|rB{`C$+AT#N=CXU7wLMb0Q53JAWN(&)9Wm57Lj$9fGAS{s?Gw`>#RefWJxE= zmUMdQ64c9=gpz|Kly$CcDDx7P7NOvP&_}N}Aa7Z335enajVN8v=!FYNFIy1Fq5=5I zlI0s3U64kRD!52Mun~X~M1Z;=IwUwrI>AcP>AWPUGn0gpn}CFM+pz{xq3qj;%f5|J z8YDt#kO-y0B9sP;Q1%jpYAjWIIZplA1PeDgmLOe)`rZ)Yvacgujk)?B67VXn@BP^A zYD3Vj@AYGitf$-T)&Q|nY7?s}bi1n3h6WG9sSusj7*_&*((ti167jM!~Dm zxE^w7`giT}Kf;Rt*xD1{D~tc7+XZ)2kVJe#n+UlRz2P8p!X>z$;BewK;d8hPa38_p z6l}ula4)IwIfN(S9)rW_+Jvnt|3<*g;QHVFH(#UDs?c>kPFqr(uJnigd!Q|#VOM1v z?&+YUZ7?M3-nJX*TtCHud}QGFa9_e*ggXcKJlrWb{AcKa$KYDw@D0sCoYWn-6K)&a zYPf}P;--cF>puw_qz#Ccz7MAo8O~e!!vap@1o}R-_CmDqXw=sXJu;lNjKR6f9e{R& zW*g3oQJmLAeeFw0kKi20lZ(@ol!wVOaZVU1C8TrkkciKvoltR}7`b~wGT(^Pgc^d4 zCgeXP=-FPt9RF7>JlH>2+zEi23yiNA@gKDD-`0AVe=&b4HAW391-0=-q~J?@y)5vQ zw;gHYM0u|DLZj?qz#S-97lAbll@zy0iW8E8$^QsUaN6f-dDjk!hdso@9^zpS@vw(@ z*h4&QA^s#BY#|=D5RW>??}NJ!ZifnCAMqREn&Dv4@k`<6!_~u80apem$}51&2QCW^ z{|`BSBwPyI5V&|a^p*GsxPEZm;EaSi#i?K)^t-tb9w#~>@sB;Hbd&Yd^;^`z8BN&?&KhYyaF1*#UiIf7gzh z0(!~*oc*L7QrjQ4AF@MYK=;{q*dZ&RjrL}{Xb)mvYM*bfw?j&MnZ3ZCFL7D+arTiC zO0f^I$4kg+kFfW%2Z64ay_?-%;*54;uNRQ*H`|Z4uWg?K`WXLf?`;q6RSBK8J!3l| zq2sorwu2JdYujbpZrg$!8*FQBDvHQti4#zxgleo6IyY;Pb((ddH3xJPtfQ@IGIbc{w0#n?TcfODRx?t2 zTYFf=c-;~5wz61^UxdW|6#H!~MlV1YVn2#~Hx}&``w~j|N#dT1JsJC$gdUDP6e~-P zy)Sl0>{d@IEEPGLV^_v5mC$^|WGlp00fMc9uNyf;_lhZpi);pp!C3H$Zy*{L$K9OI$l-^x%-7r^fu|hHQXh zeiS)kkWaUg4$!9_+{Yg113-ghj<*59Aup^j=Cp*K0j}p&&fCuO6#>Fx4 z)kIz!@k|4NCBp#+9twBSgFENJz30JQi|$R(os^}#jFP18+igwMKKhhQ zeFAj*ByZ1}H&7c9c9) zKryf;+=!1KdkUa10a?K}+AN^x-f%r6)EN~0B;@Tb*=|KD6F4hs6m>~J0;kgnoL;MI z;i7&*j&DVdsL$PWQ5TR`${Y0&pnbADBPiY#kQKF$dR;(KFL~&mbJIng1pYCZidKj^ zBp@+5M#)m5?t|vOmZ>{Xl9V!Ps|UByjf?enD(%N)Ic>n0!@=yY7Hpo66Fjlivt5InudNB-u|Z0bG*6MM9cLhlFAP=`{@j)JNjLGcrIzzJN3V zxl4&?vww^7&`yLz`~vsAfW-JBdPPK=4W;T*VkIv73~;}>YbtuOzWR#z5)kSLzZaap zgwW=CFN?T{vd#&d$Se9sL|epr@ZXfUkAZtzKoKv)Juh)W;@$=Hx`a^Y0nbV3B+8R=40sGUv>p6I1NIHLPeMBo z+bW@rfSM(=(yfC5OFen#dumYc!Bx3)#P&j4?vr`TfGdztKIp!eP!`7L=LIAzRv&Zh zz}ZL2)N#n6Ln(lUNL+g<@xWOnE&@pn z$gkHlG6Xn(fg50i695VS4emz?eeHohMXIbp_{YF~AW}P6F*KxGvD1!%j3 zwgB27paEHK-Tyzrz5~9hV)_5!&i)jr*zu;eA6GUE+*^_R!K#ZG5PfeZpEoKP+ z)~s?YIBWiuX_iHkrm2>alg7>YHd^|dX&kuJG8gDd8{bSNc;nb#;-F769dxhJ#i6#e zF)uFClA9?5oRg4`defrc^xRA_#Gn2%ML<$JhJsK^t}W`TCe$amh5&Norw)LgX2=TK zY$1qxw+Xb86mbJTXb!oDSz1KI576$|27uKF9AGL5avs>mOks0C(lDaAB|G>5KF<>}mWiF?1f# zLkyh&beN&L0qtdIJD^PrtpU`@&|*t#51R+vEXGZRw5u3ugtf@A;KIfc6p0ds)iSOM znzUo61bFBM|MtSue8y!1%3vr7Pz*y6fPxtE0p!Mz10XAgWJwCWK~Tg7K)(-i1yyTwF*WE~*a~*@we>VV(R&03_W)5Y2T%QSV4y@UvscfTwpd zghaE&)Z;@SH+sp-fVku#C|AgrjB5e(DM2A0qI@VRAiSZCx*>1kUw8}U{}#V%1cf{c z4RLNEPvBn{8Hbt|`7y4_U`h7oepKwF7Ep2z1dXGyXjZ zzj}s71FB&NvV}nFNOSPZVkiaWh31fFK7M-{7l)@B7d#2^BOHxZAsz(LXcgi_P>7AC zrbBeV^V49}f(!oj|HS>&M+d!_-~GG~cg=!}udw7BN4nyCgFon_yJDdWe!UO(Vju45 zKHQ}~+->rR16QqVaVU|N2hHh%&r>PMUkiSSpx_hWcZ{LKz#|{v?(V}ucV^u7KHR4N z6St<1uCtGBaUX7;1xIUDnk=+dWtL?Yo`M!BxCBoni27D=Bjb_)jUy`+j5A8vyMN26)bDt;Hz z2Xzocbs4mfAesjRwGl+CWg0F9oHg9d*>TVe;F^gpXd-@N85#kof}!F*=q4@?xIz5% z|AfM1(4`Sw3`(u7;RPiC7sc;}0YVGyKl=>wvb18VBXfPY1la`{h%R(Ska?XekV?t8 ze$dZ-xbOR*FZ-ZR`=AdkP>_ox1-?t1Z$g1@TAl{JjHl1i)4(SzIHtSExHG_?B#3Zk zS^Dz*|KLz>S|1Dqq^;IQ%>%y!#BabU{0f zvGAkz9H%2(cuj(v_C{c<<=w!RK4_8!O29r?U_HGNI6AN&2C_zBRrfIbhn2IvDoR{~xScroB^u=W54K;WFTJHQH%EJ^-1{C^3I^#6fc zqW{-`J_GJ!|MvjB?fb9zY9R z{8Sr_3!Kc2Fxga+92-(&050Ca|nZ5T!4{ZBv|@D{0~nr@Y8#l z{%biLPZuiKk(Qrg`Ua&A@LLSe=BHzr!^=#c&-_uFpa~$1bd6~iG0i{utvQ4dW99vz znZR#NB3PhJ0W+2{ZTxf^a~Pnk2cPNmRE%T}8#tF6O#eA!T8XFRDBp?nJX0QEK5yzL zV(OgWJ;n;El!F&->c6l2^N)6F#ZGiDcIr1gy7Du#m3 zV~k<#=|)jRfwGoUa$|+TonUMofPS#1hJe)5Z5D=TrOP+ zl~g>#<^6^9AV(9dlo7p7CVFKK(d$BqUKvZ8)HxfebVHcWkA#PAiO*W*^9b_^Wj=>V zD#ej=|Bbn&FgG9O7RcPJm|MKj37iL8LEB2W;bXwbR`@!I;$$HG2O7Tz?85K*vE;`% zuVIu*1(b@PSROCpqikY6_QXf&B0lnU;-kzWKJvRPPYG+|cYbR+^IXDwc2TNS81+c+ zu$qT-u;D4B>u~EV!A~+Ai?oXK+Q)ena9-Ukb0X)plx0q4{)?FZYUba`X}ya06?q@G zy*CI}#&S#ehU@WPT)(fgCgTWJCNmzRCf<6B@go?&kv04;!YJbiqua@RV9$ZMpYf55 zpTHbUD(nDiY89S3^1IWS+dr9xTagS43qAu82PAY}{MPHt&A^-+nEoG(4^puEEnMxkOJBUMXSxVZx&qlZIC_X02)q z%6~BByQ%YE>%=i+PGl_Y2GyEdU zaEQt%f5iN+6My+EbF1X1^O*l9)Jib|k_@l#yHWhKg4^3##vdg<(hn?E4NJI#dV%r^ zJ(V|5sdcXWmM2LlHjr#e9An`3f<`N28^gyLuUU1r=i+Y0e@&8zE!)p*5`kX zXwTBmY>gLFs`wBG{Sws`;}O@0;Yosx-?Kai$to&SsYmdA-Kbf{|Bdq%D!!j3&(~Ms zX^lD-lmUh}fVoUE;9Ewd?KFaQvzY!DmhEH4Pa}*nlDU1&G{H>sAEqg%R7vHhJ^VD4 zpFYk{|Azbgg%U^@8D1XXz05O}Dd#fh|B%f1rV5_QPcxtS4Bx?&%a~hT&xeq_mQvkx zw*OpG`8DReggKvOZacXDF6W+pm?^tiCqB%xNKf|UTf;S^n^@b=vfeIoz6Y7l27(1! zMfoH>75DMG-AwZk)39t}BIAcK{Q-v8GtCTw6@1|b?|#qtzZ0zZ5{!Gp2q}jZy}6e4a;9JJ!@XPz?P(lpZ-WZ#TBNX$(HRUh9~n_ z5JRx8i8+TbJci)_P8+zTWikC6#=K>ii&DgLYR9P!wIA^grSc}GtYh6O+y@^vxZtUY zV0@_&^(Zf5O}i+ zr}|p*X`un6xVM?;2O7=+{x4}mYT~zY2o_vN@_P)k9Z)g}mb>_^M@XOY28N#|SR5f( z{*du`j9&N!HQsv*57*wmcHfmMW#_m68Q~+b($BUdGDVQjm&cpofE@si*($2 z<=vduN#^qzMRAJ+N)=zyl}>MjCDes6Je%80oADH$&N4oX*~&{)UNJ-e3M9#A_&RAu zzL#{U3}Y{Sr~Vq?f#lT#51zoof++#)Gi+tbc%oDe6Rhjzw`3YYltC0(ul#KM4^m%> zsaM{n8G$0P_i~;*6UCeT%s0u;#LcZJy9j1&L>gT2G?KVUm$_Wglu946oRO5u%Q<&n z&T%=*=F4sV7~|Xc-2#61PDsfEwXo8^(lBcn5|IKpt@XX~Mmh%&m6LTfX z(N^t*RGRmQ+pYmOQ0r1+xa>0JC^|UD8SiK<1LmjknR5~L@EA3G~z>|k+1N(|Khj)#heGS zw%_Hqp5R(efb>b7qJC?nQL~H0;?|j>YoCB3QO`nYi|&xu*rB!EaY558DGdLkE#Z3G|wRnZgQp? z|DB&|p8vCqFP6jb6!*5{=~O+n%p05*Fy=|7_n{PLdx&NpWAgdkc_gRq8^%1x=?Uic z6H)43W6T~(bzd^fqd&@nr$*+0J4#8m>GYP|z&-8?rMf)~|3GQ)0;(yTFTq>#F>0mg zJ4kT{FG>C>=k+Vo_i(zD=y3-qILmV>?Y)ERdM{hc7kM;!j^8R~nodq1VQya#USCFh z&~A~6LxfSZGR`7O@o%OnXPV_qqm2S@GavTW#6{-MGfJ691hIoL_p#M_nNo3>F)uOZ z61Na5!Wd@|tcziq#|Wd>{F7@;Igv01E2dw&6b&oOTznuP$xn-~SY3vP(bUPWDOCPq=w~F1`mmA9tVIihFQ-crtDP_ZQo7^LLoof%~@$#ZKJ6JxuJv z4crxCH*Vh^F7Cw5+il`*x;I-~!kyVC#S^&M`ZT^fa#p@9ew3e9Vnr`*dXAT;Pl!`Y{|@p~2U^EiEX z8Gj!|8$HK(I@>0CxAwjP%zd=B+dHQhs|NV?1Hnss9|8PtT3eTXptN^W?>$Ht;#(V{ zcW>_{w5JDqVa-ALIMQ8&ktN3eNNMk~-Y0QZ(TS{y%09YkO>i&Ij(QJBCcXGN)=V!#tOhA0SvH!n6Os z&o64{$r2xo&? zYq+5>*ELxQ#!t>DJ-=HWRCaz3+MrX<5A?&E(33s|Z~97sQdW+itDm3FB{0y@*4D~O z@8RPkFyoc%t>rd+xkDO~1o1NClb!MZ>k!mAnG=;+iRh*4J49mVilxHJUcK5R{Or_Mm#tr^zOnp}NSvU)B_8S!qn3#C zY6<<8sK-0hD)Eq7CC6bFqevya>vahRCn;Qtg9VAoagU0#_KQz3xP?VnS%r%*U)&Yr z>?Xx0zXk!d)Y-)|Ga<1cJtD%C>fzz(8Ye@uY3aBV5C1X(s|3iV zczB4hiK9+tmp?Y5am3=hS(oOUmWGvg56({RESxZ|BWLb23ll4b&ddwSPa82Jzhz)Z z-hz+x5&m~X#>(RPz?$~jdHZT@PCaOd2wLMCg%z^U>e7bs^A|;OmrVFdNU0Q_<1#ccGD}e+?b|$b_JKm!>#1tvlbl{f zb?_gkAK%V(rS_7oaD#DBOi9@aN)j64pr9P$|9JM5Zt$+(pK+YE*ga+Rn(=cNd8UnB zU6E0p9_*NrnqGrHX^QLY#U9CHR*#>z*ek7$9%ckPW~QZAr3E{n?OZRBQ}=8geDGNJ znC`V1hfeL8&{5T~adGJAijwwq9Wf(E_GIkZntg~KugT=c)yp<53K?BVkE2J{qO=A0 zHe@8e*cyf}rQ}Ga9M51YA0N+ydqDa%1=!^ul3cytmf6BRoI70Nv%4b@83 ziGovCF&J^|Gd5k~^aStwS=&qb~ex~rs zpA>Rm=;$c}Uwm$U<44QOJ7%S-Z&YmItG;MjhJ4*Sv$!s+WL(CSVMFG^ zCQ)DI@mrB>&cdjmOs{c=SFZN;O;A;-m*jtm#Qp8*I3BdVU(0 z7n0xg*n)ktF02{mn;M~B8vgiOtrzh9k_ch5c%k}}`mXxu>G^LxG6FgGPF638|CxEZ z64PjynxSbvT9Z$vK42po_szbry4WWrVr5)T$k49G7OIyP;=3%-qVV+mHy#^4{P8#2 zE~ppPf2hCEz$3kfRrMtXAIU-T&T(?Ev$eM|>V&o4AS?E;81b)B$IDglVV3Z8y{o4Z zZVY#od*`VtTCBP?O|-@yZWjlw)J13H*;q$CH&1vj*!h^)60CN5?KklnE<6BTweB*$ zZtif-#u}{Awb8S|HrPGbIn+5gHP|^>epu{RC#_c}iT&#pk9F#3acrG9fifRce-#PR z_c&o~bH>J?BeQ@qXG4dcXdeQ=dhzkX|R+s#C!4}s7R5l zP77!smUMV}urVVSm?yYo(>fgKnA@J?(|M+V83T4zGysD5U9YkW#kSyW1)UuwdT zhLog%lc*NpFDpygR@6hM_wC`})>XXmBRDb*&#?I1=pThs=a%XBiECWtQ%zu@5SZyS&^;K>T{(suO)M(0=k*FJE+Qbp6YR)IIW1|1IMFF`JfkmQ836&J1tMA3b7c=_6Ow#TTz_SmU?8eDka!izZAwR8>*?*1Cm9YsVd*KY!Pdu9w}S z!|#n899`L3P&Idiu4H$SuvXX2JRYWjVNhs<>9#zsa(_blg z^3{I@eKD-(s`#qfIWp4T=vx~ghVNEhzmc$0eE^@}Sz30_mV^QGLZGEGXsHDC9Y%S& zxjH#{qjtR9b!}Gt)uiPKJ@r=l9M`gMQo`<)IWzN%UXbqz z>iKa+MBTteryH}^U3GH_^SL)RBXjcVvd(Sp>?4d(~oVhx(HXCO&otoLE`vf=uI4 zb72?_QFq>6v9bRCk{brW@cu%v9!Mq(fpU7bjKP2gc8Db#llh-iS?DNa>wq;FX-Ejb z$AsoTP#;mTpnBl;hniEH!a_oZXRP|<{v@NRrK@!B!$X!o(OR*1c+Bk9s;<#4R<=6t z^s#P}Uf4DLyKmpB95f=@&bHguVX^w_-&3mMo;o${l_QOt3Ks0DT(G=wQk;8Is&7Wi z2uzkZZb7#knl(tSIo5i7!N}5$(C%sUhVx_;4=DPcxBz8^_l@86p*vPy$5N0YjYIiI z!VUz&+ZiPJG$m`wOWSAs_}y#8$;EN*@}=Yn zxxqC_!&j8#w(c0dcr}%TEdR5*M%?b<1?%i;58H1vTC8)f=9WNIXuiJ}FVf;t19Ow2 zQwslW9vGDRl(?8-u}R5;8*i-X8zki3yD(}Ie;dhDayoD8Vr(;yU1TKtMlbg2xW6Hz zrtp%X-sg=plnI~s6fK{AgPN|nFDq)1-T2yUjqW|PNS6zkE#Rw21{&p_Poe>PGCq~& z4%fftMz-?wjh_^U|A-S4#D_c8t?F5ne!qB1SBUQ+E0R&NJFC<4`1m>`8=e~MF7Vs0 zoVZb|oKVKDU8_!6yY^4{>Gb##6S9s^*76%JIKytm@y3tHFdJVtD;0Bhs$mnShng`PRK{{CTM0k&eX?LxeMMb@^SZdyqEgh2*j=}3c2-iu{Hk$N6N5Wr(huETzk79I=7{>YS;^0>z>g*Nav@)uE#;VfRI-^x=cXR97)P@yz4xg|xrQXFQGbUwp zVUkbNhv&P0erjFOieFF9`bXW=Pdl2|MHYAr9WbnOVtUb|^AevSWSI{Tmum!rj$7b4P~dq-8WN?=IQ!$?D;|U;p3w)4l5wLzhO!%61QJ z6|%Kc?!0|1I>P<&@g1S=R` zXc(KT1fPE7zAV-Fs~9Hsn2b)cA*ec*oFwtK8ixRM(DJeEW2;lZ#e)P`u(H$$~(nv2&x)m*b{QuCVa z>S6VUuxmd)Zr#2g0{*?OMG1mF8_l8zxU%vw7f>`bjP5 zX}dAEaX{-0f~9b_QQCYg^BAoGp#J*}S6sX1%9ZyY`eJF$@+W%kn)*Q5(5#JPmOQ(o zA@P|9Mja}zIO4YM_XpMq#~&Y?5jkLU;D9mvUjFC2%U45ucKQXQ$3PQ$ifgka`*Fb& z<+rnxpNE?u$=OXd7<8c~bHfS^rE-oGHX^oo%bV)=$L>75>fJ-LGp=5OLe)>yzwcIm z`QYx_?Zehw_-M)JSk4u!ZCW9;rTWxcSz*;dZnL&zLk<;ZWnKsfHX;N0UqL<3PL{p= zd%l<_4-V2}LBh1N=byX#akHi#ZsEqvv^9tRyqHr!&(m{c@1UN~SAk8>=ev8l&AP6F zu0yHz0|EmjCu^n6&)Yj#(>Z&Y@SGr70aS2{=HX#kIu@;Cpxeop)E@Ptl8w*&xN+r+ zb-Cqdrmpzt$heUwS5|LGY*4QRiND=B{w+5VD)a|M;CCm-$3^Z8O`Lm0y?SclW7EBz z#1lJv28TpLs?n&81r|+Vu}3mMQ&xY}6fc|zS}++cQjf2e_pdstzH;BTp2@iAt7n@$ z8`JF5J(Xt7%gNxUNOli{lcpt7P%=JDnSNBoiaFeEp+2E5m(xM#EZLp2li}==CBpat z8~j;k;E&pWOzcd*%RZJRP{l>XTDg0;uWwK~!# zfS;Y@OMOc*>hxTczDj3D1Y#(}7m6_|sH$UkbjYQz^(+$&kErjR!-{-Q!Ya9}r}M_k z@|$aWE@*r?5>78UoJV;uXoga}pOMi`Y5BK6;Ut>pbeQQtgq*(Jo^PS=yL`lyx8)!X4I{rTUA z>K?ydqHL<^UJ!NtN4>*<5Q>m6M^?63m=dq&dgZFx%e^%C7HA@#H7rrkiuhs?I33*Md^ z&-wywl{WWVtOZ63n)adh{&CjlA)kKe$HlR=%MZUW?-$`7S+(%Mi*swYFR057di=iH zor}lk1lJyR+XPp1+1GD9&{3eiq5in&i#H$WDBLw@#`XuFp8Vp+nbW!-e0JKZCz?qT zvZZTLUVCU6C3dj0lgNzxu}7NSvCvS(^%jdcY~SV4)d#-YziaUw-Rqv2wq+Z3N9s?^ zU395yXwQ@Kn-%vg>X<|2RS)Z_yx#C`kxQ5(%%c}B)}oc3*OQZZPMC|X7)nm9rR0iH zh+Ag79?Hp&tR1Z${P&W+@<)$YesrpERFg*USTJ^Q;7t{%hee6NW>D{`n|fIC%{!-- z1*&(7kN&I*O%oJxi4_jXKgYq^(b3*s@`f&K`}bfMxJ^8wu-Ab>j5VV9*=$>Lu3alR zwCab8S9(|6b9(abro?UA5h?q~!!u5IhV?9x8+-1Se~PbNT)KQJTAK-;ayaZ*qU4w3 zWEbl+z%AS$1$isz!#5Yf{xn%Nv6gPmeY90CPhKRDMaRs;g+orX7IZXaCeIpEwy3ad z^#cuaPgLe++*LbdW=&k;gu*fF%F4Gq?G_Y#Ur=gLe#@|dbt4mFn^sR~zH54VoT)n? zF*vUwC%3k6K+2T0&9jcoOebsf2wDn7Zy9NBW3*~BV0wdwLMz!YHVr(t2^RR^u7^4` zNckW@ovMDN58A!^`Zap{Fh)n(Rr0|&2+bcBPGZ1I8M@Z%p2vfRA;#YuLy zPaJ>J)^2C_lEagIUpe+_NcZ<{_2;@m8#9zeH#*`b1ua@P>i*@*xf|JszFLZ$o`e;q zST;wNQ*yZ3nyKy@lw{Wco-4BLnt1PhjRtGPIl9HC2Z?VVyYaLB{EcC0vs>chC(X)M z*uF|vCCNy$x=^x1PKH25c!yfGdD;=@=p@bQ(>Qw=jLEbPne6J}LGw|{BUC|kk&7cl zz>uRsie7KA51j&b+UBDS8Yx&&ydoMLmk#)zbm}Jr1t4FKdOKY6qr@Y{iw$wi@m& ztwTVGV5x|V1wS3XAijUHXTuyhLh0`Dl)t($z2_=q`xabwfr~YH4F-IT6+3Q5qt=sX z4T$E$P3hW%IN0dsqa5y;8Kl&E-54)F9=KAsZ|~~gr(g`|{T}P1zkml6Z!0*&2{~f4aGF)GO-FpMIH$+LvRd`R4Y|n8EkcLnzCBYy0cn>0q zY2@sk3GNj6M%DRg6ARpF_(XL5S75=s+tURT##Wb+oorZH6 z!Ce;{<}QGwS&%dnkpq7CdO9S9j1dv8M)*u^Mw>RBUXLXpUT)Q7)MV8pHHA{`!$z2w zeQ33y-)0Cckjg>jb4uK;^EyPy(JF@^Z=F@b?A4Xy)|JMO?K(cEaQ^t*kn!UZn#NTQ z44LkhJ~rjF!A)H^bY-2@;Ay!dk1lDydU;#ThQdV$&W)ef9$!0e)Bfsd*sGT&^?s{7 z2<>Wqz7c*ti)dc2?VHc>s+4A#&3?YPzWBoAn7Ls|>X!F=LB?)z_j!!P1g&H;3Iop;WQO~dUH73ki;}zd9u4-^db8Er; z`rOcQZX@nnN{Xq@7?T_~YHWg-{oA)!vRiUC#Epc4hPQMSF23jCaa^~Pp{FOHrvNF6 zY(Suki)6Bfv=((~GY75JVQS3FC|Cf3r9|CXRAzBDu#zW!TykbYdG{+Fd-j*#^Pi&; zveMaExw*V#w|d2^^WCnpih?yW-I^X*v08|?mac6dzG+IPON{HPU9sU?L!#xSu~TX@ zibozKyNT06$|^*^L}2~X+1}1BB*Y=m(B|tVFzh4j2M70 zA0Xv=Vl$MT1wx{KYl~|p+6~o~*R&}y5a#tZPY*HpsSmath%ikWn^Ph324(CS6Hyi} z+PvQP^b~o6)Ax+`jdC1T=2OxsARC*>9)Tm08}Cj}OvP~KQ)u#Y zc68L+I}B|b(XuZwEMiemKydwF-xz1#5TlbzQRCp6amv(D+iIhSdU$#z*0$wF<$0!% zh0enGLvc&N@Z}t>j7C2{J0r|H{A?SpVT&!9`v)UpFk&y;Z112lB@4!5w3VlqosDVw z*0HrqhT2(o-?!byYFJh2k&WTaFc~wV>*``sTZ*%nj5k%s^h}h`#O8PntD5}&gQ`FE zT}UGP>#xD)JHhv{#US6tGDOo#+FbZKyrFCE0EVou?)&QX$W-6t@R%4xhW&m~ZxtUM zoD~{3U|=B_sfebN{lLu`{R)1Vw~L*juhkMmn)GSy)ja9DW$|!bc%G;C;Na-Ict>G8 zqWhI2zwpSW{zL_<4%FG&dB45?f!Dy?KYz(AQdy7i@ zriEmcHJ8JvWQdYu>pUX-j5^uxIW;J^dr=t93p$Va;o6=Pk4`Mz(IPUB1EAcFVDy z*4D+PscqA1eWoAXZf%`cnmTvJSl?z{@s*V6!`93jJhx=S%J>mlOUMftT9GyX%tF;# zelTvRS5bAw!bew9yAY6G!k5GwvDyi*Qm1%%Y0^7evI;S<4u~e$WN! zMq$r60xdFtoC1qczSRxrH>O%vB*kwX=Nsb2u0A!r^WlcrF{>YJ%5EJuG(4_(WZ?n~ z5S>m_9$zu_m4`Q0PI;oM`Q?W$I_{`a;RXEXbyp(Q9njhuJbj<&skeF zb`3`QmB%KR&o1?_);U{a&)72PH;${qps#uulKz%GEoUT+X-{seh7SMn@!#_^+EMqLc_SqK_SgE3)<`RLhDoy z@uuI%v0;O!=Z?H@$^5HNZLi4~n{wv2*X4=vqvmhgSJk-2G6ZWqMVSJr-7x~&D>_G5 zLh2!!{UH;0OAFJ+wZ4`gHE~H*K(=p~Z?bP$*OVd`1WjGZoStvA+GQ|Qt~xkE)guQx zjPk>fLkw93+X0T@uE7Q=z|#n$WOj?#Dqxtmm?c`?<{2!TJo8uzHF#)al0giav?n9= zQ0>S$nc>3=%3IRM-PhW5Y;ao2n$h*MgKLTkX6H6P?iLwvXHZ65bz1b0L8j=@(+cZ1 zl*Rh5_KvC@7BOg`DRTJqis`#(Eo*%5xAG_YeDpG!(_2|NIoaCS8`?ZA(oygb*#-!R z;1V9=lZ~{$d5as;HglVm^@`(8=NDqm;^U)!(Pgwv85~hy@}BHAtut_#NuFF@_~nh| zJ$Lx!_$Li@u|&r&~Mb!@1?#%S;uILK4foawn=SKPBcbxnIf;<6<% z@|@o$LYZ@oGezL!C4VL;Wlkv_SB+y9;^)j;7jvhp8ZvY}W`{{5mnHKlRt;KbUAc|C|Y?0py|wS{E2%!Cb?32TErg%Z)GgEno&q7Am4 zKKlOHL!#m-HBCKnS-n?Ik=@irad*$ZdfpM|RGcdA9jLmp_WWsf?PFu>qLaM)4YnH3 zo5O8-`Yrl&kh2m8`}^jF*R*GpA3IiNV`nfpWaM~@VP|^s(&lVXUG1#b8f~XeUauS3 z^IG1XwNb&#)?uIH`t?a`5qsYI2{tuJ5n-z;57Aqr*0Hq!U~g*>3Tty zbvToO_4Rl%@yjUq3tq0~W@=uK!Bsf4K72GGrKlt(IUrlzNRg(x*OC)*8d8(OhsBfa z`Uh-RB-SRwB)1D;ej?P(&c>d?st{{Qu~ighNqd(!qbtp`1@qL+XcR9*FW(ziyX>y| z;6>qs=ETQG&%u;;#=y1hMO}k4H;;;PdpGE%L$hCbaP27jvja>i`l;Fwgm5KIr6(fo9*)# z)cvDFj97J`I5~KgPx6#)>N6c5EMFY77rK@X2_Kz@ZVOfelckg#Utb>|XBU@HNy6YF zj3G8oey-kD9$`9Nj0e>=!i_LZieu51t#>HL44aPspk25^f6a8nZHP){sH139IT>1)EI4tH!K~pOZbLHEv~N{@kMC`2_{@i;L#M+DOZw zr}N}>fp9yu{~ zik2+(eSZ-aiuCI%hc1fmFZ6u&y|4Oih=}r4-yb9#mLKg@l%4?j(;GdVN0+NVNpe3c zphX)%toV;UMoS}GplJ4B{d&btvAXf=?*_%@#xGPG>An9`zQEb2OsSacv_o=Mu1B;% z3iGGQJnzV}ZMf-w^m9Kn^>gPla#ti(o4pXNA`IG8FhZLcdx)myJ@ILWhOgclHD*}J zoSghw*(rtNPA-~oC_8!Rn*6k>`QxV-x8)6h@0_ zKQbgEBRIHnMtSADYTuxyVKrT)5rGSRLjv+MGKv#|%BGeUE}Re&IHqvIwrV*uY)MsY z{*X}nfujc|4a-BM{%d+C83?5Krd(x=V-wXPk^_8c%ZBxQ4GTxzl@ zB3qpi9-ETn8{#}j?-XT_?XK|2^I)~?Lwqx_i5=`%oD2tH`V%ButBDpkM|ML^H?+ox+*+(fCuL#$JcM0d$=e53~e%JN#s|}B=%InI^NNS8U!{>Y+&!PJXj# z(>D)HsDI!ax7L^O(jnpca_gK|)b9@-RKI(zQ@jZR#fs;P&i#_}< zPkivZ&1=3sH<|x_bCzP~PbjX6ll}$x!V&P`UF_`w1FcTBuYyrmiYz;BTYyeo)9x)ab@l>C#$ego9dhlkxdb?!!jaVGX^GB zWd$OvI%P`9sH3YYvSY^gG`FXhO^GkBpDe7hS1a7?mahxRw+e0TVb4YP+&7(H(0s@pW0gMVlq!cSSYa#h0|Z*{(y zr0y3V)D)KI|Nh$T8cp+`+J}(v)g+yVVHf(sEs?-)1S*k12DcR}sGjC4b@H$1C61W8 zI-Q4ZJghiMKZ#cIwZ_7hoXSm0YZPM=LRLkbpV(bp+1)r{TTRWj@r{j>Croa{MNt)- z$Bo}yS-E-qxXl%9Gf~c&<3`U!KHX6IaNTo|1vz0?R%dT-ZM3n$2d{X{L$62WA;JgYH1B`0z~T~cyac|rTq z%7^EpwcV=}DJH`e%nE&T>>|P)u~co8_Id_r*;!(6p)m8L@RnE{8mn){1e-ljgV;25 zQGD9c!7VLgM@{gm&uL7@x4kFCrY3F}Sv@1Ibz(tz`97t{-+Ng=W*UaZN9}G%TDaE*g+FUeW&Q#V<7`KJQZ$4vO9M6zYVV) z?*F()Y2CO;-TU5=Ewkg?{Hn)P`}g+p8paG=vCg2}he{FB%jyl?1Mp$pB$xA8xpZ~F z)eG@x7950<>r5lQdDoU=*+ZS>UjmC_>i5jZ95V0VBsr?}@9GVuz}h-(-s5YxJT))n z*DnpLe_Mi{cw8t#nN9{rAjns3fVsO6dX z+zYkDD&Hh$wrs;!{hUc$T-Hf_PI-PfNL~M-l>;n6OlI+Gz>^{XQHS&vw zC!iVc99yH66=IQf(q0=>+;?mg*>5Eu8pum#CklI3rH!Z12DCw?7oL9gfckxM$h`WE@KShr`R)pL$_wVgBh#pvxqz>~} zyNkqB(Uhdl3_xw1!rt;4{dt{9c9nOb4ouRL-lx=GC8L}yPrY#p6c1sJj1!qmv8O+U zG*70j2Fr1`xss=we8OIHPrg?W5%<0QQK_tbgv6PyV@w!#B z1|~JktL_&Cxuzg%czuaDV~K)9d*`@5W49p!^8p-om4=ESF&pP0TzC`w^@Kz-O;bOb zcJan-Y4VI)Eqp|Y^!;sV?l+e8(Ns&Hg2wi|?1G4nUA6Q%5OZ!gZEm5$mla2W%I=&? zfF{ySnY@f2TBt-N!rw+q_7@apwv?D*zd-KAMAUE<9x&EQr05)XovW*pgWk#k|5Ny) zHNs*zSPAnD=S)O+lKabMvkBYK+7USXbw&I`uxsdi<*@QlK+okVvY&s?=X2%kpdQaA zwN(8`tVFcyh`w+*j%=JNU!-qN?pEC>dJu9(P+O1|%W)FnDL$u_bsA~byF2Sk3qIg7 z{)whtJpC6mq<7{6y?_1}G^9~2uNJ9_HEQS8PotEZJdq`e(Io2qfjQCcJfeB1B@m5L z(vPNHI)7W51a1k;N0jt_cw3reu465)mfk#;Naxfi5&AqfMmP$REDYiS6+b^2A%VWw z&v)t{x`QgCJph^?2Hz$ylBS8ZYxe~0*>d~%$l0PaSUoRq+#&D%bD(6;Z#$^AMnIBd z+;ZtSfDv&3osie|JVjZHwdR(qMSrn&lFW5_nM)ppI53L_DVnsaA5FVF^Dk(qPMHr( z*5<#Up}wTiREsW_NRF}Y*mlpx)giChB(MuX=Zbn5Z6T z5RGDDle!;X&aB=C^^X_?>Ms^&24Zu(qv9Q1bb)eQw9XF0YmlFHo3oQsk{yp6=y%%o ztY;=U#iO)oG)0Yhc$#O>4mz4(hilPeCgI^>61`5(z!bIct$V&RsK3=VL5{8>T`nKt4E(Ier%=|OqP*#bkv2M$EIt6 zb;&7lT0GgHmdG1(!(*OP_QWJ)P1}YbyWIKIDmeCu>ypBD$ae1Kdh$gal%>A^NYgG4 z{REfU#ayK}xKEi9c?7dx4gHNuBjBJ?VPw3D0 zO5={Vbv)Ubt3D~-F1@>B*=z`+;s!e1cITgwIrGwf>UH@X%-6Nv$Tmn(zJ`Nzn}@S@PS=FdRv+a`j7f(s&Tghpsy;rIIBzNOSQ>+=Nr!nXRj6zR+AB-NnTe57eolCT zO=q&*wRQE=6;1ZmnTLPEhE14%`1~IE@;n1Y|u6ybV8ggB2@@533pO`!IK>6K2 z9gPu6=j!s!W##vbo_$|r@N8dW>a1m6nXOF~UQV`SYjWDg=Z96wvwb_?T~V1;v3ZtT z?R^Uue|mW<2RhwZ-mt9G9C@;GcS6Yau!xbP?|3JF>c9=T^Y+&bY^3O?_PZ}M%v&IS ziftY-GzSqWXbqDPk%E{aXcl7`jsA4Y1MnO}!>xp-E!0X5a?M2}kjBzVh~|@iH0`4N zwlq&0sFe^OgD3%eo3nr9@*>wiQ%Eg;r}~dh*D})TpCTFbl#_CdxJX0c0^6MBfClrp zPZU@BQMBjWj^ZLo!aR%^^(V2kf8>JiT3cwO7WGxo*qy_uPdiPoLmJqpu~?sO!OC9* z*8akBTQB20Q3I4DBiGu+e2!L#N7 zGRtC>hq$`%!TRELmlrPT4lu~sqt*Xrd>>dGT^HXNBgEkE63SvCMy!2iVbzk+89qLl z^#$YBj*6P;R)2cw{J$NYIJHP#^v{_)npgT~`LzX=#&tKZ9CF9w^XGs2_L2GxgPImD zO^Y2{8e6{d-Vt-QQ(aR$z)bF?)l}D_n!o#Ob2eTpH2YP&bI)~MV{Q-USbG!(W@!(9 zq-hru|AL0v1M`8^!14y_@{fG7SstP>i0b)7VKxXirQtTi6jIA~R7fUK#Tj@CsNqE+w?GS~QMbB%w>H5GuJWJ`_zk*1yQ zjX_=gnTBed`Jl#6-JXW)lV~socu{=@L}^*E4bXN)SsuE3#wfMpb{@_PTca$GCB6e| zA^sA+qn+dC?G+Hei;!(E0Bhc2iMfEVM>iXI{1|rPk^2PZd zBcu~i9-)i+M|sSBVOd;#{IunmQ}UiKYd|YqOK%5CM>^1or|E#^!Ucx6pi7<$i!u?y z-c!H1ur|Ky8$rY4yC{?1V?Fg7-+4~mXsqqyQ;uHthdH&G&li1s%B4$x=A-7*nLCY- zWE55M&pdiG0gpIf?On#VGM-PWvG<3$Ce@)_%lQUl!QaxRD9ZIsu4jx$L?hrSqD=+& zvosnX@<*8u{Lzlv@;S#N5AngsGx7FZD*L!p$m4FyGVHxf!C||WG*HuHv$D3n2UJ|%yzF4{*5zMizB3>UeL&i^1F}* z>l3MN_-mO{AqSe3V!v`bupE`GPe88-gH=k-@eQmfL*cgvWyrEwJhRMA@MZlklZ zHTbCOy~VQdBc`1`yVc()zPELamlj?$ZI%`q<%Xc5_Nz~!myAjF%`FI8(ec=09lwWj zh;KZiq3)`g-ch*Z0EhU(I?)`Vm3u-YYpYCnv9|iJPLO}{4EF@`ER5(02cVBzy$|NY zJwfABF5S2-pMUD9Cun@irL}*-huV_Hr$Rh?TRu;4ThjPcK%zg%gPwykj~XBDIrgA| zXR8(}mpGm(MaqHK;hY*7=`lFaXl0CxbCfzgDC7xiv8b@*A-HG4yeq&W?f#6?2K=Ixi7Nm`fOMvuRe8qn()x zuEHS8fQw0O&nvUARI@~gGExPflrb;YVbS{;_MjSj-o#m&vh)e84a zfG&vw!f{&{?ay15xiC$_);ve4Yxi@d2iN}dlK*3A$I|3|=_gVy`91mR{o8vcZ$B;X zOX``dOWyDs{@d{TYjglrDf$-Ta2$0wtX3oRVG<;t@U5wq?yiR zWYx5}kSNHQPUOYD#CoFh#TI!pP=9I0?;%&EndTt(OP!@( z5+8LSooEIP_;8spCZTp7RtHifyY?>Od&{}3XcIa=3q5%b(kH!lVMiX9S4z^w-rpah zCP+F#-m}=lpu7!oVsbB0Su<6yg^F4jZkV#<4vTf7XNvZ={HWz^j9wMemp!;o8+((I zG7eV69HxHQf!j+YxfC{nqA#78FV2U8uO)>44_t93VEMev*H0eZwk-8; zd*tDT)0Qd0J(a_zF7Fu#=|}JyI_)&^8alkwXmWN3Gh?K^O5_`QA;4ZhhS(zi1_{KZl6uo(DpU;=$ zpqu5yInKns5d5w-$!RDOf6f+ zj$D#&XMN7S!g3%gZtPH{I5y8OzmiYPXs0N$7mSV@8FS-8)3!>~7$(uCt|+! z6xU4u{SsdFU@w!_BFM``OfXtR{}m19!xmZNQ!Wnw1t0P+FhZ}^nO1KdV6PBqdy;AD3$#VnFGvhVKw-k5@u@jj>noEvg>bF+8%vTAd( zm)inELNFl<@b%RKuxQtVen{D|)MCb6wDl|uwR{gJ{&L`n`L-CE2xvD3(Jid9T(Z=3o*yv)pzDW1TlX$2%b0wk;cBCDw(G#x}duR4g41h>5Yx zXEfoR$tgA;m89rVT5h?W7p(1RM<7!2X0#R0YWlgx;@=mp#Eo5Zde)vJ;U#Z(tT_K>>pdZwb?J{rR62q(Ftr@P}q(GpG)O!{ww~zB7bP*wpOQqKFzB! ztD9Rpmn|Q@na>Mi)i!i{ctrA)@+nI1$vL$l21P?Mc&3CZUH0BG>+VsJo;HFj37GPI)L^DK5UI1;}PUBj4U|0 z0X*I%eDCnS5o8yS#+)y#lyuvC-(V|6`5Hti1hF~WKUcnYfTd3}OSY7`B;o1-%lJnp zJLa^OFeA%_|4o2ECt4c<_j2AZa6)15d(&;&qZQ+UNuTY}%?lkE3Q zBVnisNsI>KTk|DnfAy^o&UtIT$^CD=0?J=yl#jva&^z+zJ9BY!qh18>tQLR!DgEse z*zK$Ajd&K)C13J5d!rFUKoHM6Doyy6a%=IE^v+JbOEpUuGeXuf%@XqVJROI{)EL^I z35UEWb0dI$0mlzSrRQ63@auDHX@0H#Xp@8;!vDGwr#=I`LLWiV_qdZQ-XyOT=GsD?m zghL}^C#+U{hQMP0+Ir8?`NcRhl@IV9(sA_m$V{Bs;B#f;Q1*pm)THk z9B9>aAC*_{A7?eQzSuZ;2DxSInfu3~YZu4KmxwpS{o}mE)=tNPq=p$m-(>Km6X6bI zU^gP-E)+LqZZP~;LgD&aIu_yH=#^sc5aXKKU{CCwn|HKD^|%E&WqZY!MOg};+lxVL zl%tP~%EC7uPP12`6y2|-{Fj>x5uV1C0*PFYJXbFo`8P9lb8~T#I9O9y8hw2{RY91e zp?oITF4o7hRB17r-cTPu>7A-^h;>V8w7vDL=`Cl+mfh_U-P%Cs4DaeTN9~^-zPoK- zHmNT@9v`J&5!dy3lHKhjU(;Y`dl zh%j4P!Ue~YwDdtgTJgn#|174!@{kRPbI>ak)^pNxd40+!rx@X0)HtlJZ5fLRJaH;P zF!}}B<{lq~O6KH}>31_KH7zncW1hmRqSWPW=fUx4nW;l=@Pgry*5n4KI~qMyU1HwW zB$BkHfUHi5j!vOpWZc@Vrrz7irJ>m!=8fppM@J)y&=G^EZm}dT#YM#F$j_a~EG|wC zj}4@57C41%)U$XUqEq-|q=O^16r%h83vS1ph}zuDiNvd-#N}fpO*LzulIBt*!n;}>J zm!0E{;u+=MXXj5nN3&uX2J9T4)ju=E&R-RPP7Lyi2$qgjwwbdP0e)n{+F$ zsRhj~wkFoVZ^3raIv@%!P1B1hgbUEFC?kGDJO}%oA5su5Q z*}uR~W1pPu71Qo5BE#@|+2|BiS!&M9C-jNS4kG?G#$(_xNY9NxeyD=HW0jEN(C!!= ztx2AY9Uz`V11{3{BKtagHFn84Ua{?78g1VqxMK*gw?E)L(7gz?_t$39q3LAZ6n}K$ z+?=EzFnmwqgca7yzQifCdd%Et!t|cZX?+GfaLdJFwg?Kgmotu;mvDHn&5$#NqbPdNvZrH|2jG_X^`Kfv^AAZXpGG*i6vzZrio#A zYR|dgi(%W-q;Xmz_(!Xhx`d$OtR$zvNKL10T5Y^vUS^tmK%|}Y!}4LELwH1`mJcU* zgm}c^vf*fcJO4h*Z>PCiDur4tvzj0(mBd^_H0*7&nqTTny1U!Vzqkyq>T;)tZSrdy ze1wwOf8Ub@KPMp`9K`kGowQS*8TP?QJ^@#iVIQJUCCKvWxqh3lut1B;s@R+YH|cwh z-Ys1n{!ZHFzHUF5N+bgHO;4kn9NkNbwPoUBZ*PhdB+*wo(eY?<5Vm$kUIWww;T2KB zyUqDG7i1R`t$y6Oye0e}sDTRe^GqgJ+=C;-e0-8*y*7S5gJJ5x-ht4%0Pn!808~R6 zzC!}h_^!R|F-Q(iDiuh%?q^^&A-)I5I@)iA(l%t3TT0IH%c0q|LbUtJ z9)st{m-;r@Cx2w0Bvx}j37?)^#hwd5e;e_+&wl+m?)U7uK*_0lpHtp59yG;oWw+qD z4F?b@oi7~?bv~gmdLF;~wZUH)hWY)0jh9Fq_2a!HzSo>;jfRzB9dcWY=Yx?jfA}e# zC4i>Fh!VNOJtKC)*7%3~n%tXE=qd%NxEqJ(EK-blj<_Qa^fjiEw1R{6! z(H~X>Rgax|bSVPGr*Q!eBe}Tho3|fXl>r>a z@0b7Szb=er?oI69ed)Qj*FHgt=*1U1*FM|Hx$I&YK%fcp^vkLDUZfH8n9G^RoG=V`XlydA@+k}Ka0q(r~@%VGYNqEV`pZ(P;Hpp*Yf&AJvqrMk5FaGR_5&5TNkoP0`Yc968Z!eN5O4eU$ zZ`f2M#q14ufuLFf+kw}?16iON#lf<$v$iH)2>H0!Dq$~)r;m9W#h6};HBu|7sGY&6 zu#Jd7kO0v=`Wqlxd+=ODquE0*RGXSqJz8g`Ok20W{Kj*`y=^F_~W$`?{8rEwO}=z!XMI18|@ekJqY zupjKHdo4p_8!?et)Zl2|>L&RJq|^$GB`4)h)XD;g<&3VF2;a>q_QbgO#544XV2jx& zn5UdQF)2RbL7#9zDyu=?q_XfBdqR|j<-%=H8Gbj#tWLTHGs$7{avK|^VzEJ5`prs9 zF;_&o3BJ`p$vj$X3BQeL3{A!KLstY(PlHR9xr4#mEVa?#O{E(`yW$`)2IsHdaYOg+ z*U|houQ?S~zvg7~l66Rza`jekw2O|k$yX>mTvQ&G7Mbn|rQWER&>l93q@9axuEaV*K7=7upB zH%skiRoTe-h0#e9!π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: string { + return new LocalizedText(strings).localize(...languages) + } return { header: { - title: new LocalizedText({ - en: 'ENGAGE-HF Mobile App Health Summary', - }).localize(...languages), + title: localize({ + en: 'ENGAGE-HF Health Summary', + }), dateOfBirthLine(date: Date | null) { - return new LocalizedText({ + return localize({ en: `DOB: ${date !== null ? formatDate(date) : '---'}`, - }).localize(...languages) + }) }, providerLine(name: string | null) { - return new LocalizedText({ + return localize({ en: `Provider: ${name ?? '---'}`, - }).localize(...languages) + }) }, nextAppointmentLine(appointment: FHIRAppointment | null) { const date = appointment?.start const providerNames = appointment?.providerNames ?? [] const providerText = providerNames.length === 0 ? '' : providerNames.join(', ') + ' ' - return new LocalizedText({ - en: `Next Appointment: ${providerText}${date !== undefined ? formatDate(date) : '---'}`, - }).localize(...languages) + return localize({ + en: `Next Appointment: ${providerText}${date !== undefined ? `${formatDate(date)} at ${formatTime(date)}` : '---'}`, + }) }, pageNumberTitle(number: number) { - return new LocalizedText({ + return localize({ en: `Page ${number}`, - }).localize(...languages) - }, - }, - medicationsSection: { - title: new LocalizedText({ - en: 'MEDICATIONS', - }).localize(...languages), - currentTitle: new LocalizedText({ - en: 'Current Medications', - }).localize(...languages), - currentText: new LocalizedText({ - en: 'Before your next clinic appointment, check off which medications you have been taking below:', - }).localize(...languages), - recommendationsTitle: new LocalizedText({ - en: 'Potential Positive Changes', - }).localize(...languages), - recommendationsText: new LocalizedText({ - en: 'Please discuss optimizing these medications with your care them at your next clinic appointment.', - }).localize(...languages), - recommendationsHint: new LocalizedText({ - en: 'Aim to make one positive change!', - }).localize(...languages), - }, - medicationsTable: { - nameHeader: new LocalizedText({ - en: 'My medications', - }).localize(...languages), - doseHeader: new LocalizedText({ - en: 'Dose', - }).localize(...languages), - targetDoseHeader: new LocalizedText({ - en: 'Target dose', - }).localize(...languages), - recommendationHeader: new LocalizedText({ - en: 'Potential Positive Change', - }).localize(...languages), - commentsHeader: new LocalizedText({ - en: 'Questions/Comments', - }).localize(...languages), - doseSchedule( - schedule: UserMedicationRecommendationDoseSchedule, - unit: string, - ): string { - const prefix = - schedule.quantity.map((quantity) => quantity.toString()).join('/') + - ' ' + - unit + - ' ' - switch (schedule.frequency) { - case 1: - return new LocalizedText({ - en: prefix + 'daily', - }).localize(...languages) - case 2: - return new LocalizedText({ - en: prefix + 'twice daily', - }).localize(...languages) - default: - return new LocalizedText({ - en: prefix + `${schedule.frequency}x daily`, - }).localize(...languages) - } - }, - }, - vitalsSection: { - title: new LocalizedText({ - en: 'VITALS OVER LAST 2 WEEKS', - }).localize(...languages), - averageSystolicText(number: number | null) { - const observationText = formatValue(number, null, { - fractionalDigitCount: 0, - }) - return new LocalizedText({ - en: `Average Systolic Blood Pressure: ${observationText}`, - }).localize(...languages) - }, - averageDiastolicText(number: number | null) { - const observationText = formatValue(number, null, { - fractionalDigitCount: 0, }) - return new LocalizedText({ - en: `Average Diastolic Blood Pressure: ${observationText}`, - }).localize(...languages) }, - averageHeartRateText(number: number | null) { - const observationText = formatValue(number, null, { - fractionalDigitCount: 0, - }) - return new LocalizedText({ - en: `Average Heart Rate: ${observationText}`, - }).localize(...languages) + }, + keyPointsSection: { + title: localize({ + en: 'KEY POINTS', + }), + messageTexts: { + optimizationsAvailable: localize({ + en: 'There are possible options to improve your heart medicines. See the list of "Potential Med Changes" below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }), + missingHeartObservations: localize({ + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }), + onTargetDose: localize({ + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable, and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }), + symptomsWorsened: localize({ + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', + }), + weightIncreased: localize({ + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }), + dizzinessIncreased: localize({ + en: 'Your dizziness is more bothersome. Discuss with your care team options to improve your dizziness, and watch the dizziness educational video.', + }), + missingAllObservations: localize({ + en: 'Check your blood pressure, heart rate, and weight more frequently and discuss with your care team how adjusting your medicines can help you feel better.', + }), }, - currentBodyWeightText( - observation: { value: number; unit: QuantityUnit } | null, - ) { - const observationText = formatValue( - observation?.value ?? null, - observation?.unit ?? null, - { fractionalDigitCount: 0 }, + messages(data: HealthSummaryData): string[] { + const currentScore = data.symptomScores.at(0) + const previousScore = data.symptomScores.at(1) + const scoreAbove90 = + currentScore !== undefined ? + currentScore.overallScore >= 90 + : undefined + const scoreDecreased = + currentScore !== undefined && previousScore !== undefined ? + currentScore.overallScore - previousScore.overallScore < -10 + : undefined + const dizzinessScoreIncreased = + currentScore !== undefined && previousScore !== undefined ? + currentScore.dizzinessScore - previousScore.dizzinessScore > 1 + : undefined + + const optimizingRecommendations = data.recommendations.filter( + (recommendation) => + [ + UserMedicationRecommendationType.notStarted, + UserMedicationRecommendationType.improvementAvailable, + ].includes(recommendation.displayInformation.type), ) - return new LocalizedText({ - en: `Current Weight: ${observationText}`, - }).localize(...languages) - }, - averageBodyWeightText( - observation: { value: number; unit: QuantityUnit } | null, - ) { - const observationText = formatValue( - observation?.value ?? null, - observation?.unit ?? null, - { fractionalDigitCount: 0 }, + const observationsRequiredRecommendations = data.recommendations.filter( + (recommendation) => + [ + UserMedicationRecommendationType.moreLabObservationsRequired, + UserMedicationRecommendationType.morePatientObservationsRequired, + ].includes(recommendation.displayInformation.type), ) - return new LocalizedText({ - en: `Last Week Average Weight: ${observationText}`, - }).localize(...languages) - }, - dryWeightText(observation: { value: number; unit: QuantityUnit } | null) { - const observationText = formatValue( - observation?.value ?? null, - observation?.unit ?? null, - { fractionalDigitCount: 0 }, + const recommendationsAtTargetDose = data.recommendations.filter( + (recommendation) => + recommendation.displayInformation.type === + UserMedicationRecommendationType.targetDoseReached, ) - return new LocalizedText({ - en: `Prior Dry Weight: ${observationText}`, - }).localize(...languages) + + const result: string[] = [] + + if (optimizingRecommendations.length > 0) { + result.push(this.messageTexts.optimizationsAvailable) + } else if (observationsRequiredRecommendations.length > 0) { + result.push(this.messageTexts.missingHeartObservations) + } + + if (scoreDecreased === true) { + result.push(this.messageTexts.symptomsWorsened) + } else if (scoreDecreased === false && scoreAbove90 === true) { + result.push(this.messageTexts.weightIncreased) + } else if (scoreDecreased === false && scoreAbove90 === false) { + result.push(this.messageTexts.onTargetDose) + } + + if (result.length === 0) { + result.push(this.messageTexts.onTargetDose) + } + + return [] }, }, - symptomScoresSection: { - title: new LocalizedText({ - en: 'SYMPTOM SURVEY [KCCQ-12] REPORT', - }).localize(...languages), - description: new LocalizedText({ - en: 'These symptom scores range from 0-100.\nA score of 0 indicates severe symptoms.\nA score of 100 indicates you are doing extremely well.', - }).localize(...languages), - personalSummary: { - title: new LocalizedText({ - en: 'Personal Summary:', - }).localize(...languages), - above90Improving: new LocalizedText({ - en: 'Your heart symptoms score has increased. This means you are feeling better. Your score is overall very good. Continuing to take your meds will be important for keeping you feeling well.', - }).localize(...languages), - above90NotImproving: new LocalizedText({ - en: 'Your heart symptom score remains very good. Continuing to take your meds will be important for keeping you feeling well.', - }).localize(...languages), - below90Improving: new LocalizedText({ - en: 'Your heart symptoms score has increased. This means you have been feeling better. There is still room to continue improving how you feel. Getting on the best doses of heart failure medicines can help you feeling better. Consider discussing further with your care team.', - }).localize(...languages), - below90Stable: new LocalizedText({ - en: 'Your heart symptom score is stable. There is still room to continue improving how you feel. Getting on the best doses of heart failure medicines can help you feeling better. Consider discussing further with your care team.', - }).localize(...languages), - below90Worsening: new LocalizedText({ - en: 'Your heart symptoms score has decreased. This means you are feeling worse. Consider talking to your care team about adjusting your heart failure medications as these have been shown to improve symptoms long term.', - }).localize(...languages), + currentMedicationsSection: { + title: localize({ + en: 'CURRENT HEART MEDICATIONS', + }), + description: localize({ + en: 'Here are meds you are taking for your heart function, your current dose, and the target dose that we aim to get to. The target dose is the dose we know best helps strengthen your heart.', + }), + table: { + nameHeader: localize({ + en: 'My Medications', + }), + currentDoseHeader: localize({ + en: 'Current Dose', + }), + targetDoseHeader: localize({ + en: 'Target Dose', + }), + doseSchedule( + schedule: UserMedicationRecommendationDoseSchedule, + unit: string, + ): string { + const prefix = + schedule.quantity.map((quantity) => quantity.toString()).join('/') + + ' ' + + unit + + ' ' + switch (schedule.frequency) { + case 1: + return localize({ + en: prefix + 'daily', + }) + case 2: + return localize({ + en: prefix + 'twice daily', + }) + default: + return localize({ + en: prefix + `${schedule.frequency}x daily`, + }) + } + }, }, }, - symptomScoresTable: { - dateHeader: new LocalizedText({ + medicationRecommendationsSection: { + title: localize({ + en: 'POTENTIAL MED CHANGES TO HELP HEART', + }), + description: localize({ + en: 'These potential changes are based on your meds, vital signs, and lab values. These changes are expected to help your heart work better. Discuss these potential changes with your care team at your next clinic appointment.', + }), + hint: localize({ + en: 'Aim to make one positive change!', + }), + }, + symptomScoresSummarySection: { + title: localize({ + en: 'SYMPTOM [KCCQ-12] REPORT', + }), + description: localize({ + en: 'These symptom scores range from 0-100. 0 indicates severe symptoms. 100 indicates you are doing extremely well. Blue line is current and grey line is previous. This is the overall score. The Overall Score is an average of physical limits, social limits, quality of life, and symptoms. Detailed results for each are on the Table on page 2.', + }), + personalSummary(input: { + previousScore: SymptomScore | null + currentScore: SymptomScore | null + }): string | undefined { + const currentScore = input.currentScore + const previousScore = input.previousScore + if (currentScore !== null && previousScore !== null) { + const currentScoreText = currentScore.overallScore.toString() + '%' + const previousScoreText = previousScore.overallScore.toString() + '%' + + if (currentScore.overallScore >= 90) { + if (currentScore.overallScore - previousScore.overallScore >= 10) { + return localize({ + en: `Your heart symptoms score increased from ${previousScoreText} to ${currentScoreText}. This means you are feeling better. Your score is overall very good. Continuing to take your meds will be important for keeping you feeling well.`, + }) + } else { + return localize({ + en: 'Your heart symptom score remains very good. Continuing to take your meds will be important for keeping you feeling well.', + }) + } + } else { + const improvement = + currentScore.overallScore - previousScore.overallScore + if (improvement >= 10) { + return localize({ + en: `Your heart symptoms score increased from ${previousScoreText} to ${currentScoreText}. This means you have been feeling better. There is still room to continue improving how you feel. Getting on the best doses of heart failure medicines can help you feeling better. Consider discussing further with your care team.`, + }) + } else if (improvement > -10) { + return localize({ + en: 'Your heart symptom score is stable. There is still room to continue improving how you feel. Getting on the best doses of heart failure medicines can help you feeling better. Consider discussing further with your care team.', + }) + } else { + return localize({ + en: `Your heart symptoms score decreased from ${previousScoreText} to ${currentScoreText}. This means you are feeling worse. Consider talking to your care team about adjusting your heart failure medications as these have been shown to decrease symptoms long term.`, + }) + } + } + } + return undefined + }, + }, + symptomScoresTableSection: { + title: localize({ + en: 'Symptom Scores [KCCQ-12] Over Time', + }), + description: localize({ + en: 'This is a detailed report of your symptom scores over time. The graph above shows the overall score. 100 is better and 0 is worse. Each KCCQ-12 question is from one of these categories. Your Overall Score is the average of the other categories.', + }), + dateHeader: localize({ en: '', - }).localize(...languages), - overallScoreHeader: new LocalizedText({ + }), + overallScoreHeader: localize({ en: 'Overall Score', - }).localize(...languages), - physicalLimitsScoreHeader: new LocalizedText({ + }), + physicalLimitsScoreHeader: localize({ en: 'Physical Limits', - }).localize(...languages), - socialLimitsScoreHeader: new LocalizedText({ + }), + socialLimitsScoreHeader: localize({ en: 'Social Limits', - }).localize(...languages), - qualityOfLifeScoreHeader: new LocalizedText({ + }), + qualityOfLifeScoreHeader: localize({ en: 'Quality of Life', - }).localize(...languages), - symptomFrequencyScoreHeader: new LocalizedText({ + }), + symptomFrequencyScoreHeader: localize({ en: 'Heart Failure Symptoms', - }).localize(...languages), - dizzinessScoreHeader: new LocalizedText({ + }), + dizzinessScoreHeader: localize({ en: 'Dizziness', - }).localize(...languages), + }), formatDate(date: Date) { return formatDate(date) }, }, - detailedVitalsSection: { - title: new LocalizedText({ - en: 'DETAILS OF VITALS', - }).localize(...languages), - bodyWeightTitle: new LocalizedText({ + vitalsSection: { + title: localize({ + en: 'VITALS OVER LAST 2 WEEKS', + }), + bodyWeightTitle: localize({ en: 'Weight', - }).localize(...languages), + }), bodyWeightTable: { - titleHeader: new LocalizedText({ + titleHeader: localize({ en: '', - }).localize(...languages), - currentHeader: new LocalizedText({ + }), + currentHeader: localize({ en: 'Current', - }).localize(...languages), - sevenDayAverageHeader: new LocalizedText({ + }), + sevenDayAverageHeader: localize({ en: '7-Day Average', - }).localize(...languages), - lastVisitHeader: new LocalizedText({ + }), + lastVisitHeader: localize({ en: 'Last Visit', - }).localize(...languages), - rangeHeader: new LocalizedText({ + }), + rangeHeader: localize({ en: 'Range', - }).localize(...languages), - rowTitle: new LocalizedText({ + }), + rowTitle: localize({ en: 'Weight', - }).localize(...languages), + }), }, - heartRateTitle: new LocalizedText({ + heartRateTitle: localize({ en: 'Heart Rate', - }).localize(...languages), + }), heartRateTable: { - titleHeader: new LocalizedText({ + titleHeader: localize({ en: '', - }).localize(...languages), - medianHeader: new LocalizedText({ + }), + medianHeader: localize({ en: 'Median', - }).localize(...languages), - iqrHeader: new LocalizedText({ + }), + iqrHeader: localize({ en: 'IQR', - }).localize(...languages), - percentageUnder50Header: new LocalizedText({ + }), + percentageUnder50Header: localize({ en: '% Under 50', - }).localize(...languages), - percentageOver120Header: new LocalizedText({ + }), + percentageOver120Header: localize({ en: '% Over 120', - }).localize(...languages), - rowTitle: new LocalizedText({ + }), + rowTitle: localize({ en: 'Heart Rate', - }).localize(...languages), + }), }, - systolicBloodPressureTitle: new LocalizedText({ + systolicBloodPressureTitle: localize({ en: 'Systolic Blood Pressure', - }).localize(...languages), - diastolicBloodPressureTitle: new LocalizedText({ + }), + diastolicBloodPressureTitle: localize({ en: 'Diastolic Blood Pressure', - }).localize(...languages), + }), bloodPressureTable: { - titleHeader: new LocalizedText({ + titleHeader: localize({ en: '', - }).localize(...languages), - medianHeader: new LocalizedText({ + }), + medianHeader: localize({ en: 'Median', - }).localize(...languages), - iqrHeader: new LocalizedText({ + }), + iqrHeader: localize({ en: 'IQR', - }).localize(...languages), - percentageUnder90Header: new LocalizedText({ + }), + percentageUnder90Header: localize({ en: '% Under 90 mmHg', - }).localize(...languages), - percentageOver180Header: new LocalizedText({ + }), + percentageOver180Header: localize({ en: '% Over 180 mmHg', - }).localize(...languages), - systolicRowTitle: new LocalizedText({ + }), + systolicRowTitle: localize({ en: 'Systolic', - }).localize(...languages), - diastolicRowTitle: new LocalizedText({ + }), + diastolicRowTitle: localize({ en: 'Diastolic', - }).localize(...languages), + }), }, }, } @@ -313,15 +360,9 @@ function formatDate(date: Date): string { }) } -function formatValue( - value: number | null, - unit: QuantityUnit | null, - options: { fractionalDigitCount: number } = { - fractionalDigitCount: 0, - }, -): string { - if (value === null) return '---' - const valueString = value.toFixed(options.fractionalDigitCount) - if (unit === null) return valueString - return `${valueString} ${unit.unit}` +function formatTime(date: Date): string { + return date.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + }) } diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 88fdaf30..0710d077 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -6,8 +6,6 @@ // SPDX-License-Identifier: MIT // -import fs from 'fs' -import { Resvg, type ResvgRenderOptions } from '@resvg/resvg-js' import { average, percentage, @@ -17,17 +15,13 @@ import { UserMedicationRecommendationType, } from '@stanfordbdhg/engagehf-models' import { logger } from 'firebase-functions' -import { jsPDF } from 'jspdf' import 'jspdf-autotable' /* eslint-disable-line */ -import { - type Styles, - type CellDef, - type UserOptions, -} from 'jspdf-autotable' /* eslint-disable-line */ +import { type CellDef } from 'jspdf-autotable' /* eslint-disable-line */ import { healthSummaryLocalizations } from './generate+localizations.js' import { generateChartSvg } from './generateChart.js' import { generateSpeedometerSvg } from './generateSpeedometer.js' import { type HealthSummaryData } from '../models/healthSummaryData.js' +import { PdfGenerator } from './pdfGenerator.js' export interface HealthSummaryOptions { languages: string[] @@ -58,192 +52,99 @@ export function generateHealthSummary( logger.debug( `generateHealthSummary: ${data.symptomScores.length} symptom scores.`, ) - const generator = new HealthSummaryPDFGenerator(data, options) - generator.addFirstPage() - generator.addSecondPage() - return generator.finish() -} + const generator = new HealthSummaryPdfGenerator(data, options) -enum FontStyle { - normal = 'normal', - bold = 'bold', -} + generator.addPageHeader() + generator.addKeyPointsSection() + generator.addCurrentMedicationSection() + generator.addMedicationRecommendationsSection() + generator.addSymptomScoresSummarySection() -interface TextStyle { - fontName: string - fontStyle: FontStyle - fontWeight?: string - fontSize: number - color?: [number, number, number] + generator.newPage() + generator.addSymptomScoresTableSection() + generator.addVitalsSection() + + return generator.finish() } -class HealthSummaryPDFGenerator { +class HealthSummaryPdfGenerator extends PdfGenerator { // Properties data: HealthSummaryData options: HealthSummaryOptions - doc: jsPDF - pageWidth = 612 - pageHeight = 792 - margins = { top: 50, bottom: 50, left: 40, right: 40 } - cursor = { x: this.margins.left, y: this.margins.top } - - colors = { - black: [0, 0, 0] as [number, number, number], - primary: [0, 117, 116] as [number, number, number], - lightGray: [211, 211, 211] as [number, number, number], - } - texts: ReturnType - fontName = 'Open Sans' - textStyles = { - h1: { - fontName: this.fontName, - fontStyle: FontStyle.bold, - fontSize: 18, - } as TextStyle, - h2: { - fontName: this.fontName, - fontStyle: FontStyle.normal, - fontSize: 16, - color: this.colors.primary, - } as TextStyle, - h3: { - fontName: this.fontName, - fontStyle: FontStyle.normal, - fontSize: 15, - } as TextStyle, - body: { - fontName: this.fontName, - fontStyle: FontStyle.normal, - fontSize: 10, - } as TextStyle, - bodyColored: { - fontName: this.fontName, - fontStyle: FontStyle.normal, - fontSize: 10, - color: this.colors.primary, - } as TextStyle, - bodyColoredBold: { - fontName: this.fontName, - fontStyle: FontStyle.bold, - fontSize: 10, - color: this.colors.primary, - } as TextStyle, - bodyBold: { - fontName: this.fontName, - fontStyle: FontStyle.bold, - fontSize: 10, - } as TextStyle, - } - // Constructor constructor(data: HealthSummaryData, options: HealthSummaryOptions) { + super() this.data = data this.options = options this.texts = healthSummaryLocalizations(options.languages) - this.doc = new jsPDF('p', 'pt', [this.pageWidth, this.pageHeight], true) - this.addFont( - 'resources/fonts/OpenSans-Regular.ttf', - this.fontName, - FontStyle.normal, - ) - this.addFont( - 'resources/fonts/OpenSans-Bold.ttf', - this.fontName, - FontStyle.bold, - ) } // Methods - addFirstPage() { - this.addPageHeader() - this.addMedicationSection() - this.addVitalsSection() - this.addSymptomScoresSection() - } + addPageHeader() { + this.addText(this.texts.header.title, this.textStyles.h2) + this.addText(this.data.name ?? '---', this.textStyles.h1) + this.addText( + this.texts.header.dateOfBirthLine(this.data.dateOfBirth ?? null), + ) + this.addText(this.texts.header.providerLine(this.data.providerName ?? null)) + this.addText( + this.texts.header.nextAppointmentLine(this.data.nextAppointment ?? null), + ) - addSecondPage() { - this.addPage() - this.addVitalsChartsSection() - } + const innerWidth = this.pageWidth - this.margins.left - this.margins.right + const pageNumberText = this.texts.header.pageNumberTitle( + this.doc.getNumberOfPages(), + ) + const pageNumberWidth = this.doc.getTextWidth(pageNumberText) + this.cursor.x = this.margins.left + innerWidth - pageNumberWidth + this.cursor.y -= this.textStyles.body.fontSize + this.addText(pageNumberText, this.textStyles.body, pageNumberWidth) + this.cursor.x = this.margins.left - finish(): Buffer { - logger.debug( - `HealthSummaryPDFGenerator.finish: ${this.doc.getNumberOfPages()} pages total.`, + this.addLine( + { x: this.cursor.x, y: this.cursor.y }, + { x: this.cursor.x + innerWidth, y: this.cursor.y }, + this.colors.black, + 1, ) - return Buffer.from(this.doc.output('arraybuffer')) + this.moveDown(this.textStyles.body.fontSize / 2) } - // Helpers + addKeyPointsSection() { + this.addSectionTitle(this.texts.keyPointsSection.title) - private addMedicationSection() { - this.addSectionTitle(this.texts.medicationsSection.title) - this.moveDown(4) + const messages = this.texts.keyPointsSection.messages(this.data) + messages.forEach((message, index) => { + this.addText(` ${index + 1}. ${message}`, this.textStyles.bodyColored) + }) + } - this.splitTwoColumns( - (columnWidth) => { - this.addText( - this.texts.medicationsSection.currentTitle, - this.textStyles.bodyBold, - columnWidth, - ) - this.moveDown(this.textStyles.body.fontSize + 4) - this.addText( - this.texts.medicationsSection.currentText, - this.textStyles.body, - columnWidth, - ) - this.moveDown(this.textStyles.body.fontSize) - }, - (columnWidth) => { - this.addText( - this.texts.medicationsSection.recommendationsTitle, - this.textStyles.bodyBold, - columnWidth, - ) - this.moveDown(this.textStyles.body.fontSize + 4) - this.addText( - this.texts.medicationsSection.recommendationsText, - this.textStyles.body, - columnWidth, - ) - this.moveDown(4) - this.addText( - this.texts.medicationsSection.recommendationsHint, - this.textStyles.bodyColoredBold, - columnWidth, - ) - this.moveDown(this.textStyles.body.fontSize) - }, + addCurrentMedicationSection() { + const currentMedication = this.data.recommendations.filter( + (recommendation) => + recommendation.displayInformation.dosageInformation.currentSchedule + .length > 0, + ) + if (currentMedication.length === 0) return + this.addSectionTitle(this.texts.currentMedicationsSection.title) + this.addText( + this.texts.currentMedicationsSection.description, + this.textStyles.bodyItalic, ) - - function colorForRecommendationType( - category: UserMedicationRecommendationType, - ): string | undefined { - switch (category) { - case UserMedicationRecommendationType.targetDoseReached: - return 'rgb(0,255,0)' - case UserMedicationRecommendationType.improvementAvailable: - return 'rgb(255,255,0)' - case UserMedicationRecommendationType.notStarted: - return 'rgb(211,211,211)' - } - } const tableContent: CellDef[][] = [ [ - this.texts.medicationsTable.nameHeader, - this.texts.medicationsTable.doseHeader, - this.texts.medicationsTable.targetDoseHeader, - this.texts.medicationsTable.recommendationHeader, - this.texts.medicationsTable.commentsHeader, - ].map((title) => this.cell(title)), - ...this.data.recommendations.map((recommendation, index) => [ + this.texts.currentMedicationsSection.table.nameHeader, + this.texts.currentMedicationsSection.table.currentDoseHeader, + this.texts.currentMedicationsSection.table.targetDoseHeader, + ].map((title) => this.cell(title, { fontStyle: 'bold' })), + ...currentMedication.map((recommendation) => [ this.cell( '[ ] ' + recommendation.displayInformation.title.localize( @@ -253,7 +154,7 @@ class HealthSummaryPDFGenerator { this.cell( recommendation.displayInformation.dosageInformation.currentSchedule .map((schedule) => - this.texts.medicationsTable.doseSchedule( + this.texts.currentMedicationsSection.table.doseSchedule( schedule, recommendation.displayInformation.dosageInformation.unit, ), @@ -263,31 +164,13 @@ class HealthSummaryPDFGenerator { this.cell( recommendation.displayInformation.dosageInformation.targetSchedule .map((schedule) => - this.texts.medicationsTable.doseSchedule( + this.texts.currentMedicationsSection.table.doseSchedule( schedule, recommendation.displayInformation.dosageInformation.unit, ), ) .join('\n'), - { - fillColor: colorForRecommendationType( - recommendation.displayInformation.type, - ), - }, - ), - this.cell( - recommendation.displayInformation.description.localize( - ...this.options.languages, - ), ), - this.cell('', { - lineWidth: { - bottom: index === this.data.recommendations.length - 1 ? 0.5 : 0, - top: index == 0 ? 0.5 : 0, - left: 0.5, - right: 0.5, - }, - }), ]), ] @@ -295,171 +178,136 @@ class HealthSummaryPDFGenerator { this.moveDown(this.textStyles.body.fontSize) } - private addVitalsSection() { - this.addSectionTitle(this.texts.vitalsSection.title) - this.moveDown(4) - this.splitTwoColumns( - (columnWidth) => { - const avgSystolic = average( - this.data.vitals.systolicBloodPressure.map( - (observation) => observation.value, - ), - ) - this.addText( - this.texts.vitalsSection.averageSystolicText(avgSystolic ?? null), - this.textStyles.body, - columnWidth, - ) - this.moveDown(4) - const avgDiastolic = average( - this.data.vitals.diastolicBloodPressure.map( - (observation) => observation.value, - ), - ) - this.addText( - this.texts.vitalsSection.averageDiastolicText(avgDiastolic ?? null), - this.textStyles.body, - columnWidth, - ) - this.moveDown(4) - const avgHeartRate = average( - this.data.vitals.heartRate.map((observation) => observation.value), - ) - this.addText( - this.texts.vitalsSection.averageHeartRateText(avgHeartRate ?? null), - this.textStyles.body, - columnWidth, - ) - this.moveDown(this.textStyles.body.fontSize) - }, - (columnWidth) => { - const currentWeight = this.data.vitals.bodyWeight.at(0) - this.addText( - this.texts.vitalsSection.currentBodyWeightText(currentWeight ?? null), - this.textStyles.body, - columnWidth, - ) - this.moveDown(4) - const avgWeight = average( - this.data.vitals.bodyWeight.map((observation) => observation.value), - ) - this.addText( - this.texts.vitalsSection.averageBodyWeightText( - avgWeight !== undefined && currentWeight !== undefined ? - { value: avgWeight, unit: currentWeight.unit } - : null, - ), - this.textStyles.body, - columnWidth, - ) - this.moveDown(4) - this.addText( - this.texts.vitalsSection.dryWeightText( - this.data.vitals.dryWeight ?? null, - ), - this.textStyles.body, - columnWidth, + addMedicationRecommendationsSection() { + const optimizations = this.data.recommendations.filter((recommendation) => + [ + UserMedicationRecommendationType.improvementAvailable, + UserMedicationRecommendationType.notStarted, + ].includes(recommendation.displayInformation.type), + ) + if (optimizations.length === 0) return + this.addSectionTitle(this.texts.medicationRecommendationsSection.title) + + optimizations.forEach((recommendation, index) => { + const title = recommendation.displayInformation.title.localize( + ...this.options.languages, + ) + const description = + recommendation.displayInformation.description.localize( + ...this.options.languages, ) - this.moveDown(this.textStyles.body.fontSize) - }, + this.addText( + ` ${index + 1}. ${title}: ${description}`, + this.textStyles.bodyColored, + ) + }) + this.moveDown(this.textStyles.body.fontSize / 2) + this.addText( + this.texts.medicationRecommendationsSection.description, + this.textStyles.bodyItalic, + ) + this.addText( + this.texts.medicationRecommendationsSection.hint, + this.textStyles.bodyColoredBoldItalic, ) } - private addSymptomScoresSection() { - this.addSectionTitle(this.texts.symptomScoresSection.title) - this.moveDown(4) + addSymptomScoresSummarySection() { + if (this.data.symptomScores.length === 0) return + this.addSectionTitle(this.texts.symptomScoresSummarySection.title) this.splitTwoColumns( (columnWidth) => { this.addSpeedometer(columnWidth) }, (columnWidth) => { - this.moveDown(this.textStyles.body.fontSize) - this.texts.symptomScoresSection.description - .split('\n') - .forEach((line) => { - this.addText(line, this.textStyles.body, columnWidth) - this.moveDown(4) - }) - - const currentScore = this.data.symptomScores.at(0) - const previousScore = this.data.symptomScores.at(1) - if ( - this.data.symptomScores.length >= 2 && - currentScore !== undefined && - previousScore !== undefined - ) { - let personalSummaryText = - this.texts.symptomScoresSection.personalSummary.title + ' ' - - if (currentScore.overallScore >= 90) { - if (currentScore.overallScore - previousScore.overallScore >= 10) { - personalSummaryText += - this.texts.symptomScoresSection.personalSummary.above90Improving - } else { - personalSummaryText = - this.texts.symptomScoresSection.personalSummary - .above90NotImproving - } - } else { - const improvement = - currentScore.overallScore - previousScore.overallScore - if (improvement >= 10) { - personalSummaryText += - this.texts.symptomScoresSection.personalSummary.below90Improving - } else if (improvement > -10) { - personalSummaryText += - this.texts.symptomScoresSection.personalSummary.below90Stable - } else { - personalSummaryText += - this.texts.symptomScoresSection.personalSummary.below90Worsening - } - } + this.addText( + this.texts.symptomScoresSummarySection.description, + this.textStyles.bodyItalic, + columnWidth, + ) + this.moveDown(this.textStyles.body.fontSize / 2) + const personalSummaryText = + this.texts.symptomScoresSummarySection.personalSummary({ + currentScore: this.data.latestSymptomScore, + previousScore: this.data.secondLatestSymptomScore, + }) + if (personalSummaryText !== undefined) { this.addText( personalSummaryText, this.textStyles.bodyColored, columnWidth, ) - this.moveDown(this.textStyles.body.fontSize) } }, ) + } + + addSymptomScoresTableSection() { + if (this.data.symptomScores.length === 0) return + this.addSectionTitle(this.texts.symptomScoresTableSection.title) + this.addText( + this.texts.symptomScoresTableSection.description, + this.textStyles.bodyItalic, + ) const tableContent: CellDef[][] = [ [ - this.texts.symptomScoresTable.dateHeader, - this.texts.symptomScoresTable.overallScoreHeader, - this.texts.symptomScoresTable.physicalLimitsScoreHeader, - this.texts.symptomScoresTable.socialLimitsScoreHeader, - this.texts.symptomScoresTable.qualityOfLifeScoreHeader, - this.texts.symptomScoresTable.symptomFrequencyScoreHeader, - this.texts.symptomScoresTable.dizzinessScoreHeader, + this.texts.symptomScoresTableSection.dateHeader, + this.texts.symptomScoresTableSection.overallScoreHeader, + this.texts.symptomScoresTableSection.physicalLimitsScoreHeader, + this.texts.symptomScoresTableSection.socialLimitsScoreHeader, + this.texts.symptomScoresTableSection.qualityOfLifeScoreHeader, + this.texts.symptomScoresTableSection.symptomFrequencyScoreHeader, + this.texts.symptomScoresTableSection.dizzinessScoreHeader, ].map((title) => this.cell(title)), - ...[...this.data.symptomScores].reverse().map((score, index) => [ - this.cell(this.texts.symptomScoresTable.formatDate(score.date), { - fontStyle: - index == this.data.symptomScores.length - 1 ? 'bold' : 'normal', + ...this.data.symptomScores.map((score, index) => [ + this.cell(this.texts.symptomScoresTableSection.formatDate(score.date), { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.overallScore.toFixed(0), { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.physicalLimitsScore?.toFixed(0) ?? '---', { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.socialLimitsScore?.toFixed(0) ?? '---', { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.qualityOfLifeScore?.toFixed(0) ?? '---', { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.symptomFrequencyScore?.toFixed(0) ?? '---', { + fontStyle: index === 0 ? 'bold' : 'normal', + }), + this.cell(score.dizzinessScore.toFixed(0), { + fontStyle: index === 0 ? 'bold' : 'normal', }), - this.cell(score.overallScore.toFixed(0)), - this.cell(score.physicalLimitsScore?.toFixed(0) ?? '---'), - this.cell(score.socialLimitsScore?.toFixed(0) ?? '---'), - this.cell(score.qualityOfLifeScore?.toFixed(0) ?? '---'), - this.cell(score.symptomFrequencyScore?.toFixed(0) ?? '---'), - this.cell(score.dizzinessScore.toFixed(0)), ]), ] this.addTable(tableContent) this.moveDown(this.textStyles.body.fontSize) } - private addVitalsChartsSection() { - this.addSectionTitle(this.texts.detailedVitalsSection.title) + addVitalsSection() { + this.addSectionTitle(this.texts.vitalsSection.title) + + this.addWeightAndHeartRateVitalsPart() + this.addBloodPressureVitalsPart() + } + private addWeightAndHeartRateVitalsPart() { + if ( + this.data.vitals.bodyWeight.length === 0 && + this.data.vitals.heartRate.length === 0 + ) + return this.splitTwoColumns( (columnWidth) => { + if (this.data.vitals.bodyWeight.length === 0) return this.addText( - this.texts.detailedVitalsSection.bodyWeightTitle, + this.texts.vitalsSection.bodyWeightTitle, this.textStyles.bodyBold, columnWidth, ) @@ -478,15 +326,14 @@ class HealthSummaryPDFGenerator { this.addTable( [ [ - this.texts.detailedVitalsSection.bodyWeightTable.titleHeader, - this.texts.detailedVitalsSection.bodyWeightTable.currentHeader, - this.texts.detailedVitalsSection.bodyWeightTable - .sevenDayAverageHeader, - this.texts.detailedVitalsSection.bodyWeightTable.lastVisitHeader, - this.texts.detailedVitalsSection.bodyWeightTable.rangeHeader, + this.texts.vitalsSection.bodyWeightTable.titleHeader, + this.texts.vitalsSection.bodyWeightTable.currentHeader, + this.texts.vitalsSection.bodyWeightTable.sevenDayAverageHeader, + this.texts.vitalsSection.bodyWeightTable.lastVisitHeader, + this.texts.vitalsSection.bodyWeightTable.rangeHeader, ].map((title) => this.cell(title)), [ - this.texts.detailedVitalsSection.bodyWeightTable.rowTitle, + this.texts.vitalsSection.bodyWeightTable.rowTitle, this.data.vitals.bodyWeight.at(0)?.value.toFixed(0) ?? '---', avgWeight?.toFixed(0) ?? '---', '-', @@ -497,11 +344,11 @@ class HealthSummaryPDFGenerator { ], columnWidth, ) - this.moveDown(this.textStyles.body.fontSize * 2) }, (columnWidth) => { + if (this.data.vitals.heartRate.length === 0) return this.addText( - this.texts.detailedVitalsSection.heartRateTitle, + this.texts.vitalsSection.heartRateTitle, this.textStyles.bodyBold, columnWidth, ) @@ -514,16 +361,14 @@ class HealthSummaryPDFGenerator { this.addTable( [ [ - this.texts.detailedVitalsSection.heartRateTable.titleHeader, - this.texts.detailedVitalsSection.heartRateTable.medianHeader, - this.texts.detailedVitalsSection.heartRateTable.iqrHeader, - this.texts.detailedVitalsSection.heartRateTable - .percentageUnder50Header, - this.texts.detailedVitalsSection.heartRateTable - .percentageOver120Header, + this.texts.vitalsSection.heartRateTable.titleHeader, + this.texts.vitalsSection.heartRateTable.medianHeader, + this.texts.vitalsSection.heartRateTable.iqrHeader, + this.texts.vitalsSection.heartRateTable.percentageUnder50Header, + this.texts.vitalsSection.heartRateTable.percentageOver120Header, ].map((title) => this.cell(title)), [ - this.texts.detailedVitalsSection.heartRateTable.rowTitle, + this.texts.vitalsSection.heartRateTable.rowTitle, presortedMedian(values)?.toFixed(0) ?? '---', upperMedian && lowerMedian ? (upperMedian - lowerMedian).toFixed(0) @@ -534,22 +379,29 @@ class HealthSummaryPDFGenerator { ], columnWidth, ) - this.moveDown(this.textStyles.body.fontSize * 2) }, ) + this.moveDown(this.textStyles.body.fontSize * 2) + } + private addBloodPressureVitalsPart() { + const hasSystolic = this.data.vitals.systolicBloodPressure.length > 0 + const hasDiastolic = this.data.vitals.diastolicBloodPressure.length > 0 + if (!hasSystolic && !hasDiastolic) return this.splitTwoColumns( (columnWidth) => { + if (!hasSystolic) return this.addText( - this.texts.detailedVitalsSection.systolicBloodPressureTitle, + this.texts.vitalsSection.systolicBloodPressureTitle, this.textStyles.bodyBold, columnWidth, ) this.addChart(this.data.vitals.systolicBloodPressure, columnWidth) }, (columnWidth) => { + if (!hasDiastolic) return this.addText( - this.texts.detailedVitalsSection.diastolicBloodPressureTitle, + this.texts.vitalsSection.diastolicBloodPressureTitle, this.textStyles.bodyBold, columnWidth, ) @@ -575,95 +427,55 @@ class HealthSummaryPDFGenerator { this.addTable([ [ - this.texts.detailedVitalsSection.bloodPressureTable.titleHeader, - this.texts.detailedVitalsSection.bloodPressureTable.medianHeader, - this.texts.detailedVitalsSection.bloodPressureTable.iqrHeader, - this.texts.detailedVitalsSection.bloodPressureTable - .percentageUnder90Header, - this.texts.detailedVitalsSection.bloodPressureTable - .percentageOver180Header, - ].map((title) => this.cell(title)), - [ - this.texts.detailedVitalsSection.bloodPressureTable.systolicRowTitle, - presortedMedian(systolicValues)?.toFixed(0) ?? '---', - systolicUpperMedian && systolicLowerMedian ? - (systolicUpperMedian - systolicLowerMedian).toFixed(0) - : '---', - percentage(systolicValues, (value) => value < 90)?.toFixed(0) ?? '---', - percentage(systolicValues, (value) => value > 180)?.toFixed(0) ?? '---', - ].map((title) => this.cell(title)), - [ - this.texts.detailedVitalsSection.bloodPressureTable.diastolicRowTitle, - presortedMedian(diastolicValues)?.toFixed(0) ?? '---', - diastolicUpperMedian && diastolicLowerMedian ? - (diastolicUpperMedian - diastolicLowerMedian).toFixed(0) - : '---', - '-', - '-', + this.texts.vitalsSection.bloodPressureTable.titleHeader, + this.texts.vitalsSection.bloodPressureTable.medianHeader, + this.texts.vitalsSection.bloodPressureTable.iqrHeader, + this.texts.vitalsSection.bloodPressureTable.percentageUnder90Header, + this.texts.vitalsSection.bloodPressureTable.percentageOver180Header, ].map((title) => this.cell(title)), + (hasSystolic ? + [ + this.texts.vitalsSection.bloodPressureTable.systolicRowTitle, + presortedMedian(systolicValues)?.toFixed(0) ?? '---', + systolicUpperMedian && systolicLowerMedian ? + (systolicUpperMedian - systolicLowerMedian).toFixed(0) + : '---', + percentage(systolicValues, (value) => value < 90)?.toFixed(0) ?? + '---', + percentage(systolicValues, (value) => value > 180)?.toFixed(0) ?? + '---', + ] + : ['', '', '', '', ''] + ).map((title) => this.cell(title)), + (hasDiastolic ? + [ + this.texts.vitalsSection.bloodPressureTable.diastolicRowTitle, + presortedMedian(diastolicValues)?.toFixed(0) ?? '---', + diastolicUpperMedian && diastolicLowerMedian ? + (diastolicUpperMedian - diastolicLowerMedian).toFixed(0) + : '---', + '-', + '-', + ] + : ['', '', '', '', ''] + ).map((title) => this.cell(title)), ]) - this.moveDown(this.textStyles.body.fontSize * 2) - } - - private addPage() { - this.doc.addPage([this.pageWidth, this.pageHeight]) - this.cursor = { x: this.margins.left, y: this.margins.top } - this.addPageHeader() - } - - private addPageHeader() { - this.addText(this.texts.header.title, this.textStyles.h2) - this.moveDown(4) - this.addText(this.data.name ?? '---', this.textStyles.h1) - this.moveDown(4) - this.addText( - this.texts.header.dateOfBirthLine(this.data.dateOfBirth ?? null), - ) - this.moveDown(4) - this.addText(this.texts.header.providerLine(this.data.providerName ?? null)) - this.moveDown(4) - this.addText( - this.texts.header.nextAppointmentLine(this.data.nextAppointment ?? null), - ) - - const innerWidth = this.pageWidth - this.margins.left - this.margins.right - const pageNumberText = this.texts.header.pageNumberTitle( - this.doc.getNumberOfPages(), - ) - const pageNumberWidth = this.doc.getTextWidth(pageNumberText) - this.cursor.x = this.margins.left + innerWidth - pageNumberWidth - this.cursor.y -= this.textStyles.body.fontSize - this.addText(pageNumberText, this.textStyles.body, pageNumberWidth) - this.cursor.x = this.margins.left - - this.moveDown(8) - this.addLine( - { x: this.cursor.x, y: this.cursor.y }, - { x: this.cursor.x + innerWidth, y: this.cursor.y }, - this.colors.black, - 1, - ) - this.moveDown(8) + this.moveDown(this.textStyles.body.fontSize) } - private addSectionTitle(title: string) { - this.moveDown(8) - this.addText(title, this.textStyles.h3) - this.moveDown(4) - } + // Helpers private addChart(data: Observation[], maxWidth?: number, baseline?: number) { const width = maxWidth ?? this.pageWidth - this.cursor.x - this.margins.right - const height = width * 0.75 + const height = width * (9 / 16) const svg = generateChartSvg( data, { width: width, height: height }, - { top: 20, right: 40, bottom: 40, left: 40 }, + { top: 10, right: 20, bottom: 40, left: 20 }, baseline, ) - const img = this.convertSvgToPng(svg) - this.addPNG(img, width) + this.addSvg(svg, width) } private addSpeedometer(maxWidth?: number) { @@ -672,153 +484,6 @@ class HealthSummaryPDFGenerator { const svg = generateSpeedometerSvg(this.data.symptomScores, width, { languages: this.options.languages, }) - const img = this.convertSvgToPng(svg) - this.addPNG(img, width) - } - - private convertSvgToPng(svg: string): Buffer { - const options: ResvgRenderOptions = { - font: { - loadSystemFonts: false, - fontDirs: ['resources/fonts'], - defaultFontFamily: this.fontName, - serifFamily: this.fontName, - sansSerifFamily: this.fontName, - cursiveFamily: this.fontName, - fantasyFamily: this.fontName, - monospaceFamily: this.fontName, - }, - shapeRendering: 2, - textRendering: 1, - imageRendering: 0, - fitTo: { mode: 'zoom', value: 3 }, - } - return new Resvg(svg, options).render().asPng() - } - - private addPNG(data: Buffer, maxWidth?: number) { - const width = - maxWidth ?? this.pageWidth - this.margins.left - this.margins.right - const imgData = 'data:image/png;base64,' + data.toString('base64') - const imgProperties = this.doc.getImageProperties(imgData) - const height = width / (imgProperties.width / imgProperties.height) - this.doc.addImage( - imgData, - this.cursor.x, - this.cursor.y, - width, - height, - undefined, - 'FAST', - ) - this.moveDown(height) - } - - private addTable(rows: CellDef[][], maxWidth?: number) { - const textStyle = this.textStyles.body - const options: UserOptions = { - margin: { left: this.cursor.x }, - theme: 'grid', - startY: this.cursor.y, - tableWidth: - maxWidth ?? this.pageWidth - this.margins.left - this.margins.right, - body: rows, - styles: { - font: textStyle.fontName, - fontStyle: textStyle.fontStyle, - fontSize: textStyle.fontSize, - }, - } - ;(this.doc as any).autoTable(options) // eslint-disable-line - this.cursor.y = (this.doc as any).lastAutoTable.finalY // eslint-disable-line - } - - private addText( - text: string, - textStyle: TextStyle = this.textStyles.body, - maxWidth?: number, - ) { - this.doc.setFont( - textStyle.fontName, - textStyle.fontStyle, - textStyle.fontWeight, - ) - this.doc.setFontSize(textStyle.fontSize) - const previousTextColor = this.doc.getTextColor() - if (textStyle.color) { - this.doc.setTextColor(...textStyle.color) - } - const textWidth = - maxWidth ?? this.pageWidth - this.cursor.x - this.margins.right - const splitText = this.doc.splitTextToSize(text, textWidth) as string[] - for (const textSegment of splitText) { - this.doc.text( - textSegment, - this.cursor.x, - this.cursor.y + textStyle.fontSize / 2, - ) - this.cursor.y += textStyle.fontSize - } - if (textStyle.color) { - this.doc.setTextColor(previousTextColor) - } - } - - private addLine( - start: { x: number; y: number }, - end: { x: number; y: number }, - color: [number, number, number], - width: number, - ) { - this.doc.setLineWidth(width) - this.doc.setDrawColor(color[0], color[1], color[2]) - this.doc.line(start.x, start.y, end.x, end.y) - } - - private splitTwoColumns( - firstColumn: (width: number) => void, - secondColumn: (width: number) => void, - ) { - const cursorBeforeSplit = structuredClone(this.cursor) - const splitMargin = 8 - const innerWidth = this.pageWidth - this.margins.left - this.margins.right - const columnWidth = innerWidth / 2 - splitMargin - firstColumn(columnWidth) - const firstColumnMaxY = this.cursor.y - this.cursor = structuredClone(cursorBeforeSplit) - this.cursor.x += columnWidth + splitMargin - secondColumn(columnWidth) - this.cursor = { - x: cursorBeforeSplit.x, - y: Math.max(firstColumnMaxY, this.cursor.y), - } - } - - private moveDown(deltaY: number) { - this.cursor.y += deltaY - } - - private addFont(file: string, name: string, style: FontStyle) { - const fontFileContent = fs.readFileSync(file).toString('base64') - const fileName = file.split('/').at(-1) ?? file - this.doc.addFileToVFS(fileName, fontFileContent) - this.doc.addFont(fileName, name, style.toString()) - } - - private cell(title: string, styles: Partial = {}): CellDef { - styles.cellPadding = styles.cellPadding ?? { vertical: 0, horizontal: 0 } - styles.cellPadding = styles.fontSize ?? 4 - styles.lineWidth = styles.lineWidth ?? { - top: 0.5, - bottom: 0.5, - left: 0.5, - right: 0.5, - } - styles.textColor = styles.textColor ?? 'black' - styles.lineColor = styles.lineColor ?? this.colors.black - return { - styles: styles, - title: title, - } + this.addSvg(svg, width) } } diff --git a/functions/src/healthSummary/pdfGenerator.ts b/functions/src/healthSummary/pdfGenerator.ts new file mode 100644 index 00000000..89b606f6 --- /dev/null +++ b/functions/src/healthSummary/pdfGenerator.ts @@ -0,0 +1,318 @@ +// +// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import fs from 'fs' +import { Resvg, type ResvgRenderOptions } from '@resvg/resvg-js' +import { logger } from 'firebase-functions' +import { jsPDF } from 'jspdf' +import 'jspdf-autotable' /* eslint-disable-line */ +import { + type Styles, + type CellDef, + type UserOptions, +} from 'jspdf-autotable' /* eslint-disable-line */ + +enum FontStyle { + normal = 'normal', + bold = 'bold', + italic = 'italic', + boldItalic = 'bolditalic', +} + +interface TextStyle { + fontName: string + fontStyle: FontStyle + fontWeight?: string + fontSize: number + color?: [number, number, number] +} + +export class PdfGenerator { + // Properties + + doc: jsPDF + pageWidth = 612 + pageHeight = 792 + margins = { top: 50, bottom: 50, left: 40, right: 40 } + cursor = { x: this.margins.left, y: this.margins.top } + + colors = { + black: [0, 0, 0] as [number, number, number], + primary: [0, 117, 116] as [number, number, number], + lightGray: [211, 211, 211] as [number, number, number], + } + + fontName = 'Open Sans' + textStyles = { + h1: { + fontName: this.fontName, + fontStyle: FontStyle.bold, + fontSize: 18, + } as TextStyle, + h2: { + fontName: this.fontName, + fontStyle: FontStyle.normal, + fontSize: 16, + color: this.colors.primary, + } as TextStyle, + h3: { + fontName: this.fontName, + fontStyle: FontStyle.normal, + fontSize: 15, + } as TextStyle, + body: { + fontName: this.fontName, + fontStyle: FontStyle.normal, + fontSize: 10, + } as TextStyle, + bodyItalic: { + fontName: this.fontName, + fontStyle: FontStyle.italic, + fontSize: 10, + } as TextStyle, + bodyColored: { + fontName: this.fontName, + fontStyle: FontStyle.normal, + fontSize: 10, + color: this.colors.primary, + } as TextStyle, + bodyColoredItalic: { + fontName: this.fontName, + fontStyle: FontStyle.italic, + fontSize: 10, + color: this.colors.primary, + } as TextStyle, + bodyColoredBold: { + fontName: this.fontName, + fontStyle: FontStyle.bold, + fontSize: 10, + color: this.colors.primary, + } as TextStyle, + bodyColoredBoldItalic: { + fontName: this.fontName, + fontStyle: FontStyle.boldItalic, + fontSize: 10, + color: this.colors.primary, + } as TextStyle, + bodyBold: { + fontName: this.fontName, + fontStyle: FontStyle.bold, + fontSize: 10, + } as TextStyle, + bodyBoldItalic: { + fontName: this.fontName, + fontStyle: FontStyle.boldItalic, + fontSize: 10, + } as TextStyle, + } + + // Constructor + + constructor() { + this.doc = new jsPDF('p', 'pt', [this.pageWidth, this.pageHeight], true) + this.addFont( + 'resources/fonts/OpenSans-Regular.ttf', + this.fontName, + FontStyle.normal, + ) + this.addFont( + 'resources/fonts/OpenSans-Bold.ttf', + this.fontName, + FontStyle.bold, + ) + this.addFont( + 'resources/fonts/OpenSans-Italic.ttf', + this.fontName, + FontStyle.italic, + ) + this.addFont( + 'resources/fonts/OpenSans-BoldItalic.ttf', + this.fontName, + FontStyle.boldItalic, + ) + } + + // Methods + + finish(): Buffer { + logger.debug( + `HealthSummaryPDFGenerator.finish: ${this.doc.getNumberOfPages()} pages total.`, + ) + return Buffer.from(this.doc.output('arraybuffer')) + } + + newPage() { + this.doc.addPage([this.pageWidth, this.pageHeight]) + this.cursor = { x: this.margins.left, y: this.margins.top } + } + + addSectionTitle(title: string) { + this.moveDown(8) + this.addText(title, this.textStyles.h3) + this.moveDown(4) + } + + addSvg(svg: string, maxWidth?: number) { + const img = this.convertSvgToPng(svg) + this.addPng(img, maxWidth) + } + + private convertSvgToPng(svg: string): Buffer { + const options: ResvgRenderOptions = { + font: { + loadSystemFonts: false, + fontDirs: ['resources/fonts'], + defaultFontFamily: this.fontName, + serifFamily: this.fontName, + sansSerifFamily: this.fontName, + cursiveFamily: this.fontName, + fantasyFamily: this.fontName, + monospaceFamily: this.fontName, + }, + shapeRendering: 2, + textRendering: 1, + imageRendering: 0, + fitTo: { mode: 'zoom', value: 3 }, + } + return new Resvg(svg, options).render().asPng() + } + + addPng(data: Buffer, maxWidth?: number) { + const width = + maxWidth ?? this.pageWidth - this.margins.left - this.margins.right + const imgData = 'data:image/png;base64,' + data.toString('base64') + const imgProperties = this.doc.getImageProperties(imgData) + const height = width / (imgProperties.width / imgProperties.height) + this.doc.addImage( + imgData, + this.cursor.x, + this.cursor.y, + width, + height, + undefined, + 'FAST', + ) + this.moveDown(height) + } + + addTable(rows: CellDef[][], maxWidth?: number) { + const textStyle = this.textStyles.body + const options: UserOptions = { + margin: { + left: this.cursor.x, + right: + maxWidth !== undefined ? + maxWidth - this.cursor.x + : this.margins.right, + }, + theme: 'grid', + startY: this.cursor.y, + tableWidth: 'wrap', + body: rows, + styles: { + font: textStyle.fontName, + fontStyle: textStyle.fontStyle, + fontSize: textStyle.fontSize, + }, + } + ;(this.doc as any).autoTable(options) // eslint-disable-line + this.cursor.y = (this.doc as any).lastAutoTable.finalY // eslint-disable-line + } + + addText( + text: string, + textStyle: TextStyle = this.textStyles.body, + maxWidth?: number, + ) { + this.doc.setFont( + textStyle.fontName, + textStyle.fontStyle, + textStyle.fontWeight, + ) + this.doc.setFontSize(textStyle.fontSize) + const previousTextColor = this.doc.getTextColor() + if (textStyle.color) { + this.doc.setTextColor(...textStyle.color) + } + const textWidth = + maxWidth ?? this.pageWidth - this.cursor.x - this.margins.right + const splitText = this.doc.splitTextToSize(text, textWidth) as string[] + for (const textSegment of splitText) { + this.doc.text( + textSegment, + this.cursor.x, + this.cursor.y + textStyle.fontSize / 2, + { + lineHeightFactor: 1.5, + }, + ) + this.cursor.y += textStyle.fontSize * 1.5 + } + if (textStyle.color) { + this.doc.setTextColor(previousTextColor) + } + } + + addLine( + start: { x: number; y: number }, + end: { x: number; y: number }, + color: [number, number, number], + width: number, + ) { + this.doc.setLineWidth(width) + this.doc.setDrawColor(color[0], color[1], color[2]) + this.doc.line(start.x, start.y, end.x, end.y) + } + + splitTwoColumns( + firstColumn: (width: number) => void, + secondColumn: (width: number) => void, + ) { + const cursorBeforeSplit = structuredClone(this.cursor) + const splitMargin = 8 + const innerWidth = this.pageWidth - this.margins.left - this.margins.right + const columnWidth = innerWidth / 2 - splitMargin + firstColumn(columnWidth) + const firstColumnMaxY = this.cursor.y + this.cursor = structuredClone(cursorBeforeSplit) + this.cursor.x += columnWidth + splitMargin + secondColumn(columnWidth) + this.cursor = { + x: cursorBeforeSplit.x, + y: Math.max(firstColumnMaxY, this.cursor.y), + } + } + + moveDown(deltaY: number) { + this.cursor.y += deltaY + } + + private addFont(file: string, name: string, style: FontStyle) { + const fontFileContent = fs.readFileSync(file).toString('base64') + const fileName = file.split('/').at(-1) ?? file + this.doc.addFileToVFS(fileName, fontFileContent) + this.doc.addFont(fileName, name, style.toString()) + } + + cell(title: string, styles: Partial = {}): CellDef { + styles.cellPadding = styles.cellPadding ?? { vertical: 0, horizontal: 0 } + styles.cellPadding = styles.fontSize ?? 4 + styles.lineWidth = styles.lineWidth ?? { + top: 0.5, + bottom: 0.5, + left: 0.5, + right: 0.5, + } + styles.textColor = styles.textColor ?? 'black' + styles.lineColor = styles.lineColor ?? this.colors.black + return { + styles: styles, + title: title, + } + } +} diff --git a/functions/src/models/healthSummaryData.ts b/functions/src/models/healthSummaryData.ts index fcd01b0f..6470dcac 100644 --- a/functions/src/models/healthSummaryData.ts +++ b/functions/src/models/healthSummaryData.ts @@ -7,13 +7,47 @@ // import { + average, + compactMap, + QuantityUnit, + UserMedicationRecommendationType, type FHIRAppointment, type Observation, type SymptomScore, type UserMedicationRecommendation, } from '@stanfordbdhg/engagehf-models' -export interface HealthSummaryData { +export interface HealthSummaryVitals { + systolicBloodPressure: Observation[] + diastolicBloodPressure: Observation[] + heartRate: Observation[] + bodyWeight: Observation[] + + dryWeight?: Observation +} + +export enum HealthSummarySymptomScoreCategory { + ABOVE_90_STABLE_OR_IMPROVING, + ABOVE_90_WORSENING, + BELOW_90_STABLE_OR_IMPROVING, + BELOW_90_WORSENING, +} + +export enum HealthSummaryMedicationRecommendationCategory { + OPTIMIZATIONS_AVAILABLE, + OBSERVATIONS_REQUIRED, + AT_TARGET, +} + +export enum HealthSummaryWeightCategory { + INCREASING, + MISSING, + STABLE, +} + +export class HealthSummaryData { + // Stored Properties + name?: string dateOfBirth?: Date providerName?: string @@ -21,13 +55,96 @@ export interface HealthSummaryData { recommendations: UserMedicationRecommendation[] vitals: HealthSummaryVitals symptomScores: SymptomScore[] -} -export interface HealthSummaryVitals { - systolicBloodPressure: Observation[] - diastolicBloodPressure: Observation[] - heartRate: Observation[] - bodyWeight: Observation[] + // Computed Properties - dryWeight?: Observation + get latestSymptomScore(): SymptomScore | null { + return this.symptomScores.at(0) ?? null + } + + get secondLatestSymptomScore(): SymptomScore | null { + return this.symptomScores.at(1) ?? null + } + + get symptomScoreCategory(): HealthSummarySymptomScoreCategory | null { + const latestScore = this.latestSymptomScore + const secondLatestScore = this.secondLatestSymptomScore + + if (latestScore === null || secondLatestScore === null) { + return null + } + + if (latestScore.overallScore >= 90) { + return latestScore.overallScore - secondLatestScore.overallScore > -10 ? + HealthSummarySymptomScoreCategory.ABOVE_90_STABLE_OR_IMPROVING + : HealthSummarySymptomScoreCategory.ABOVE_90_WORSENING + } else { + return latestScore.overallScore - secondLatestScore.overallScore > -10 ? + HealthSummarySymptomScoreCategory.BELOW_90_STABLE_OR_IMPROVING + : HealthSummarySymptomScoreCategory.BELOW_90_WORSENING + } + } + + get weightCategory(): HealthSummaryWeightCategory { + const weight = average( + compactMap(this.vitals.bodyWeight, (observation) => + observation.unit.convert(observation.value, QuantityUnit.lbs), + ), + ) + if (weight.length < 2) return HealthSummaryWeightCategory.MISSING + + return true + } + + get recommendationCategory(): HealthSummaryMedicationRecommendationCategory { + const hasOptimizations = this.recommendations.some((recommendation) => + [ + UserMedicationRecommendationType.improvementAvailable, + UserMedicationRecommendationType.notStarted, + ].includes(recommendation.displayInformation.type), + ) + if (hasOptimizations) + return HealthSummaryMedicationRecommendationCategory.OPTIMIZATIONS_AVAILABLE + + const hasObservationsRequired = this.recommendations.some( + (recommendation) => + [ + UserMedicationRecommendationType.moreLabObservationsRequired, + UserMedicationRecommendationType.morePatientObservationsRequired, + ].includes(recommendation.displayInformation.type), + ) + if (hasObservationsRequired) + return HealthSummaryMedicationRecommendationCategory.OBSERVATIONS_REQUIRED + + return HealthSummaryMedicationRecommendationCategory.AT_TARGET + } + + get dizzinessWorsened(): boolean { + const latestScore = this.latestSymptomScore?.dizzinessScore + const secondLatestScore = this.secondLatestSymptomScore?.dizzinessScore + + return latestScore !== undefined && secondLatestScore !== undefined ? + latestScore - secondLatestScore < -1 + : false + } + + // Initialization + + constructor(input: { + name?: string + dateOfBirth?: Date + providerName?: string + nextAppointment?: FHIRAppointment + recommendations: UserMedicationRecommendation[] + vitals: HealthSummaryVitals + symptomScores: SymptomScore[] + }) { + this.name = input.name + this.dateOfBirth = input.dateOfBirth + this.providerName = input.providerName + this.nextAppointment = input.nextAppointment + this.recommendations = input.recommendations + this.vitals = input.vitals + this.symptomScores = input.symptomScores + } } diff --git a/functions/src/services/healthSummary/databaseHealthSummaryService.ts b/functions/src/services/healthSummary/databaseHealthSummaryService.ts index c0d79d1a..cae802e4 100644 --- a/functions/src/services/healthSummary/databaseHealthSummaryService.ts +++ b/functions/src/services/healthSummary/databaseHealthSummaryService.ts @@ -12,8 +12,8 @@ import { } from '@stanfordbdhg/engagehf-models' import { type HealthSummaryService } from './healthSummaryService.js' import { + HealthSummaryData, type HealthSummaryVitals, - type HealthSummaryData, } from '../../models/healthSummaryData.js' import { type PatientService } from '../patient/patientService.js' import { type UserService } from '../user/userService.js' @@ -58,7 +58,7 @@ export class DefaultHealthSummaryService implements HealthSummaryService { await this.userService.getAuth(patient.content.clinician) : undefined - return { + return new HealthSummaryData({ name: auth.displayName, dateOfBirth: patient?.content.dateOfBirth, providerName: clinician?.displayName, @@ -66,7 +66,7 @@ export class DefaultHealthSummaryService implements HealthSummaryService { recommendations: recommendations.map((doc) => doc.content), vitals: vitals, symptomScores: symptomScores.map((doc) => doc.content), - } + }) } // Helpers diff --git a/functions/src/services/healthSummary/healthSummaryService.mock.ts b/functions/src/services/healthSummary/healthSummaryService.mock.ts index 3d24f3cc..f930e871 100644 --- a/functions/src/services/healthSummary/healthSummaryService.mock.ts +++ b/functions/src/services/healthSummary/healthSummaryService.mock.ts @@ -17,8 +17,8 @@ import { } from '@stanfordbdhg/engagehf-models' import { type HealthSummaryService } from './healthSummaryService.js' import { + HealthSummaryData, type HealthSummaryVitals, - type HealthSummaryData, } from '../../models/healthSummaryData.js' /* eslint-disable @typescript-eslint/require-await */ @@ -38,7 +38,7 @@ export class MockHealthSummaryService implements HealthSummaryService { // Methods async getHealthSummaryData(userId: string): Promise { - return { + return new HealthSummaryData({ name: 'John Doe', dateOfBirth: new Date('1970-01-02'), providerName: 'Dr. XXX', @@ -141,7 +141,7 @@ export class MockHealthSummaryService implements HealthSummaryService { date: this.startDateAdvancedByDays(-49), }, ], - } + }) } async getVitals(userId: string): Promise { diff --git a/functions/src/tests/mocks/healthSummaryData.ts b/functions/src/tests/mocks/healthSummaryData.ts index f5c3c9d9..be976064 100644 --- a/functions/src/tests/mocks/healthSummaryData.ts +++ b/functions/src/tests/mocks/healthSummaryData.ts @@ -10,7 +10,7 @@ import { MockHealthSummaryService } from '../../services/healthSummary/healthSum export function mockHealthSummaryData( userId: string, - startDate: Date = new Date('2024-02-02'), + startDate: Date = new Date(2024, 2, 2, 12, 30), ): Promise { const service = new MockHealthSummaryService(startDate) return service.getHealthSummaryData(userId) diff --git a/functions/src/tests/resources/emptyHealthSummary.pdf b/functions/src/tests/resources/emptyHealthSummary.pdf index 273c4b83569a96ddb82c30b9b1aaa06910cf31dc..cc00f6686d0c5ede8fbcb90ce3b2000c61fef28e 100644 GIT binary patch delta 83 zcmV~$yA8k~3$>JNU86$t>C2Bg#Ou)$8?PDKby};CZsII{2UvdhP3_Ylk3G=r}sBYnc=v%KEP~H5V{B$h1D32C?hZS=dah(ct)jCKWh&etozT5< f2P*RbB&jPUaWSMaiQAOI`p25_z8*iaLQLZan?4nD From f52f71eb5ede46ab6c766896b50b47cdab480cf9 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Tue, 7 Jan 2025 17:48:43 +0100 Subject: [PATCH 02/12] updatee --- functions/models/src/codes/quantityUnit.ts | 14 ++- .../healthSummary/generate+localizations.ts | 119 ++++++------------ functions/src/healthSummary/generate.test.ts | 72 ++++++++++- functions/src/healthSummary/generate.ts | 15 +-- functions/src/models/healthSummaryData.ts | 82 ++++++++---- functions/src/tests/helpers/csv.ts | 18 ++- .../src/tests/resources/keyPointMessages.csv | 116 +++++++++++++++++ 7 files changed, 317 insertions(+), 119 deletions(-) create mode 100644 functions/src/tests/resources/keyPointMessages.csv diff --git a/functions/models/src/codes/quantityUnit.ts b/functions/models/src/codes/quantityUnit.ts index f9db0c45..6fb52ff9 100644 --- a/functions/models/src/codes/quantityUnit.ts +++ b/functions/models/src/codes/quantityUnit.ts @@ -7,6 +7,7 @@ // import { type FHIRQuantity } from '../fhir/baseTypes/fhirQuantity.js' +import { Observation } from '../types/observation.js' export class QuantityUnit { // Static Properties @@ -72,14 +73,17 @@ export class QuantityUnit { ) } - convert(value: number, target: QuantityUnit): number | undefined { - return QuantityUnitConverter.allValues + convert(observation: Observation): Observation | undefined { + const value = QuantityUnitConverter.allValues .find( (converter) => - converter.sourceUnit.equals(this) && - converter.targetUnit.equals(target), + converter.sourceUnit.equals(observation.unit) && + converter.targetUnit.equals(this), ) - ?.convert(value) + ?.convert(observation.value) + return value !== undefined ? + { ...observation, value, unit: this } + : undefined } fhirQuantity(value: number): FHIRQuantity { diff --git a/functions/src/healthSummary/generate+localizations.ts b/functions/src/healthSummary/generate+localizations.ts index 58723ee5..64350195 100644 --- a/functions/src/healthSummary/generate+localizations.ts +++ b/functions/src/healthSummary/generate+localizations.ts @@ -13,7 +13,7 @@ import { SymptomScore, UserMedicationRecommendationType, } from '@stanfordbdhg/engagehf-models' -import { HealthSummaryData } from '../models/healthSummaryData' +import { HealthSummaryKeyPointMessage } from '../models/healthSummaryData.js' export function healthSummaryLocalizations(languages: string[]) { function localize(strings: Record): string { @@ -53,86 +53,45 @@ export function healthSummaryLocalizations(languages: string[]) { title: localize({ en: 'KEY POINTS', }), - messageTexts: { - optimizationsAvailable: localize({ - en: 'There are possible options to improve your heart medicines. See the list of "Potential Med Changes" below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', - }), - missingHeartObservations: localize({ - en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', - }), - onTargetDose: localize({ - en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable, and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', - }), - symptomsWorsened: localize({ - en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', - }), - weightIncreased: localize({ - en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', - }), - dizzinessIncreased: localize({ - en: 'Your dizziness is more bothersome. Discuss with your care team options to improve your dizziness, and watch the dizziness educational video.', - }), - missingAllObservations: localize({ - en: 'Check your blood pressure, heart rate, and weight more frequently and discuss with your care team how adjusting your medicines can help you feel better.', - }), - }, - messages(data: HealthSummaryData): string[] { - const currentScore = data.symptomScores.at(0) - const previousScore = data.symptomScores.at(1) - const scoreAbove90 = - currentScore !== undefined ? - currentScore.overallScore >= 90 - : undefined - const scoreDecreased = - currentScore !== undefined && previousScore !== undefined ? - currentScore.overallScore - previousScore.overallScore < -10 - : undefined - const dizzinessScoreIncreased = - currentScore !== undefined && previousScore !== undefined ? - currentScore.dizzinessScore - previousScore.dizzinessScore > 1 - : undefined - - const optimizingRecommendations = data.recommendations.filter( - (recommendation) => - [ - UserMedicationRecommendationType.notStarted, - UserMedicationRecommendationType.improvementAvailable, - ].includes(recommendation.displayInformation.type), - ) - const observationsRequiredRecommendations = data.recommendations.filter( - (recommendation) => - [ - UserMedicationRecommendationType.moreLabObservationsRequired, - UserMedicationRecommendationType.morePatientObservationsRequired, - ].includes(recommendation.displayInformation.type), - ) - const recommendationsAtTargetDose = data.recommendations.filter( - (recommendation) => - recommendation.displayInformation.type === - UserMedicationRecommendationType.targetDoseReached, - ) - - const result: string[] = [] - - if (optimizingRecommendations.length > 0) { - result.push(this.messageTexts.optimizationsAvailable) - } else if (observationsRequiredRecommendations.length > 0) { - result.push(this.messageTexts.missingHeartObservations) - } - - if (scoreDecreased === true) { - result.push(this.messageTexts.symptomsWorsened) - } else if (scoreDecreased === false && scoreAbove90 === true) { - result.push(this.messageTexts.weightIncreased) - } else if (scoreDecreased === false && scoreAbove90 === false) { - result.push(this.messageTexts.onTargetDose) - } - - if (result.length === 0) { - result.push(this.messageTexts.onTargetDose) - } + text(keyPoints: HealthSummaryKeyPointMessage[]): string { + const messages = keyPoints.map((keyPoint) => { + switch (keyPoint) { + case HealthSummaryKeyPointMessage.OPTIMIZATIONS_AVAILABLE: + return localize({ + en: 'There are possible options to improve your heart medicines. See the list of "Potential Med Changes" below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }) + case HealthSummaryKeyPointMessage.MISSING_HEART_OBSERVATIONS: + return localize({ + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }) + case HealthSummaryKeyPointMessage.ON_TARGET_DOSE: + return localize({ + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable, and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }) + case HealthSummaryKeyPointMessage.SYMPTOMS_WORSENED: + return localize({ + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', + }) + case HealthSummaryKeyPointMessage.WEIGHT_INCREASED: + return localize({ + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }) + case HealthSummaryKeyPointMessage.DIZZINESS_WORSENED: + return localize({ + en: 'Your dizziness is more bothersome. Discuss with your care team options to improve your dizziness, and watch the dizziness educational video.', + }) + case HealthSummaryKeyPointMessage.MISSING_ALL_OBSERVATIONS: + return localize({ + en: 'Check your blood pressure, heart rate, and weight more frequently and discuss with your care team how adjusting your medicines can help you feel better.', + }) + } + }) - return [] + return messages.length > 1 ? + messages + .map((message, index) => ` ${index + 1}. ${message}`) + .join('\n') + : messages.join('\n') }, }, currentMedicationsSection: { diff --git a/functions/src/healthSummary/generate.test.ts b/functions/src/healthSummary/generate.test.ts index 8eacc56a..df1a3842 100644 --- a/functions/src/healthSummary/generate.test.ts +++ b/functions/src/healthSummary/generate.test.ts @@ -9,9 +9,13 @@ import fs from 'fs' import { assert, expect } from 'chai' import { generateHealthSummary } from './generate.js' -import { type HealthSummaryData } from '../models/healthSummaryData.js' +import { + HealthSummaryKeyPointMessage, + type HealthSummaryData, +} from '../models/healthSummaryData.js' import { mockHealthSummaryData } from '../tests/mocks/healthSummaryData.js' import { TestFlags } from '../tests/testFlags.js' +import { readCsv } from '../tests/helpers/csv.js' describe('generateHealthSummary', () => { function comparePdf(actual: Buffer, expected: Buffer): boolean { @@ -78,4 +82,70 @@ describe('generateHealthSummary', () => { comparePdf(actualData, expectedData) } }) + + it('should generate the correct key point messages', () => { + const allMessages = new Set() + readCsv('src/tests/resources/keyPointMessages.csv', 55, (line, index) => { + if (index === 0) return + + switch (line[0]) { + case 'Eligible meds for optimization': + break + case 'No eligible meds at optimization; measure BP': + break + case 'No eligible meds at optimization; at target doses': + break + default: + console.log('Unknown item at 0:', line[0]) + } + + switch (line[1]) { + case 'Change >-10 and KCCQ<90': + break + case 'Change >-10 and KCCQ>=90': + break + case 'Change <-10': + break + default: + console.log('Unknown item at 1:', line[1]) + } + + switch (line[2]) { + case 'No decrease <-25': + break + case 'Decrease <-25': + break + default: + console.log('Unknown item at 2:', line[2]) + } + + switch (line[3]) { + case 'No weight gain but weight measured': + break + case 'Weight increase': + break + case 'No weight measured': + break + default: + console.log('Unknown item at 3:', line[3]) + } + + const newMessages = separateKeyPointMessages(line[4]) + for (const newMessage of newMessages) { + allMessages.add(newMessage) + } + // console.log(line) + }) + console.log(Array.from(allMessages.values()).sort()) + }) }) + +function separateKeyPointMessages(string: string): string[] { + return string + .split('\n') + .map((line) => + (line.match(/[0-9]\.\) .*/g) ? line.substring(4) : line) + .replace(/\s+/g, ' ') + .trim(), + ) +} diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 0710d077..6f8792da 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -13,6 +13,7 @@ import { presortedPercentile, type Observation, UserMedicationRecommendationType, + QuantityUnit, } from '@stanfordbdhg/engagehf-models' import { logger } from 'firebase-functions' import 'jspdf-autotable' /* eslint-disable-line */ @@ -119,10 +120,8 @@ class HealthSummaryPdfGenerator extends PdfGenerator { addKeyPointsSection() { this.addSectionTitle(this.texts.keyPointsSection.title) - const messages = this.texts.keyPointsSection.messages(this.data) - messages.forEach((message, index) => { - this.addText(` ${index + 1}. ${message}`, this.textStyles.bodyColored) - }) + const text = this.texts.keyPointsSection.text(this.data.keyPointMessages) + this.addText(text, this.textStyles.bodyColored) } addCurrentMedicationSection() { @@ -334,12 +333,10 @@ class HealthSummaryPdfGenerator extends PdfGenerator { ].map((title) => this.cell(title)), [ this.texts.vitalsSection.bodyWeightTable.rowTitle, - this.data.vitals.bodyWeight.at(0)?.value.toFixed(0) ?? '---', - avgWeight?.toFixed(0) ?? '---', + this.data.latestBodyWeight?.toFixed(0) ?? '---', + this.data.averageBodyWeight?.toFixed(0) ?? '---', '-', - isFinite(maxWeight) && isFinite(minWeight) ? - (maxWeight - minWeight).toFixed(0) - : '---', + this.data.bodyWeightRange?.toFixed(0) ?? '---', ].map((title) => this.cell(title)), ], columnWidth, diff --git a/functions/src/models/healthSummaryData.ts b/functions/src/models/healthSummaryData.ts index 6470dcac..f88bf39c 100644 --- a/functions/src/models/healthSummaryData.ts +++ b/functions/src/models/healthSummaryData.ts @@ -8,8 +8,6 @@ import { average, - compactMap, - QuantityUnit, UserMedicationRecommendationType, type FHIRAppointment, type Observation, @@ -42,7 +40,17 @@ export enum HealthSummaryMedicationRecommendationCategory { export enum HealthSummaryWeightCategory { INCREASING, MISSING, - STABLE, + STABLE_OR_DECREASING, +} + +export enum HealthSummaryKeyPointMessage { + OPTIMIZATIONS_AVAILABLE, + MISSING_HEART_OBSERVATIONS, + ON_TARGET_DOSE, + SYMPTOMS_WORSENED, + WEIGHT_INCREASED, + DIZZINESS_WORSENED, + MISSING_ALL_OBSERVATIONS, } export class HealthSummaryData { @@ -56,7 +64,48 @@ export class HealthSummaryData { vitals: HealthSummaryVitals symptomScores: SymptomScore[] - // Computed Properties + // Computed Properties - Key Points + + get keyPointMessages(): HealthSummaryKeyPointMessage[] { + return [] + } + + // Computed Properties - Body Weight + + get latestBodyWeight(): number | null { + return this.vitals.bodyWeight.at(0)?.value ?? null + } + + get averageBodyWeight(): number | null { + return ( + average(this.vitals.bodyWeight.map((observation) => observation.value)) ?? + null + ) + } + + get bodyWeightRange(): number | null { + const bodyWeightValues = this.vitals.bodyWeight.map( + (observation) => observation.value, + ) + const minWeight = Math.min(...bodyWeightValues) + const maxWeight = Math.max(...bodyWeightValues) + return isFinite(minWeight) && isFinite(maxWeight) ? + maxWeight - minWeight + : null + } + + get weightCategory(): HealthSummaryWeightCategory { + const averageWeight = this.averageBodyWeight + const latestWeight = this.latestBodyWeight + if (averageWeight === null || latestWeight === null) + return HealthSummaryWeightCategory.MISSING + + return latestWeight - averageWeight > 1 ? + HealthSummaryWeightCategory.INCREASING + : HealthSummaryWeightCategory.STABLE_OR_DECREASING + } + + // Computed Properties - Symptom Scores get latestSymptomScore(): SymptomScore | null { return this.symptomScores.at(0) ?? null @@ -85,17 +134,17 @@ export class HealthSummaryData { } } - get weightCategory(): HealthSummaryWeightCategory { - const weight = average( - compactMap(this.vitals.bodyWeight, (observation) => - observation.unit.convert(observation.value, QuantityUnit.lbs), - ), - ) - if (weight.length < 2) return HealthSummaryWeightCategory.MISSING + get dizzinessWorsened(): boolean { + const latestScore = this.latestSymptomScore?.dizzinessScore + const secondLatestScore = this.secondLatestSymptomScore?.dizzinessScore - return true + return latestScore !== undefined && secondLatestScore !== undefined ? + latestScore - secondLatestScore < -1 + : false } + // Computed Properties - Medication Recommendations + get recommendationCategory(): HealthSummaryMedicationRecommendationCategory { const hasOptimizations = this.recommendations.some((recommendation) => [ @@ -119,15 +168,6 @@ export class HealthSummaryData { return HealthSummaryMedicationRecommendationCategory.AT_TARGET } - get dizzinessWorsened(): boolean { - const latestScore = this.latestSymptomScore?.dizzinessScore - const secondLatestScore = this.secondLatestSymptomScore?.dizzinessScore - - return latestScore !== undefined && secondLatestScore !== undefined ? - latestScore - secondLatestScore < -1 - : false - } - // Initialization constructor(input: { diff --git a/functions/src/tests/helpers/csv.ts b/functions/src/tests/helpers/csv.ts index d97aa865..85e6fde6 100644 --- a/functions/src/tests/helpers/csv.ts +++ b/functions/src/tests/helpers/csv.ts @@ -16,15 +16,27 @@ export function readCsv( ) { const fileContent = fs.readFileSync(path, 'utf8') const lines = fileContent - .replace(/"(.*?)"/g, (str) => - str.slice(1, -1).split(',').join('###COMMA###'), + .replace(/"([\s\S]*?)"/g, (str) => + str + .slice(1, -1) + .split(',') + .join('###COMMA###') + .split('\n') + .join('###NEWLINE###'), ) .split('\n') expect(lines).to.have.length(expectedLines) lines.forEach((line, index) => { const values = line .split(',') - .map((x) => x.split('###COMMA###').join(',').trim()) + .map((x) => + x + .split('###COMMA###') + .join(',') + .split('###NEWLINE###') + .join('\n') + .trim(), + ) perform(values, index) }) } diff --git a/functions/src/tests/resources/keyPointMessages.csv b/functions/src/tests/resources/keyPointMessages.csv new file mode 100644 index 00000000..34260d81 --- /dev/null +++ b/functions/src/tests/resources/keyPointMessages.csv @@ -0,0 +1,116 @@ +Meds,Symptoms,Dizziness,Weight,Message +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +Eligible meds for optimization,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better." +No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better." +No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better." +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +" +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +Eligible meds for optimization,Change <-10,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change <-10,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team how adjusting your medications can help you feel better. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team potential options for helping you feel better. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +Eligible meds for optimization,Change <-10,No decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better." +No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better." +No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better." +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Check your blood pressure and heart rate multiple times a week to understand if your heart medicines can be adjusted to keep you feeling well and strengthen your heart. +2.) Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +Eligible meds for optimization,Change <-10,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your meds can help you feel better. +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." +No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight and discuss with your care team how adjusting your meds can help you feel better. +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." +No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team options for helping you feel better. +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." \ No newline at end of file From 5a44af8ca5980eefdbdf16fe6b87a787a968c027 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 15:47:02 +0100 Subject: [PATCH 03/12] update --- .../healthSummary/generate+localizations.ts | 77 +- functions/src/healthSummary/generate.test.ts | 79 +- functions/src/healthSummary/generate.ts | 23 +- .../healthSummary/keyPointsMessage.test.ts | 121 +++ .../src/healthSummary/keyPointsMessage.ts | 872 ++++++++++++++++++ functions/src/models/healthSummaryData.ts | 128 ++- .../databaseHealthSummaryService.test.ts | 1 + .../healthSummaryService.mock.ts | 2 +- .../services/patient/patientService.mock.ts | 102 +- .../tests/resources/emptyHealthSummary.pdf | Bin 130 -> 130 bytes .../src/tests/resources/keyPointMessages.csv | 130 +-- .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 12 files changed, 1244 insertions(+), 291 deletions(-) create mode 100644 functions/src/healthSummary/keyPointsMessage.test.ts create mode 100644 functions/src/healthSummary/keyPointsMessage.ts diff --git a/functions/src/healthSummary/generate+localizations.ts b/functions/src/healthSummary/generate+localizations.ts index 64350195..7002e4db 100644 --- a/functions/src/healthSummary/generate+localizations.ts +++ b/functions/src/healthSummary/generate+localizations.ts @@ -11,9 +11,14 @@ import { type UserMedicationRecommendationDoseSchedule, type FHIRAppointment, SymptomScore, - UserMedicationRecommendationType, } from '@stanfordbdhg/engagehf-models' -import { HealthSummaryKeyPointMessage } from '../models/healthSummaryData.js' +import { + HealthSummaryDizzinessCategory, + healthSummaryKeyPointTexts, + HealthSummaryMedicationRecommendationsCategory, + HealthSummarySymptomScoreCategory, + HealthSummaryWeightCategory, +} from './keyPointsMessage.js' export function healthSummaryLocalizations(languages: string[]) { function localize(strings: Record): string { @@ -53,45 +58,41 @@ export function healthSummaryLocalizations(languages: string[]) { title: localize({ en: 'KEY POINTS', }), - text(keyPoints: HealthSummaryKeyPointMessage[]): string { - const messages = keyPoints.map((keyPoint) => { - switch (keyPoint) { - case HealthSummaryKeyPointMessage.OPTIMIZATIONS_AVAILABLE: - return localize({ - en: 'There are possible options to improve your heart medicines. See the list of "Potential Med Changes" below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', - }) - case HealthSummaryKeyPointMessage.MISSING_HEART_OBSERVATIONS: - return localize({ - en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', - }) - case HealthSummaryKeyPointMessage.ON_TARGET_DOSE: - return localize({ - en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable, and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', - }) - case HealthSummaryKeyPointMessage.SYMPTOMS_WORSENED: - return localize({ - en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', - }) - case HealthSummaryKeyPointMessage.WEIGHT_INCREASED: - return localize({ - en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', - }) - case HealthSummaryKeyPointMessage.DIZZINESS_WORSENED: - return localize({ - en: 'Your dizziness is more bothersome. Discuss with your care team options to improve your dizziness, and watch the dizziness educational video.', - }) - case HealthSummaryKeyPointMessage.MISSING_ALL_OBSERVATIONS: - return localize({ - en: 'Check your blood pressure, heart rate, and weight more frequently and discuss with your care team how adjusting your medicines can help you feel better.', - }) - } - }) + defaultText: localize({ + en: 'No key points available. Please discuss with your care team for more information.', + }), + text(input: { + recommendations: HealthSummaryMedicationRecommendationsCategory | null + symptomScore: HealthSummarySymptomScoreCategory | null + dizziness: HealthSummaryDizzinessCategory | null + weight: HealthSummaryWeightCategory | null + }): string | null { + if ( + input.recommendations === null || + input.symptomScore === null || + input.dizziness === null || + input.weight === null + ) + return null - return messages.length > 1 ? - messages + const messages = + healthSummaryKeyPointTexts({ + recommendations: input.recommendations, + symptomScore: input.symptomScore, + dizziness: input.dizziness, + weight: input.weight, + })?.map((text) => text.localize(...languages)) ?? [] + + switch (messages.length) { + case 0: + return null + case 1: + return messages[0] + default: + return messages .map((message, index) => ` ${index + 1}. ${message}`) .join('\n') - : messages.join('\n') + } }, }, currentMedicationsSection: { diff --git a/functions/src/healthSummary/generate.test.ts b/functions/src/healthSummary/generate.test.ts index df1a3842..3f517505 100644 --- a/functions/src/healthSummary/generate.test.ts +++ b/functions/src/healthSummary/generate.test.ts @@ -9,13 +9,18 @@ import fs from 'fs' import { assert, expect } from 'chai' import { generateHealthSummary } from './generate.js' -import { - HealthSummaryKeyPointMessage, - type HealthSummaryData, -} from '../models/healthSummaryData.js' +import { type HealthSummaryData } from '../models/healthSummaryData.js' import { mockHealthSummaryData } from '../tests/mocks/healthSummaryData.js' import { TestFlags } from '../tests/testFlags.js' import { readCsv } from '../tests/helpers/csv.js' +import { + HealthSummaryDizzinessCategory, + HealthSummaryKeyPointMessage, + HealthSummaryMedicationRecommendationsCategory, + HealthSummarySymptomScoreCategory, + HealthSummaryWeightCategory, +} from './keyPointsMessage.js' +import { LocalizedText } from '@stanfordbdhg/engagehf-models' describe('generateHealthSummary', () => { function comparePdf(actual: Buffer, expected: Buffer): boolean { @@ -82,70 +87,4 @@ describe('generateHealthSummary', () => { comparePdf(actualData, expectedData) } }) - - it('should generate the correct key point messages', () => { - const allMessages = new Set() - readCsv('src/tests/resources/keyPointMessages.csv', 55, (line, index) => { - if (index === 0) return - - switch (line[0]) { - case 'Eligible meds for optimization': - break - case 'No eligible meds at optimization; measure BP': - break - case 'No eligible meds at optimization; at target doses': - break - default: - console.log('Unknown item at 0:', line[0]) - } - - switch (line[1]) { - case 'Change >-10 and KCCQ<90': - break - case 'Change >-10 and KCCQ>=90': - break - case 'Change <-10': - break - default: - console.log('Unknown item at 1:', line[1]) - } - - switch (line[2]) { - case 'No decrease <-25': - break - case 'Decrease <-25': - break - default: - console.log('Unknown item at 2:', line[2]) - } - - switch (line[3]) { - case 'No weight gain but weight measured': - break - case 'Weight increase': - break - case 'No weight measured': - break - default: - console.log('Unknown item at 3:', line[3]) - } - - const newMessages = separateKeyPointMessages(line[4]) - for (const newMessage of newMessages) { - allMessages.add(newMessage) - } - // console.log(line) - }) - console.log(Array.from(allMessages.values()).sort()) - }) }) - -function separateKeyPointMessages(string: string): string[] { - return string - .split('\n') - .map((line) => - (line.match(/[0-9]\.\) .*/g) ? line.substring(4) : line) - .replace(/\s+/g, ' ') - .trim(), - ) -} diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 6f8792da..a747b274 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -120,8 +120,20 @@ class HealthSummaryPdfGenerator extends PdfGenerator { addKeyPointsSection() { this.addSectionTitle(this.texts.keyPointsSection.title) - const text = this.texts.keyPointsSection.text(this.data.keyPointMessages) - this.addText(text, this.textStyles.bodyColored) + const text = this.texts.keyPointsSection.text({ + recommendations: this.data.recommendationsCategory, + symptomScore: this.data.symptomScoreCategory, + dizziness: this.data.dizzinessCategory, + weight: this.data.weightCategory, + }) + if (text !== null) { + this.addText(text, this.textStyles.bodyColored) + } else { + this.addText( + this.texts.keyPointsSection.defaultText, + this.textStyles.bodyItalic, + ) + } } addCurrentMedicationSection() { @@ -315,13 +327,6 @@ class HealthSummaryPdfGenerator extends PdfGenerator { columnWidth, this.data.vitals.dryWeight?.value, ) - const bodyWeightValues = this.data.vitals.bodyWeight.map( - (observation) => observation.value, - ) - const avgWeight = average(bodyWeightValues) - const maxWeight = Math.max(...bodyWeightValues) - const minWeight = Math.min(...bodyWeightValues) - this.addTable( [ [ diff --git a/functions/src/healthSummary/keyPointsMessage.test.ts b/functions/src/healthSummary/keyPointsMessage.test.ts new file mode 100644 index 00000000..07a81f85 --- /dev/null +++ b/functions/src/healthSummary/keyPointsMessage.test.ts @@ -0,0 +1,121 @@ +// +// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import { LocalizedText } from '@stanfordbdhg/engagehf-models' +import { readCsv } from '../tests/helpers/csv.js' +import { + HealthSummaryDizzinessCategory, + HealthSummaryKeyPointMessage, + healthSummaryKeyPointMessages, + healthSummaryKeyPointTexts, + HealthSummaryMedicationRecommendationsCategory, + HealthSummarySymptomScoreCategory, + HealthSummaryWeightCategory, +} from './keyPointsMessage.js' +import { expect } from 'chai' + +describe('keyPointsMessage', () => { + it('should generate the key point message json', () => { + const keyPointMessages: HealthSummaryKeyPointMessage[] = [] + readCsv('src/tests/resources/keyPointMessages.csv', 55, (line, index) => { + if (index === 0) return + + const recommendations = + Object.values(HealthSummaryMedicationRecommendationsCategory).find( + (category) => line[0] === category.toString(), + ) ?? null + const symptoms = + Object.values(HealthSummarySymptomScoreCategory).find( + (category) => line[1] === category.toString(), + ) ?? null + const dizziness = + Object.values(HealthSummaryDizzinessCategory).find( + (category) => line[2] === category.toString(), + ) ?? null + const weight = + Object.values(HealthSummaryWeightCategory).find( + (category) => line[3] === category.toString(), + ) ?? null + const texts = separateKeyPointTexts(line[4]).map( + (text) => new LocalizedText({ en: text }), + ) + + expect(recommendations, `recommendation: ${line[0]}`).to.not.be.null + expect(symptoms, `symptoms: ${line[1]}`).to.not.be.null + expect(dizziness, `dizziness: ${line[2]}`).to.not.be.null + expect(weight, `weight: ${line[3]}`).to.not.be.null + expect(texts).to.not.be.empty + + if ( + recommendations === null || + symptoms === null || + dizziness === null || + weight === null || + texts.length === 0 + ) + expect.fail('Invalid key point message') + + const message: HealthSummaryKeyPointMessage = { + recommendationsCategory: recommendations, + symptomScoreCategory: symptoms, + dizzinessCategory: dizziness, + weightCategory: weight, + texts, + } + + keyPointMessages.push(message) + }) + + expect( + keyPointMessages, + JSON.stringify( + keyPointMessages.map((message) => ({ + ...message, + texts: message.texts.map((text) => text.content), + })), + null, + 2, + ), + ).to.deep.equal(healthSummaryKeyPointMessages.value) + }) + + it('should cover all combinations', () => { + for (const recommendations of Object.values( + HealthSummaryMedicationRecommendationsCategory, + )) { + for (const symptomScore of Object.values( + HealthSummarySymptomScoreCategory, + )) { + for (const dizziness of Object.values(HealthSummaryDizzinessCategory)) { + for (const weight of Object.values(HealthSummaryWeightCategory)) { + const texts = healthSummaryKeyPointTexts({ + recommendations, + symptomScore, + dizziness, + weight, + }) + expect( + texts ?? [], + `${recommendations}, ${symptomScore}, ${dizziness}, ${weight}`, + ).to.not.be.empty + } + } + } + } + }) +}) + +function separateKeyPointTexts(string: string): string[] { + return string + .split('\n') + .map((line) => + (line.match(/[0-9]\.\) .*/g) ? line.substring(4) : line) + .replace(/\s+/g, ' ') + .trim(), + ) +} diff --git a/functions/src/healthSummary/keyPointsMessage.ts b/functions/src/healthSummary/keyPointsMessage.ts new file mode 100644 index 00000000..d782e2c9 --- /dev/null +++ b/functions/src/healthSummary/keyPointsMessage.ts @@ -0,0 +1,872 @@ +// +// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import { + Lazy, + LocalizedText, + localizedTextConverter, +} from '@stanfordbdhg/engagehf-models' +import { z } from 'zod' + +export enum HealthSummarySymptomScoreCategory { + HIGH_STABLE_OR_IMPROVING = 'Change >-10 and KCCQ>=90', + LOW_STABLE_OR_IMPROVING = 'Change >-10 and KCCQ<90', + WORSENING = 'Change <-10', +} + +export enum HealthSummaryMedicationRecommendationsCategory { + OPTIMIZATIONS_AVAILABLE = 'Eligible meds for optimization', + OBSERVATIONS_REQUIRED = 'No eligible meds at optimization; measure BP', + AT_TARGET = 'No eligible meds at optimization; at target doses', +} + +export enum HealthSummaryWeightCategory { + INCREASING = 'Weight increase', + MISSING = 'No weight measured', + STABLE_OR_DECREASING = 'No weight gain but weight measured', +} + +export enum HealthSummaryDizzinessCategory { + WORSENING = 'Decrease <-25', + STABLE_OR_IMPROVING = 'No decrease <-25', +} + +export interface HealthSummaryKeyPointMessage { + recommendationsCategory: HealthSummaryMedicationRecommendationsCategory + symptomScoreCategory: HealthSummarySymptomScoreCategory + dizzinessCategory: HealthSummaryDizzinessCategory + weightCategory: HealthSummaryWeightCategory + texts: LocalizedText[] +} + +export function healthSummaryKeyPointTexts(input: { + recommendations: HealthSummaryMedicationRecommendationsCategory + symptomScore: HealthSummarySymptomScoreCategory + dizziness: HealthSummaryDizzinessCategory + weight: HealthSummaryWeightCategory +}): LocalizedText[] | null { + return ( + healthSummaryKeyPointMessages.value.find( + (message) => + message.recommendationsCategory === input.recommendations && + message.symptomScoreCategory === input.symptomScore && + message.dizzinessCategory === input.dizziness && + message.weightCategory === input.weight, + )?.texts ?? null + ) +} + +export const healthSummaryKeyPointMessages = new Lazy< + HealthSummaryKeyPointMessage[] +>(() => + z + .object({ + recommendationsCategory: z.nativeEnum( + HealthSummaryMedicationRecommendationsCategory, + ), + symptomScoreCategory: z.nativeEnum(HealthSummarySymptomScoreCategory), + dizzinessCategory: z.nativeEnum(HealthSummaryDizzinessCategory), + weightCategory: z.nativeEnum(HealthSummaryWeightCategory), + texts: z.array(localizedTextConverter.schema), + }) + .array() + .parse([ + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight gain but weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team how adjusting your medications can help you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'Weight increase', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team potential options for helping you feel better.', + }, + { + en: 'You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'No decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ<90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart.', + }, + { + en: 'You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + }, + { + en: 'Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Check your blood pressure and heart rate multiple times a week to understand if your heart medicines can be adjusted to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change >-10 and KCCQ>=90', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart.', + }, + { + en: 'Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'Eligible meds for optimization', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your meds can help you feel better.', + }, + { + en: 'Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: 'No eligible meds at optimization; measure BP', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight and discuss with your care team how adjusting your meds can help you feel better.', + }, + { + en: 'Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.', + }, + ], + }, + { + recommendationsCategory: + 'No eligible meds at optimization; at target doses', + symptomScoreCategory: 'Change <-10', + dizzinessCategory: 'Decrease <-25', + weightCategory: 'No weight measured', + texts: [ + { + en: 'Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart.', + }, + { + en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team options for helping you feel better.', + }, + { + en: 'Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.', + }, + ], + }, + ]), +) diff --git a/functions/src/models/healthSummaryData.ts b/functions/src/models/healthSummaryData.ts index f88bf39c..5c99d869 100644 --- a/functions/src/models/healthSummaryData.ts +++ b/functions/src/models/healthSummaryData.ts @@ -14,6 +14,12 @@ import { type SymptomScore, type UserMedicationRecommendation, } from '@stanfordbdhg/engagehf-models' +import { + HealthSummaryDizzinessCategory, + HealthSummaryMedicationRecommendationsCategory, + HealthSummarySymptomScoreCategory, + HealthSummaryWeightCategory, +} from '../healthSummary/keyPointsMessage.js' export interface HealthSummaryVitals { systolicBloodPressure: Observation[] @@ -24,35 +30,6 @@ export interface HealthSummaryVitals { dryWeight?: Observation } -export enum HealthSummarySymptomScoreCategory { - ABOVE_90_STABLE_OR_IMPROVING, - ABOVE_90_WORSENING, - BELOW_90_STABLE_OR_IMPROVING, - BELOW_90_WORSENING, -} - -export enum HealthSummaryMedicationRecommendationCategory { - OPTIMIZATIONS_AVAILABLE, - OBSERVATIONS_REQUIRED, - AT_TARGET, -} - -export enum HealthSummaryWeightCategory { - INCREASING, - MISSING, - STABLE_OR_DECREASING, -} - -export enum HealthSummaryKeyPointMessage { - OPTIMIZATIONS_AVAILABLE, - MISSING_HEART_OBSERVATIONS, - ON_TARGET_DOSE, - SYMPTOMS_WORSENED, - WEIGHT_INCREASED, - DIZZINESS_WORSENED, - MISSING_ALL_OBSERVATIONS, -} - export class HealthSummaryData { // Stored Properties @@ -64,12 +41,6 @@ export class HealthSummaryData { vitals: HealthSummaryVitals symptomScores: SymptomScore[] - // Computed Properties - Key Points - - get keyPointMessages(): HealthSummaryKeyPointMessage[] { - return [] - } - // Computed Properties - Body Weight get latestBodyWeight(): number | null { @@ -94,17 +65,6 @@ export class HealthSummaryData { : null } - get weightCategory(): HealthSummaryWeightCategory { - const averageWeight = this.averageBodyWeight - const latestWeight = this.latestBodyWeight - if (averageWeight === null || latestWeight === null) - return HealthSummaryWeightCategory.MISSING - - return latestWeight - averageWeight > 1 ? - HealthSummaryWeightCategory.INCREASING - : HealthSummaryWeightCategory.STABLE_OR_DECREASING - } - // Computed Properties - Symptom Scores get latestSymptomScore(): SymptomScore | null { @@ -115,37 +75,21 @@ export class HealthSummaryData { return this.symptomScores.at(1) ?? null } - get symptomScoreCategory(): HealthSummarySymptomScoreCategory | null { - const latestScore = this.latestSymptomScore - const secondLatestScore = this.secondLatestSymptomScore + // Computed Properties - KeyPoints - if (latestScore === null || secondLatestScore === null) { - return null - } - - if (latestScore.overallScore >= 90) { - return latestScore.overallScore - secondLatestScore.overallScore > -10 ? - HealthSummarySymptomScoreCategory.ABOVE_90_STABLE_OR_IMPROVING - : HealthSummarySymptomScoreCategory.ABOVE_90_WORSENING - } else { - return latestScore.overallScore - secondLatestScore.overallScore > -10 ? - HealthSummarySymptomScoreCategory.BELOW_90_STABLE_OR_IMPROVING - : HealthSummarySymptomScoreCategory.BELOW_90_WORSENING - } - } + get dizzinessCategory(): HealthSummaryDizzinessCategory | null { + const latestScore = this.latestSymptomScore?.dizzinessScore ?? null + const secondLatestScore = + this.secondLatestSymptomScore?.dizzinessScore ?? null - get dizzinessWorsened(): boolean { - const latestScore = this.latestSymptomScore?.dizzinessScore - const secondLatestScore = this.secondLatestSymptomScore?.dizzinessScore + if (latestScore === null || secondLatestScore === null) return null - return latestScore !== undefined && secondLatestScore !== undefined ? - latestScore - secondLatestScore < -1 - : false + return latestScore - secondLatestScore < 0 ? + HealthSummaryDizzinessCategory.WORSENING + : HealthSummaryDizzinessCategory.STABLE_OR_IMPROVING } - // Computed Properties - Medication Recommendations - - get recommendationCategory(): HealthSummaryMedicationRecommendationCategory { + get recommendationsCategory(): HealthSummaryMedicationRecommendationsCategory | null { const hasOptimizations = this.recommendations.some((recommendation) => [ UserMedicationRecommendationType.improvementAvailable, @@ -153,7 +97,7 @@ export class HealthSummaryData { ].includes(recommendation.displayInformation.type), ) if (hasOptimizations) - return HealthSummaryMedicationRecommendationCategory.OPTIMIZATIONS_AVAILABLE + return HealthSummaryMedicationRecommendationsCategory.OPTIMIZATIONS_AVAILABLE const hasObservationsRequired = this.recommendations.some( (recommendation) => @@ -163,9 +107,43 @@ export class HealthSummaryData { ].includes(recommendation.displayInformation.type), ) if (hasObservationsRequired) - return HealthSummaryMedicationRecommendationCategory.OBSERVATIONS_REQUIRED + return HealthSummaryMedicationRecommendationsCategory.OBSERVATIONS_REQUIRED + + const hasAtTarget = this.recommendations.some((recommendation) => + [ + UserMedicationRecommendationType.personalTargetDoseReached, + UserMedicationRecommendationType.targetDoseReached, + ].includes(recommendation.displayInformation.type), + ) + return hasAtTarget ? + HealthSummaryMedicationRecommendationsCategory.AT_TARGET + : null + } + + get symptomScoreCategory(): HealthSummarySymptomScoreCategory | null { + const latestScore = this.latestSymptomScore + const secondLatestScore = this.secondLatestSymptomScore + + if (latestScore === null || secondLatestScore === null) return null - return HealthSummaryMedicationRecommendationCategory.AT_TARGET + if (latestScore.overallScore - secondLatestScore.overallScore <= -10) + return HealthSummarySymptomScoreCategory.WORSENING + + if (latestScore.overallScore < 90) + return HealthSummarySymptomScoreCategory.LOW_STABLE_OR_IMPROVING + + return HealthSummarySymptomScoreCategory.HIGH_STABLE_OR_IMPROVING + } + + get weightCategory(): HealthSummaryWeightCategory { + const averageWeight = this.averageBodyWeight + const latestWeight = this.latestBodyWeight + if (averageWeight === null || latestWeight === null) + return HealthSummaryWeightCategory.MISSING + + return latestWeight - averageWeight > 1 ? + HealthSummaryWeightCategory.INCREASING + : HealthSummaryWeightCategory.STABLE_OR_DECREASING } // Initialization diff --git a/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts b/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts index 367e97e8..6c96912c 100644 --- a/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts +++ b/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts @@ -26,6 +26,7 @@ describe('HealthSummaryService', () => { 'mockUser', QuantityUnit.lbs, ) + console.log('actualData:', actualData.nextAppointment?.start.toString()) const expectedData = await mockHealthSummaryData('mockUser') // TODO: Remove the next line to check whether medication optimizations also match the expected value. expectedData.recommendations = [] diff --git a/functions/src/services/healthSummary/healthSummaryService.mock.ts b/functions/src/services/healthSummary/healthSummaryService.mock.ts index f930e871..e793dd72 100644 --- a/functions/src/services/healthSummary/healthSummaryService.mock.ts +++ b/functions/src/services/healthSummary/healthSummaryService.mock.ts @@ -31,7 +31,7 @@ export class MockHealthSummaryService implements HealthSummaryService { // Constructor - constructor(startDate: Date = new Date('2024-02-02')) { + constructor(startDate: Date = new Date(2024, 2, 2, 12, 30)) { this.startDate = startDate } diff --git a/functions/src/services/patient/patientService.mock.ts b/functions/src/services/patient/patientService.mock.ts index b03d6c2c..83817442 100644 --- a/functions/src/services/patient/patientService.mock.ts +++ b/functions/src/services/patient/patientService.mock.ts @@ -33,7 +33,7 @@ export class MockPatientService implements PatientService { // Constructor - constructor(startDate: Date = new Date('2024-02-02')) { + constructor(startDate: Date = new Date(2024, 2, 2, 12, 30)) { this.startDate = startDate } @@ -120,15 +120,15 @@ export class MockPatientService implements PatientService { userId: string, ): Promise<[Observation[], Observation[]]> { const values = [ - this.bloodPressureObservations(110, 70, new Date('2024-02-01')), - this.bloodPressureObservations(114, 82, new Date('2024-01-31')), - this.bloodPressureObservations(123, 75, new Date('2024-01-30')), - this.bloodPressureObservations(109, 77, new Date('2024-01-29')), - this.bloodPressureObservations(105, 72, new Date('2024-01-28')), - this.bloodPressureObservations(98, 68, new Date('2024-01-27')), - this.bloodPressureObservations(94, 65, new Date('2024-01-26')), - this.bloodPressureObservations(104, 72, new Date('2024-01-25')), - this.bloodPressureObservations(102, 80, new Date('2024-01-24')), + this.bloodPressureObservations(110, 70, new Date(2024, 1, 30, 12, 30)), + this.bloodPressureObservations(114, 82, new Date(2024, 1, 29, 12, 30)), + this.bloodPressureObservations(123, 75, new Date(2024, 1, 28, 12, 30)), + this.bloodPressureObservations(109, 77, new Date(2024, 1, 27, 12, 30)), + this.bloodPressureObservations(105, 72, new Date(2024, 1, 26, 12, 30)), + this.bloodPressureObservations(98, 68, new Date(2024, 1, 25, 12, 30)), + this.bloodPressureObservations(94, 65, new Date(2024, 1, 24, 12, 30)), + this.bloodPressureObservations(104, 72, new Date(2024, 1, 23, 12, 30)), + this.bloodPressureObservations(102, 80, new Date(2024, 1, 22, 12, 30)), ] return [values.map((value) => value[0]), values.map((value) => value[1])] } @@ -154,15 +154,51 @@ export class MockPatientService implements PatientService { async getBodyWeightObservations(userId: string): Promise { return [ - this.bodyWeightObservation(269, QuantityUnit.lbs, new Date('2024-02-01')), - this.bodyWeightObservation(267, QuantityUnit.lbs, new Date('2024-01-31')), - this.bodyWeightObservation(267, QuantityUnit.lbs, new Date('2024-01-30')), - this.bodyWeightObservation(265, QuantityUnit.lbs, new Date('2024-01-29')), - this.bodyWeightObservation(268, QuantityUnit.lbs, new Date('2024-01-28')), - this.bodyWeightObservation(268, QuantityUnit.lbs, new Date('2024-01-27')), - this.bodyWeightObservation(266, QuantityUnit.lbs, new Date('2024-01-26')), - this.bodyWeightObservation(266, QuantityUnit.lbs, new Date('2024-01-25')), - this.bodyWeightObservation(267, QuantityUnit.lbs, new Date('2024-01-24')), + this.bodyWeightObservation( + 269, + QuantityUnit.lbs, + new Date(2024, 1, 30, 12, 30), + ), + this.bodyWeightObservation( + 267, + QuantityUnit.lbs, + new Date(2024, 1, 29, 12, 30), + ), + this.bodyWeightObservation( + 267, + QuantityUnit.lbs, + new Date(2024, 1, 28, 12, 30), + ), + this.bodyWeightObservation( + 265, + QuantityUnit.lbs, + new Date(2024, 1, 27, 12, 30), + ), + this.bodyWeightObservation( + 268, + QuantityUnit.lbs, + new Date(2024, 1, 26, 12, 30), + ), + this.bodyWeightObservation( + 268, + QuantityUnit.lbs, + new Date(2024, 1, 25, 12, 30), + ), + this.bodyWeightObservation( + 266, + QuantityUnit.lbs, + new Date(2024, 1, 24, 12, 30), + ), + this.bodyWeightObservation( + 266, + QuantityUnit.lbs, + new Date(2024, 1, 23, 12, 30), + ), + this.bodyWeightObservation( + 267, + QuantityUnit.lbs, + new Date(2024, 1, 22, 12, 30), + ), ] } @@ -180,15 +216,15 @@ export class MockPatientService implements PatientService { async getHeartRateObservations(userId: string): Promise { return [ - this.heartRateObservation(79, new Date('2024-02-01')), - this.heartRateObservation(62, new Date('2024-01-31')), - this.heartRateObservation(77, new Date('2024-01-30')), - this.heartRateObservation(63, new Date('2024-01-29')), - this.heartRateObservation(61, new Date('2024-01-28')), - this.heartRateObservation(70, new Date('2024-01-27')), - this.heartRateObservation(67, new Date('2024-01-26')), - this.heartRateObservation(80, new Date('2024-01-25')), - this.heartRateObservation(65, new Date('2024-01-24')), + this.heartRateObservation(79, new Date(2024, 1, 30, 12, 30)), + this.heartRateObservation(62, new Date(2024, 1, 29, 12, 30)), + this.heartRateObservation(77, new Date(2024, 1, 28, 12, 30)), + this.heartRateObservation(63, new Date(2024, 1, 27, 12, 30)), + this.heartRateObservation(61, new Date(2024, 1, 26, 12, 30)), + this.heartRateObservation(70, new Date(2024, 1, 25, 12, 30)), + this.heartRateObservation(67, new Date(2024, 1, 24, 12, 30)), + this.heartRateObservation(80, new Date(2024, 1, 23, 12, 30)), + this.heartRateObservation(65, new Date(2024, 1, 22, 12, 30)), ] } @@ -215,7 +251,7 @@ export class MockPatientService implements PatientService { unit: QuantityUnit, ): Promise { return { - date: new Date('2024-01-29'), + date: new Date(2024, 1, 27, 12, 30), value: 267.5, unit: QuantityUnit.lbs, } @@ -265,7 +301,7 @@ export class MockPatientService implements PatientService { qualityOfLifeScore: 20, symptomFrequencyScore: 60, dizzinessScore: 3, - date: new Date('2024-01-24'), + date: new Date(2024, 1, 22, 12, 30), }), new SymptomScore({ questionnaireResponseId: '3', @@ -275,7 +311,7 @@ export class MockPatientService implements PatientService { qualityOfLifeScore: 37, symptomFrequencyScore: 72, dizzinessScore: 2, - date: new Date('2024-01-15'), + date: new Date(2024, 1, 13, 12, 30), }), new SymptomScore({ questionnaireResponseId: '2', @@ -285,7 +321,7 @@ export class MockPatientService implements PatientService { qualityOfLifeScore: 25, symptomFrequencyScore: 60, dizzinessScore: 1, - date: new Date('2023-12-30'), + date: new Date(2023, 12, 28, 12, 30), }), new SymptomScore({ questionnaireResponseId: '1', @@ -295,7 +331,7 @@ export class MockPatientService implements PatientService { qualityOfLifeScore: 60, symptomFrequencyScore: 80, dizzinessScore: 1, - date: new Date('2023-12-15'), + date: new Date(2023, 12, 13, 12, 30), }), ] return values.map((value, index) => ({ diff --git a/functions/src/tests/resources/emptyHealthSummary.pdf b/functions/src/tests/resources/emptyHealthSummary.pdf index cc00f6686d0c5ede8fbcb90ce3b2000c61fef28e..11a830fda467540482b2aeea332a0e285ea9f31a 100644 GIT binary patch delta 83 zcmV~$yAgmO3;@uhWeP_~$cJPHmxvJVtnDnBz>$4#mt8(R3C4_&iIFhfm^0mTNh%FS dG^1e(?ki9b8YR0(3%#tPD@1+nHxHq1T7S%477+jd delta 83 zcmV~$yA8k~3$>JNU86$t-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. -No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. -No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -Eligible meds for optimization,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better." +Meds,Symptoms,Dizziness,Weight,Message, +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , +Eligible meds for optimization,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better." +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better." +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.", Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -" +", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." -Eligible meds for optimization,Change <-10,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +Eligible meds for optimization,Change <-10,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart." +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +3.) Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +3.) Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. +3.) Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change <-10,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team how adjusting your medications can help you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team potential options for helping you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. -No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. -No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -Eligible meds for optimization,Change <-10,No decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better." +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , +Eligible meds for optimization,Change <-10,No decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better." +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better." +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better.", Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you keep feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Check your blood pressure and heart rate multiple times a week to understand if your heart medicines can be adjusted to keep you feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video." +2.) Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video." -Eligible meds for optimization,Change <-10,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you start feeling better and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your meds can help you feel better. -3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight and discuss with your care team how adjusting your meds can help you feel better. -3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team options for helping you feel better. -3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video." \ No newline at end of file +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.", \ No newline at end of file diff --git a/functions/src/tests/resources/mockHealthSummary.pdf b/functions/src/tests/resources/mockHealthSummary.pdf index 95241a3b8d90500c7f642526d415b2b2b6d2205e..6570a9d079f18d857158b87dd9e9af4993abfd78 100644 GIT binary patch delta 84 zcmWN|u@QhU2nEoy%@mG62nYNj96|-|tnF;GfFtYc-(2SM>Lzhm3LNURTe(v~kv$Tf e#)tuy&`Lcmi;EEy_(vr)EJwSqCs#ICvGxJqix&|9 delta 84 zcmV~$yAgmO3;@uhWeP`tg#3nZ2ou7cwVf>sII{2UvdhP3_Ylk3G=r}sBY Date: Thu, 9 Jan 2025 15:50:42 +0100 Subject: [PATCH 04/12] reuse --- ...nSans-BoldItalic.license => OpenSans-BoldItalic.ttf.license} | 0 .../{OpenSans-Italic.license => OpenSans-Italic.ttf.license} | 0 functions/src/tests/resources/keyPointMessages.csv.license | 2 ++ 3 files changed, 2 insertions(+) rename functions/resources/fonts/{OpenSans-BoldItalic.license => OpenSans-BoldItalic.ttf.license} (100%) rename functions/resources/fonts/{OpenSans-Italic.license => OpenSans-Italic.ttf.license} (100%) create mode 100644 functions/src/tests/resources/keyPointMessages.csv.license diff --git a/functions/resources/fonts/OpenSans-BoldItalic.license b/functions/resources/fonts/OpenSans-BoldItalic.ttf.license similarity index 100% rename from functions/resources/fonts/OpenSans-BoldItalic.license rename to functions/resources/fonts/OpenSans-BoldItalic.ttf.license diff --git a/functions/resources/fonts/OpenSans-Italic.license b/functions/resources/fonts/OpenSans-Italic.ttf.license similarity index 100% rename from functions/resources/fonts/OpenSans-Italic.license rename to functions/resources/fonts/OpenSans-Italic.ttf.license diff --git a/functions/src/tests/resources/keyPointMessages.csv.license b/functions/src/tests/resources/keyPointMessages.csv.license new file mode 100644 index 00000000..aecd0247 --- /dev/null +++ b/functions/src/tests/resources/keyPointMessages.csv.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2023 Stanford University +# SPDX-License-Identifier: MIT \ No newline at end of file From 6ccbfded2b42d27354b5c5c816a09f657b1346d2 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 15:53:49 +0100 Subject: [PATCH 05/12] update --- functions/models/src/codes/quantityUnit.ts | 2 +- functions/src/healthSummary/generate+localizations.ts | 10 +++++----- functions/src/healthSummary/generate.test.ts | 9 --------- functions/src/healthSummary/generate.ts | 4 +--- functions/src/healthSummary/keyPointsMessage.test.ts | 6 +++--- functions/src/healthSummary/keyPointsMessage.ts | 2 +- 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/functions/models/src/codes/quantityUnit.ts b/functions/models/src/codes/quantityUnit.ts index 6fb52ff9..2214ed97 100644 --- a/functions/models/src/codes/quantityUnit.ts +++ b/functions/models/src/codes/quantityUnit.ts @@ -7,7 +7,7 @@ // import { type FHIRQuantity } from '../fhir/baseTypes/fhirQuantity.js' -import { Observation } from '../types/observation.js' +import { type Observation } from '../types/observation.js' export class QuantityUnit { // Static Properties diff --git a/functions/src/healthSummary/generate+localizations.ts b/functions/src/healthSummary/generate+localizations.ts index 7002e4db..42c16148 100644 --- a/functions/src/healthSummary/generate+localizations.ts +++ b/functions/src/healthSummary/generate+localizations.ts @@ -10,14 +10,14 @@ import { LocalizedText, type UserMedicationRecommendationDoseSchedule, type FHIRAppointment, - SymptomScore, + type SymptomScore, } from '@stanfordbdhg/engagehf-models' import { - HealthSummaryDizzinessCategory, + type HealthSummaryDizzinessCategory, healthSummaryKeyPointTexts, - HealthSummaryMedicationRecommendationsCategory, - HealthSummarySymptomScoreCategory, - HealthSummaryWeightCategory, + type HealthSummaryMedicationRecommendationsCategory, + type HealthSummarySymptomScoreCategory, + type HealthSummaryWeightCategory, } from './keyPointsMessage.js' export function healthSummaryLocalizations(languages: string[]) { diff --git a/functions/src/healthSummary/generate.test.ts b/functions/src/healthSummary/generate.test.ts index 3f517505..8eacc56a 100644 --- a/functions/src/healthSummary/generate.test.ts +++ b/functions/src/healthSummary/generate.test.ts @@ -12,15 +12,6 @@ import { generateHealthSummary } from './generate.js' import { type HealthSummaryData } from '../models/healthSummaryData.js' import { mockHealthSummaryData } from '../tests/mocks/healthSummaryData.js' import { TestFlags } from '../tests/testFlags.js' -import { readCsv } from '../tests/helpers/csv.js' -import { - HealthSummaryDizzinessCategory, - HealthSummaryKeyPointMessage, - HealthSummaryMedicationRecommendationsCategory, - HealthSummarySymptomScoreCategory, - HealthSummaryWeightCategory, -} from './keyPointsMessage.js' -import { LocalizedText } from '@stanfordbdhg/engagehf-models' describe('generateHealthSummary', () => { function comparePdf(actual: Buffer, expected: Buffer): boolean { diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index a747b274..1524c9bf 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -7,13 +7,11 @@ // import { - average, percentage, presortedMedian, presortedPercentile, type Observation, UserMedicationRecommendationType, - QuantityUnit, } from '@stanfordbdhg/engagehf-models' import { logger } from 'firebase-functions' import 'jspdf-autotable' /* eslint-disable-line */ @@ -21,8 +19,8 @@ import { type CellDef } from 'jspdf-autotable' /* eslint-disable-line */ import { healthSummaryLocalizations } from './generate+localizations.js' import { generateChartSvg } from './generateChart.js' import { generateSpeedometerSvg } from './generateSpeedometer.js' -import { type HealthSummaryData } from '../models/healthSummaryData.js' import { PdfGenerator } from './pdfGenerator.js' +import { type HealthSummaryData } from '../models/healthSummaryData.js' export interface HealthSummaryOptions { languages: string[] diff --git a/functions/src/healthSummary/keyPointsMessage.test.ts b/functions/src/healthSummary/keyPointsMessage.test.ts index 07a81f85..68dede71 100644 --- a/functions/src/healthSummary/keyPointsMessage.test.ts +++ b/functions/src/healthSummary/keyPointsMessage.test.ts @@ -7,17 +7,17 @@ // import { LocalizedText } from '@stanfordbdhg/engagehf-models' -import { readCsv } from '../tests/helpers/csv.js' +import { expect } from 'chai' import { HealthSummaryDizzinessCategory, - HealthSummaryKeyPointMessage, + type HealthSummaryKeyPointMessage, healthSummaryKeyPointMessages, healthSummaryKeyPointTexts, HealthSummaryMedicationRecommendationsCategory, HealthSummarySymptomScoreCategory, HealthSummaryWeightCategory, } from './keyPointsMessage.js' -import { expect } from 'chai' +import { readCsv } from '../tests/helpers/csv.js' describe('keyPointsMessage', () => { it('should generate the key point message json', () => { diff --git a/functions/src/healthSummary/keyPointsMessage.ts b/functions/src/healthSummary/keyPointsMessage.ts index d782e2c9..ea15b802 100644 --- a/functions/src/healthSummary/keyPointsMessage.ts +++ b/functions/src/healthSummary/keyPointsMessage.ts @@ -8,7 +8,7 @@ import { Lazy, - LocalizedText, + type LocalizedText, localizedTextConverter, } from '@stanfordbdhg/engagehf-models' import { z } from 'zod' From af81979671d1ec4ebfb659bde1b80fefbc4500c7 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 16:20:47 +0100 Subject: [PATCH 06/12] update --- .../healthSummary/generate+localizations.ts | 17 +++------ functions/src/healthSummary/generate.ts | 33 ++++++++++-------- functions/src/healthSummary/pdfGenerator.ts | 15 ++++++++ .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/functions/src/healthSummary/generate+localizations.ts b/functions/src/healthSummary/generate+localizations.ts index 42c16148..22ad1bb7 100644 --- a/functions/src/healthSummary/generate+localizations.ts +++ b/functions/src/healthSummary/generate+localizations.ts @@ -66,7 +66,7 @@ export function healthSummaryLocalizations(languages: string[]) { symptomScore: HealthSummarySymptomScoreCategory | null dizziness: HealthSummaryDizzinessCategory | null weight: HealthSummaryWeightCategory | null - }): string | null { + }): string[] | null { if ( input.recommendations === null || input.symptomScore === null || @@ -81,18 +81,11 @@ export function healthSummaryLocalizations(languages: string[]) { symptomScore: input.symptomScore, dizziness: input.dizziness, weight: input.weight, - })?.map((text) => text.localize(...languages)) ?? [] + }) ?? [] - switch (messages.length) { - case 0: - return null - case 1: - return messages[0] - default: - return messages - .map((message, index) => ` ${index + 1}. ${message}`) - .join('\n') - } + if (messages.length === 0) return null + + return messages.map((text) => text.localize(...languages)) }, }, currentMedicationsSection: { diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 1524c9bf..6bacf65f 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -118,14 +118,18 @@ class HealthSummaryPdfGenerator extends PdfGenerator { addKeyPointsSection() { this.addSectionTitle(this.texts.keyPointsSection.title) - const text = this.texts.keyPointsSection.text({ + const texts = this.texts.keyPointsSection.text({ recommendations: this.data.recommendationsCategory, symptomScore: this.data.symptomScoreCategory, dizziness: this.data.dizzinessCategory, weight: this.data.weightCategory, }) - if (text !== null) { - this.addText(text, this.textStyles.bodyColored) + if (texts !== null) { + if (texts.length === 1) { + this.addText(texts[0], this.textStyles.bodyColored) + } else { + this.addList(texts, this.textStyles.bodyColored) + } } else { this.addText( this.texts.keyPointsSection.defaultText, @@ -197,19 +201,20 @@ class HealthSummaryPdfGenerator extends PdfGenerator { if (optimizations.length === 0) return this.addSectionTitle(this.texts.medicationRecommendationsSection.title) - optimizations.forEach((recommendation, index) => { - const title = recommendation.displayInformation.title.localize( - ...this.options.languages, - ) - const description = - recommendation.displayInformation.description.localize( + this.addList( + optimizations.map((recommendation) => { + const title = recommendation.displayInformation.title.localize( ...this.options.languages, ) - this.addText( - ` ${index + 1}. ${title}: ${description}`, - this.textStyles.bodyColored, - ) - }) + const description = + recommendation.displayInformation.description.localize( + ...this.options.languages, + ) + return `${title}: ${description}` + }), + this.textStyles.bodyColored, + ) + this.moveDown(this.textStyles.body.fontSize / 2) this.addText( this.texts.medicationRecommendationsSection.description, diff --git a/functions/src/healthSummary/pdfGenerator.ts b/functions/src/healthSummary/pdfGenerator.ts index 89b606f6..7108ca3f 100644 --- a/functions/src/healthSummary/pdfGenerator.ts +++ b/functions/src/healthSummary/pdfGenerator.ts @@ -258,6 +258,17 @@ export class PdfGenerator { } } + addList(texts: string[], textStyle: TextStyle) { + texts.forEach((text, index) => { + this.indent(1, textStyle) + this.addText(`${index + 1}.`, textStyle) + this.moveDown(-textStyle.fontSize * 1.5) + this.indent(1.5, textStyle) + this.addText(text, textStyle) + this.indent(-2.5, textStyle) + }) + } + addLine( start: { x: number; y: number }, end: { x: number; y: number }, @@ -315,4 +326,8 @@ export class PdfGenerator { title: title, } } + + private indent(amount: number, textStyle: TextStyle) { + this.cursor.x += amount * textStyle.fontSize + } } diff --git a/functions/src/tests/resources/mockHealthSummary.pdf b/functions/src/tests/resources/mockHealthSummary.pdf index 6570a9d079f18d857158b87dd9e9af4993abfd78..97a57e05f00822fa0de0c1a221893fd0cf37a393 100644 GIT binary patch delta 84 zcmV~$yA6Oa3lEbHD3^nn+7O&r%j( delta 84 zcmWN|u@QhU2nEoy%@mG62nYNj96|-|tnF;GfFtYc-(2SM>Lzhm3LNURTe(v~kv$Tf e#)tuy&`Lcmi;EEy_(vr)EJwSqCs#ICvGxJqix&|9 From 58b44c4e7be8b95e1ed440a7d560c51183c731af Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 16:22:27 +0100 Subject: [PATCH 07/12] remove last visit header --- functions/src/healthSummary/generate+localizations.ts | 3 --- functions/src/healthSummary/generate.ts | 2 -- 2 files changed, 5 deletions(-) diff --git a/functions/src/healthSummary/generate+localizations.ts b/functions/src/healthSummary/generate+localizations.ts index 22ad1bb7..40097285 100644 --- a/functions/src/healthSummary/generate+localizations.ts +++ b/functions/src/healthSummary/generate+localizations.ts @@ -239,9 +239,6 @@ export function healthSummaryLocalizations(languages: string[]) { sevenDayAverageHeader: localize({ en: '7-Day Average', }), - lastVisitHeader: localize({ - en: 'Last Visit', - }), rangeHeader: localize({ en: 'Range', }), diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 6bacf65f..5d45964d 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -336,14 +336,12 @@ class HealthSummaryPdfGenerator extends PdfGenerator { this.texts.vitalsSection.bodyWeightTable.titleHeader, this.texts.vitalsSection.bodyWeightTable.currentHeader, this.texts.vitalsSection.bodyWeightTable.sevenDayAverageHeader, - this.texts.vitalsSection.bodyWeightTable.lastVisitHeader, this.texts.vitalsSection.bodyWeightTable.rangeHeader, ].map((title) => this.cell(title)), [ this.texts.vitalsSection.bodyWeightTable.rowTitle, this.data.latestBodyWeight?.toFixed(0) ?? '---', this.data.averageBodyWeight?.toFixed(0) ?? '---', - '-', this.data.bodyWeightRange?.toFixed(0) ?? '---', ].map((title) => this.cell(title)), ], From 57baf0e184431fe276fa42809ce311e54024f304 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 16:24:06 +0100 Subject: [PATCH 08/12] update --- .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/functions/src/tests/resources/mockHealthSummary.pdf b/functions/src/tests/resources/mockHealthSummary.pdf index 97a57e05f00822fa0de0c1a221893fd0cf37a393..3c1bf3a5ef7f36cfa1316a97aa7ac3e0213103a6 100644 GIT binary patch delta 84 zcmV~$xeMAI~k4{e!6k!W_hM0`h4Nx+>JMHxGS+T5F_fa83-CfnL2D0L< euQE-2O^zV9u7VY6pbjHe2vy|ge&cAOV9P(;a~44W delta 84 zcmV~$yA6Oa3lEbHD3^nn+7O&r%j( From e59aed841027d8030450896b71670c370a9981fb Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 9 Jan 2025 16:32:57 +0100 Subject: [PATCH 09/12] update --- .../src/healthSummary/keyPointsMessage.ts | 46 +++--- .../src/tests/resources/keyPointMessages.csv | 142 +++++++++--------- .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/functions/src/healthSummary/keyPointsMessage.ts b/functions/src/healthSummary/keyPointsMessage.ts index ea15b802..5854a266 100644 --- a/functions/src/healthSummary/keyPointsMessage.ts +++ b/functions/src/healthSummary/keyPointsMessage.ts @@ -83,7 +83,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, ], }, @@ -117,7 +117,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, ], }, @@ -151,7 +151,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', @@ -168,7 +168,7 @@ export const healthSummaryKeyPointMessages = new Lazy< en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', }, { - en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better.', }, ], }, @@ -194,7 +194,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, { en: 'You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', @@ -237,7 +237,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, { en: 'You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.', @@ -280,7 +280,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight gain but weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', @@ -300,7 +300,7 @@ export const healthSummaryKeyPointMessages = new Lazy< en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', }, { - en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better.', }, { en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', @@ -332,7 +332,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, { en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', @@ -375,7 +375,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, { en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', @@ -418,7 +418,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.', @@ -438,7 +438,7 @@ export const healthSummaryKeyPointMessages = new Lazy< en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', }, { - en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better.', }, { en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', @@ -470,7 +470,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, { en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', @@ -522,7 +522,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, { en: 'Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.', @@ -574,7 +574,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'Weight increase', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team how adjusting your medications can help you feel better.', @@ -594,7 +594,7 @@ export const healthSummaryKeyPointMessages = new Lazy< en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', }, { - en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.', + en: 'Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better.', }, { en: 'You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.', @@ -626,7 +626,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, ], }, @@ -660,7 +660,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, ], }, @@ -694,7 +694,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better.', @@ -711,7 +711,7 @@ export const healthSummaryKeyPointMessages = new Lazy< en: 'We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart.', }, { - en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better.', + en: 'Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting your medicines can help you feel better.', }, ], }, @@ -737,7 +737,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart.", }, { en: 'You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', @@ -780,7 +780,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart.", }, { en: 'Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.', @@ -823,7 +823,7 @@ export const healthSummaryKeyPointMessages = new Lazy< weightCategory: 'No weight measured', texts: [ { - en: 'There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.', + en: "There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart.", }, { en: 'Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your meds can help you feel better.', diff --git a/functions/src/tests/resources/keyPointMessages.csv b/functions/src/tests/resources/keyPointMessages.csv index 960f2ab1..41607002 100644 --- a/functions/src/tests/resources/keyPointMessages.csv +++ b/functions/src/tests/resources/keyPointMessages.csv @@ -1,116 +1,116 @@ -Meds,Symptoms,Dizziness,Weight,Message, -Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , -No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , -No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change -No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , -No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , -Eligible meds for optimization,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.",Minor change +Meds,Symptoms,Dizziness,Weight,Message, +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight gain but weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , +Eligible meds for optimization,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better.", +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.", -Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", +2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better.", +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +2.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better. +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight gain but weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -", +", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", -Eligible meds for optimization,Change <-10,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +Eligible meds for optimization,Change <-10,No decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team how adjusting your medications can help you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better. +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Discuss with your care team potential options for helping you feel better. -3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", -Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +3.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart.", +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", +3.) Your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +3.) Your dizziness is bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +3.) Your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change +3.) You are noting your dizziness is more bothersome. Would discuss options with your care team for improving your dizziness and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Changing your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. 2.) Your weight is increasing. This is a sign that you may be retaining fluid. Discuss with your care team and watch the weight educational video. Taking your heart medicines can lower your risk of having fluid gain in the future by strengthening your heart. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change <-10,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,Weight increase,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team how adjusting your medications can help you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,Weight increase,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting yoru medicines can help you feel better. -3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +2.) Your heart symptoms worsened (see symptom report) and your weight increased. Check your blood pressure and heart rate and discuss with your care team how adjusting your medicines can help you feel better. +3.) You are noting your dizziness is more bothersome. Check your blood pressure and heart rate and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,Weight increase,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report) and your weight increased. Discuss with your care team potential options for helping you feel better. -3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , -No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , -No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , -Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change -No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , -No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , -Eligible meds for optimization,Change <-10,No decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better.",Minor change +3.) You are noting your dizziness is more bothersome. Would discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. , +No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. , +Eligible meds for optimization,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. ,Minor change +No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to keep you feeling well and strengthen your heart. , +No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,No decrease <-25,No weight measured,Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. , +Eligible meds for optimization,Change <-10,No decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your medications can help you feel better.",Minor change No eligible meds at optimization; measure BP,Change <-10,No decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting yoru medicines can help you feel better.", +2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight, and discuss with your care team how adjusting your medicines can help you feel better.", No eligible meds at optimization; at target doses,Change <-10,No decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. -2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better.", -Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team potential options for helping you feel better.", +Eligible meds for optimization,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you feel better and strengthen your heart. +2.) You are noting your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; measure BP,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +2.) You are noting your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ<90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable and your weight is not rising. Make sure to keep taking your heart meds to help you feel better and strengthen your heart. -2.) You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.",Minor change +2.) You are noting your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can keep you feeling well and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Check your blood pressure and heart rate multiple times a week to understand if your heart medicines can be adjusted to keep you feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", +2.) Your dizziness is more bothersome. Check your blood pressure, heart rate, and weight more frequently and discuss options with your care team for improving your dizziness. Also watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change >-10 and KCCQ>=90,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Your symptoms are stable. Make sure to keep taking your heart meds to keep you feeling well and strengthen your heart. -2.) Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", -Eligible meds for optimization,Change <-10,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of “Potential Med Changes” below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. +2.) Your dizziness is more bothersome. Check your weight and discuss ways to improve your dizziness with your care team. Also watch the dizziness educational video.", +Eligible meds for optimization,Change <-10,Decrease <-25,No weight measured,"1.) There are possible options to improve your heart medicines. See the list of 'Potential Med Changes' below to discuss these options with your care team. These meds can help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team how adjusting your meds can help you feel better. -3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.",Minor change +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.",Minor change No eligible meds at optimization; measure BP,Change <-10,Decrease <-25,No weight measured,"1.) We are missing blood pressure and heart rate checks in the last two weeks. Try to check your blood pressure and heart rate multiple times a week to understand if your medications can be adjusted to help you start feeling better and strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your blood pressure, heart rate, and weight and discuss with your care team how adjusting your meds can help you feel better. -3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.", +3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.", No eligible meds at optimization; at target doses,Change <-10,Decrease <-25,No weight measured,"1.) Great news! You are on the target dose for your heart medicines at this time. Make sure to keep taking your heart meds to strengthen your heart. 2.) Your heart symptoms worsened (see symptom report). Check your weight and discuss with your care team options for helping you feel better. 3.) Your dizziness is more bothersome. Discuss ways to improve your dizziness with your care team and watch the dizziness educational video.", \ No newline at end of file diff --git a/functions/src/tests/resources/mockHealthSummary.pdf b/functions/src/tests/resources/mockHealthSummary.pdf index 3c1bf3a5ef7f36cfa1316a97aa7ac3e0213103a6..f16b1b04c57254946192161b75f3a083cb942b46 100644 GIT binary patch delta 84 zcmWN|u@QhE3)7EsT7mO4CY2t fNqZe)K#|UcHf%uGdQQ+}pW^KCxnDc-UXMAI~k4{e!6k!W_hM0`h4Nx+>JMHxGS+T5F_fa83-CfnL2D0L< euQE-2O^zV9u7VY6pbjHe2vy|ge&cAOV9P(;a~44W From bcfc59f600c7bbe8a09036e72b0e8beab1b25c4b Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Tue, 14 Jan 2025 12:17:25 +0100 Subject: [PATCH 10/12] update --- functions/src/healthSummary/generate.ts | 2 +- functions/src/models/healthSummaryData.ts | 21 ++- .../databaseHealthSummaryService.test.ts | 1 + .../databaseHealthSummaryService.ts | 4 +- .../healthSummaryService.mock.ts | 177 +++++++----------- .../healthSummary/healthSummaryService.ts | 1 + .../src/services/trigger/triggerService.ts | 2 +- .../src/tests/mocks/healthSummaryData.ts | 4 +- 8 files changed, 89 insertions(+), 123 deletions(-) diff --git a/functions/src/healthSummary/generate.ts b/functions/src/healthSummary/generate.ts index 5d45964d..502cc0cc 100644 --- a/functions/src/healthSummary/generate.ts +++ b/functions/src/healthSummary/generate.ts @@ -341,7 +341,7 @@ class HealthSummaryPdfGenerator extends PdfGenerator { [ this.texts.vitalsSection.bodyWeightTable.rowTitle, this.data.latestBodyWeight?.toFixed(0) ?? '---', - this.data.averageBodyWeight?.toFixed(0) ?? '---', + this.data.lastSevenDayAverageBodyWeight?.toFixed(0) ?? '---', this.data.bodyWeightRange?.toFixed(0) ?? '---', ].map((title) => this.cell(title)), ], diff --git a/functions/src/models/healthSummaryData.ts b/functions/src/models/healthSummaryData.ts index 5c99d869..112b7261 100644 --- a/functions/src/models/healthSummaryData.ts +++ b/functions/src/models/healthSummaryData.ts @@ -7,6 +7,7 @@ // import { + advanceDateByDays, average, UserMedicationRecommendationType, type FHIRAppointment, @@ -40,6 +41,7 @@ export class HealthSummaryData { recommendations: UserMedicationRecommendation[] vitals: HealthSummaryVitals symptomScores: SymptomScore[] + now: Date // Computed Properties - Body Weight @@ -47,7 +49,16 @@ export class HealthSummaryData { return this.vitals.bodyWeight.at(0)?.value ?? null } - get averageBodyWeight(): number | null { + get lastSevenDayAverageBodyWeight(): number | null { + const bodyWeightValues = this.vitals.bodyWeight + .filter( + (observation) => observation.date >= advanceDateByDays(this.now, -7), + ) + .map((observation) => observation.value) + return average(bodyWeightValues) ?? null + } + + get medianBodyWeight(): number | null { return ( average(this.vitals.bodyWeight.map((observation) => observation.value)) ?? null @@ -136,12 +147,12 @@ export class HealthSummaryData { } get weightCategory(): HealthSummaryWeightCategory { - const averageWeight = this.averageBodyWeight + const medianWeight = this.medianBodyWeight const latestWeight = this.latestBodyWeight - if (averageWeight === null || latestWeight === null) + if (medianWeight === null || latestWeight === null) return HealthSummaryWeightCategory.MISSING - return latestWeight - averageWeight > 1 ? + return latestWeight - medianWeight >= 3 ? HealthSummaryWeightCategory.INCREASING : HealthSummaryWeightCategory.STABLE_OR_DECREASING } @@ -156,6 +167,7 @@ export class HealthSummaryData { recommendations: UserMedicationRecommendation[] vitals: HealthSummaryVitals symptomScores: SymptomScore[] + now: Date }) { this.name = input.name this.dateOfBirth = input.dateOfBirth @@ -164,5 +176,6 @@ export class HealthSummaryData { this.recommendations = input.recommendations this.vitals = input.vitals this.symptomScores = input.symptomScores + this.now = input.now } } diff --git a/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts b/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts index 6c96912c..511f1979 100644 --- a/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts +++ b/functions/src/services/healthSummary/databaseHealthSummaryService.test.ts @@ -24,6 +24,7 @@ describe('HealthSummaryService', () => { it('should fetch health summary data', async () => { const actualData = await healthSummaryService.getHealthSummaryData( 'mockUser', + new Date(2024, 2, 2, 12, 30), QuantityUnit.lbs, ) console.log('actualData:', actualData.nextAppointment?.start.toString()) diff --git a/functions/src/services/healthSummary/databaseHealthSummaryService.ts b/functions/src/services/healthSummary/databaseHealthSummaryService.ts index cae802e4..475a3e4d 100644 --- a/functions/src/services/healthSummary/databaseHealthSummaryService.ts +++ b/functions/src/services/healthSummary/databaseHealthSummaryService.ts @@ -35,6 +35,7 @@ export class DefaultHealthSummaryService implements HealthSummaryService { async getHealthSummaryData( userId: string, + date: Date, weightUnit: QuantityUnit, ): Promise { const [ @@ -50,7 +51,7 @@ export class DefaultHealthSummaryService implements HealthSummaryService { this.patientService.getNextAppointment(userId), this.patientService.getMedicationRecommendations(userId), this.patientService.getSymptomScores(userId, { limit: 5 }), - this.getVitals(userId, advanceDateByDays(new Date(), -14), weightUnit), + this.getVitals(userId, advanceDateByDays(date, -14), weightUnit), ]) const clinician = @@ -66,6 +67,7 @@ export class DefaultHealthSummaryService implements HealthSummaryService { recommendations: recommendations.map((doc) => doc.content), vitals: vitals, symptomScores: symptomScores.map((doc) => doc.content), + now: date, }) } diff --git a/functions/src/services/healthSummary/healthSummaryService.mock.ts b/functions/src/services/healthSummary/healthSummaryService.mock.ts index e793dd72..49bf69fd 100644 --- a/functions/src/services/healthSummary/healthSummaryService.mock.ts +++ b/functions/src/services/healthSummary/healthSummaryService.mock.ts @@ -8,6 +8,7 @@ import { advanceDateByDays, + advanceDateBySeconds, FHIRAppointment, FHIRAppointmentStatus, LocalizedText, @@ -25,19 +26,12 @@ import { /* eslint-disable @typescript-eslint/no-unused-vars */ export class MockHealthSummaryService implements HealthSummaryService { - // Properties - - private readonly startDate: Date - - // Constructor - - constructor(startDate: Date = new Date(2024, 2, 2, 12, 30)) { - this.startDate = startDate - } - // Methods - async getHealthSummaryData(userId: string): Promise { + async getHealthSummaryData( + userId: string, + date: Date, + ): Promise { return new HealthSummaryData({ name: 'John Doe', dateOfBirth: new Date('1970-01-02'), @@ -45,8 +39,8 @@ export class MockHealthSummaryService implements HealthSummaryService { nextAppointment: FHIRAppointment.create({ userId, status: FHIRAppointmentStatus.booked, - created: this.startDateAdvancedByDays(-10), - start: this.startDateAdvancedByDays(1), + created: advanceDateByDays(date, -10), + start: advanceDateByDays(date, 1), durationInMinutes: 60, }), recommendations: [ @@ -98,7 +92,7 @@ export class MockHealthSummaryService implements HealthSummaryService { }, }, ], - vitals: await this.getVitals(userId), + vitals: await this.getVitals(date), symptomScores: [ { questionnaireResponseId: '4', @@ -108,7 +102,7 @@ export class MockHealthSummaryService implements HealthSummaryService { qualityOfLifeScore: 20, symptomFrequencyScore: 60, dizzinessScore: 3, - date: this.startDateAdvancedByDays(-9), + date: advanceDateByDays(date, -9), }, { questionnaireResponseId: '3', @@ -118,7 +112,7 @@ export class MockHealthSummaryService implements HealthSummaryService { qualityOfLifeScore: 37, symptomFrequencyScore: 72, dizzinessScore: 2, - date: this.startDateAdvancedByDays(-18), + date: advanceDateByDays(date, -18), }, { questionnaireResponseId: '2', @@ -128,7 +122,7 @@ export class MockHealthSummaryService implements HealthSummaryService { qualityOfLifeScore: 25, symptomFrequencyScore: 60, dizzinessScore: 1, - date: this.startDateAdvancedByDays(-34), + date: advanceDateByDays(date, -34), }, { questionnaireResponseId: '1', @@ -138,123 +132,121 @@ export class MockHealthSummaryService implements HealthSummaryService { qualityOfLifeScore: 60, symptomFrequencyScore: 80, dizzinessScore: 1, - date: this.startDateAdvancedByDays(-49), + date: advanceDateByDays(date, -49), }, ], + now: date, }) } - async getVitals(userId: string): Promise { + // Helpers + + private async getVitals(date: Date): Promise { const [systolicBloodPressure, diastolicBloodPressure] = - await this.getBloodPressureObservations(userId, this.startDate) + await this.getBloodPressureObservations(date) return { systolicBloodPressure: systolicBloodPressure, diastolicBloodPressure: diastolicBloodPressure, - heartRate: await this.getHeartRateObservations(userId, this.startDate), - bodyWeight: await this.getBodyWeightObservations( - userId, - this.startDate, - QuantityUnit.lbs, - ), - dryWeight: await this.getMostRecentDryWeightObservation(userId), + heartRate: await this.getHeartRateObservations(date), + bodyWeight: await this.getBodyWeightObservations(date), + dryWeight: await this.getMostRecentDryWeightObservation(date), } } - async getBloodPressureObservations( - userId: string, - cutoffDate: Date, + private async getBloodPressureObservations( + date: Date, ): Promise<[Observation[], Observation[]]> { return [ [ { - date: this.startDateAdvancedByDays(-1), + date: advanceDateByDays(date, -1), value: 110, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-2), + date: advanceDateByDays(date, -2), value: 114, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-3), + date: advanceDateByDays(date, -3), value: 123, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-4), + date: advanceDateByDays(date, -4), value: 109, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-5), + date: advanceDateByDays(date, -5), value: 105, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-6), + date: advanceDateByDays(date, -6), value: 98, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-7), + date: advanceDateByDays(date, -7), value: 94, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-8), + date: advanceDateByDays(date, -8), value: 104, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-9), + date: advanceDateByDays(date, -9), value: 102, unit: QuantityUnit.mmHg, }, ], [ { - date: this.startDateAdvancedByDays(-1), + date: advanceDateByDays(date, -1), value: 70, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-2), + date: advanceDateByDays(date, -2), value: 82, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-3), + date: advanceDateByDays(date, -3), value: 75, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-4), + date: advanceDateByDays(date, -4), value: 77, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-5), + date: advanceDateByDays(date, -5), value: 72, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-6), + date: advanceDateByDays(date, -6), value: 68, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-7), + date: advanceDateByDays(date, -7), value: 65, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-8), + date: advanceDateByDays(date, -8), value: 72, unit: QuantityUnit.mmHg, }, { - date: this.startDateAdvancedByDays(-9), + date: advanceDateByDays(date, -9), value: 80, unit: QuantityUnit.mmHg, }, @@ -262,156 +254,113 @@ export class MockHealthSummaryService implements HealthSummaryService { ] } - async getHeartRateObservations( - userId: string, - cutoffDate: Date, - ): Promise { + private async getHeartRateObservations(date: Date): Promise { return [ { - date: this.startDateAdvancedByDays(-1), + date: advanceDateByDays(date, -1), value: 79, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-2), + date: advanceDateByDays(date, -2), value: 62, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-3), + date: advanceDateByDays(date, -3), value: 77, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-4), + date: advanceDateByDays(date, -4), value: 63, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-5), + date: advanceDateByDays(date, -5), value: 61, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-6), + date: advanceDateByDays(date, -6), value: 70, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-7), + date: advanceDateByDays(date, -7), value: 67, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-8), + date: advanceDateByDays(date, -8), value: 80, unit: QuantityUnit.bpm, }, { - date: this.startDateAdvancedByDays(-9), + date: advanceDateByDays(date, -9), value: 65, unit: QuantityUnit.bpm, }, ] } - async getBodyWeightObservations( - userId: string, - cutoffDate: Date, - unit: QuantityUnit, - ): Promise { + private async getBodyWeightObservations(date: Date): Promise { return [ { - date: this.startDateAdvancedByDays(-1), + date: advanceDateByDays(date, -1), value: 269, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-2), + date: advanceDateByDays(date, -2), value: 267, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-3), + date: advanceDateByDays(date, -3), value: 267, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-4), + date: advanceDateByDays(date, -4), value: 265, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-5), + date: advanceDateByDays(date, -5), value: 268, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-6), + date: advanceDateByDays(date, -6), value: 268, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-7), + date: advanceDateByDays(date, -7), value: 266, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-8), + date: advanceDateByDays(date, -8), value: 266, unit: QuantityUnit.lbs, }, { - date: this.startDateAdvancedByDays(-9), + date: advanceDateByDays(date, -9), value: 267, unit: QuantityUnit.lbs, }, ] } - async getMostRecentDryWeightObservation( - userId: string, + private async getMostRecentDryWeightObservation( + date: Date, ): Promise { return { - date: this.startDateAdvancedByDays(-4), + date: advanceDateByDays(date, -4), value: 267.5, unit: QuantityUnit.lbs, } } - - async getMostRecentCreatinineObservation( - userId: string, - ): Promise { - return { - date: this.startDateAdvancedByDays(-4), - value: 1.1, - unit: QuantityUnit.mg_dL, - } - } - - async getMostRecentPotassiumObservation( - userId: string, - ): Promise { - return { - date: this.startDateAdvancedByDays(-4), - value: 4.2, - unit: QuantityUnit.mEq_L, - } - } - - async getMostRecentEstimatedGlomerularFiltrationRateObservation( - userId: string, - ): Promise { - return { - date: this.startDateAdvancedByDays(-4), - value: 60, - unit: QuantityUnit.mL_min_173m2, - } - } - - // Helpers - - private startDateAdvancedByDays(days: number): Date { - return advanceDateByDays(this.startDate, days) - } } diff --git a/functions/src/services/healthSummary/healthSummaryService.ts b/functions/src/services/healthSummary/healthSummaryService.ts index 0eab74cf..7e52deea 100644 --- a/functions/src/services/healthSummary/healthSummaryService.ts +++ b/functions/src/services/healthSummary/healthSummaryService.ts @@ -12,6 +12,7 @@ import { type HealthSummaryData } from '../../models/healthSummaryData.js' export interface HealthSummaryService { getHealthSummaryData( userId: string, + date: Date, weightUnit: QuantityUnit, ): Promise } diff --git a/functions/src/services/trigger/triggerService.ts b/functions/src/services/trigger/triggerService.ts index 58009c2b..a964bced 100644 --- a/functions/src/services/trigger/triggerService.ts +++ b/functions/src/services/trigger/triggerService.ts @@ -218,7 +218,7 @@ export class TriggerService { logger.debug( `TriggerService.userObservationWritten(${userId}, ${collection}): Most recent body weight is ${mostRecentBodyWeight} compared to a median of ${bodyWeightMedian}`, ) - if (mostRecentBodyWeight - bodyWeightMedian >= 7) { + if (mostRecentBodyWeight - bodyWeightMedian >= 3) { const messageService = this.factory.message() const messageDoc = await messageService.addMessage( userId, diff --git a/functions/src/tests/mocks/healthSummaryData.ts b/functions/src/tests/mocks/healthSummaryData.ts index be976064..f64a9c55 100644 --- a/functions/src/tests/mocks/healthSummaryData.ts +++ b/functions/src/tests/mocks/healthSummaryData.ts @@ -12,6 +12,6 @@ export function mockHealthSummaryData( userId: string, startDate: Date = new Date(2024, 2, 2, 12, 30), ): Promise { - const service = new MockHealthSummaryService(startDate) - return service.getHealthSummaryData(userId) + const service = new MockHealthSummaryService() + return service.getHealthSummaryData(userId, startDate) } From 5007e8967f232bb805b9a5a05ef25e0379366cc3 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Tue, 14 Jan 2025 12:19:19 +0100 Subject: [PATCH 11/12] Add now date to HealthSummaryData --- functions/src/functions/exportHealthSummary.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/functions/src/functions/exportHealthSummary.ts b/functions/src/functions/exportHealthSummary.ts index f3a1f22f..5b2b118a 100644 --- a/functions/src/functions/exportHealthSummary.ts +++ b/functions/src/functions/exportHealthSummary.ts @@ -45,9 +45,11 @@ export const exportHealthSummary = validatedOnCall( if (request.data.weightUnit !== undefined && weightUnit === undefined) throw new https.HttpsError('invalid-argument', 'Invalid weight unit') + const now = new Date() const healthSummaryService = factory.healthSummary() const data = await healthSummaryService.getHealthSummaryData( request.data.userId, + now, weightUnit ?? QuantityUnit.lbs, ) const pdf = generateHealthSummary(data, { From 479eb68df35b396d0e0ed9a3fa026ec9b8d6a80c Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Tue, 14 Jan 2025 12:29:11 +0100 Subject: [PATCH 12/12] lint:fix --- .../healthSummary/healthSummaryService.mock.ts | 1 - .../src/tests/resources/mockHealthSummary.pdf | Bin 131 -> 131 bytes 2 files changed, 1 deletion(-) diff --git a/functions/src/services/healthSummary/healthSummaryService.mock.ts b/functions/src/services/healthSummary/healthSummaryService.mock.ts index 49bf69fd..868a3e37 100644 --- a/functions/src/services/healthSummary/healthSummaryService.mock.ts +++ b/functions/src/services/healthSummary/healthSummaryService.mock.ts @@ -8,7 +8,6 @@ import { advanceDateByDays, - advanceDateBySeconds, FHIRAppointment, FHIRAppointmentStatus, LocalizedText, diff --git a/functions/src/tests/resources/mockHealthSummary.pdf b/functions/src/tests/resources/mockHealthSummary.pdf index f16b1b04c57254946192161b75f3a083cb942b46..d512bf7c3f156173b3f21577b085b2d28e29cac4 100644 GIT binary patch delta 84 zcmV~$yA^;i2mrvBHd8nP;S*p8hrmE#ZD*SW9ND|wcKhZT3_KxADbhnM3r%bRI9d4` dvPg@uNCOKqbs$SRFwD9lxj*+?2dW`K{{huo7LWh{ delta 84 zcmWN|u@QhE3)7EsT7mO4CY2t fNqZe)K#|UcHf%uGdQQ+}pW^KCxnDc-UX