Feat: Add quetiapine support to medication tracking

- Implement migration script to add quetiapine and quetiapine_doses columns to existing CSV data.
- Update DataManager to include quetiapine and quetiapine_doses in data handling.
- Modify MedTrackerApp to manage quetiapine entries and doses.
- Enhance UIManager to include quetiapine in the user interface for medication selection and display.
- Update tests to cover new quetiapine functionality, including sample data and DataManager tests.
This commit is contained in:
William Valentin
2025-07-29 13:22:35 -07:00
parent 1a6fb9fcd4
commit 2b037a83e8
7 changed files with 467 additions and 317 deletions
+292 -286
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" ?>
<coverage version="7.10.1" timestamp="1753772483216" lines-valid="696" lines-covered="513" line-rate="0.7371" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
<coverage version="7.10.1" timestamp="1753820440721" lines-valid="702" lines-covered="511" line-rate="0.7279" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.10.1 -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/home/will/Code/thechart/src</source>
</sources>
<packages>
<package name="." line-rate="0.7371" branch-rate="0" complexity="0">
<package name="." line-rate="0.7279" branch-rate="0" complexity="0">
<classes>
<class name="__init__.py" filename="__init__.py" complexity="0" line-rate="0" branch-rate="0">
<methods/>
@@ -27,7 +27,7 @@
<line number="9" hits="1"/>
</lines>
</class>
<class name="data_manager.py" filename="data_manager.py" complexity="0" line-rate="0.5577" branch-rate="0">
<class name="data_manager.py" filename="data_manager.py" complexity="0" line-rate="0.5673" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
@@ -44,96 +44,96 @@
<line number="19" hits="1"/>
<line number="20" hits="1"/>
<line number="21" hits="1"/>
<line number="40" hits="1"/>
<line number="42" hits="1"/>
<line number="43" hits="1"/>
<line number="44" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="1"/>
<line number="47" hits="1"/>
<line number="66" hits="1"/>
<line number="67" hits="1"/>
<line number="68" hits="0"/>
<line number="69" hits="0"/>
<line number="48" hits="1"/>
<line number="49" hits="1"/>
<line number="70" hits="1"/>
<line number="71" hits="1"/>
<line number="72" hits="1"/>
<line number="72" hits="0"/>
<line number="73" hits="0"/>
<line number="74" hits="1"/>
<line number="75" hits="1"/>
<line number="76" hits="1"/>
<line number="78" hits="1"/>
<line number="79" hits="1"/>
<line number="81" hits="1"/>
<line number="80" hits="1"/>
<line number="82" hits="1"/>
<line number="83" hits="1"/>
<line number="85" hits="1"/>
<line number="86" hits="1"/>
<line number="87" hits="1"/>
<line number="88" hits="1"/>
<line number="89" hits="1"/>
<line number="90" hits="1"/>
<line number="91" hits="1"/>
<line number="92" hits="1"/>
<line number="93" hits="1"/>
<line number="94" hits="1"/>
<line number="95" hits="1"/>
<line number="96" hits="1"/>
<line number="97" hits="1"/>
<line number="99" hits="1"/>
<line number="100" hits="1"/>
<line number="101" hits="1"/>
<line number="104" hits="1"/>
<line number="105" hits="1"/>
<line number="108" hits="1"/>
<line number="110" hits="0"/>
<line number="131" hits="1"/>
<line number="146" hits="1"/>
<line number="147" hits="1"/>
<line number="148" hits="0"/>
<line number="149" hits="0"/>
<line number="112" hits="1"/>
<line number="114" hits="0"/>
<line number="135" hits="1"/>
<line number="150" hits="0"/>
<line number="151" hits="0"/>
<line number="152" hits="1"/>
<line number="153" hits="1"/>
<line number="154" hits="1"/>
<line number="155" hits="1"/>
<line number="157" hits="1"/>
<line number="156" hits="1"/>
<line number="158" hits="1"/>
<line number="159" hits="1"/>
<line number="160" hits="1"/>
<line number="161" hits="0"/>
<line number="162" hits="0"/>
<line number="163" hits="0"/>
<line number="165" hits="1"/>
<line number="161" hits="1"/>
<line number="163" hits="1"/>
<line number="164" hits="1"/>
<line number="165" hits="0"/>
<line number="166" hits="0"/>
<line number="167" hits="0"/>
<line number="169" hits="0"/>
<line number="170" hits="0"/>
<line number="169" hits="1"/>
<line number="171" hits="0"/>
<line number="172" hits="0"/>
<line number="173" hits="0"/>
<line number="174" hits="0"/>
<line number="175" hits="0"/>
<line number="177" hits="0"/>
<line number="193" hits="0"/>
<line number="196" hits="0"/>
<line number="176" hits="0"/>
<line number="179" hits="0"/>
<line number="181" hits="0"/>
<line number="197" hits="0"/>
<line number="198" hits="0"/>
<line number="200" hits="0"/>
<line number="201" hits="0"/>
<line number="203" hits="0"/>
<line number="206" hits="0"/>
<line number="208" hits="0"/>
<line number="209" hits="0"/>
<line number="202" hits="0"/>
<line number="204" hits="0"/>
<line number="205" hits="0"/>
<line number="207" hits="0"/>
<line number="210" hits="0"/>
<line number="211" hits="0"/>
<line number="212" hits="0"/>
<line number="214" hits="1"/>
<line number="218" hits="0"/>
<line number="219" hits="0"/>
<line number="220" hits="0"/>
<line number="221" hits="0"/>
<line number="213" hits="0"/>
<line number="214" hits="0"/>
<line number="215" hits="0"/>
<line number="216" hits="0"/>
<line number="218" hits="1"/>
<line number="222" hits="0"/>
<line number="223" hits="0"/>
<line number="224" hits="0"/>
<line number="226" hits="0"/>
<line number="225" hits="0"/>
<line number="227" hits="0"/>
<line number="229" hits="0"/>
<line number="228" hits="0"/>
<line number="230" hits="0"/>
<line number="231" hits="0"/>
<line number="232" hits="0"/>
<line number="233" hits="0"/>
<line number="234" hits="0"/>
<line number="235" hits="0"/>
<line number="236" hits="0"/>
<line number="237" hits="0"/>
<line number="238" hits="0"/>
<line number="239" hits="0"/>
<line number="240" hits="0"/>
<line number="241" hits="0"/>
<line number="242" hits="0"/>
</lines>
</class>
<class name="graph_manager.py" filename="graph_manager.py" complexity="0" line-rate="0.971" branch-rate="0">
@@ -270,7 +270,7 @@
<line number="40" hits="1"/>
</lines>
</class>
<class name="main.py" filename="main.py" complexity="0" line-rate="0.8278" branch-rate="0">
<class name="main.py" filename="main.py" complexity="0" line-rate="0.7857" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
@@ -346,87 +346,90 @@
<line number="124" hits="0"/>
<line number="125" hits="0"/>
<line number="127" hits="0"/>
<line number="145" hits="0"/>
<line number="148" hits="0"/>
<line number="154" hits="0"/>
<line number="156" hits="1"/>
<line number="173" hits="0"/>
<line number="190" hits="0"/>
<line number="191" hits="0"/>
<line number="192" hits="0"/>
<line number="147" hits="0"/>
<line number="150" hits="0"/>
<line number="156" hits="0"/>
<line number="158" hits="1"/>
<line number="176" hits="0"/>
<line number="195" hits="0"/>
<line number="196" hits="0"/>
<line number="199" hits="0"/>
<line number="197" hits="0"/>
<line number="200" hits="0"/>
<line number="201" hits="0"/>
<line number="208" hits="0"/>
<line number="210" hits="1"/>
<line number="211" hits="1"/>
<line number="214" hits="1"/>
<line number="204" hits="0"/>
<line number="205" hits="0"/>
<line number="206" hits="0"/>
<line number="213" hits="0"/>
<line number="215" hits="1"/>
<line number="217" hits="1"/>
<line number="216" hits="1"/>
<line number="219" hits="1"/>
<line number="220" hits="1"/>
<line number="221" hits="1"/>
<line number="222" hits="1"/>
<line number="223" hits="1"/>
<line number="224" hits="1"/>
<line number="225" hits="1"/>
<line number="226" hits="1"/>
<line number="227" hits="1"/>
<line number="228" hits="1"/>
<line number="231" hits="1"/>
<line number="229" hits="1"/>
<line number="230" hits="1"/>
<line number="232" hits="1"/>
<line number="236" hits="1"/>
<line number="233" hits="1"/>
<line number="234" hits="1"/>
<line number="237" hits="1"/>
<line number="240" hits="1"/>
<line number="238" hits="1"/>
<line number="241" hits="1"/>
<line number="243" hits="1"/>
<line number="259" hits="1"/>
<line number="262" hits="1"/>
<line number="263" hits="1"/>
<line number="264" hits="1"/>
<line number="266" hits="1"/>
<line number="267" hits="1"/>
<line number="270" hits="1"/>
<line number="271" hits="1"/>
<line number="244" hits="1"/>
<line number="247" hits="1"/>
<line number="248" hits="1"/>
<line number="249" hits="1"/>
<line number="251" hits="1"/>
<line number="269" hits="1"/>
<line number="272" hits="1"/>
<line number="273" hits="1"/>
<line number="274" hits="1"/>
<line number="275" hits="1"/>
<line number="276" hits="1"/>
<line number="283" hits="0"/>
<line number="285" hits="1"/>
<line number="287" hits="1"/>
<line number="288" hits="1"/>
<line number="294" hits="1"/>
<line number="295" hits="0"/>
<line number="297" hits="0"/>
<line number="298" hits="0"/>
<line number="299" hits="0"/>
<line number="302" hits="0"/>
<line number="304" hits="0"/>
<line number="306" hits="1"/>
<line number="308" hits="1"/>
<line number="309" hits="1"/>
<line number="310" hits="1"/>
<line number="311" hits="1"/>
<line number="312" hits="1"/>
<line number="313" hits="1"/>
<line number="314" hits="1"/>
<line number="276" hits="0"/>
<line number="277" hits="0"/>
<line number="280" hits="0"/>
<line number="281" hits="0"/>
<line number="284" hits="0"/>
<line number="285" hits="0"/>
<line number="286" hits="0"/>
<line number="293" hits="0"/>
<line number="295" hits="1"/>
<line number="297" hits="1"/>
<line number="298" hits="1"/>
<line number="304" hits="1"/>
<line number="305" hits="0"/>
<line number="307" hits="0"/>
<line number="308" hits="0"/>
<line number="309" hits="0"/>
<line number="312" hits="0"/>
<line number="314" hits="0"/>
<line number="316" hits="1"/>
<line number="318" hits="1"/>
<line number="319" hits="1"/>
<line number="320" hits="1"/>
<line number="321" hits="1"/>
<line number="322" hits="1"/>
<line number="325" hits="1"/>
<line number="323" hits="1"/>
<line number="324" hits="1"/>
<line number="326" hits="1"/>
<line number="328" hits="1"/>
<line number="330" hits="1"/>
<line number="344" hits="1"/>
<line number="345" hits="0"/>
<line number="348" hits="1"/>
<line number="350" hits="1"/>
<line number="351" hits="1"/>
<line number="352" hits="1"/>
<line number="331" hits="1"/>
<line number="332" hits="1"/>
<line number="335" hits="1"/>
<line number="338" hits="1"/>
<line number="340" hits="1"/>
<line number="355" hits="1"/>
<line number="356" hits="0"/>
<line number="359" hits="1"/>
<line number="361" hits="1"/>
<line number="362" hits="1"/>
<line number="363" hits="1"/>
<line number="366" hits="1"/>
</lines>
</class>
<class name="ui_manager.py" filename="ui_manager.py" complexity="0" line-rate="0.6645" branch-rate="0">
<class name="ui_manager.py" filename="ui_manager.py" complexity="0" line-rate="0.6614" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
@@ -500,248 +503,251 @@
<line number="132" hits="1"/>
<line number="133" hits="1"/>
<line number="136" hits="1"/>
<line number="143" hits="1"/>
<line number="145" hits="1"/>
<line number="150" hits="1"/>
<line number="144" hits="1"/>
<line number="146" hits="1"/>
<line number="151" hits="1"/>
<line number="153" hits="1"/>
<line number="156" hits="1"/>
<line number="160" hits="1"/>
<line number="163" hits="1"/>
<line number="168" hits="1"/>
<line number="171" hits="1"/>
<line number="179" hits="1"/>
<line number="181" hits="1"/>
<line number="184" hits="1"/>
<line number="187" hits="1"/>
<line number="152" hits="1"/>
<line number="154" hits="1"/>
<line number="157" hits="1"/>
<line number="161" hits="1"/>
<line number="164" hits="1"/>
<line number="169" hits="1"/>
<line number="172" hits="1"/>
<line number="180" hits="1"/>
<line number="182" hits="1"/>
<line number="185" hits="1"/>
<line number="188" hits="1"/>
<line number="190" hits="1"/>
<line number="203" hits="1"/>
<line number="189" hits="1"/>
<line number="191" hits="1"/>
<line number="205" hits="1"/>
<line number="218" hits="1"/>
<line number="219" hits="1"/>
<line number="207" hits="1"/>
<line number="221" hits="1"/>
<line number="234" hits="1"/>
<line number="235" hits="1"/>
<line number="237" hits="1"/>
<line number="240" hits="1"/>
<line number="222" hits="1"/>
<line number="224" hits="1"/>
<line number="238" hits="1"/>
<line number="239" hits="1"/>
<line number="241" hits="1"/>
<line number="242" hits="1"/>
<line number="244" hits="1"/>
<line number="245" hits="1"/>
<line number="246" hits="1"/>
<line number="248" hits="1"/>
<line number="249" hits="1"/>
<line number="250" hits="1"/>
<line number="252" hits="1"/>
<line number="253" hits="1"/>
<line number="254" hits="1"/>
<line number="256" hits="1"/>
<line number="257" hits="1"/>
<line number="259" hits="1"/>
<line number="260" hits="1"/>
<line number="271" hits="1"/>
<line number="273" hits="1"/>
<line number="261" hits="1"/>
<line number="263" hits="1"/>
<line number="264" hits="1"/>
<line number="275" hits="1"/>
<line number="277" hits="1"/>
<line number="278" hits="1"/>
<line number="279" hits="1"/>
<line number="280" hits="1"/>
<line number="281" hits="1"/>
<line number="282" hits="1"/>
<line number="283" hits="1"/>
<line number="286" hits="1"/>
<line number="288" hits="1"/>
<line number="289" hits="1"/>
<line number="290" hits="0"/>
<line number="292" hits="0"/>
<line number="310" hits="0"/>
<line number="312" hits="0"/>
<line number="313" hits="0"/>
<line number="331" hits="1"/>
<line number="334" hits="1"/>
<line number="335" hits="1"/>
<line number="338" hits="1"/>
<line number="341" hits="1"/>
<line number="342" hits="1"/>
<line number="352" hits="1"/>
<line number="355" hits="1"/>
<line number="356" hits="1"/>
<line number="357" hits="1"/>
<line number="360" hits="1"/>
<line number="284" hits="1"/>
<line number="287" hits="1"/>
<line number="290" hits="1"/>
<line number="292" hits="1"/>
<line number="293" hits="1"/>
<line number="300" hits="1"/>
<line number="301" hits="0"/>
<line number="303" hits="0"/>
<line number="319" hits="0"/>
<line number="320" hits="0"/>
<line number="322" hits="0"/>
<line number="342" hits="0"/>
<line number="344" hits="0"/>
<line number="345" hits="0"/>
<line number="365" hits="1"/>
<line number="366" hits="1"/>
<line number="368" hits="1"/>
<line number="369" hits="1"/>
<line number="370" hits="1"/>
<line number="371" hits="1"/>
<line number="373" hits="1"/>
<line number="372" hits="1"/>
<line number="375" hits="1"/>
<line number="385" hits="1"/>
<line number="388" hits="1"/>
<line number="389" hits="1"/>
<line number="390" hits="0"/>
<line number="391" hits="0"/>
<line number="392" hits="0"/>
<line number="394" hits="1"/>
<line number="402" hits="1"/>
<line number="376" hits="1"/>
<line number="387" hits="1"/>
<line number="390" hits="1"/>
<line number="391" hits="1"/>
<line number="392" hits="1"/>
<line number="395" hits="1"/>
<line number="400" hits="1"/>
<line number="401" hits="1"/>
<line number="404" hits="1"/>
<line number="405" hits="1"/>
<line number="406" hits="1"/>
<line number="408" hits="1"/>
<line number="410" hits="1"/>
<line number="411" hits="1"/>
<line number="412" hits="1"/>
<line number="413" hits="1"/>
<line number="414" hits="1"/>
<line number="415" hits="1"/>
<line number="416" hits="0"/>
<line number="417" hits="0"/>
<line number="418" hits="0"/>
<line number="422" hits="1"/>
<line number="423" hits="0"/>
<line number="424" hits="0"/>
<line number="420" hits="1"/>
<line number="423" hits="1"/>
<line number="424" hits="1"/>
<line number="425" hits="0"/>
<line number="426" hits="0"/>
<line number="427" hits="0"/>
<line number="429" hits="1"/>
<line number="430" hits="1"/>
<line number="434" hits="1"/>
<line number="435" hits="1"/>
<line number="437" hits="1"/>
<line number="441" hits="1"/>
<line number="443" hits="1"/>
<line number="445" hits="1"/>
<line number="446" hits="1"/>
<line number="447" hits="1"/>
<line number="448" hits="1"/>
<line number="449" hits="1"/>
<line number="451" hits="1"/>
<line number="454" hits="1"/>
<line number="450" hits="1"/>
<line number="451" hits="0"/>
<line number="452" hits="0"/>
<line number="453" hits="0"/>
<line number="457" hits="1"/>
<line number="458" hits="1"/>
<line number="461" hits="1"/>
<line number="462" hits="1"/>
<line number="458" hits="0"/>
<line number="459" hits="0"/>
<line number="460" hits="0"/>
<line number="464" hits="1"/>
<line number="465" hits="1"/>
<line number="466" hits="1"/>
<line number="467" hits="1"/>
<line number="469" hits="1"/>
<line number="479" hits="1"/>
<line number="470" hits="1"/>
<line number="472" hits="1"/>
<line number="476" hits="1"/>
<line number="478" hits="1"/>
<line number="482" hits="1"/>
<line number="483" hits="1"/>
<line number="485" hits="1"/>
<line number="484" hits="1"/>
<line number="486" hits="1"/>
<line number="489" hits="1"/>
<line number="492" hits="1"/>
<line number="493" hits="1"/>
<line number="494" hits="1"/>
<line number="495" hits="1"/>
<line number="496" hits="1"/>
<line number="497" hits="1"/>
<line number="499" hits="1"/>
<line number="500" hits="1"/>
<line number="501" hits="1"/>
<line number="509" hits="1"/>
<line number="510" hits="1"/>
<line number="513" hits="1"/>
<line number="515" hits="0"/>
<line number="517" hits="0"/>
<line number="518" hits="0"/>
<line number="520" hits="0"/>
<line number="523" hits="0"/>
<line number="524" hits="0"/>
<line number="528" hits="0"/>
<line number="529" hits="0"/>
<line number="531" hits="0"/>
<line number="502" hits="1"/>
<line number="504" hits="1"/>
<line number="515" hits="1"/>
<line number="518" hits="1"/>
<line number="519" hits="1"/>
<line number="521" hits="1"/>
<line number="529" hits="1"/>
<line number="530" hits="1"/>
<line number="531" hits="1"/>
<line number="532" hits="1"/>
<line number="536" hits="1"/>
<line number="538" hits="1"/>
<line number="546" hits="1"/>
<line number="553" hits="1"/>
<line number="558" hits="1"/>
<line number="564" hits="1"/>
<line number="568" hits="1"/>
<line number="572" hits="1"/>
<line number="573" hits="1"/>
<line number="574" hits="1"/>
<line number="576" hits="1"/>
<line number="578" hits="1"/>
<line number="580" hits="1"/>
<line number="581" hits="1"/>
<line number="584" hits="1"/>
<line number="585" hits="1"/>
<line number="586" hits="1"/>
<line number="547" hits="1"/>
<line number="550" hits="1"/>
<line number="552" hits="0"/>
<line number="554" hits="0"/>
<line number="561" hits="0"/>
<line number="563" hits="0"/>
<line number="566" hits="0"/>
<line number="567" hits="0"/>
<line number="571" hits="0"/>
<line number="573" hits="0"/>
<line number="589" hits="1"/>
<line number="592" hits="1"/>
<line number="593" hits="1"/>
<line number="596" hits="1"/>
<line number="599" hits="1"/>
<line number="602" hits="1"/>
<line number="603" hits="0"/>
<line number="605" hits="1"/>
<line number="601" hits="1"/>
<line number="607" hits="1"/>
<line number="613" hits="1"/>
<line number="611" hits="1"/>
<line number="615" hits="1"/>
<line number="616" hits="1"/>
<line number="617" hits="0"/>
<line number="618" hits="0"/>
<line number="619" hits="0"/>
<line number="620" hits="0"/>
<line number="621" hits="0"/>
<line number="623" hits="0"/>
<line number="624" hits="0"/>
<line number="625" hits="0"/>
<line number="626" hits="0"/>
<line number="627" hits="0"/>
<line number="628" hits="0"/>
<line number="630" hits="0"/>
<line number="631" hits="0"/>
<line number="633" hits="0"/>
<line number="617" hits="1"/>
<line number="619" hits="1"/>
<line number="621" hits="1"/>
<line number="623" hits="1"/>
<line number="624" hits="1"/>
<line number="627" hits="1"/>
<line number="628" hits="1"/>
<line number="629" hits="1"/>
<line number="632" hits="1"/>
<line number="635" hits="1"/>
<line number="638" hits="1"/>
<line number="644" hits="1"/>
<line number="646" hits="1"/>
<line number="636" hits="1"/>
<line number="639" hits="1"/>
<line number="642" hits="1"/>
<line number="645" hits="1"/>
<line number="646" hits="0"/>
<line number="648" hits="1"/>
<line number="655" hits="0"/>
<line number="658" hits="0"/>
<line number="650" hits="1"/>
<line number="656" hits="1"/>
<line number="659" hits="1"/>
<line number="660" hits="0"/>
<line number="661" hits="0"/>
<line number="662" hits="0"/>
<line number="663" hits="0"/>
<line number="664" hits="0"/>
<line number="666" hits="0"/>
<line number="667" hits="0"/>
<line number="668" hits="0"/>
<line number="669" hits="0"/>
<line number="670" hits="0"/>
<line number="671" hits="0"/>
<line number="673" hits="0"/>
<line number="674" hits="0"/>
<line number="676" hits="0"/>
<line number="678" hits="0"/>
<line number="679" hits="0"/>
<line number="680" hits="0"/>
<line number="682" hits="0"/>
<line number="685" hits="0"/>
<line number="688" hits="0"/>
<line number="694" hits="1"/>
<line number="696" hits="0"/>
<line number="697" hits="0"/>
<line number="699" hits="0"/>
<line number="700" hits="0"/>
<line number="702" hits="0"/>
<line number="705" hits="0"/>
<line number="707" hits="0"/>
<line number="708" hits="0"/>
<line number="678" hits="1"/>
<line number="681" hits="1"/>
<line number="687" hits="1"/>
<line number="689" hits="1"/>
<line number="691" hits="1"/>
<line number="698" hits="0"/>
<line number="701" hits="0"/>
<line number="703" hits="0"/>
<line number="704" hits="0"/>
<line number="709" hits="0"/>
<line number="712" hits="0"/>
<line number="713" hits="0"/>
<line number="716" hits="0"/>
<line number="717" hits="0"/>
<line number="720" hits="0"/>
<line number="719" hits="0"/>
<line number="721" hits="0"/>
<line number="722" hits="0"/>
<line number="723" hits="0"/>
<line number="725" hits="0"/>
<line number="726" hits="0"/>
<line number="727" hits="0"/>
<line number="729" hits="0"/>
<line number="732" hits="0"/>
<line number="735" hits="0"/>
<line number="741" hits="1"/>
<line number="728" hits="0"/>
<line number="731" hits="0"/>
<line number="737" hits="1"/>
<line number="739" hits="0"/>
<line number="740" hits="0"/>
<line number="742" hits="0"/>
<line number="743" hits="0"/>
<line number="744" hits="0"/>
<line number="746" hits="0"/>
<line number="747" hits="0"/>
<line number="749" hits="0"/>
<line number="745" hits="0"/>
<line number="748" hits="0"/>
<line number="750" hits="0"/>
<line number="751" hits="0"/>
<line number="752" hits="0"/>
<line number="755" hits="0"/>
<line number="756" hits="0"/>
<line number="759" hits="0"/>
<line number="762" hits="0"/>
<line number="764" hits="0"/>
<line number="765" hits="0"/>
<line number="760" hits="0"/>
<line number="763" hits="0"/>
<line number="766" hits="0"/>
<line number="768" hits="0"/>
<line number="769" hits="0"/>
<line number="770" hits="0"/>
<line number="773" hits="0"/>
<line number="772" hits="0"/>
<line number="775" hits="0"/>
<line number="777" hits="0"/>
<line number="780" hits="0"/>
<line number="781" hits="0"/>
<line number="785" hits="0"/>
<line number="778" hits="0"/>
<line number="784" hits="1"/>
<line number="786" hits="0"/>
<line number="787" hits="0"/>
<line number="789" hits="0"/>
<line number="791" hits="0"/>
<line number="790" hits="0"/>
<line number="792" hits="0"/>
<line number="793" hits="0"/>
<line number="794" hits="0"/>
<line number="795" hits="0"/>
<line number="798" hits="0"/>
<line number="799" hits="0"/>
<line number="802" hits="0"/>
<line number="805" hits="0"/>
<line number="807" hits="0"/>
<line number="808" hits="0"/>
<line number="809" hits="0"/>
<line number="811" hits="0"/>
<line number="813" hits="0"/>
<line number="816" hits="0"/>
<line number="818" hits="0"/>
<line number="820" hits="0"/>
<line number="823" hits="0"/>
<line number="824" hits="0"/>
<line number="828" hits="0"/>
<line number="829" hits="0"/>
<line number="830" hits="0"/>
<line number="832" hits="0"/>
<line number="834" hits="0"/>
</lines>
</class>
</classes>
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Migration script to add quetiapine columns to existing CSV data.
This script will backup the existing CSV and add the new columns.
"""
import os
import shutil
from datetime import datetime
import pandas as pd
def migrate_csv_add_quetiapine(csv_file: str = "thechart_data.csv"):
"""Add quetiapine and quetiapine_doses columns to existing CSV."""
if not os.path.exists(csv_file):
print(f"CSV file {csv_file} not found. No migration needed.")
return
# Create backup
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_file = f"{csv_file}.backup_quetiapine_{timestamp}"
shutil.copy2(csv_file, backup_file)
print(f"Backup created: {backup_file}")
# Load existing data
try:
df = pd.read_csv(csv_file)
print(f"Loaded {len(df)} rows from {csv_file}")
# Check if quetiapine columns already exist
if "quetiapine" in df.columns:
print("Quetiapine columns already exist. No migration needed.")
return
# Add new columns
# Insert quetiapine columns before the note column
note_col_index = (
df.columns.get_loc("note") if "note" in df.columns else len(df.columns)
)
# Insert quetiapine column
df.insert(note_col_index, "quetiapine", 0)
df.insert(note_col_index + 1, "quetiapine_doses", "")
# Save updated CSV
df.to_csv(csv_file, index=False)
print(f"Successfully added quetiapine columns to {csv_file}")
print(f"New column order: {list(df.columns)}")
except Exception as e:
print(f"Error during migration: {e}")
# Restore backup on error
if os.path.exists(backup_file):
shutil.copy2(backup_file, csv_file)
print("Restored backup due to error")
if __name__ == "__main__":
migrate_csv_add_quetiapine()
+4
View File
@@ -33,6 +33,8 @@ class DataManager:
"gabapentin_doses",
"propranolol",
"propranolol_doses",
"quetiapine",
"quetiapine_doses",
"note",
]
)
@@ -59,6 +61,8 @@ class DataManager:
"gabapentin_doses": str,
"propranolol": int,
"propranolol_doses": str,
"quetiapine": int,
"quetiapine_doses": str,
"note": str,
"date": str,
},
+11
View File
@@ -138,6 +138,8 @@ class MedTrackerApp:
full_row["gabapentin_doses"],
full_row["propranolol"],
full_row["propranolol_doses"],
full_row.get("quetiapine", 0),
full_row.get("quetiapine_doses", ""),
full_row["note"],
)
else:
@@ -166,6 +168,7 @@ class MedTrackerApp:
hydro: int,
gaba: int,
prop: int,
quet: int,
note: str,
dose_data: dict[str, str],
) -> None:
@@ -184,6 +187,8 @@ class MedTrackerApp:
dose_data.get("gabapentin", ""),
prop,
dose_data.get("propranolol", ""),
quet,
dose_data.get("quetiapine", ""),
note,
]
@@ -222,6 +227,7 @@ class MedTrackerApp:
hydroxyzine_doses = ""
gabapentin_doses = ""
propranolol_doses = ""
quetiapine_doses = ""
if today:
bup_doses = self.data_manager.get_today_medicine_doses(today, "bupropion")
@@ -232,6 +238,7 @@ class MedTrackerApp:
prop_doses = self.data_manager.get_today_medicine_doses(
today, "propranolol"
)
quet_doses = self.data_manager.get_today_medicine_doses(today, "quetiapine")
bupropion_doses = "|".join([f"{ts}:{dose}" for ts, dose in bup_doses])
hydroxyzine_doses = "|".join(
@@ -239,6 +246,7 @@ class MedTrackerApp:
)
gabapentin_doses = "|".join([f"{ts}:{dose}" for ts, dose in gaba_doses])
propranolol_doses = "|".join([f"{ts}:{dose}" for ts, dose in prop_doses])
quetiapine_doses = "|".join([f"{ts}:{dose}" for ts, dose in quet_doses])
entry: list[str | int] = [
self.date_var.get(),
@@ -254,6 +262,8 @@ class MedTrackerApp:
gabapentin_doses,
self.medicine_vars["propranolol"][0].get(),
propranolol_doses,
self.medicine_vars["quetiapine"][0].get(),
quetiapine_doses,
self.note_var.get(),
]
logger.debug(f"Adding entry: {entry}")
@@ -337,6 +347,7 @@ class MedTrackerApp:
"hydroxyzine",
"gabapentin",
"propranolol",
"quetiapine",
"note",
]
+54 -11
View File
@@ -138,6 +138,7 @@ class UIManager:
"hydroxyzine": (tk.IntVar(value=0), "Hydroxyzine 25mg"),
"gabapentin": (tk.IntVar(value=0), "Gabapentin 100mg"),
"propranolol": (tk.IntVar(value=0), "Propranolol 10mg"),
"quetiapine": (tk.IntVar(value=0), "Quetiapine 25mg"),
}
for idx, (_med_name, (var, text)) in enumerate(medicine_vars.items()):
@@ -197,6 +198,7 @@ class UIManager:
"Hydroxyzine",
"Gabapentin",
"Propranolol",
"Quetiapine",
"Note",
]
@@ -212,6 +214,7 @@ class UIManager:
"Hydroxyzine 25mg",
"Gabapentin 100mg",
"Propranolol 10mg",
"Quetiapine 25mg",
"Note",
]
@@ -228,6 +231,7 @@ class UIManager:
("Hydroxyzine", 120, "center"),
("Gabapentin", 120, "center"),
("Propranolol", 120, "center"),
("Quetiapine", 120, "center"),
("Note", 300, "w"),
]
@@ -286,9 +290,16 @@ class UIManager:
if len(values) == 10:
# Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
bup_doses, hydro_doses, gaba_doses, prop_doses = "", "", "", ""
bup_doses, hydro_doses, gaba_doses, prop_doses, quet_doses = (
"",
"",
"",
"",
"",
)
quet = 0
elif len(values) == 14:
# New format with dose tracking
# Old new format with dose tracking (without quetiapine)
(
date,
dep,
@@ -305,11 +316,9 @@ class UIManager:
prop_doses,
note,
) = values
else:
# Fallback for unexpected format
self.logger.warning(f"Unexpected number of values in edit: {len(values)}")
# Pad with default values
values_list = list(values) + [""] * (14 - len(values))
quet, quet_doses = 0, ""
elif len(values) == 16:
# New format with quetiapine and dose tracking
(
date,
dep,
@@ -324,8 +333,33 @@ class UIManager:
gaba_doses,
prop,
prop_doses,
quet,
quet_doses,
note,
) = values_list[:14]
) = values
else:
# Fallback for unexpected format
self.logger.warning(f"Unexpected number of values in edit: {len(values)}")
# Pad with default values
values_list = list(values) + [""] * (16 - len(values))
(
date,
dep,
anx,
slp,
app,
bup,
bup_doses,
hydro,
hydro_doses,
gaba,
gaba_doses,
prop,
prop_doses,
quet,
quet_doses,
note,
) = values_list[:16]
# Create variables and fields
vars_dict = self._create_edit_fields(edit_win, date, dep, anx, slp, app)
@@ -333,7 +367,7 @@ class UIManager:
# Medicine checkboxes
current_row = 6 # After the 5 fields (date, dep, anx, slp, app)
med_vars = self._create_medicine_checkboxes(
edit_win, current_row, bup, hydro, gaba, prop
edit_win, current_row, bup, hydro, gaba, prop, quet
)
vars_dict.update(med_vars)
@@ -347,6 +381,7 @@ class UIManager:
"hydroxyzine": hydro_doses,
"gabapentin": gaba_doses,
"propranolol": prop_doses,
"quetiapine": quet_doses,
},
)
vars_dict.update(dose_vars)
@@ -474,6 +509,7 @@ class UIManager:
hydro: int,
gaba: int,
prop: int,
quet: int,
) -> dict[str, tk.IntVar]:
"""Create medicine checkboxes in the edit window."""
ttk.Label(parent, text="Treatment:").grid(
@@ -487,6 +523,7 @@ class UIManager:
"hydroxyzine": (hydro, "Hydroxyzine 25mg"),
"gabapentin": (gaba, "Gabapentin 100mg"),
"propranolol": (prop, "Propranolol 10mg"),
"quetiapine": (quet, "Quetiapine 25mg"),
}
vars_dict: dict[str, tk.IntVar] = {}
@@ -514,7 +551,13 @@ class UIManager:
# Extract dose data from the text widgets
dose_data = {}
for medicine in ["bupropion", "hydroxyzine", "gabapentin", "propranolol"]:
for medicine in [
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"quetiapine",
]:
dose_text_key = f"{medicine}_doses_text"
if dose_text_key in vars_dict and isinstance(
@@ -526,7 +569,6 @@ class UIManager:
)
else:
dose_data[medicine] = ""
dose_data[medicine] = ""
callbacks["save"](
parent,
@@ -539,6 +581,7 @@ class UIManager:
vars_dict["hydroxyzine"].get(),
vars_dict["gabapentin"].get(),
vars_dict["propranolol"].get(),
vars_dict["quetiapine"].get(),
vars_dict["note"].get(),
dose_data,
)
+9 -3
View File
@@ -24,9 +24,9 @@ def temp_csv_file():
def sample_data():
"""Sample data for testing."""
return [
["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note 1"],
["2024-01-02", 2, 3, 3, 4, 1, 1, 2, 0, "Test note 2"],
["2024-01-03", 4, 1, 5, 2, 0, 0, 1, 1, ""],
["2024-01-01", 3, 2, 4, 3, 1, "", 0, "", 2, "", 1, "", 0, "", "Test note 1"],
["2024-01-02", 2, 3, 3, 4, 1, "", 1, "", 2, "", 0, "", 1, "", "Test note 2"],
["2024-01-03", 4, 1, 5, 2, 0, "", 0, "", 1, "", 1, "", 0, "", ""],
]
@@ -40,9 +40,15 @@ def sample_dataframe():
'sleep': [4, 3, 5],
'appetite': [3, 4, 2],
'bupropion': [1, 1, 0],
'bupropion_doses': ['', '', ''],
'hydroxyzine': [0, 1, 0],
'hydroxyzine_doses': ['', '', ''],
'gabapentin': [2, 2, 1],
'gabapentin_doses': ['', '', ''],
'propranolol': [1, 0, 1],
'propranolol_doses': ['', '', ''],
'quetiapine': [0, 1, 0],
'quetiapine_doses': ['', '', ''],
'note': ['Test note 1', 'Test note 2', '']
})
+36 -17
View File
@@ -40,7 +40,8 @@ class TestDataManager:
expected_headers = [
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses", "note"
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
]
assert headers == expected_headers
@@ -78,7 +79,9 @@ class TestDataManager:
# Write headers first
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
# Write sample data
writer.writerows(sample_data)
@@ -90,7 +93,9 @@ class TestDataManager:
assert len(df) == 3
assert list(df.columns) == [
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
]
# Check data types
assert df["depression"].dtype == int
@@ -101,16 +106,18 @@ class TestDataManager:
"""Test that loaded data is sorted by date."""
# Write data in random order
unsorted_data = [
["2024-01-03", 1, 1, 1, 1, 1, 1, 1, 1, "third"],
["2024-01-01", 2, 2, 2, 2, 2, 2, 2, 2, "first"],
["2024-01-02", 3, 3, 3, 3, 3, 3, 3, 3, "second"],
["2024-01-03", 1, 1, 1, 1, 1, "", 1, "", 1, "", 1, "", 0, "", "third"],
["2024-01-01", 2, 2, 2, 2, 2, "", 2, "", 2, "", 2, "", 1, "", "first"],
["2024-01-02", 3, 3, 3, 3, 3, "", 3, "", 3, "", 3, "", 0, "", "second"],
]
with open(temp_csv_file, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(unsorted_data)
@@ -143,13 +150,15 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)
dm = DataManager(temp_csv_file, mock_logger)
# Try to add entry with existing date
duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, 1, 1, 1, "Duplicate"]
duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, "", 1, "", 1, "", 1, "", 0, "", "Duplicate"]
result = dm.add_entry(duplicate_entry)
assert result is False
@@ -162,12 +171,14 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)
dm = DataManager(temp_csv_file, mock_logger)
updated_values = ["2024-01-01", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
updated_values = ["2024-01-01", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"]
result = dm.update_entry("2024-01-01", updated_values)
assert result is True
@@ -185,12 +196,14 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)
dm = DataManager(temp_csv_file, mock_logger)
updated_values = ["2024-01-05", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
updated_values = ["2024-01-05", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"]
result = dm.update_entry("2024-01-01", updated_values)
assert result is True
@@ -207,13 +220,15 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)
dm = DataManager(temp_csv_file, mock_logger)
# Try to change date to one that already exists
updated_values = ["2024-01-02", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
updated_values = ["2024-01-02", 5, 5, 5, 5, 2, "", 2, "", 2, "", 2, "", 1, "", "Updated note"]
result = dm.update_entry("2024-01-01", updated_values)
assert result is False
@@ -228,7 +243,9 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)
@@ -249,7 +266,9 @@ class TestDataManager:
writer = csv.writer(f)
writer.writerow([
"date", "depression", "anxiety", "sleep", "appetite",
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
"bupropion", "bupropion_doses", "hydroxyzine", "hydroxyzine_doses",
"gabapentin", "gabapentin_doses", "propranolol", "propranolol_doses",
"quetiapine", "quetiapine_doses", "note"
])
writer.writerows(sample_data)