From 98eff715467cea759d8a80eb65e6eb266c5746bb Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 5 Apr 2024 17:33:46 +0000 Subject: [PATCH 01/27] feat(devcontainer): setup for jupyter notebooks --- .devcontainer/devcontainer.json | 2 ++ pyproject.toml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5d4179b..1402349 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,10 +19,12 @@ "extensions": [ "eamodio.gitlens", "esbenp.prettier-vscode", + "mechatroner.rainbow-csv", "mhutchie.git-graph", "ms-python.python", "ms-python.black-formatter", "ms-python.flake8", + "ms-toolsai.jupyter", "tamasfe.even-better-toml" ] } diff --git a/pyproject.toml b/pyproject.toml index 49f92e3..2c1640a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,8 @@ dev = [ "black", "build", "flake8", + "ipykernel", + "pandas", "pre-commit", "setuptools_scm>=8" ] @@ -64,5 +66,8 @@ norecursedirs = [ ".vscode", ] +[tool.setuptools] +packages = ["compiler_admin"] + [tool.setuptools_scm] # intentionally left blank, but we need the section header to activate the tool From dde5e33b82f6f2601357c8308c333c64b11ecf94 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 5 Apr 2024 18:24:53 +0000 Subject: [PATCH 02/27] chore(data): add sample harvest export data --- .env.sample | 1 + .gitignore | 3 + notebooks/data/harvest-sample.csv | 251 ++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 .env.sample create mode 100644 notebooks/data/harvest-sample.csv diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..6c504b2 --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +HARVEST_DATA=data/harvest-sample.csv diff --git a/.gitignore b/.gitignore index 30e1f60..98cfdfb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ __pycache__ !.config/README.md .coverage .downloads +.env .pytest_cache *.csv *.egg-info +notebooks/data/* +!notebooks/data/harvest-sample.csv diff --git a/notebooks/data/harvest-sample.csv b/notebooks/data/harvest-sample.csv new file mode 100644 index 0000000..771b61b --- /dev/null +++ b/notebooks/data/harvest-sample.csv @@ -0,0 +1,251 @@ +Date,Client,Project,Project Code,Task,Notes,Hours,Billable?,Invoiced?,Approved?,First Name,Last Name,Roles,Employee?,External Reference URL +2023-01-21,Client1,Project5,PROJECT,Project Consulting,nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit,2.51,true,false,false,Hetti,Becken,Team Member,true, +2023-01-07,Client1,Project4,PROJECT,Project Consulting,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur,1.84,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-25,Client1,Project5,PROJECT,Project Consulting,odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est,2.11,true,false,true,Silas,Idenden,Team Member,true, +2023-01-08,Client1,Project3,PROJECT,Project Consulting,in purus eu magna vulputate luctus,1.61,false,true,true,Silas,Idenden,Team Member,true, +2023-01-18,Client1,Project1,PROJECT,Project Consulting,nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque,1.72,true,false,true,Hetti,Becken,Team Member,false, +2023-01-11,Client1,Project3,PROJECT,Project Consulting,varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit,3.16,true,true,false,Aymer,Hauck,Team Member,false, +2023-01-29,Client1,Project5,PROJECT,Project Consulting,aliquam convallis nunc proin at turpis a pede posuere nonummy,0.33,false,true,false,Hetti,Becken,Team Member,false, +2023-01-26,Client1,Project2,PROJECT,Project Consulting,volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis donec semper sapien,3.58,true,false,false,Aymer,Hauck,Team Member,true, +2023-01-14,Client1,Project7,PROJECT,Project Consulting,morbi vestibulum velit id pretium iaculis diam,3.0,false,true,false,Silas,Idenden,Team Member,true, +2023-01-13,Client1,Project6,PROJECT,Project Consulting,blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit,0.68,false,true,false,Silas,Idenden,Team Member,false, +2023-01-20,Client1,Project1,PROJECT,Project Consulting,tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui,2.72,false,false,false,Aymer,Hauck,Team Member,false, +2023-01-24,Client1,Project6,PROJECT,Project Consulting,duis aliquam convallis nunc proin at turpis,2.76,true,false,true,Silas,Idenden,Team Member,false, +2023-01-26,Client1,Project4,PROJECT,Project Consulting,nam congue risus semper porta volutpat quam pede lobortis ligula sit,2.48,true,false,false,Gusella,Swaile,Team Member,true, +2023-01-24,Client1,Project7,PROJECT,Project Consulting,aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum,2.23,true,true,false,Aymer,Hauck,Team Member,false, +2023-01-24,Client1,Project3,PROJECT,Project Consulting,odio consequat varius integer ac leo pellentesque ultrices mattis odio,0.92,true,true,true,Hetti,Becken,Team Member,false, +2023-01-15,Client1,Project6,PROJECT,Project Consulting,dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum,3.14,false,true,false,Silas,Idenden,Team Member,true, +2023-01-22,Client1,Project4,PROJECT,Project Consulting,quis odio consequat varius integer ac leo pellentesque ultrices,3.43,false,false,false,Hetti,Becken,Team Member,false, +2023-01-08,Client1,Project4,PROJECT,Project Consulting,sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt,2.01,true,false,true,Aymer,Hauck,Team Member,true, +2023-01-04,Client1,Project1,PROJECT,Project Consulting,eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis,2.84,true,false,false,Hetti,Becken,Team Member,false, +2023-01-27,Client1,Project6,PROJECT,Project Consulting,blandit non interdum in ante vestibulum ante,1.83,true,false,false,Silas,Idenden,Team Member,false, +2023-01-30,Client1,Project4,PROJECT,Project Consulting,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus,1.8,false,false,false,Aymer,Hauck,Team Member,true, +2023-01-11,Client1,Project1,PROJECT,Project Consulting,maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur gravida nisi at nibh,0.83,true,true,false,Aymer,Hauck,Team Member,false, +2023-01-09,Client1,Project1,PROJECT,Project Consulting,quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse,3.83,true,true,false,Hetti,Becken,Team Member,true, +2023-01-30,Client1,Project3,PROJECT,Project Consulting,vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci,1.07,false,false,false,Hetti,Becken,Team Member,true, +2023-01-19,Client1,Project1,PROJECT,Project Consulting,faucibus orci luctus et ultrices posuere cubilia curae donec pharetra,3.9,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-02,Client1,Project7,PROJECT,Project Consulting,in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum,0.57,true,true,false,Hetti,Becken,Team Member,true, +2023-01-06,Client1,Project7,PROJECT,Project Consulting,nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat,2.2,true,false,false,Hetti,Becken,Team Member,true, +2023-01-07,Client1,Project5,PROJECT,Project Consulting,nulla suspendisse potenti cras in purus eu,1.24,true,false,true,Aymer,Hauck,Team Member,true, +2023-01-23,Client1,Project5,PROJECT,Project Consulting,nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros,0.34,true,true,false,Silas,Idenden,Team Member,false, +2023-01-17,Client1,Project4,PROJECT,Project Consulting,luctus rutrum nulla tellus in sagittis dui vel nisl duis,1.59,true,false,false,Gusella,Swaile,Team Member,false, +2023-01-10,Client1,Project2,PROJECT,Project Consulting,blandit nam nulla integer pede justo,0.5,true,true,false,Hetti,Becken,Team Member,false, +2023-01-21,Client1,Project6,PROJECT,Project Consulting,convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien,2.22,false,false,true,Gusella,Swaile,Team Member,true, +2023-01-23,Client1,Project7,PROJECT,Project Consulting,tortor sollicitudin mi sit amet lobortis sapien sapien non,3.29,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-11,Client1,Project6,PROJECT,Project Consulting,gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate,2.21,false,false,true,Gusella,Swaile,Team Member,false, +2023-01-24,Client1,Project5,PROJECT,Project Consulting,vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy,2.28,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-18,Client1,Project4,PROJECT,Project Consulting,imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce,1.67,false,false,false,Sawyer,Berrey,Team Member,false, +2023-01-12,Client1,Project5,PROJECT,Project Consulting,massa quis augue luctus tincidunt nulla mollis molestie lorem,1.55,true,false,false,Hetti,Becken,Team Member,false, +2023-01-02,Client1,Project3,PROJECT,Project Consulting,nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis,2.98,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-14,Client1,Project5,PROJECT,Project Consulting,pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula,2.33,false,true,true,Silas,Idenden,Team Member,false, +2023-01-09,Client1,Project1,PROJECT,Project Consulting,donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien,1.03,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-21,Client1,Project4,PROJECT,Project Consulting,ac lobortis vel dapibus at diam nam tristique tortor eu pede,0.21,true,true,false,Gusella,Swaile,Team Member,true, +2023-01-05,Client1,Project1,PROJECT,Project Consulting,cursus vestibulum proin eu mi nulla ac enim in tempor,3.79,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-25,Client1,Project6,PROJECT,Project Consulting,neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut,3.86,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-13,Client1,Project4,PROJECT,Project Consulting,et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non,3.82,false,false,false,Aymer,Hauck,Team Member,true, +2023-01-10,Client1,Project1,PROJECT,Project Consulting,nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique,0.98,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-27,Client1,Project5,PROJECT,Project Consulting,egestas metus aenean fermentum donec ut mauris eget massa tempor,2.54,false,true,true,Gusella,Swaile,Team Member,true, +2023-01-14,Client1,Project4,PROJECT,Project Consulting,feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu,2.11,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-18,Client1,Project4,PROJECT,Project Consulting,tortor duis mattis egestas metus aenean fermentum donec ut mauris eget,2.74,false,false,true,Silas,Idenden,Team Member,false, +2023-01-12,Client1,Project7,PROJECT,Project Consulting,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros,2.19,false,true,false,Hetti,Becken,Team Member,false, +2023-01-30,Client1,Project4,PROJECT,Project Consulting,volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla,2.73,true,true,true,Gusella,Swaile,Team Member,false, +2023-01-05,Client1,Project7,PROJECT,Project Consulting,malesuada in imperdiet et commodo vulputate justo in blandit,0.84,true,true,false,Gusella,Swaile,Team Member,false, +2023-01-16,Client1,Project2,PROJECT,Project Consulting,erat id mauris vulputate elementum nullam varius nulla facilisi,3.18,true,true,true,Sawyer,Berrey,Team Member,false, +2023-01-12,Client1,Project3,PROJECT,Project Consulting,vel augue vestibulum rutrum rutrum neque aenean auctor gravida,2.42,false,true,false,Hetti,Becken,Team Member,true, +2023-01-21,Client1,Project5,PROJECT,Project Consulting,in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem,0.56,false,true,true,Gusella,Swaile,Team Member,false, +2023-01-23,Client1,Project6,PROJECT,Project Consulting,elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque,1.07,true,true,true,Sawyer,Berrey,Team Member,false, +2023-01-25,Client1,Project4,PROJECT,Project Consulting,elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis,2.48,false,true,false,Silas,Idenden,Team Member,false, +2023-01-12,Client1,Project4,PROJECT,Project Consulting,morbi a ipsum integer a nibh,1.4,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-04,Client1,Project3,PROJECT,Project Consulting,aliquet at feugiat non pretium quis,2.36,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-26,Client1,Project4,PROJECT,Project Consulting,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in,1.26,false,true,true,Sawyer,Berrey,Team Member,false, +2023-01-09,Client1,Project5,PROJECT,Project Consulting,at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis,0.47,true,true,true,Gusella,Swaile,Team Member,false, +2023-01-06,Client1,Project1,PROJECT,Project Consulting,vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis,2.25,true,false,true,Aymer,Hauck,Team Member,true, +2023-01-12,Client1,Project3,PROJECT,Project Consulting,pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula,3.84,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-12,Client1,Project3,PROJECT,Project Consulting,curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec,2.06,true,false,true,Sawyer,Berrey,Team Member,true, +2023-01-27,Client1,Project7,PROJECT,Project Consulting,hac habitasse platea dictumst aliquam augue quam,2.93,true,true,false,Silas,Idenden,Team Member,false, +2023-01-07,Client1,Project5,PROJECT,Project Consulting,praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in,2.42,false,false,false,Silas,Idenden,Team Member,false, +2023-01-26,Client1,Project5,PROJECT,Project Consulting,sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse,2.65,true,false,true,Aymer,Hauck,Team Member,true, +2023-01-08,Client1,Project5,PROJECT,Project Consulting,porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam,1.55,true,true,true,Sawyer,Berrey,Team Member,false, +2023-01-06,Client1,Project4,PROJECT,Project Consulting,leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu,3.49,false,false,false,Aymer,Hauck,Team Member,true, +2023-01-27,Client1,Project4,PROJECT,Project Consulting,nullam orci pede venenatis non sodales sed tincidunt eu,3.67,true,true,true,Silas,Idenden,Team Member,true, +2023-01-23,Client1,Project7,PROJECT,Project Consulting,congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci,0.23,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-30,Client1,Project1,PROJECT,Project Consulting,nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et,3.53,true,true,false,Silas,Idenden,Team Member,false, +2023-01-28,Client1,Project6,PROJECT,Project Consulting,nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec,2.13,true,true,true,Aymer,Hauck,Team Member,true, +2023-01-11,Client1,Project3,PROJECT,Project Consulting,maecenas tristique est et tempus semper est,1.62,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-04,Client1,Project7,PROJECT,Project Consulting,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas,0.8,false,false,false,Sawyer,Berrey,Team Member,true, +2023-01-22,Client1,Project5,PROJECT,Project Consulting,nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere,3.41,false,true,false,Hetti,Becken,Team Member,true, +2023-01-16,Client1,Project3,PROJECT,Project Consulting,nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante,0.74,false,false,true,Sawyer,Berrey,Team Member,true, +2023-01-22,Client1,Project7,PROJECT,Project Consulting,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis,1.47,true,true,true,Sawyer,Berrey,Team Member,false, +2023-01-30,Client1,Project2,PROJECT,Project Consulting,dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend,3.01,false,true,false,Gusella,Swaile,Team Member,false, +2023-01-26,Client1,Project3,PROJECT,Project Consulting,ligula vehicula consequat morbi a ipsum,3.81,true,false,false,Hetti,Becken,Team Member,true, +2023-01-05,Client1,Project6,PROJECT,Project Consulting,aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet,2.68,false,true,true,Silas,Idenden,Team Member,true, +2023-01-09,Client1,Project1,PROJECT,Project Consulting,et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a,2.63,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-28,Client1,Project4,PROJECT,Project Consulting,amet lobortis sapien sapien non mi,3.72,true,true,true,Gusella,Swaile,Team Member,true, +2023-01-07,Client1,Project5,PROJECT,Project Consulting,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas,1.24,false,false,true,Hetti,Becken,Team Member,true, +2023-01-03,Client1,Project1,PROJECT,Project Consulting,praesent blandit lacinia erat vestibulum,1.51,true,false,true,Hetti,Becken,Team Member,true, +2023-01-03,Client1,Project3,PROJECT,Project Consulting,elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo,3.14,false,false,false,Aymer,Hauck,Team Member,false, +2023-01-24,Client1,Project6,PROJECT,Project Consulting,porttitor lorem id ligula suspendisse ornare consequat lectus,3.34,false,false,true,Silas,Idenden,Team Member,true, +2023-01-17,Client1,Project3,PROJECT,Project Consulting,ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis,2.19,true,false,false,Gusella,Swaile,Team Member,true, +2023-01-07,Client1,Project6,PROJECT,Project Consulting,nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla,2.76,false,false,false,Hetti,Becken,Team Member,true, +2023-01-14,Client1,Project3,PROJECT,Project Consulting,in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis,0.91,false,false,false,Aymer,Hauck,Team Member,true, +2023-01-17,Client1,Project2,PROJECT,Project Consulting,cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien,2.42,true,true,true,Silas,Idenden,Team Member,true, +2023-01-14,Client1,Project6,PROJECT,Project Consulting,bibendum felis sed interdum venenatis turpis enim blandit mi,1.27,true,false,true,Sawyer,Berrey,Team Member,false, +2023-01-06,Client1,Project5,PROJECT,Project Consulting,nibh ligula nec sem duis aliquam convallis,0.91,false,true,true,Silas,Idenden,Team Member,true, +2023-01-17,Client1,Project5,PROJECT,Project Consulting,vestibulum sagittis sapien cum sociis natoque,3.86,true,true,false,Silas,Idenden,Team Member,true, +2023-01-20,Client1,Project2,PROJECT,Project Consulting,diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra,2.43,false,true,true,Hetti,Becken,Team Member,false, +2023-01-23,Client1,Project7,PROJECT,Project Consulting,id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam,1.57,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-03,Client1,Project4,PROJECT,Project Consulting,aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est,2.06,false,false,true,Sawyer,Berrey,Team Member,true, +2023-01-16,Client1,Project4,PROJECT,Project Consulting,eu massa donec dapibus duis at velit eu,1.25,true,true,false,Sawyer,Berrey,Team Member,false, +2023-01-18,Client1,Project3,PROJECT,Project Consulting,aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu,1.63,true,true,true,Sawyer,Berrey,Team Member,false, +2023-01-29,Client1,Project3,PROJECT,Project Consulting,vestibulum sagittis sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum,3.51,false,true,true,Gusella,Swaile,Team Member,true, +2023-01-25,Client1,Project6,PROJECT,Project Consulting,id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque,3.64,true,false,false,Hetti,Becken,Team Member,false, +2023-01-06,Client1,Project2,PROJECT,Project Consulting,at nunc commodo placerat praesent blandit,3.42,false,false,false,Silas,Idenden,Team Member,false, +2023-01-13,Client1,Project7,PROJECT,Project Consulting,varius ut blandit non interdum in ante vestibulum ante ipsum,1.1,true,true,false,Hetti,Becken,Team Member,true, +2023-01-02,Client1,Project3,PROJECT,Project Consulting,elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at,0.71,false,false,false,Hetti,Becken,Team Member,true, +2023-01-05,Client1,Project3,PROJECT,Project Consulting,dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia,3.48,true,false,false,Gusella,Swaile,Team Member,false, +2023-01-18,Client1,Project3,PROJECT,Project Consulting,auctor sed tristique in tempus sit,1.9,false,false,false,Hetti,Becken,Team Member,false, +2023-01-05,Client1,Project7,PROJECT,Project Consulting,ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo,2.43,true,true,false,Gusella,Swaile,Team Member,false, +2023-01-26,Client1,Project2,PROJECT,Project Consulting,et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien,3.04,true,true,true,Gusella,Swaile,Team Member,true, +2023-01-27,Client1,Project1,PROJECT,Project Consulting,consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede,3.21,false,false,false,Gusella,Swaile,Team Member,true, +2023-01-06,Client1,Project7,PROJECT,Project Consulting,feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien,0.59,false,false,true,Sawyer,Berrey,Team Member,false, +2023-01-13,Client1,Project6,PROJECT,Project Consulting,aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at,2.04,false,false,false,Hetti,Becken,Team Member,false, +2023-01-25,Client1,Project6,PROJECT,Project Consulting,vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra,2.03,true,false,true,Hetti,Becken,Team Member,false, +2023-01-22,Client1,Project1,PROJECT,Project Consulting,sollicitudin mi sit amet lobortis sapien sapien non mi integer,0.08,true,true,true,Hetti,Becken,Team Member,true, +2023-01-13,Client1,Project6,PROJECT,Project Consulting,platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget,1.12,true,true,false,Sawyer,Berrey,Team Member,false, +2023-01-20,Client1,Project7,PROJECT,Project Consulting,quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus,3.76,false,true,false,Aymer,Hauck,Team Member,true, +2023-01-26,Client1,Project3,PROJECT,Project Consulting,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis donec,0.88,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-05,Client1,Project2,PROJECT,Project Consulting,sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque,1.26,true,true,true,Silas,Idenden,Team Member,true, +2023-01-30,Client1,Project6,PROJECT,Project Consulting,ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis,1.94,true,false,false,Silas,Idenden,Team Member,false, +2023-01-15,Client1,Project7,PROJECT,Project Consulting,vulputate justo in blandit ultrices,3.61,true,false,false,Gusella,Swaile,Team Member,false, +2023-01-15,Client1,Project4,PROJECT,Project Consulting,viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et,0.05,true,true,false,Silas,Idenden,Team Member,false, +2023-01-07,Client1,Project1,PROJECT,Project Consulting,in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum sociis natoque,3.16,true,true,false,Hetti,Becken,Team Member,true, +2023-01-25,Client1,Project2,PROJECT,Project Consulting,nibh in lectus pellentesque at,2.14,true,true,true,Sawyer,Berrey,Team Member,true, +2023-01-16,Client1,Project4,PROJECT,Project Consulting,vestibulum proin eu mi nulla ac enim in tempor,2.99,true,false,true,Silas,Idenden,Team Member,true, +2023-01-20,Client1,Project6,PROJECT,Project Consulting,metus sapien ut nunc vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam,1.31,true,true,true,Silas,Idenden,Team Member,true, +2023-01-08,Client1,Project3,PROJECT,Project Consulting,curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non,2.59,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-26,Client1,Project5,PROJECT,Project Consulting,in felis eu sapien cursus vestibulum proin eu mi nulla ac enim,2.42,false,true,true,Gusella,Swaile,Team Member,false, +2023-01-15,Client1,Project3,PROJECT,Project Consulting,luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus,1.66,true,false,true,Sawyer,Berrey,Team Member,true, +2023-01-26,Client1,Project2,PROJECT,Project Consulting,convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh,1.3,false,false,true,Gusella,Swaile,Team Member,false, +2023-01-06,Client1,Project2,PROJECT,Project Consulting,sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna,3.69,false,true,false,Hetti,Becken,Team Member,false, +2023-01-10,Client1,Project6,PROJECT,Project Consulting,curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non,2.67,true,true,false,Aymer,Hauck,Team Member,true, +2023-01-18,Client1,Project1,PROJECT,Project Consulting,ullamcorper augue a suscipit nulla elit ac nulla sed,0.53,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-11,Client1,Project7,PROJECT,Project Consulting,elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in,1.92,true,true,false,Sawyer,Berrey,Team Member,true, +2023-01-14,Client1,Project6,PROJECT,Project Consulting,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at,4.0,true,false,false,Hetti,Becken,Team Member,false, +2023-01-03,Client1,Project2,PROJECT,Project Consulting,praesent blandit nam nulla integer pede,0.7,false,false,true,Silas,Idenden,Team Member,true, +2023-01-21,Client1,Project5,PROJECT,Project Consulting,vivamus in felis eu sapien cursus vestibulum,2.42,true,false,false,Sawyer,Berrey,Team Member,true, +2023-01-11,Client1,Project7,PROJECT,Project Consulting,blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in,2.54,false,false,false,Aymer,Hauck,Team Member,true, +2023-01-13,Client1,Project3,PROJECT,Project Consulting,sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus,0.22,false,false,true,Hetti,Becken,Team Member,true, +2023-01-25,Client1,Project6,PROJECT,Project Consulting,consequat lectus in est risus auctor sed tristique in tempus sit amet,2.83,true,true,false,Aymer,Hauck,Team Member,false, +2023-01-26,Client1,Project5,PROJECT,Project Consulting,ultrices posuere cubilia curae mauris viverra diam,1.51,false,true,true,Silas,Idenden,Team Member,true, +2023-01-14,Client1,Project6,PROJECT,Project Consulting,viverra diam vitae quam suspendisse potenti nullam porttitor lacus at,3.07,true,true,true,Gusella,Swaile,Team Member,false, +2023-01-29,Client1,Project7,PROJECT,Project Consulting,ut at dolor quis odio consequat varius integer,3.39,false,true,false,Sawyer,Berrey,Team Member,false, +2023-01-24,Client1,Project5,PROJECT,Project Consulting,ut blandit non interdum in,0.44,false,false,true,Silas,Idenden,Team Member,true, +2023-01-18,Client1,Project4,PROJECT,Project Consulting,faucibus orci luctus et ultrices posuere,1.14,true,false,true,Silas,Idenden,Team Member,false, +2023-01-06,Client1,Project3,PROJECT,Project Consulting,vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit,0.44,false,false,true,Sawyer,Berrey,Team Member,false, +2023-01-11,Client1,Project7,PROJECT,Project Consulting,ac consequat metus sapien ut nunc vestibulum ante ipsum primis in faucibus,0.69,false,false,false,Silas,Idenden,Team Member,false, +2023-01-09,Client1,Project5,PROJECT,Project Consulting,nulla integer pede justo lacinia eget tincidunt eget,1.98,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-07,Client1,Project4,PROJECT,Project Consulting,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque,0.69,false,false,false,Gusella,Swaile,Team Member,true, +2023-01-13,Client1,Project6,PROJECT,Project Consulting,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat,1.87,true,false,false,Gusella,Swaile,Team Member,true, +2023-01-29,Client1,Project1,PROJECT,Project Consulting,quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec,0.21,false,false,true,Hetti,Becken,Team Member,false, +2023-01-30,Client1,Project1,PROJECT,Project Consulting,pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc,0.33,true,false,false,Sawyer,Berrey,Team Member,false, +2023-01-21,Client1,Project3,PROJECT,Project Consulting,nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam,3.75,false,true,true,Aymer,Hauck,Team Member,true, +2023-01-12,Client1,Project1,PROJECT,Project Consulting,eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus,1.01,true,false,true,Sawyer,Berrey,Team Member,false, +2023-01-21,Client1,Project1,PROJECT,Project Consulting,placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan,1.33,false,true,false,Hetti,Becken,Team Member,true, +2023-01-18,Client1,Project7,PROJECT,Project Consulting,ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc,3.8,false,true,false,Aymer,Hauck,Team Member,true, +2023-01-25,Client1,Project5,PROJECT,Project Consulting,sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede,2.58,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-08,Client1,Project2,PROJECT,Project Consulting,lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet,1.33,true,true,false,Silas,Idenden,Team Member,true, +2023-01-28,Client1,Project3,PROJECT,Project Consulting,felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed,3.92,true,false,true,Hetti,Becken,Team Member,true, +2023-01-09,Client1,Project1,PROJECT,Project Consulting,enim leo rhoncus sed vestibulum sit amet cursus id turpis integer,1.53,true,true,true,Hetti,Becken,Team Member,true, +2023-01-15,Client1,Project7,PROJECT,Project Consulting,eros vestibulum ac est lacinia,2.14,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-05,Client1,Project1,PROJECT,Project Consulting,justo sollicitudin ut suscipit a feugiat et eros,2.68,false,false,true,Aymer,Hauck,Team Member,false, +2023-01-21,Client1,Project1,PROJECT,Project Consulting,cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue,0.26,true,false,false,Aymer,Hauck,Team Member,false, +2023-01-21,Client1,Project6,PROJECT,Project Consulting,mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla,1.64,true,false,true,Hetti,Becken,Team Member,true, +2023-01-22,Client1,Project1,PROJECT,Project Consulting,pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla,3.17,true,true,true,Silas,Idenden,Team Member,false, +2023-01-11,Client1,Project1,PROJECT,Project Consulting,nulla elit ac nulla sed vel enim sit amet nunc viverra,3.53,false,false,true,Gusella,Swaile,Team Member,true, +2023-01-19,Client1,Project6,PROJECT,Project Consulting,at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam,2.45,true,false,true,Hetti,Becken,Team Member,true, +2023-01-16,Client1,Project7,PROJECT,Project Consulting,elit ac nulla sed vel enim sit amet,0.73,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-15,Client1,Project7,PROJECT,Project Consulting,vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis,2.6,true,false,false,Hetti,Becken,Team Member,false, +2023-01-23,Client1,Project7,PROJECT,Project Consulting,posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis,3.57,false,false,false,Hetti,Becken,Team Member,true, +2023-01-30,Client1,Project3,PROJECT,Project Consulting,sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam,1.11,true,true,true,Gusella,Swaile,Team Member,false, +2023-01-23,Client1,Project7,PROJECT,Project Consulting,ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget,1.53,true,false,true,Aymer,Hauck,Team Member,false, +2023-01-30,Client1,Project2,PROJECT,Project Consulting,magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum sociis natoque penatibus et magnis,1.61,false,false,false,Hetti,Becken,Team Member,true, +2023-01-03,Client1,Project1,PROJECT,Project Consulting,eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum,0.41,true,false,false,Sawyer,Berrey,Team Member,true, +2023-01-17,Client1,Project1,PROJECT,Project Consulting,vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin,3.88,true,false,false,Hetti,Becken,Team Member,true, +2023-01-30,Client1,Project4,PROJECT,Project Consulting,phasellus in felis donec semper sapien a libero nam dui proin leo odio porttitor id consequat in consequat ut nulla sed accumsan,1.72,false,false,false,Hetti,Becken,Team Member,true, +2023-01-10,Client1,Project6,PROJECT,Project Consulting,eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque,0.78,false,true,false,Hetti,Becken,Team Member,false, +2023-01-20,Client1,Project2,PROJECT,Project Consulting,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam,3.25,false,false,false,Silas,Idenden,Team Member,false, +2023-01-09,Client1,Project7,PROJECT,Project Consulting,nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum,1.97,true,false,false,Aymer,Hauck,Team Member,false, +2023-01-11,Client1,Project1,PROJECT,Project Consulting,eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla,2.95,true,true,false,Sawyer,Berrey,Team Member,true, +2023-01-25,Client1,Project7,PROJECT,Project Consulting,leo pellentesque ultrices mattis odio donec vitae,1.17,false,false,false,Silas,Idenden,Team Member,false, +2023-01-03,Client1,Project2,PROJECT,Project Consulting,ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam,1.61,false,false,false,Gusella,Swaile,Team Member,true, +2023-01-22,Client1,Project1,PROJECT,Project Consulting,mollis molestie lorem quisque ut erat curabitur gravida nisi at nibh in hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget,1.41,false,false,false,Silas,Idenden,Team Member,false, +2023-01-25,Client1,Project7,PROJECT,Project Consulting,adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit,3.77,false,true,false,Hetti,Becken,Team Member,true, +2023-01-22,Client1,Project6,PROJECT,Project Consulting,arcu libero rutrum ac lobortis vel dapibus at diam nam tristique,2.38,true,false,false,Silas,Idenden,Team Member,false, +2023-01-30,Client1,Project3,PROJECT,Project Consulting,nulla mollis molestie lorem quisque ut erat curabitur gravida nisi at,1.81,false,false,false,Aymer,Hauck,Team Member,false, +2023-01-04,Client1,Project3,PROJECT,Project Consulting,mauris vulputate elementum nullam varius nulla facilisi cras,2.58,false,false,false,Gusella,Swaile,Team Member,false, +2023-01-30,Client1,Project4,PROJECT,Project Consulting,augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae,2.34,true,false,true,Gusella,Swaile,Team Member,false, +2023-01-17,Client1,Project2,PROJECT,Project Consulting,sit amet justo morbi ut odio,2.19,true,false,false,Hetti,Becken,Team Member,false, +2023-01-25,Client1,Project1,PROJECT,Project Consulting,odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel,2.9,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-06,Client1,Project1,PROJECT,Project Consulting,primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna,2.46,false,true,true,Silas,Idenden,Team Member,true, +2023-01-30,Client1,Project5,PROJECT,Project Consulting,a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien,0.48,false,true,false,Aymer,Hauck,Team Member,true, +2023-01-03,Client1,Project6,PROJECT,Project Consulting,vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis,1.51,true,false,false,Hetti,Becken,Team Member,true, +2023-01-18,Client1,Project7,PROJECT,Project Consulting,ligula sit amet eleifend pede libero quis orci nullam molestie,2.88,true,true,true,Sawyer,Berrey,Team Member,true, +2023-01-03,Client1,Project4,PROJECT,Project Consulting,ut erat curabitur gravida nisi at nibh in hac habitasse platea dictumst aliquam,0.82,true,true,true,Aymer,Hauck,Team Member,false, +2023-01-10,Client1,Project1,PROJECT,Project Consulting,duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue,3.25,true,true,true,Gusella,Swaile,Team Member,false, +2023-01-22,Client1,Project7,PROJECT,Project Consulting,orci eget orci vehicula condimentum curabitur in libero ut massa,0.77,false,true,true,Gusella,Swaile,Team Member,true, +2023-01-11,Client1,Project2,PROJECT,Project Consulting,curabitur convallis duis consequat dui nec,2.63,false,true,false,Aymer,Hauck,Team Member,true, +2023-01-30,Client1,Project3,PROJECT,Project Consulting,platea dictumst maecenas ut massa quis augue luctus tincidunt nulla,0.27,false,true,false,Gusella,Swaile,Team Member,true, +2023-01-06,Client1,Project6,PROJECT,Project Consulting,accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus,2.11,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-25,Client1,Project2,PROJECT,Project Consulting,mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus,2.21,true,true,false,Aymer,Hauck,Team Member,true, +2023-01-30,Client1,Project1,PROJECT,Project Consulting,metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget,3.41,true,false,false,Sawyer,Berrey,Team Member,true, +2023-01-16,Client1,Project6,PROJECT,Project Consulting,amet turpis elementum ligula vehicula consequat morbi a,0.11,false,false,true,Aymer,Hauck,Team Member,true, +2023-01-14,Client1,Project2,PROJECT,Project Consulting,elementum ligula vehicula consequat morbi a,0.7,true,true,true,Hetti,Becken,Team Member,false, +2023-01-15,Client1,Project1,PROJECT,Project Consulting,eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum,3.03,false,true,false,Silas,Idenden,Team Member,false, +2023-01-18,Client1,Project2,PROJECT,Project Consulting,volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam,0.73,false,true,true,Hetti,Becken,Team Member,true, +2023-01-28,Client1,Project5,PROJECT,Project Consulting,bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis dui vel nisl,3.71,false,false,false,Hetti,Becken,Team Member,false, +2023-01-05,Client1,Project4,PROJECT,Project Consulting,primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat,1.35,false,true,true,Silas,Idenden,Team Member,true, +2023-01-27,Client1,Project7,PROJECT,Project Consulting,risus semper porta volutpat quam pede lobortis ligula,3.21,true,false,true,Hetti,Becken,Team Member,true, +2023-01-15,Client1,Project2,PROJECT,Project Consulting,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur gravida nisi,1.58,true,true,true,Gusella,Swaile,Team Member,true, +2023-01-04,Client1,Project3,PROJECT,Project Consulting,viverra eget congue eget semper rutrum nulla nunc purus phasellus in,2.95,false,true,true,Aymer,Hauck,Team Member,false, +2023-01-26,Client1,Project4,PROJECT,Project Consulting,condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra,1.63,true,false,false,Gusella,Swaile,Team Member,true, +2023-01-15,Client1,Project1,PROJECT,Project Consulting,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna,2.32,false,true,false,Hetti,Becken,Team Member,true, +2023-01-20,Client1,Project6,PROJECT,Project Consulting,elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum,3.96,false,true,false,Silas,Idenden,Team Member,true, +2023-01-12,Client1,Project7,PROJECT,Project Consulting,luctus et ultrices posuere cubilia curae nulla dapibus,1.25,false,true,true,Aymer,Hauck,Team Member,false, +2023-01-27,Client1,Project1,PROJECT,Project Consulting,quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio,4.0,false,false,true,Sawyer,Berrey,Team Member,false, +2023-01-29,Client1,Project3,PROJECT,Project Consulting,mi pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim,1.63,false,true,true,Gusella,Swaile,Team Member,true, +2023-01-07,Client1,Project1,PROJECT,Project Consulting,proin leo odio porttitor id consequat in consequat,1.03,true,false,true,Sawyer,Berrey,Team Member,true, +2023-01-17,Client1,Project5,PROJECT,Project Consulting,porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis,3.62,false,false,false,Hetti,Becken,Team Member,true, +2023-01-21,Client1,Project4,PROJECT,Project Consulting,justo morbi ut odio cras mi pede malesuada in,1.07,false,true,true,Hetti,Becken,Team Member,true, +2023-01-06,Client1,Project7,PROJECT,Project Consulting,leo odio porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor,3.62,true,true,false,Silas,Idenden,Team Member,false, +2023-01-26,Client1,Project1,PROJECT,Project Consulting,est quam pharetra magna ac consequat metus sapien ut,2.19,false,false,true,Hetti,Becken,Team Member,true, +2023-01-14,Client1,Project7,PROJECT,Project Consulting,non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur,0.83,true,false,false,Gusella,Swaile,Team Member,false, +2023-01-19,Client1,Project2,PROJECT,Project Consulting,neque aenean auctor gravida sem praesent id massa id,2.15,true,false,false,Aymer,Hauck,Team Member,true, +2023-01-04,Client1,Project7,PROJECT,Project Consulting,convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum,3.92,true,true,true,Sawyer,Berrey,Team Member,true, +2023-01-11,Client1,Project2,PROJECT,Project Consulting,suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce,1.13,true,false,true,Silas,Idenden,Team Member,false, +2023-01-28,Client1,Project4,PROJECT,Project Consulting,volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna,2.62,false,false,true,Silas,Idenden,Team Member,false, +2023-01-14,Client1,Project5,PROJECT,Project Consulting,pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium,1.74,false,true,true,Sawyer,Berrey,Team Member,true, +2023-01-13,Client1,Project3,PROJECT,Project Consulting,rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet at feugiat non,0.49,true,true,false,Hetti,Becken,Team Member,true, +2023-01-12,Client1,Project7,PROJECT,Project Consulting,nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam,2.64,true,true,true,Silas,Idenden,Team Member,false, +2023-01-08,Client1,Project5,PROJECT,Project Consulting,id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices,4.0,true,true,false,Gusella,Swaile,Team Member,true, +2023-01-13,Client1,Project7,PROJECT,Project Consulting,eget orci vehicula condimentum curabitur in,0.47,false,false,false,Gusella,Swaile,Team Member,true, +2023-01-04,Client1,Project2,PROJECT,Project Consulting,elit sodales scelerisque mauris sit amet eros suspendisse,2.86,true,false,false,Silas,Idenden,Team Member,false, +2023-01-21,Client1,Project3,PROJECT,Project Consulting,quam sapien varius ut blandit non interdum in ante,0.73,false,false,true,Hetti,Becken,Team Member,true, +2023-01-30,Client1,Project7,PROJECT,Project Consulting,ligula suspendisse ornare consequat lectus in est risus,0.76,false,true,true,Gusella,Swaile,Team Member,true, +2023-01-11,Client1,Project7,PROJECT,Project Consulting,in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing,3.15,true,false,true,Silas,Idenden,Team Member,true, +2023-01-26,Client1,Project1,PROJECT,Project Consulting,magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui,2.79,false,true,true,Hetti,Becken,Team Member,true, +2023-01-15,Client1,Project7,PROJECT,Project Consulting,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla,0.99,true,false,false,Sawyer,Berrey,Team Member,true, +2023-01-02,Client1,Project1,PROJECT,Project Consulting,odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla,0.73,true,false,true,Gusella,Swaile,Team Member,false, +2023-01-06,Client1,Project4,PROJECT,Project Consulting,pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum,1.86,true,false,true,Silas,Idenden,Team Member,true, +2023-01-06,Client1,Project4,PROJECT,Project Consulting,consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel,2.55,false,false,false,Hetti,Becken,Team Member,true, +2023-01-07,Client1,Project4,PROJECT,Project Consulting,mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer,3.8,false,false,true,Gusella,Swaile,Team Member,false, +2023-01-20,Client1,Project2,PROJECT,Project Consulting,tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat,2.11,true,true,true,Silas,Idenden,Team Member,true, +2023-01-16,Client1,Project1,PROJECT,Project Consulting,at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus,1.2,true,true,false,Hetti,Becken,Team Member,true, +2023-01-13,Client1,Project4,PROJECT,Project Consulting,lectus aliquam sit amet diam in,3.32,true,false,false,Aymer,Hauck,Team Member,false, +2023-01-20,Client1,Project6,PROJECT,Project Consulting,nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem,2.53,false,true,false,Sawyer,Berrey,Team Member,true, +2023-01-05,Client1,Project3,PROJECT,Project Consulting,condimentum neque sapien placerat ante nulla justo aliquam quis,1.44,true,true,false,Hetti,Becken,Team Member,true, +2023-01-12,Client1,Project6,PROJECT,Project Consulting,etiam faucibus cursus urna ut,1.37,false,false,true,Sawyer,Berrey,Team Member,false, +2023-01-20,Client1,Project4,PROJECT,Project Consulting,varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci,2.95,true,false,true,Gusella,Swaile,Team Member,true, +2023-01-05,Client1,Project5,PROJECT,Project Consulting,vestibulum ante ipsum primis in faucibus,1.65,true,true,false,Aymer,Hauck,Team Member,true, +2023-01-22,Client1,Project3,PROJECT,Project Consulting,habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel,0.09,false,false,false,Aymer,Hauck,Team Member,false, +2023-01-12,Client1,Project2,PROJECT,Project Consulting,magna bibendum imperdiet nullam orci,3.33,true,false,false,Gusella,Swaile,Team Member,false, +2023-01-07,Client1,Project3,PROJECT,Project Consulting,sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc,0.75,false,true,true,Aymer,Hauck,Team Member,true, From 0c754c5ce3c5f8108170ec914b70abbcef114b27 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 5 Apr 2024 18:38:59 +0000 Subject: [PATCH 03/27] feat(harvest): setup notebook for toggl conversion --- notebooks/harvest-to-toggl.ipynb | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 notebooks/harvest-to-toggl.ipynb diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb new file mode 100644 index 0000000..72b133f --- /dev/null +++ b/notebooks/harvest-to-toggl.ipynb @@ -0,0 +1,50 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "import pandas as pd\n", + "\n", + "\n", + "DATA_DIR = Path(\"./data\")\n", + "DATA_SOURCE = Path(os.environ.get(\"HARVEST_DATA\", \"./data/harvest-sample.csv\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "source = pd.read_csv(DATA_SOURCE)\n", + "source.head()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 7e90102ab97db20823f2bcfcb6addfd8468621df Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Fri, 5 Apr 2024 20:49:58 +0000 Subject: [PATCH 04/27] feat(harvest): simple column updates/conversions * rename columns that can be imported as-is * add static calculated columns * calculate email column, drop name columns * calculate string duration column --- notebooks/harvest-to-toggl.ipynb | 73 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 72b133f..3419630 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -6,6 +6,7 @@ "metadata": {}, "outputs": [], "source": [ + "from datetime import datetime\n", "import os\n", "from pathlib import Path\n", "import pandas as pd\n", @@ -21,8 +22,76 @@ "metadata": {}, "outputs": [], "source": [ - "source = pd.read_csv(DATA_SOURCE)\n", - "source.head()" + "# assign category dtype for efficiency on repeating text columns\n", + "dtypes = {\n", + " \"Client\": \"category\",\n", + " \"Project\": \"category\",\n", + " \"First Name\": \"category\",\n", + " \"Last Name\": \"category\",\n", + "}\n", + "# skip reading the columns we don't care about for Toggl\n", + "cols = list(dtypes) + [\n", + " \"Date\",\n", + " \"Notes\",\n", + " \"Hours\",\n", + "]\n", + "# read CSV file, parsing dates\n", + "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Date\"], cache_dates=True)\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# rename columns that can be imported as-is\n", + "source.rename(columns={\"Project\": \"Task\", \"Notes\": \"Description\", \"Date\": \"Start date\"}, inplace=True)\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# update static calculated columns\n", + "source[\"Client\"] = \"Xentrans\"\n", + "source[\"Client\"] = source[\"Client\"].astype(\"category\")\n", + "source[\"Project\"] = \"Xentrans\"\n", + "source[\"Project\"] = source[\"Project\"].astype(\"category\")\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# add the Email column\n", + "source[\"Email\"] = source[\"First Name\"].apply(lambda x: f\"{x.lower()}@compiler.la\").astype(\"category\")\n", + "# drop individual name columns\n", + "source.drop(columns=[\"First Name\", \"Last Name\"], inplace=True)\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert numeric Hours to string Duration\n", + "source[\"Duration\"] = source[\"Hours\"].apply(\n", + " # first convert the numeric hours e.g. 1.5 to a timedelta\n", + " # then use the total seconds to convert to a datetime\n", + " # and format as a string e.g. 01:30\n", + " lambda x: datetime.fromtimestamp(pd.to_timedelta(x, unit=\"hours\").total_seconds()).strftime(\"%H:%M\")\n", + ")\n", + "source[\"Duration\"].head()" ] } ], From 51d917d19f76d00cca119ae455909debcb615bc8 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:12:39 +0000 Subject: [PATCH 05/27] feat(harvest): add billable static column --- notebooks/harvest-to-toggl.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 3419630..5060899 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -62,6 +62,8 @@ "source[\"Client\"] = source[\"Client\"].astype(\"category\")\n", "source[\"Project\"] = \"Xentrans\"\n", "source[\"Project\"] = source[\"Project\"].astype(\"category\")\n", + "source[\"Billable\"] = \"Yes\"\n", + "source[\"Billable\"] = source[\"Billable\"].astype(\"category\")\n", "source.dtypes" ] }, @@ -111,7 +113,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.6" } }, "nbformat": 4, From d8bb63c66ce9b08facac906e718c945d6fa6fb79 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:28:10 +0000 Subject: [PATCH 06/27] refactor(harvest): helper formats duration string --- notebooks/harvest-to-toggl.ipynb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 5060899..b696a19 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -6,12 +6,19 @@ "metadata": {}, "outputs": [], "source": [ - "from datetime import datetime\n", + "from datetime import datetime, timedelta\n", "import os\n", "from pathlib import Path\n", "import pandas as pd\n", "\n", "\n", + "def duration_str(duration: timedelta):\n", + " \"\"\"\n", + " Use total seconds to convert to a datetime and format as a string e.g. 01:30\n", + " \"\"\"\n", + " return datetime.fromtimestamp(duration.total_seconds()).strftime(\"%H:%M\")\n", + "\n", + "\n", "DATA_DIR = Path(\"./data\")\n", "DATA_SOURCE = Path(os.environ.get(\"HARVEST_DATA\", \"./data/harvest-sample.csv\"))" ] @@ -89,11 +96,8 @@ "# Convert numeric Hours to string Duration\n", "source[\"Duration\"] = source[\"Hours\"].apply(\n", " # first convert the numeric hours e.g. 1.5 to a timedelta\n", - " # then use the total seconds to convert to a datetime\n", - " # and format as a string e.g. 01:30\n", - " lambda x: datetime.fromtimestamp(pd.to_timedelta(x, unit=\"hours\").total_seconds()).strftime(\"%H:%M\")\n", - ")\n", - "source[\"Duration\"].head()" + " lambda x: duration_str(pd.to_timedelta(x, unit=\"hours\"))\n", + ")" ] } ], From 2e767028e8fd20449ce9a4632b0142a884b66b07 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:29:44 +0000 Subject: [PATCH 07/27] fix(harvest): sentence case field --- notebooks/harvest-to-toggl.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index b696a19..f9031cd 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -54,7 +54,7 @@ "outputs": [], "source": [ "# rename columns that can be imported as-is\n", - "source.rename(columns={\"Project\": \"Task\", \"Notes\": \"Description\", \"Date\": \"Start date\"}, inplace=True)\n", + "source.rename(columns={\"Project\": \"Task\", \"Notes\": \"Description\", \"Date\": \"Start Date\"}, inplace=True)\n", "source.dtypes" ] }, From 84858e656283676fd50de1cf89d2671ebbc4cca3 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:45:00 +0000 Subject: [PATCH 08/27] fix(harvest): keep duration as timedelta for offset calc --- notebooks/harvest-to-toggl.ipynb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index f9031cd..11ff0a2 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -93,11 +93,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Convert numeric Hours to string Duration\n", - "source[\"Duration\"] = source[\"Hours\"].apply(\n", - " # first convert the numeric hours e.g. 1.5 to a timedelta\n", - " lambda x: duration_str(pd.to_timedelta(x, unit=\"hours\"))\n", - ")" + "# Convert numeric Hours to timedelta Duration\n", + "source[\"Duration\"] = source[\"Hours\"].apply(pd.to_timedelta, unit=\"hours\")" ] } ], From 2abd102d4e9c7cf2cb93d92b98beb8d43a55890a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:33:19 +0000 Subject: [PATCH 09/27] feat(harvest): calculate the start time default to 09:00, then for each user day, offset the next start time by the previous duration --- notebooks/harvest-to-toggl.ipynb | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 11ff0a2..5157f42 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -96,6 +96,43 @@ "# Convert numeric Hours to timedelta Duration\n", "source[\"Duration\"] = source[\"Hours\"].apply(pd.to_timedelta, unit=\"hours\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Default start time to 09:00\n", + "source[\"Start Time\"] = pd.to_timedelta(\"09:00:00\")\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def calc_start_time(group):\n", + " \"\"\"\n", + " Start time is offset by the previous record's duration, with a default of 0 offset for the first record\n", + " \"\"\"\n", + " group[\"Start Time\"] = group[\"Start Time\"] + group[\"Duration\"].shift(fill_value=pd.to_timedelta(\"00:00:00\")).cumsum()\n", + " return group" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# sort and group users into their distinct days\n", + "source.sort_values([\"Email\", \"Start Date\"], inplace=True)\n", + "user_days = source.groupby([\"Email\", \"Start Date\"])\n", + "user_days = user_days.apply(calc_start_time)" + ] } ], "metadata": { From ff6cf7bbaac87f13f5caaf27339ad3cab8a47dfd Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:45:38 +0000 Subject: [PATCH 10/27] feat(harvest): convert back to strings, export to CSV --- notebooks/harvest-to-toggl.ipynb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 5157f42..7d8761f 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -133,6 +133,21 @@ "user_days = source.groupby([\"Email\", \"Start Date\"])\n", "user_days = user_days.apply(calc_start_time)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# convert timedeltas to duration strings\n", + "user_days[\"Duration\"] = user_days[\"Duration\"].apply(duration_str)\n", + "user_days[\"Start Time\"] = user_days[\"Start Time\"].apply(duration_str)\n", + "\n", + "# export to CSV, reordering columns\n", + "columns=[\"Email\", \"Start Date\", \"Start Time\", \"Duration\", \"Project\", \"Task\", \"Client\", \"Billable\", \"Description\"]\n", + "user_days.to_csv(DATA_DIR / \"toggl.csv\", index=False, columns=columns)" + ] } ], "metadata": { From ea6934aabcf2a6719b6a28cac138faf1af13c55b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 16:59:53 +0000 Subject: [PATCH 11/27] chore(harvest): remove debug prints --- notebooks/harvest-to-toggl.ipynb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/notebooks/harvest-to-toggl.ipynb b/notebooks/harvest-to-toggl.ipynb index 7d8761f..f74ca64 100644 --- a/notebooks/harvest-to-toggl.ipynb +++ b/notebooks/harvest-to-toggl.ipynb @@ -43,8 +43,7 @@ " \"Hours\",\n", "]\n", "# read CSV file, parsing dates\n", - "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Date\"], cache_dates=True)\n", - "source.dtypes" + "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Date\"], cache_dates=True)" ] }, { @@ -54,8 +53,7 @@ "outputs": [], "source": [ "# rename columns that can be imported as-is\n", - "source.rename(columns={\"Project\": \"Task\", \"Notes\": \"Description\", \"Date\": \"Start Date\"}, inplace=True)\n", - "source.dtypes" + "source.rename(columns={\"Project\": \"Task\", \"Notes\": \"Description\", \"Date\": \"Start Date\"}, inplace=True)" ] }, { @@ -70,8 +68,7 @@ "source[\"Project\"] = \"Xentrans\"\n", "source[\"Project\"] = source[\"Project\"].astype(\"category\")\n", "source[\"Billable\"] = \"Yes\"\n", - "source[\"Billable\"] = source[\"Billable\"].astype(\"category\")\n", - "source.dtypes" + "source[\"Billable\"] = source[\"Billable\"].astype(\"category\")" ] }, { @@ -83,8 +80,7 @@ "# add the Email column\n", "source[\"Email\"] = source[\"First Name\"].apply(lambda x: f\"{x.lower()}@compiler.la\").astype(\"category\")\n", "# drop individual name columns\n", - "source.drop(columns=[\"First Name\", \"Last Name\"], inplace=True)\n", - "source.dtypes" + "source.drop(columns=[\"First Name\", \"Last Name\"], inplace=True)" ] }, { @@ -104,8 +100,7 @@ "outputs": [], "source": [ "# Default start time to 09:00\n", - "source[\"Start Time\"] = pd.to_timedelta(\"09:00:00\")\n", - "source.dtypes" + "source[\"Start Time\"] = pd.to_timedelta(\"09:00:00\")" ] }, { From 5676a1fd36aa162c9dce028e19a250de1898dd5b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 17:06:40 +0000 Subject: [PATCH 12/27] chore(data): add sample toggl data --- .env.sample | 2 + .gitignore | 2 + notebooks/data/toggl-sample.csv | 251 +++++++++++++++++++++ notebooks/data/toggl-user-info-sample.json | 22 ++ 4 files changed, 277 insertions(+) create mode 100644 notebooks/data/toggl-sample.csv create mode 100644 notebooks/data/toggl-user-info-sample.json diff --git a/.env.sample b/.env.sample index 6c504b2..c2f5e8b 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,3 @@ HARVEST_DATA=data/harvest-sample.csv +TOGGL_DATA=data/toggl-sample.csv +TOGGL_USER_INFO=data/toggl-user-info-sample.json diff --git a/.gitignore b/.gitignore index 98cfdfb..f984950 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ __pycache__ *.egg-info notebooks/data/* !notebooks/data/harvest-sample.csv +!notebooks/data/toggl-sample.csv +!notebooks/data/toggl-user-info-sample.json diff --git a/notebooks/data/toggl-sample.csv b/notebooks/data/toggl-sample.csv new file mode 100644 index 0000000..95e014c --- /dev/null +++ b/notebooks/data/toggl-sample.csv @@ -0,0 +1,251 @@ +Email,Start date,Start time,Duration,Project,Task,Client,Billable,Description +aymer@compiler.la,2023-01-02,09:00:00,02:58:00,Prime1,Project3,Client1,Yes,nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus tortor duis mattis +aymer@compiler.la,2023-01-03,09:00:00,03:08:00,Prime1,Project3,Client1,Yes,elementum ligula vehicula consequat morbi a ipsum integer a nibh in quis justo +aymer@compiler.la,2023-01-03,12:08:00,00:49:00,Prime1,Project4,Client1,Yes,ut erat curabitur gravida nisi at nibh in hac habitasse platea dictumst aliquam +aymer@compiler.la,2023-01-04,09:00:00,02:57:00,Prime1,Project3,Client1,Yes,viverra eget congue eget semper rutrum nulla nunc purus phasellus in +aymer@compiler.la,2023-01-05,09:00:00,02:40:00,Prime1,Project1,Client1,Yes,justo sollicitudin ut suscipit a feugiat et eros +aymer@compiler.la,2023-01-05,11:40:00,01:39:00,Prime1,Project5,Client1,Yes,vestibulum ante ipsum primis in faucibus +aymer@compiler.la,2023-01-06,09:00:00,02:15:00,Prime1,Project1,Client1,Yes,vehicula consequat morbi a ipsum integer a nibh in quis justo maecenas rhoncus aliquam lacus morbi quis +aymer@compiler.la,2023-01-06,11:15:00,03:29:00,Prime1,Project4,Client1,Yes,leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu +aymer@compiler.la,2023-01-07,09:00:00,01:14:00,Prime1,Project5,Client1,Yes,nulla suspendisse potenti cras in purus eu +aymer@compiler.la,2023-01-07,10:14:00,00:45:00,Prime1,Project3,Client1,Yes,sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc +aymer@compiler.la,2023-01-08,09:00:00,02:00:00,Prime1,Project4,Client1,Yes,sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia eget tincidunt +aymer@compiler.la,2023-01-09,09:00:00,01:58:00,Prime1,Project7,Client1,Yes,nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum +aymer@compiler.la,2023-01-10,09:00:00,02:40:00,Prime1,Project6,Client1,Yes,curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non +aymer@compiler.la,2023-01-11,09:00:00,03:09:00,Prime1,Project3,Client1,Yes,varius nulla facilisi cras non velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit +aymer@compiler.la,2023-01-11,12:09:00,00:49:00,Prime1,Project1,Client1,Yes,maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur gravida nisi at nibh +aymer@compiler.la,2023-01-11,12:59:00,02:32:00,Prime1,Project7,Client1,Yes,blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in +aymer@compiler.la,2023-01-11,15:31:00,02:37:00,Prime1,Project2,Client1,Yes,curabitur convallis duis consequat dui nec +aymer@compiler.la,2023-01-12,09:00:00,01:24:00,Prime1,Project4,Client1,Yes,morbi a ipsum integer a nibh +aymer@compiler.la,2023-01-12,10:24:00,03:50:00,Prime1,Project3,Client1,Yes,pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula +aymer@compiler.la,2023-01-12,14:14:00,01:15:00,Prime1,Project7,Client1,Yes,luctus et ultrices posuere cubilia curae nulla dapibus +aymer@compiler.la,2023-01-13,09:00:00,03:49:00,Prime1,Project4,Client1,Yes,et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non +aymer@compiler.la,2023-01-13,12:49:00,03:19:00,Prime1,Project4,Client1,Yes,lectus aliquam sit amet diam in +aymer@compiler.la,2023-01-14,09:00:00,00:54:00,Prime1,Project3,Client1,Yes,in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis +aymer@compiler.la,2023-01-16,09:00:00,00:06:00,Prime1,Project6,Client1,Yes,amet turpis elementum ligula vehicula consequat morbi a +aymer@compiler.la,2023-01-18,09:00:00,03:48:00,Prime1,Project7,Client1,Yes,ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc +aymer@compiler.la,2023-01-19,09:00:00,03:54:00,Prime1,Project1,Client1,Yes,faucibus orci luctus et ultrices posuere cubilia curae donec pharetra +aymer@compiler.la,2023-01-19,12:54:00,02:09:00,Prime1,Project2,Client1,Yes,neque aenean auctor gravida sem praesent id massa id +aymer@compiler.la,2023-01-20,09:00:00,02:43:00,Prime1,Project1,Client1,Yes,tincidunt eu felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui +aymer@compiler.la,2023-01-20,11:43:00,03:45:00,Prime1,Project7,Client1,Yes,quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis sed ante vivamus +aymer@compiler.la,2023-01-21,09:00:00,03:45:00,Prime1,Project3,Client1,Yes,nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam +aymer@compiler.la,2023-01-21,12:45:00,00:15:00,Prime1,Project1,Client1,Yes,cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue +aymer@compiler.la,2023-01-22,09:00:00,00:05:00,Prime1,Project3,Client1,Yes,habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel +aymer@compiler.la,2023-01-23,09:00:00,01:31:00,Prime1,Project7,Client1,Yes,ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget +aymer@compiler.la,2023-01-24,09:00:00,02:13:00,Prime1,Project7,Client1,Yes,aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis elementum +aymer@compiler.la,2023-01-25,09:00:00,02:49:00,Prime1,Project6,Client1,Yes,consequat lectus in est risus auctor sed tristique in tempus sit amet +aymer@compiler.la,2023-01-25,11:49:00,02:34:00,Prime1,Project5,Client1,Yes,sed nisl nunc rhoncus dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede +aymer@compiler.la,2023-01-25,14:24:00,02:12:00,Prime1,Project2,Client1,Yes,mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus +aymer@compiler.la,2023-01-26,09:00:00,03:34:00,Prime1,Project2,Client1,Yes,volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis donec semper sapien +aymer@compiler.la,2023-01-26,12:34:00,02:39:00,Prime1,Project5,Client1,Yes,sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse +aymer@compiler.la,2023-01-28,09:00:00,02:07:00,Prime1,Project6,Client1,Yes,nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec +aymer@compiler.la,2023-01-30,09:00:00,01:48:00,Prime1,Project4,Client1,Yes,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus +aymer@compiler.la,2023-01-30,10:48:00,01:48:00,Prime1,Project3,Client1,Yes,nulla mollis molestie lorem quisque ut erat curabitur gravida nisi at +aymer@compiler.la,2023-01-30,12:36:00,00:28:00,Prime1,Project5,Client1,Yes,a feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien +gusella@compiler.la,2023-01-02,09:00:00,00:43:00,Prime1,Project1,Client1,Yes,odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla +gusella@compiler.la,2023-01-03,09:00:00,01:36:00,Prime1,Project2,Client1,Yes,ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam +gusella@compiler.la,2023-01-04,09:00:00,02:34:00,Prime1,Project3,Client1,Yes,mauris vulputate elementum nullam varius nulla facilisi cras +gusella@compiler.la,2023-01-05,09:00:00,03:47:00,Prime1,Project1,Client1,Yes,cursus vestibulum proin eu mi nulla ac enim in tempor +gusella@compiler.la,2023-01-05,12:47:00,00:50:00,Prime1,Project7,Client1,Yes,malesuada in imperdiet et commodo vulputate justo in blandit +gusella@compiler.la,2023-01-05,13:37:00,03:28:00,Prime1,Project3,Client1,Yes,dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia +gusella@compiler.la,2023-01-05,17:06:00,02:25:00,Prime1,Project7,Client1,Yes,ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo +gusella@compiler.la,2023-01-07,09:00:00,00:41:00,Prime1,Project4,Client1,Yes,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque +gusella@compiler.la,2023-01-07,09:41:00,03:48:00,Prime1,Project4,Client1,Yes,mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer +gusella@compiler.la,2023-01-08,09:00:00,04:00:00,Prime1,Project5,Client1,Yes,id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices +gusella@compiler.la,2023-01-09,09:00:00,00:28:00,Prime1,Project5,Client1,Yes,at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis +gusella@compiler.la,2023-01-09,09:28:00,02:37:00,Prime1,Project1,Client1,Yes,et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a +gusella@compiler.la,2023-01-10,09:00:00,00:58:00,Prime1,Project1,Client1,Yes,nulla quisque arcu libero rutrum ac lobortis vel dapibus at diam nam tristique +gusella@compiler.la,2023-01-10,09:58:00,03:15:00,Prime1,Project1,Client1,Yes,duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue +gusella@compiler.la,2023-01-11,09:00:00,02:12:00,Prime1,Project6,Client1,Yes,gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate +gusella@compiler.la,2023-01-11,11:12:00,03:31:00,Prime1,Project1,Client1,Yes,nulla elit ac nulla sed vel enim sit amet nunc viverra +gusella@compiler.la,2023-01-12,09:00:00,03:19:00,Prime1,Project2,Client1,Yes,magna bibendum imperdiet nullam orci +gusella@compiler.la,2023-01-13,09:00:00,01:52:00,Prime1,Project6,Client1,Yes,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat +gusella@compiler.la,2023-01-13,10:52:00,00:28:00,Prime1,Project7,Client1,Yes,eget orci vehicula condimentum curabitur in +gusella@compiler.la,2023-01-14,09:00:00,03:04:00,Prime1,Project6,Client1,Yes,viverra diam vitae quam suspendisse potenti nullam porttitor lacus at +gusella@compiler.la,2023-01-14,12:04:00,00:49:00,Prime1,Project7,Client1,Yes,non interdum in ante vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur +gusella@compiler.la,2023-01-15,09:00:00,03:36:00,Prime1,Project7,Client1,Yes,vulputate justo in blandit ultrices +gusella@compiler.la,2023-01-15,12:36:00,02:08:00,Prime1,Project7,Client1,Yes,eros vestibulum ac est lacinia +gusella@compiler.la,2023-01-15,14:45:00,01:34:00,Prime1,Project2,Client1,Yes,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur gravida nisi +gusella@compiler.la,2023-01-16,09:00:00,00:43:00,Prime1,Project7,Client1,Yes,elit ac nulla sed vel enim sit amet +gusella@compiler.la,2023-01-17,09:00:00,01:35:00,Prime1,Project4,Client1,Yes,luctus rutrum nulla tellus in sagittis dui vel nisl duis +gusella@compiler.la,2023-01-17,10:35:00,02:11:00,Prime1,Project3,Client1,Yes,ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis +gusella@compiler.la,2023-01-18,09:00:00,00:31:00,Prime1,Project1,Client1,Yes,ullamcorper augue a suscipit nulla elit ac nulla sed +gusella@compiler.la,2023-01-20,09:00:00,02:57:00,Prime1,Project4,Client1,Yes,varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus orci +gusella@compiler.la,2023-01-21,09:00:00,02:13:00,Prime1,Project6,Client1,Yes,convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien +gusella@compiler.la,2023-01-21,11:13:00,00:12:00,Prime1,Project4,Client1,Yes,ac lobortis vel dapibus at diam nam tristique tortor eu pede +gusella@compiler.la,2023-01-21,11:25:00,00:33:00,Prime1,Project5,Client1,Yes,in tempor turpis nec euismod scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem +gusella@compiler.la,2023-01-22,09:00:00,00:46:00,Prime1,Project7,Client1,Yes,orci eget orci vehicula condimentum curabitur in libero ut massa +gusella@compiler.la,2023-01-23,09:00:00,03:17:00,Prime1,Project7,Client1,Yes,tortor sollicitudin mi sit amet lobortis sapien sapien non +gusella@compiler.la,2023-01-25,09:00:00,03:51:00,Prime1,Project6,Client1,Yes,neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut +gusella@compiler.la,2023-01-25,12:51:00,02:54:00,Prime1,Project1,Client1,Yes,odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel +gusella@compiler.la,2023-01-26,09:00:00,02:28:00,Prime1,Project4,Client1,Yes,nam congue risus semper porta volutpat quam pede lobortis ligula sit +gusella@compiler.la,2023-01-26,11:28:00,03:02:00,Prime1,Project2,Client1,Yes,et ultrices posuere cubilia curae donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien +gusella@compiler.la,2023-01-26,14:31:00,00:52:00,Prime1,Project3,Client1,Yes,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis donec +gusella@compiler.la,2023-01-26,15:24:00,02:25:00,Prime1,Project5,Client1,Yes,in felis eu sapien cursus vestibulum proin eu mi nulla ac enim +gusella@compiler.la,2023-01-26,17:49:00,01:18:00,Prime1,Project2,Client1,Yes,convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh +gusella@compiler.la,2023-01-26,19:07:00,01:37:00,Prime1,Project4,Client1,Yes,condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra +gusella@compiler.la,2023-01-27,09:00:00,02:32:00,Prime1,Project5,Client1,Yes,egestas metus aenean fermentum donec ut mauris eget massa tempor +gusella@compiler.la,2023-01-27,11:32:00,03:12:00,Prime1,Project1,Client1,Yes,consequat nulla nisl nunc nisl duis bibendum felis sed interdum venenatis turpis enim blandit mi in porttitor pede +gusella@compiler.la,2023-01-28,09:00:00,03:43:00,Prime1,Project4,Client1,Yes,amet lobortis sapien sapien non mi +gusella@compiler.la,2023-01-29,09:00:00,03:30:00,Prime1,Project3,Client1,Yes,vestibulum sagittis sapien cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum +gusella@compiler.la,2023-01-29,12:30:00,01:37:00,Prime1,Project3,Client1,Yes,mi pede malesuada in imperdiet et commodo vulputate justo in blandit ultrices enim +gusella@compiler.la,2023-01-30,09:00:00,02:43:00,Prime1,Project4,Client1,Yes,volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla +gusella@compiler.la,2023-01-30,11:43:00,03:00:00,Prime1,Project2,Client1,Yes,dui vel sem sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend +gusella@compiler.la,2023-01-30,14:44:00,01:06:00,Prime1,Project3,Client1,Yes,sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis orci nullam +gusella@compiler.la,2023-01-30,15:51:00,02:20:00,Prime1,Project4,Client1,Yes,augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae +gusella@compiler.la,2023-01-30,18:11:00,00:16:00,Prime1,Project3,Client1,Yes,platea dictumst maecenas ut massa quis augue luctus tincidunt nulla +gusella@compiler.la,2023-01-30,18:27:00,00:45:00,Prime1,Project7,Client1,Yes,ligula suspendisse ornare consequat lectus in est risus +hetti@compiler.la,2023-01-02,09:00:00,00:34:00,Prime1,Project7,Client1,Yes,in faucibus orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum +hetti@compiler.la,2023-01-02,09:34:00,00:42:00,Prime1,Project3,Client1,Yes,elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at +hetti@compiler.la,2023-01-03,09:00:00,01:30:00,Prime1,Project1,Client1,Yes,praesent blandit lacinia erat vestibulum +hetti@compiler.la,2023-01-03,10:30:00,01:30:00,Prime1,Project6,Client1,Yes,vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis +hetti@compiler.la,2023-01-04,09:00:00,02:50:00,Prime1,Project1,Client1,Yes,eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan tortor quis turpis +hetti@compiler.la,2023-01-05,09:00:00,01:26:00,Prime1,Project3,Client1,Yes,condimentum neque sapien placerat ante nulla justo aliquam quis +hetti@compiler.la,2023-01-06,09:00:00,02:12:00,Prime1,Project7,Client1,Yes,nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat +hetti@compiler.la,2023-01-06,11:12:00,03:41:00,Prime1,Project2,Client1,Yes,sit amet eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse potenti cras in purus eu magna +hetti@compiler.la,2023-01-06,14:53:00,02:33:00,Prime1,Project4,Client1,Yes,consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel +hetti@compiler.la,2023-01-07,09:00:00,01:14:00,Prime1,Project5,Client1,Yes,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas +hetti@compiler.la,2023-01-07,10:14:00,02:45:00,Prime1,Project6,Client1,Yes,nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla +hetti@compiler.la,2023-01-07,13:00:00,03:09:00,Prime1,Project1,Client1,Yes,in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum sociis natoque +hetti@compiler.la,2023-01-09,09:00:00,03:49:00,Prime1,Project1,Client1,Yes,quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse +hetti@compiler.la,2023-01-09,12:49:00,01:31:00,Prime1,Project1,Client1,Yes,enim leo rhoncus sed vestibulum sit amet cursus id turpis integer +hetti@compiler.la,2023-01-10,09:00:00,00:30:00,Prime1,Project2,Client1,Yes,blandit nam nulla integer pede justo +hetti@compiler.la,2023-01-10,09:30:00,00:46:00,Prime1,Project6,Client1,Yes,eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque +hetti@compiler.la,2023-01-12,09:00:00,01:33:00,Prime1,Project5,Client1,Yes,massa quis augue luctus tincidunt nulla mollis molestie lorem +hetti@compiler.la,2023-01-12,10:33:00,02:11:00,Prime1,Project7,Client1,Yes,eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros +hetti@compiler.la,2023-01-12,12:44:00,02:25:00,Prime1,Project3,Client1,Yes,vel augue vestibulum rutrum rutrum neque aenean auctor gravida +hetti@compiler.la,2023-01-13,09:00:00,01:06:00,Prime1,Project7,Client1,Yes,varius ut blandit non interdum in ante vestibulum ante ipsum +hetti@compiler.la,2023-01-13,10:06:00,02:02:00,Prime1,Project6,Client1,Yes,aliquam augue quam sollicitudin vitae consectetuer eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at +hetti@compiler.la,2023-01-13,12:08:00,00:13:00,Prime1,Project3,Client1,Yes,sapien varius ut blandit non interdum in ante vestibulum ante ipsum primis in faucibus +hetti@compiler.la,2023-01-13,12:21:00,00:29:00,Prime1,Project3,Client1,Yes,rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet at feugiat non +hetti@compiler.la,2023-01-14,09:00:00,04:00:00,Prime1,Project6,Client1,Yes,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at +hetti@compiler.la,2023-01-14,13:00:00,00:42:00,Prime1,Project2,Client1,Yes,elementum ligula vehicula consequat morbi a +hetti@compiler.la,2023-01-15,09:00:00,02:36:00,Prime1,Project7,Client1,Yes,vel est donec odio justo sollicitudin ut suscipit a feugiat et eros vestibulum ac est lacinia nisi venenatis +hetti@compiler.la,2023-01-15,11:36:00,02:19:00,Prime1,Project1,Client1,Yes,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna +hetti@compiler.la,2023-01-16,09:00:00,01:12:00,Prime1,Project1,Client1,Yes,at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus +hetti@compiler.la,2023-01-17,09:00:00,03:52:00,Prime1,Project1,Client1,Yes,vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit proin +hetti@compiler.la,2023-01-17,12:52:00,02:11:00,Prime1,Project2,Client1,Yes,sit amet justo morbi ut odio +hetti@compiler.la,2023-01-17,15:04:00,03:37:00,Prime1,Project5,Client1,Yes,porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis +hetti@compiler.la,2023-01-18,09:00:00,01:43:00,Prime1,Project1,Client1,Yes,nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam neque +hetti@compiler.la,2023-01-18,10:43:00,01:54:00,Prime1,Project3,Client1,Yes,auctor sed tristique in tempus sit +hetti@compiler.la,2023-01-18,12:37:00,00:43:00,Prime1,Project2,Client1,Yes,volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam +hetti@compiler.la,2023-01-19,09:00:00,02:27:00,Prime1,Project6,Client1,Yes,at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam +hetti@compiler.la,2023-01-20,09:00:00,02:25:00,Prime1,Project2,Client1,Yes,diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra +hetti@compiler.la,2023-01-21,09:00:00,02:30:00,Prime1,Project5,Client1,Yes,nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit +hetti@compiler.la,2023-01-21,11:30:00,01:19:00,Prime1,Project1,Client1,Yes,placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque mauris sit amet eros suspendisse accumsan +hetti@compiler.la,2023-01-21,12:50:00,01:38:00,Prime1,Project6,Client1,Yes,mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla +hetti@compiler.la,2023-01-21,14:28:00,01:04:00,Prime1,Project4,Client1,Yes,justo morbi ut odio cras mi pede malesuada in +hetti@compiler.la,2023-01-21,15:33:00,00:43:00,Prime1,Project3,Client1,Yes,quam sapien varius ut blandit non interdum in ante +hetti@compiler.la,2023-01-22,09:00:00,03:25:00,Prime1,Project4,Client1,Yes,quis odio consequat varius integer ac leo pellentesque ultrices +hetti@compiler.la,2023-01-22,12:25:00,03:24:00,Prime1,Project5,Client1,Yes,nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere +hetti@compiler.la,2023-01-22,15:50:00,00:04:00,Prime1,Project1,Client1,Yes,sollicitudin mi sit amet lobortis sapien sapien non mi integer +hetti@compiler.la,2023-01-23,09:00:00,03:34:00,Prime1,Project7,Client1,Yes,posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis +hetti@compiler.la,2023-01-24,09:00:00,00:55:00,Prime1,Project3,Client1,Yes,odio consequat varius integer ac leo pellentesque ultrices mattis odio +hetti@compiler.la,2023-01-25,09:00:00,03:38:00,Prime1,Project6,Client1,Yes,id nulla ultrices aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque +hetti@compiler.la,2023-01-25,12:38:00,02:01:00,Prime1,Project6,Client1,Yes,vestibulum eget vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra +hetti@compiler.la,2023-01-25,14:40:00,03:46:00,Prime1,Project7,Client1,Yes,adipiscing lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit +hetti@compiler.la,2023-01-26,09:00:00,03:48:00,Prime1,Project3,Client1,Yes,ligula vehicula consequat morbi a ipsum +hetti@compiler.la,2023-01-26,12:48:00,02:11:00,Prime1,Project1,Client1,Yes,est quam pharetra magna ac consequat metus sapien ut +hetti@compiler.la,2023-01-26,15:00:00,02:47:00,Prime1,Project1,Client1,Yes,magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec dui +hetti@compiler.la,2023-01-27,09:00:00,03:12:00,Prime1,Project7,Client1,Yes,risus semper porta volutpat quam pede lobortis ligula +hetti@compiler.la,2023-01-28,09:00:00,03:55:00,Prime1,Project3,Client1,Yes,felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed +hetti@compiler.la,2023-01-28,12:55:00,03:42:00,Prime1,Project5,Client1,Yes,bibendum morbi non quam nec dui luctus rutrum nulla tellus in sagittis dui vel nisl +hetti@compiler.la,2023-01-29,09:00:00,00:19:00,Prime1,Project5,Client1,Yes,aliquam convallis nunc proin at turpis a pede posuere nonummy +hetti@compiler.la,2023-01-29,09:19:00,00:12:00,Prime1,Project1,Client1,Yes,quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec +hetti@compiler.la,2023-01-30,09:00:00,01:04:00,Prime1,Project3,Client1,Yes,vestibulum sit amet cursus id turpis integer aliquet massa id lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci +hetti@compiler.la,2023-01-30,10:04:00,01:36:00,Prime1,Project2,Client1,Yes,magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum sagittis sapien cum sociis natoque penatibus et magnis +hetti@compiler.la,2023-01-30,11:40:00,01:43:00,Prime1,Project4,Client1,Yes,phasellus in felis donec semper sapien a libero nam dui proin leo odio porttitor id consequat in consequat ut nulla sed accumsan +sawyer@compiler.la,2023-01-03,09:00:00,02:03:00,Prime1,Project4,Client1,Yes,aliquet maecenas leo odio condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est +sawyer@compiler.la,2023-01-03,11:03:00,00:24:00,Prime1,Project1,Client1,Yes,eget rutrum at lorem integer tincidunt ante vel ipsum praesent blandit lacinia erat vestibulum +sawyer@compiler.la,2023-01-04,09:00:00,02:21:00,Prime1,Project3,Client1,Yes,aliquet at feugiat non pretium quis +sawyer@compiler.la,2023-01-04,11:21:00,00:48:00,Prime1,Project7,Client1,Yes,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in leo maecenas +sawyer@compiler.la,2023-01-04,12:09:00,03:55:00,Prime1,Project7,Client1,Yes,convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum +sawyer@compiler.la,2023-01-06,09:00:00,00:35:00,Prime1,Project7,Client1,Yes,feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien +sawyer@compiler.la,2023-01-06,09:35:00,00:26:00,Prime1,Project3,Client1,Yes,vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit +sawyer@compiler.la,2023-01-06,10:01:00,02:06:00,Prime1,Project6,Client1,Yes,accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor morbi vel lectus in quam fringilla rhoncus +sawyer@compiler.la,2023-01-07,09:00:00,01:50:00,Prime1,Project4,Client1,Yes,quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur +sawyer@compiler.la,2023-01-07,10:50:00,01:01:00,Prime1,Project1,Client1,Yes,proin leo odio porttitor id consequat in consequat +sawyer@compiler.la,2023-01-08,09:00:00,01:33:00,Prime1,Project5,Client1,Yes,porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non lectus aliquam +sawyer@compiler.la,2023-01-08,10:33:00,02:35:00,Prime1,Project3,Client1,Yes,curae mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus vitae ipsum aliquam non mauris morbi non +sawyer@compiler.la,2023-01-09,09:00:00,01:01:00,Prime1,Project1,Client1,Yes,donec pharetra magna vestibulum aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien +sawyer@compiler.la,2023-01-09,10:01:00,01:58:00,Prime1,Project5,Client1,Yes,nulla integer pede justo lacinia eget tincidunt eget +sawyer@compiler.la,2023-01-11,09:00:00,01:37:00,Prime1,Project3,Client1,Yes,maecenas tristique est et tempus semper est +sawyer@compiler.la,2023-01-11,10:37:00,01:55:00,Prime1,Project7,Client1,Yes,elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in +sawyer@compiler.la,2023-01-11,12:32:00,02:57:00,Prime1,Project1,Client1,Yes,eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla +sawyer@compiler.la,2023-01-12,09:00:00,02:03:00,Prime1,Project3,Client1,Yes,curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec +sawyer@compiler.la,2023-01-12,11:03:00,01:00:00,Prime1,Project1,Client1,Yes,eu interdum eu tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus +sawyer@compiler.la,2023-01-12,12:04:00,01:22:00,Prime1,Project6,Client1,Yes,etiam faucibus cursus urna ut +sawyer@compiler.la,2023-01-13,09:00:00,01:07:00,Prime1,Project6,Client1,Yes,platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat ante nulla justo aliquam quis turpis eget +sawyer@compiler.la,2023-01-14,09:00:00,02:06:00,Prime1,Project4,Client1,Yes,feugiat et eros vestibulum ac est lacinia nisi venenatis tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu +sawyer@compiler.la,2023-01-14,11:06:00,01:16:00,Prime1,Project6,Client1,Yes,bibendum felis sed interdum venenatis turpis enim blandit mi +sawyer@compiler.la,2023-01-14,12:22:00,01:44:00,Prime1,Project5,Client1,Yes,pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium +sawyer@compiler.la,2023-01-15,09:00:00,01:39:00,Prime1,Project3,Client1,Yes,luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus +sawyer@compiler.la,2023-01-15,10:39:00,00:59:00,Prime1,Project7,Client1,Yes,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam varius nulla +sawyer@compiler.la,2023-01-16,09:00:00,03:10:00,Prime1,Project2,Client1,Yes,erat id mauris vulputate elementum nullam varius nulla facilisi +sawyer@compiler.la,2023-01-16,12:10:00,00:44:00,Prime1,Project3,Client1,Yes,nonummy integer non velit donec diam neque vestibulum eget vulputate ut ultrices vel augue vestibulum ante +sawyer@compiler.la,2023-01-16,12:55:00,01:15:00,Prime1,Project4,Client1,Yes,eu massa donec dapibus duis at velit eu +sawyer@compiler.la,2023-01-18,09:00:00,01:40:00,Prime1,Project4,Client1,Yes,imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis fusce +sawyer@compiler.la,2023-01-18,10:40:00,01:37:00,Prime1,Project3,Client1,Yes,aliquam sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu +sawyer@compiler.la,2023-01-18,12:18:00,02:52:00,Prime1,Project7,Client1,Yes,ligula sit amet eleifend pede libero quis orci nullam molestie +sawyer@compiler.la,2023-01-20,09:00:00,02:31:00,Prime1,Project6,Client1,Yes,nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem +sawyer@compiler.la,2023-01-21,09:00:00,02:25:00,Prime1,Project5,Client1,Yes,vivamus in felis eu sapien cursus vestibulum +sawyer@compiler.la,2023-01-22,09:00:00,01:28:00,Prime1,Project7,Client1,Yes,interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis +sawyer@compiler.la,2023-01-23,09:00:00,01:04:00,Prime1,Project6,Client1,Yes,elementum in hac habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque +sawyer@compiler.la,2023-01-23,10:04:00,00:13:00,Prime1,Project7,Client1,Yes,congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc donec quis orci eget orci +sawyer@compiler.la,2023-01-23,10:18:00,01:34:00,Prime1,Project7,Client1,Yes,id luctus nec molestie sed justo pellentesque viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam +sawyer@compiler.la,2023-01-24,09:00:00,02:16:00,Prime1,Project5,Client1,Yes,vulputate elementum nullam varius nulla facilisi cras non velit nec nisi vulputate nonummy +sawyer@compiler.la,2023-01-25,09:00:00,02:08:00,Prime1,Project2,Client1,Yes,nibh in lectus pellentesque at +sawyer@compiler.la,2023-01-26,09:00:00,01:15:00,Prime1,Project4,Client1,Yes,id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in +sawyer@compiler.la,2023-01-27,09:00:00,04:00:00,Prime1,Project1,Client1,Yes,quis justo maecenas rhoncus aliquam lacus morbi quis tortor id nulla ultrices aliquet maecenas leo odio +sawyer@compiler.la,2023-01-29,09:00:00,03:23:00,Prime1,Project7,Client1,Yes,ut at dolor quis odio consequat varius integer +sawyer@compiler.la,2023-01-30,09:00:00,00:19:00,Prime1,Project1,Client1,Yes,pellentesque ultrices phasellus id sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget nunc +sawyer@compiler.la,2023-01-30,09:19:00,03:24:00,Prime1,Project1,Client1,Yes,metus arcu adipiscing molestie hendrerit at vulputate vitae nisl aenean lectus pellentesque eget +silas@compiler.la,2023-01-03,09:00:00,00:42:00,Prime1,Project2,Client1,Yes,praesent blandit nam nulla integer pede +silas@compiler.la,2023-01-04,09:00:00,02:51:00,Prime1,Project2,Client1,Yes,elit sodales scelerisque mauris sit amet eros suspendisse +silas@compiler.la,2023-01-05,09:00:00,02:40:00,Prime1,Project6,Client1,Yes,aenean auctor gravida sem praesent id massa id nisl venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet +silas@compiler.la,2023-01-05,11:40:00,01:15:00,Prime1,Project2,Client1,Yes,sapien placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque +silas@compiler.la,2023-01-05,12:56:00,01:21:00,Prime1,Project4,Client1,Yes,primis in faucibus orci luctus et ultrices posuere cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat +silas@compiler.la,2023-01-06,09:00:00,00:54:00,Prime1,Project5,Client1,Yes,nibh ligula nec sem duis aliquam convallis +silas@compiler.la,2023-01-06,09:54:00,03:25:00,Prime1,Project2,Client1,Yes,at nunc commodo placerat praesent blandit +silas@compiler.la,2023-01-06,13:19:00,02:27:00,Prime1,Project1,Client1,Yes,primis in faucibus orci luctus et ultrices posuere cubilia curae donec pharetra magna +silas@compiler.la,2023-01-06,15:47:00,03:37:00,Prime1,Project7,Client1,Yes,leo odio porttitor id consequat in consequat ut nulla sed accumsan felis ut at dolor +silas@compiler.la,2023-01-06,19:24:00,01:51:00,Prime1,Project4,Client1,Yes,pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum +silas@compiler.la,2023-01-07,09:00:00,02:25:00,Prime1,Project5,Client1,Yes,praesent blandit nam nulla integer pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula suspendisse ornare consequat lectus in +silas@compiler.la,2023-01-08,09:00:00,01:36:00,Prime1,Project3,Client1,Yes,in purus eu magna vulputate luctus +silas@compiler.la,2023-01-08,10:36:00,01:19:00,Prime1,Project2,Client1,Yes,lobortis convallis tortor risus dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet +silas@compiler.la,2023-01-11,09:00:00,00:41:00,Prime1,Project7,Client1,Yes,ac consequat metus sapien ut nunc vestibulum ante ipsum primis in faucibus +silas@compiler.la,2023-01-11,09:41:00,01:07:00,Prime1,Project2,Client1,Yes,suspendisse ornare consequat lectus in est risus auctor sed tristique in tempus sit amet sem fusce +silas@compiler.la,2023-01-11,10:49:00,03:09:00,Prime1,Project7,Client1,Yes,in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing +silas@compiler.la,2023-01-12,09:00:00,02:38:00,Prime1,Project7,Client1,Yes,nibh ligula nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam +silas@compiler.la,2023-01-13,09:00:00,00:40:00,Prime1,Project6,Client1,Yes,blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit +silas@compiler.la,2023-01-14,09:00:00,03:00:00,Prime1,Project7,Client1,Yes,morbi vestibulum velit id pretium iaculis diam +silas@compiler.la,2023-01-14,12:00:00,02:19:00,Prime1,Project5,Client1,Yes,pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id ligula +silas@compiler.la,2023-01-15,09:00:00,03:08:00,Prime1,Project6,Client1,Yes,dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum +silas@compiler.la,2023-01-15,12:08:00,00:03:00,Prime1,Project4,Client1,Yes,viverra pede ac diam cras pellentesque volutpat dui maecenas tristique est et +silas@compiler.la,2023-01-15,12:11:00,03:01:00,Prime1,Project1,Client1,Yes,eu massa donec dapibus duis at velit eu est congue elementum in hac habitasse platea dictumst morbi vestibulum +silas@compiler.la,2023-01-16,09:00:00,02:59:00,Prime1,Project4,Client1,Yes,vestibulum proin eu mi nulla ac enim in tempor +silas@compiler.la,2023-01-17,09:00:00,02:25:00,Prime1,Project2,Client1,Yes,cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac consequat metus sapien +silas@compiler.la,2023-01-17,11:25:00,03:51:00,Prime1,Project5,Client1,Yes,vestibulum sagittis sapien cum sociis natoque +silas@compiler.la,2023-01-18,09:00:00,02:44:00,Prime1,Project4,Client1,Yes,tortor duis mattis egestas metus aenean fermentum donec ut mauris eget +silas@compiler.la,2023-01-18,11:44:00,01:08:00,Prime1,Project4,Client1,Yes,faucibus orci luctus et ultrices posuere +silas@compiler.la,2023-01-20,09:00:00,01:18:00,Prime1,Project6,Client1,Yes,metus sapien ut nunc vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae mauris viverra diam vitae quam suspendisse potenti nullam +silas@compiler.la,2023-01-20,10:18:00,03:15:00,Prime1,Project2,Client1,Yes,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus nulla ut erat id mauris vulputate elementum nullam +silas@compiler.la,2023-01-20,13:33:00,03:57:00,Prime1,Project6,Client1,Yes,elit proin risus praesent lectus vestibulum quam sapien varius ut blandit non interdum in ante vestibulum ante ipsum +silas@compiler.la,2023-01-20,17:31:00,02:06:00,Prime1,Project2,Client1,Yes,tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat +silas@compiler.la,2023-01-22,09:00:00,03:10:00,Prime1,Project1,Client1,Yes,pulvinar lobortis est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla +silas@compiler.la,2023-01-22,12:10:00,01:24:00,Prime1,Project1,Client1,Yes,mollis molestie lorem quisque ut erat curabitur gravida nisi at nibh in hac habitasse platea dictumst aliquam augue quam sollicitudin vitae consectetuer eget +silas@compiler.la,2023-01-22,13:34:00,02:22:00,Prime1,Project6,Client1,Yes,arcu libero rutrum ac lobortis vel dapibus at diam nam tristique +silas@compiler.la,2023-01-23,09:00:00,00:20:00,Prime1,Project5,Client1,Yes,nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat quisque erat eros +silas@compiler.la,2023-01-24,09:00:00,02:45:00,Prime1,Project6,Client1,Yes,duis aliquam convallis nunc proin at turpis +silas@compiler.la,2023-01-24,11:45:00,03:20:00,Prime1,Project6,Client1,Yes,porttitor lorem id ligula suspendisse ornare consequat lectus +silas@compiler.la,2023-01-24,15:06:00,00:26:00,Prime1,Project5,Client1,Yes,ut blandit non interdum in +silas@compiler.la,2023-01-25,09:00:00,02:06:00,Prime1,Project5,Client1,Yes,odio odio elementum eu interdum eu tincidunt in leo maecenas pulvinar lobortis est +silas@compiler.la,2023-01-25,11:06:00,02:28:00,Prime1,Project4,Client1,Yes,elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget semper rutrum nulla nunc purus phasellus in felis +silas@compiler.la,2023-01-25,13:35:00,01:10:00,Prime1,Project7,Client1,Yes,leo pellentesque ultrices mattis odio donec vitae +silas@compiler.la,2023-01-26,09:00:00,01:30:00,Prime1,Project5,Client1,Yes,ultrices posuere cubilia curae mauris viverra diam +silas@compiler.la,2023-01-27,09:00:00,01:49:00,Prime1,Project6,Client1,Yes,blandit non interdum in ante vestibulum ante +silas@compiler.la,2023-01-27,10:49:00,02:55:00,Prime1,Project7,Client1,Yes,hac habitasse platea dictumst aliquam augue quam +silas@compiler.la,2023-01-27,13:45:00,03:40:00,Prime1,Project4,Client1,Yes,nullam orci pede venenatis non sodales sed tincidunt eu +silas@compiler.la,2023-01-28,09:00:00,02:37:00,Prime1,Project4,Client1,Yes,volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna +silas@compiler.la,2023-01-30,09:00:00,03:31:00,Prime1,Project1,Client1,Yes,nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis in faucibus orci luctus et +silas@compiler.la,2023-01-30,12:31:00,01:56:00,Prime1,Project6,Client1,Yes,ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor convallis diff --git a/notebooks/data/toggl-user-info-sample.json b/notebooks/data/toggl-user-info-sample.json new file mode 100644 index 0000000..3034b97 --- /dev/null +++ b/notebooks/data/toggl-user-info-sample.json @@ -0,0 +1,22 @@ +{ + "aymer@compiler.la": { + "Last Name": "Hauck", + "First Name": "Aymer" + }, + "gusella@compiler.la": { + "Last Name": "Swaile", + "First Name": "Gusella" + }, + "hetti@compiler.la": { + "Last Name": "Becken", + "First Name": "Hetti" + }, + "sawyer@compiler.la": { + "Last Name": "Berrey", + "First Name": "Sawyer" + }, + "silas@compiler.la": { + "Last Name": "Idenden", + "First Name": "Silas" + } +} From 1f2a722803395a6a3b47fdae7b59ff1a719ca90c Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 17:54:54 +0000 Subject: [PATCH 13/27] feat(toggl): setup notebook for harvest conversion --- notebooks/toggl-to-harvest.ipynb | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 notebooks/toggl-to-harvest.ipynb diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb new file mode 100644 index 0000000..d5830a4 --- /dev/null +++ b/notebooks/toggl-to-harvest.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "import pandas as pd\n", + "\n", + "\n", + "def str_timedelta(td):\n", + " \"\"\"\n", + " Convert a string formatted duration (e.g. 01:30) to a timedelta.\n", + " \"\"\"\n", + " return pd.to_timedelta(pd.to_datetime(td, format=\"%H:%M:%S\").strftime(\"%H:%M:%S\"))\n", + "\n", + "\n", + "DATA_DIR = Path(\"./data\")\n", + "DATA_SOURCE = Path(os.environ.get(\"TOGGL_DATA\", \"./data/toggl-sample.csv\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# assign category dtype for efficiency on repeating text columns\n", + "dtypes = {\n", + " \"Email\": \"category\",\n", + " \"Task\": \"category\",\n", + " \"Client\": \"category\"\n", + "}\n", + "# skip reading the columns we don't care about for Harvest\n", + "cols = list(dtypes) + [\n", + " \"Start date\",\n", + " \"Start time\",\n", + " \"Duration\",\n", + "]\n", + "# read CSV file, parsing dates and times\n", + "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Start date\"], cache_dates=True)\n", + "source[\"Start time\"] = source[\"Start time\"].apply(str_timedelta)\n", + "source[\"Duration\"] = source[\"Duration\"].apply(str_timedelta)\n", + "source.sort_values([\"Start date\", \"Start time\", \"Email\"], inplace=True)\n", + "source.dtypes" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 2e45fe1ba9c6850f097e7ea87b60efb868fd0a6b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 15 Apr 2024 23:36:40 +0000 Subject: [PATCH 14/27] feat(toggl): simple column updates/conversions * rename columns that can be imported as-is * add static calculated columns --- .env.sample | 1 + notebooks/toggl-to-harvest.ipynb | 169 ++++++++++++++++++------------- 2 files changed, 100 insertions(+), 70 deletions(-) diff --git a/.env.sample b/.env.sample index c2f5e8b..a757187 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,4 @@ +HARVEST_CLIENT_NAME=Client1 HARVEST_DATA=data/harvest-sample.csv TOGGL_DATA=data/toggl-sample.csv TOGGL_USER_INFO=data/toggl-user-info-sample.json diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index d5830a4..60d4309 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -1,73 +1,102 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pathlib import Path\n", - "import pandas as pd\n", - "\n", - "\n", - "def str_timedelta(td):\n", - " \"\"\"\n", - " Convert a string formatted duration (e.g. 01:30) to a timedelta.\n", - " \"\"\"\n", - " return pd.to_timedelta(pd.to_datetime(td, format=\"%H:%M:%S\").strftime(\"%H:%M:%S\"))\n", - "\n", - "\n", - "DATA_DIR = Path(\"./data\")\n", - "DATA_SOURCE = Path(os.environ.get(\"TOGGL_DATA\", \"./data/toggl-sample.csv\"))" - ] + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "import pandas as pd\n", + "\n", + "\n", + "DATA_DIR = Path(\"./data\")\n", + "DATA_SOURCE = Path(os.environ.get(\"TOGGL_DATA\", \"./data/toggl-sample.csv\"))\n", + "\n", + "USER_INFO_FILE = os.environ.get(\"TOGGL_USER_INFO\")\n", + "\n", + "CLIENT_NAME = os.environ.get(\"HARVEST_CLIENT_NAME\")\n", + "\n", + "\n", + "def str_timedelta(td):\n", + " \"\"\"\n", + " Convert a string formatted duration (e.g. 01:30) to a timedelta.\n", + " \"\"\"\n", + " return pd.to_timedelta(pd.to_datetime(td, format=\"%H:%M:%S\").strftime(\"%H:%M:%S\"))\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# assign category dtype for efficiency on repeating text columns\n", + "dtypes = {\n", + " \"Email\": \"category\",\n", + " \"Task\": \"category\",\n", + " \"Client\": \"category\"\n", + "}\n", + "# skip reading the columns we don't care about for Harvest\n", + "cols = list(dtypes) + [\n", + " \"Start date\",\n", + " \"Start time\",\n", + " \"Duration\",\n", + "]\n", + "# read CSV file, parsing dates and times\n", + "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Start date\"], cache_dates=True)\n", + "source[\"Start time\"] = source[\"Start time\"].apply(str_timedelta)\n", + "source[\"Duration\"] = source[\"Duration\"].apply(str_timedelta)\n", + "source.sort_values([\"Start date\", \"Start time\", \"Email\"], inplace=True)\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# rename columns that can be imported as-is\n", + "source.rename(columns={\"Task\": \"Project\", \"Description\": \"Notes\", \"Start date\": \"Date\"}, inplace=True)\n", + "source.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# update static calculated columns\n", + "source[\"Client\"] = CLIENT_NAME\n", + "source[\"Client\"] = source[\"Client\"].astype(\"category\")\n", + "source[\"Task\"] = \"Project Consulting\"\n", + "source[\"Task\"] = source[\"Task\"].astype(\"category\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# assign category dtype for efficiency on repeating text columns\n", - "dtypes = {\n", - " \"Email\": \"category\",\n", - " \"Task\": \"category\",\n", - " \"Client\": \"category\"\n", - "}\n", - "# skip reading the columns we don't care about for Harvest\n", - "cols = list(dtypes) + [\n", - " \"Start date\",\n", - " \"Start time\",\n", - " \"Duration\",\n", - "]\n", - "# read CSV file, parsing dates and times\n", - "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Start date\"], cache_dates=True)\n", - "source[\"Start time\"] = source[\"Start time\"].apply(str_timedelta)\n", - "source[\"Duration\"] = source[\"Duration\"].apply(str_timedelta)\n", - "source.sort_values([\"Start date\", \"Start time\", \"Email\"], inplace=True)\n", - "source.dtypes" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "nbformat": 4, + "nbformat_minor": 2 } From 30a3f220f3e518369da4fa80b013d4c66cc2e501 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 02:46:51 +0000 Subject: [PATCH 15/27] feat(toggl): read/write a user info file --- notebooks/toggl-to-harvest.ipynb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index 60d4309..d8a454f 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -6,6 +6,7 @@ "metadata": {}, "outputs": [], "source": [ + "import json\n", "import os\n", "from pathlib import Path\n", "import pandas as pd\n", @@ -24,7 +25,16 @@ " Convert a string formatted duration (e.g. 01:30) to a timedelta.\n", " \"\"\"\n", " return pd.to_timedelta(pd.to_datetime(td, format=\"%H:%M:%S\").strftime(\"%H:%M:%S\"))\n", - "\n" + "\n", + "\n", + "def read_user_info():\n", + " with open(USER_INFO_FILE, \"r\") as ui:\n", + " return json.load(ui)\n", + "\n", + "\n", + "def write_user_info(info):\n", + " with open(USER_INFO_FILE, \"w\") as ui:\n", + " json.dump(info, ui, indent=2)" ] }, { From 84c5be0b61d892baacf4825c9f4cbbea7350675a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 02:48:29 +0000 Subject: [PATCH 16/27] feat(toggl): cache the user info in memory --- notebooks/toggl-to-harvest.ipynb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index d8a454f..5ee895d 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -86,6 +86,22 @@ "source[\"Task\"] = \"Project Consulting\"\n", "source[\"Task\"] = source[\"Task\"].astype(\"category\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# cache of previously seen user information, keyed on email\n", + "USER_INFO = {}\n", + "NOT_FOUND = \"NOT FOUND\"\n", + "\n", + "if USER_INFO_FILE:\n", + " file_info = read_user_info()\n", + " USER_INFO.update(file_info)\n", + " print(f\"User info: {', '.join(USER_INFO.keys())}\")" + ] } ], "metadata": { From 03307740fc38dc9d8d8a2b6bd8f73283e439590a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 02:58:36 +0000 Subject: [PATCH 17/27] feat(toggl): get first name from email lookup in user info cache before computing save in user info cache after computing --- notebooks/toggl-to-harvest.ipynb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index 5ee895d..dfa9528 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -102,6 +102,31 @@ " USER_INFO.update(file_info)\n", " print(f\"User info: {', '.join(USER_INFO.keys())}\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get cached first name or derive from email\n", + "def get_first_name(email: str):\n", + " user = USER_INFO.get(email)\n", + " first_name = user.get(\"First Name\") if user else None\n", + " if first_name is None:\n", + " parts = email.split(\"@\")\n", + " first_name = parts[0].capitalize()\n", + " data = {\"First Name\": first_name}\n", + " if email in USER_INFO:\n", + " USER_INFO[email].update(data)\n", + " else:\n", + " USER_INFO[email] = data\n", + " return first_name\n", + "\n", + "source[\"First Name\"] = source[\"Email\"].apply(get_first_name)\n", + "source[\"First Name\"] = source[\"First Name\"].astype(\"category\")\n", + "source.dtypes" + ] } ], "metadata": { From 85cb99a5741ba1e8bae67e9a2313ced3f94fdaa1 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 03:15:52 +0000 Subject: [PATCH 18/27] refactor(commands): move return codes to top level make more easily accessible elsewhere --- compiler_admin/__init__.py | 3 +++ compiler_admin/commands/__init__.py | 2 -- compiler_admin/commands/convert.py | 2 +- compiler_admin/commands/create.py | 2 +- compiler_admin/commands/delete.py | 2 +- compiler_admin/commands/info.py | 3 +-- compiler_admin/commands/init.py | 2 +- compiler_admin/commands/offboard.py | 2 +- compiler_admin/commands/reset_password.py | 2 +- compiler_admin/commands/restore.py | 2 +- compiler_admin/commands/signout.py | 2 +- tests/commands/test_convert.py | 2 +- tests/commands/test_create.py | 2 +- tests/commands/test_delete.py | 2 +- tests/commands/test_info.py | 3 +-- tests/commands/test_offboard.py | 2 +- tests/commands/test_reset_password.py | 2 +- tests/commands/test_restore.py | 2 +- tests/commands/test_signout.py | 2 +- tests/conftest.py | 2 +- 20 files changed, 21 insertions(+), 22 deletions(-) diff --git a/compiler_admin/__init__.py b/compiler_admin/__init__.py index 1a914db..8ff2840 100644 --- a/compiler_admin/__init__.py +++ b/compiler_admin/__init__.py @@ -1,5 +1,8 @@ from importlib.metadata import version, PackageNotFoundError +RESULT_SUCCESS = 0 +RESULT_FAILURE = 1 + try: __version__ = version("compiler_admin") except PackageNotFoundError: diff --git a/compiler_admin/commands/__init__.py b/compiler_admin/commands/__init__.py index 2cfbde3..e69de29 100644 --- a/compiler_admin/commands/__init__.py +++ b/compiler_admin/commands/__init__.py @@ -1,2 +0,0 @@ -RESULT_SUCCESS = 0 -RESULT_FAILURE = 1 diff --git a/compiler_admin/commands/convert.py b/compiler_admin/commands/convert.py index 049620f..6e877c1 100644 --- a/compiler_admin/commands/convert.py +++ b/compiler_admin/commands/convert.py @@ -1,6 +1,6 @@ from argparse import Namespace -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import ( GROUP_PARTNERS, GROUP_STAFF, diff --git a/compiler_admin/commands/create.py b/compiler_admin/commands/create.py index d1ac860..6576e26 100644 --- a/compiler_admin/commands/create.py +++ b/compiler_admin/commands/create.py @@ -1,7 +1,7 @@ from argparse import Namespace from typing import Sequence -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import GROUP_TEAM, add_user_to_group, CallGAMCommand, user_account_name, user_exists diff --git a/compiler_admin/commands/delete.py b/compiler_admin/commands/delete.py index 6a32565..ac5efc1 100644 --- a/compiler_admin/commands/delete.py +++ b/compiler_admin/commands/delete.py @@ -1,6 +1,6 @@ from argparse import Namespace -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import CallGAMCommand, user_account_name, user_exists diff --git a/compiler_admin/commands/info.py b/compiler_admin/commands/info.py index 419bede..7548952 100644 --- a/compiler_admin/commands/info.py +++ b/compiler_admin/commands/info.py @@ -1,5 +1,4 @@ -from compiler_admin import __version__ as version -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import __version__ as version, RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import CallGAMCommand, CallGYBCommand diff --git a/compiler_admin/commands/init.py b/compiler_admin/commands/init.py index c431684..fde1f80 100644 --- a/compiler_admin/commands/init.py +++ b/compiler_admin/commands/init.py @@ -4,7 +4,7 @@ from shutil import rmtree import subprocess -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.services.google import USER_ARCHIVE, CallGAMCommand diff --git a/compiler_admin/commands/offboard.py b/compiler_admin/commands/offboard.py index 6f52abd..7c473cd 100644 --- a/compiler_admin/commands/offboard.py +++ b/compiler_admin/commands/offboard.py @@ -1,7 +1,7 @@ from argparse import Namespace from tempfile import NamedTemporaryFile -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.commands.delete import delete from compiler_admin.commands.signout import signout from compiler_admin.services.google import ( diff --git a/compiler_admin/commands/reset_password.py b/compiler_admin/commands/reset_password.py index 610d38f..023df31 100644 --- a/compiler_admin/commands/reset_password.py +++ b/compiler_admin/commands/reset_password.py @@ -1,6 +1,6 @@ from argparse import Namespace -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.commands.signout import signout from compiler_admin.services.google import USER_HELLO, CallGAMCommand, user_account_name, user_exists diff --git a/compiler_admin/commands/restore.py b/compiler_admin/commands/restore.py index 4ee3527..1c56e95 100644 --- a/compiler_admin/commands/restore.py +++ b/compiler_admin/commands/restore.py @@ -1,7 +1,7 @@ from argparse import Namespace import pathlib -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import USER_ARCHIVE, CallGYBCommand, user_account_name diff --git a/compiler_admin/commands/signout.py b/compiler_admin/commands/signout.py index 657be90..e204578 100644 --- a/compiler_admin/commands/signout.py +++ b/compiler_admin/commands/signout.py @@ -1,6 +1,6 @@ from argparse import Namespace -from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import CallGAMCommand, user_account_name, user_exists diff --git a/tests/commands/test_convert.py b/tests/commands/test_convert.py index f102bba..307715d 100644 --- a/tests/commands/test_convert.py +++ b/tests/commands/test_convert.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.convert import convert, __name__ as MODULE diff --git a/tests/commands/test_create.py b/tests/commands/test_create.py index 4de12db..285d54a 100644 --- a/tests/commands/test_create.py +++ b/tests/commands/test_create.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.create import create, __name__ as MODULE from compiler_admin.services.google import USER_HELLO diff --git a/tests/commands/test_delete.py b/tests/commands/test_delete.py index 7728cc9..a714c14 100644 --- a/tests/commands/test_delete.py +++ b/tests/commands/test_delete.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.delete import delete, __name__ as MODULE diff --git a/tests/commands/test_info.py b/tests/commands/test_info.py index 1939366..3887c36 100644 --- a/tests/commands/test_info.py +++ b/tests/commands/test_info.py @@ -1,7 +1,6 @@ import pytest -from compiler_admin import __version__ as version -from compiler_admin.commands import RESULT_SUCCESS +from compiler_admin import __version__ as version, RESULT_SUCCESS from compiler_admin.commands.info import info, __name__ as MODULE from compiler_admin.services.google import DOMAIN diff --git a/tests/commands/test_offboard.py b/tests/commands/test_offboard.py index 5bddbfe..52b031d 100644 --- a/tests/commands/test_offboard.py +++ b/tests/commands/test_offboard.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.offboard import offboard, __name__ as MODULE diff --git a/tests/commands/test_reset_password.py b/tests/commands/test_reset_password.py index 909215d..5d56269 100644 --- a/tests/commands/test_reset_password.py +++ b/tests/commands/test_reset_password.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.reset_password import reset_password, __name__ as MODULE from compiler_admin.services.google import USER_HELLO diff --git a/tests/commands/test_restore.py b/tests/commands/test_restore.py index f2e20d7..cfd05dd 100644 --- a/tests/commands/test_restore.py +++ b/tests/commands/test_restore.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.restore import restore, __name__ as MODULE diff --git a/tests/commands/test_signout.py b/tests/commands/test_signout.py index 3caaf45..06419d0 100644 --- a/tests/commands/test_signout.py +++ b/tests/commands/test_signout.py @@ -1,7 +1,7 @@ from argparse import Namespace import pytest -from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS +from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.signout import signout, __name__ as MODULE diff --git a/tests/conftest.py b/tests/conftest.py index d57416c..f10edb7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from compiler_admin.commands import RESULT_SUCCESS +from compiler_admin import RESULT_SUCCESS @pytest.fixture From 0c887a709541c6762574b52d37c28d4c04fd9763 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 03:47:51 +0000 Subject: [PATCH 19/27] refactor(tests): don't hardcode temp file lines --- tests/commands/test_offboard.py | 4 ++-- tests/conftest.py | 2 +- tests/services/test_google.py | 15 ++++++--------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/commands/test_offboard.py b/tests/commands/test_offboard.py index 52b031d..08de141 100644 --- a/tests/commands/test_offboard.py +++ b/tests/commands/test_offboard.py @@ -20,8 +20,8 @@ def mock_input_no(mock_input): @pytest.fixture -def mock_NamedTemporaryFile(mock_NamedTemporaryFile): - return mock_NamedTemporaryFile(MODULE, ["Overall Transfer Status: completed"]) +def mock_NamedTemporaryFile(mock_NamedTemporaryFile_with_readlines): + return mock_NamedTemporaryFile_with_readlines(MODULE, ["Overall Transfer Status: completed"]) @pytest.fixture diff --git a/tests/conftest.py b/tests/conftest.py index f10edb7..99e5c2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -137,7 +137,7 @@ def mock_google_user_is_staff(mock_module_name): @pytest.fixture -def mock_NamedTemporaryFile(mocker): +def mock_NamedTemporaryFile_with_readlines(mocker): """Fixture returns a function that patches NamedTemporaryFile in a given module. Optionally provide a value for GAM stdout.readlines(). diff --git a/tests/services/test_google.py b/tests/services/test_google.py index 476e6c6..adeb8f8 100644 --- a/tests/services/test_google.py +++ b/tests/services/test_google.py @@ -50,11 +50,6 @@ def mock_subprocess_call(mocker): return mocker.patch(f"{MODULE}.subprocess.call") -@pytest.fixture -def mock_NamedTemporaryFile(mock_NamedTemporaryFile): - return mock_NamedTemporaryFile(MODULE, ["group"]) - - def test_user_account_name_None(): username = None account = user_account_name(username) @@ -208,18 +203,20 @@ def test_user_in_group_user_does_not_exist(mock_google_user_exists, capfd): assert "User does not exist" in captured.out -@pytest.mark.usefixtures("mock_NamedTemporaryFile", "mock_google_CallGAMCommand") -def test_user_in_group_user_exists_in_group(mock_google_user_exists): +@pytest.mark.usefixtures("mock_google_CallGAMCommand") +def test_user_in_group_user_exists_in_group(mock_google_user_exists, mock_NamedTemporaryFile_with_readlines): mock_google_user_exists.return_value = True + mock_NamedTemporaryFile_with_readlines(MODULE, ["group"]) res = user_in_group("username", "group") assert res is True -@pytest.mark.usefixtures("mock_NamedTemporaryFile", "mock_google_CallGAMCommand") -def test_user_in_group_user_exists_not_in_group(mock_google_user_exists): +@pytest.mark.usefixtures("mock_google_CallGAMCommand") +def test_user_in_group_user_exists_not_in_group(mock_google_user_exists, mock_NamedTemporaryFile_with_readlines): mock_google_user_exists.return_value = True + mock_NamedTemporaryFile_with_readlines(MODULE, ["group"]) res = user_in_group("username", "nope") From da48171f6d88ab31c8376df3dae0d7de3def125b Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 03:51:10 +0000 Subject: [PATCH 20/27] feat(google): get a user info dict --- compiler_admin/services/google.py | 35 +++++++++++++++++++++++++++++-- tests/conftest.py | 6 ++++++ tests/services/test_google.py | 32 ++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/compiler_admin/services/google.py b/compiler_admin/services/google.py index d93f713..50ae080 100644 --- a/compiler_admin/services/google.py +++ b/compiler_admin/services/google.py @@ -3,6 +3,8 @@ from tempfile import NamedTemporaryFile from typing import Any, Sequence, IO +from compiler_admin import RESULT_SUCCESS + # import and alias CallGAMCommand so we can simplify usage in this app from gam import CallGAMCommand as __CallGAMCommand, initializeLogging @@ -96,9 +98,38 @@ def user_exists(username: str) -> bool: print(f"User not in domain: {username}") return False - res = CallGAMCommand(("info", "user", username, "quick")) + info = user_info(username) + + return info != {} + - return res == 0 +def user_info(username: str) -> dict: + """Get a dict of basic user information. + + Args: + username (str): The user@compiler.la to get. + Returns: + A dict of user information + """ + if not str(username).endswith(DOMAIN): + print(f"User not in domain: {username}") + return {} + + with NamedTemporaryFile("w+") as stdout: + res = CallGAMCommand(("info", "user", username, "quick"), stdout=stdout.name) + if res != RESULT_SUCCESS: + # user doesn't exist + return {} + # user exists, read data + lines = stdout.readlines() + # split on newline and filter out lines that aren't line "Key:Value" and empty value lines like "Key:" + lines = [L.strip() for L in lines if len(L.split(":")) == 2 and L.split(":")[1].strip()] + # make a map by splitting the lines, trimming key and value + info = {} + for line in lines: + k, v = line.split(":") + info[k.strip()] = v.strip() + return info def user_in_group(username: str, group: str) -> bool: diff --git a/tests/conftest.py b/tests/conftest.py index 99e5c2c..549aeae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -118,6 +118,12 @@ def mock_google_user_exists(mock_module_name): return mock_module_name("user_exists") +@pytest.fixture +def mock_google_user_info(mock_module_name): + """Fixture returns a function that patches the user_info function from a given module.""" + return mock_module_name("user_info") + + @pytest.fixture def mock_google_user_in_group(mock_module_name): """Fixture returns a function that patches the user_in_group function from a given module.""" diff --git a/tests/services/test_google.py b/tests/services/test_google.py index adeb8f8..15a192d 100644 --- a/tests/services/test_google.py +++ b/tests/services/test_google.py @@ -3,6 +3,7 @@ import pytest +from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import ( DOMAIN, GAM, @@ -19,6 +20,7 @@ remove_user_from_group, user_exists, user_in_group, + user_info, user_is_partner, user_is_staff, __name__ as MODULE, @@ -40,6 +42,11 @@ def mock_google_user_exists(mock_google_user_exists): return mock_google_user_exists(MODULE) +@pytest.fixture +def mock_google_user_info(mock_google_user_info): + return mock_google_user_info(MODULE) + + @pytest.fixture def mock_google_user_in_group(mock_google_user_in_group): return mock_google_user_in_group(MODULE) @@ -177,22 +184,39 @@ def test_user_exists_username_not_in_domain(capfd): assert "User not in domain" in captured.out -def test_user_exists_user_exists(mock_google_CallGAMCommand): - mock_google_CallGAMCommand.return_value = 0 +def test_user_exists_user_exists(mock_google_user_info): + mock_google_user_info.return_value = {"First Name": "Test", "Last Name": "User"} res = user_exists(user_account_name("username")) assert res is True -def test_user_exists_user_does_not_exists(mock_google_CallGAMCommand): - mock_google_CallGAMCommand.return_value = -1 +def test_user_exists_user_does_not_exist(mock_google_user_info): + mock_google_user_info.return_value = {} res = user_exists(user_account_name("username")) assert res is False +def test_user_info_user_exists(mock_gam_CallGAMCommand, mock_NamedTemporaryFile_with_readlines): + mock_NamedTemporaryFile_with_readlines(MODULE, ["First Name:Test", "Last Name:User"]) + mock_gam_CallGAMCommand.return_value = RESULT_SUCCESS + + res = user_info(user_account_name("username")) + + assert res == {"First Name": "Test", "Last Name": "User"} + + +def test_user_info_user_does_not_exists(mock_gam_CallGAMCommand): + mock_gam_CallGAMCommand.return_value = RESULT_FAILURE + + res = user_info(user_account_name("username")) + + assert res == {} + + def test_user_in_group_user_does_not_exist(mock_google_user_exists, capfd): mock_google_user_exists.return_value = False From 39f1267d19817202148694a4d093c7988efb7721 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 03:54:27 +0000 Subject: [PATCH 21/27] feat(toggl): get last name from google lookup in user info cache before querying google save in user info cache after query --- notebooks/toggl-to-harvest.ipynb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index dfa9528..c9c3562 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -6,6 +6,7 @@ "metadata": {}, "outputs": [], "source": [ + "from compiler_admin.services.google import user_info as google_user_info\n", "import json\n", "import os\n", "from pathlib import Path\n", @@ -127,6 +128,30 @@ "source[\"First Name\"] = source[\"First Name\"].astype(\"category\")\n", "source.dtypes" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get cached last name or query from Google\n", + "def get_last_name(email: str):\n", + " user = USER_INFO.get(email)\n", + " last_name = user.get(\"Last Name\") if user else None\n", + " if last_name is None:\n", + " user = google_user_info(email)\n", + " last_name = user.get(\"Last Name\") if user else None\n", + " if email in USER_INFO:\n", + " USER_INFO[email].update(user)\n", + " else:\n", + " USER_INFO[email] = user\n", + " return last_name\n", + "\n", + "source[\"Last Name\"] = source[\"Email\"].apply(get_last_name)\n", + "source[\"Last Name\"] = source[\"Last Name\"].astype(\"category\")\n", + "source.dtypes" + ] } ], "metadata": { From d99d9068fec759fb74da03258ae9ffb2a4281ebf Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 03:59:25 +0000 Subject: [PATCH 22/27] feat(toggl): write user info back to file --- notebooks/toggl-to-harvest.ipynb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index c9c3562..5b9a1a9 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -152,6 +152,16 @@ "source[\"Last Name\"] = source[\"Last Name\"].astype(\"category\")\n", "source.dtypes" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if len(USER_INFO) > 0 and USER_INFO_FILE:\n", + " write_user_info(USER_INFO)" + ] } ], "metadata": { From 761c594178d44255b973d4deaf3891671425e1ac Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 16:25:21 +0000 Subject: [PATCH 23/27] fix(toggl): forgot to import Description field --- notebooks/toggl-to-harvest.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index 5b9a1a9..bba78b3 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -55,6 +55,7 @@ " \"Start date\",\n", " \"Start time\",\n", " \"Duration\",\n", + " \"Description\"\n", "]\n", "# read CSV file, parsing dates and times\n", "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Start date\"], cache_dates=True)\n", From 19a016578fb04242f6a54c06c3289f61f6518ee4 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 16:25:57 +0000 Subject: [PATCH 24/27] feat(toggl): calculate decimal hours from duration str --- notebooks/toggl-to-harvest.ipynb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index bba78b3..304361c 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -163,6 +163,16 @@ "if len(USER_INFO) > 0 and USER_INFO_FILE:\n", " write_user_info(USER_INFO)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "source[\"Hours\"] = (source[\"Duration\"].dt.total_seconds()/3600).round(2)\n", + "source.dtypes" + ] } ], "metadata": { From 3ea66b0b716b58927e28be0b03238ca6e83b8320 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 16:26:33 +0000 Subject: [PATCH 25/27] feat(toggl): save resulting data as CSV --- notebooks/toggl-to-harvest.ipynb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index 304361c..f3b8650 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -11,6 +11,7 @@ "import os\n", "from pathlib import Path\n", "import pandas as pd\n", + "import re\n", "\n", "\n", "DATA_DIR = Path(\"./data\")\n", @@ -173,6 +174,17 @@ "source[\"Hours\"] = (source[\"Duration\"].dt.total_seconds()/3600).round(2)\n", "source.dtypes" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "output_path = DATA_DIR / re.sub(r\"[Tt]oggl\", \"harvest\", DATA_SOURCE.name)\n", + "output_columns = [\"Date\", \"Client\", \"Project\", \"Task\", \"Notes\", \"Hours\", \"First Name\", \"Last Name\"]\n", + "source.to_csv(output_path, index=False, columns=output_columns)" + ] } ], "metadata": { From 8deb4f7efd33847509417dbdea74dbae7daadbbd Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 16:27:28 +0000 Subject: [PATCH 26/27] chore(toggl): clean up debug prints --- notebooks/toggl-to-harvest.ipynb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index f3b8650..9b8905b 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -62,8 +62,7 @@ "source = pd.read_csv(DATA_SOURCE, dtype=dtypes, usecols=cols, parse_dates=[\"Start date\"], cache_dates=True)\n", "source[\"Start time\"] = source[\"Start time\"].apply(str_timedelta)\n", "source[\"Duration\"] = source[\"Duration\"].apply(str_timedelta)\n", - "source.sort_values([\"Start date\", \"Start time\", \"Email\"], inplace=True)\n", - "source.dtypes" + "source.sort_values([\"Start date\", \"Start time\", \"Email\"], inplace=True)" ] }, { @@ -73,8 +72,7 @@ "outputs": [], "source": [ "# rename columns that can be imported as-is\n", - "source.rename(columns={\"Task\": \"Project\", \"Description\": \"Notes\", \"Start date\": \"Date\"}, inplace=True)\n", - "source.dtypes" + "source.rename(columns={\"Task\": \"Project\", \"Description\": \"Notes\", \"Start date\": \"Date\"}, inplace=True)" ] }, { @@ -127,8 +125,7 @@ " return first_name\n", "\n", "source[\"First Name\"] = source[\"Email\"].apply(get_first_name)\n", - "source[\"First Name\"] = source[\"First Name\"].astype(\"category\")\n", - "source.dtypes" + "source[\"First Name\"] = source[\"First Name\"].astype(\"category\")" ] }, { @@ -151,8 +148,7 @@ " return last_name\n", "\n", "source[\"Last Name\"] = source[\"Email\"].apply(get_last_name)\n", - "source[\"Last Name\"] = source[\"Last Name\"].astype(\"category\")\n", - "source.dtypes" + "source[\"Last Name\"] = source[\"Last Name\"].astype(\"category\")" ] }, { @@ -171,8 +167,7 @@ "metadata": {}, "outputs": [], "source": [ - "source[\"Hours\"] = (source[\"Duration\"].dt.total_seconds()/3600).round(2)\n", - "source.dtypes" + "source[\"Hours\"] = (source[\"Duration\"].dt.total_seconds()/3600).round(2)" ] }, { From 27c23ba077a18b73312448f6a6c45f421cb0ec8a Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Tue, 16 Apr 2024 16:39:15 +0000 Subject: [PATCH 27/27] feat(toggl): project info file can override project name --- .env.sample | 1 + .gitignore | 1 + notebooks/data/toggl-project-info-sample.json | 4 ++ notebooks/toggl-to-harvest.ipynb | 38 ++++++++++++++++--- 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 notebooks/data/toggl-project-info-sample.json diff --git a/.env.sample b/.env.sample index a757187..2b01856 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,5 @@ HARVEST_CLIENT_NAME=Client1 HARVEST_DATA=data/harvest-sample.csv TOGGL_DATA=data/toggl-sample.csv +TOGGL_PROJECT_INFO=data/toggl-project-info-sample.json TOGGL_USER_INFO=data/toggl-user-info-sample.json diff --git a/.gitignore b/.gitignore index f984950..7408caa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ __pycache__ *.egg-info notebooks/data/* !notebooks/data/harvest-sample.csv +!notebooks/data/toggl-project-info-sample.json !notebooks/data/toggl-sample.csv !notebooks/data/toggl-user-info-sample.json diff --git a/notebooks/data/toggl-project-info-sample.json b/notebooks/data/toggl-project-info-sample.json new file mode 100644 index 0000000..8505813 --- /dev/null +++ b/notebooks/data/toggl-project-info-sample.json @@ -0,0 +1,4 @@ +{ + "Project1": "WT1-01 - Project1", + "Project3": "WT3-01 - Project3" +} diff --git a/notebooks/toggl-to-harvest.ipynb b/notebooks/toggl-to-harvest.ipynb index 9b8905b..a48709f 100644 --- a/notebooks/toggl-to-harvest.ipynb +++ b/notebooks/toggl-to-harvest.ipynb @@ -17,6 +17,7 @@ "DATA_DIR = Path(\"./data\")\n", "DATA_SOURCE = Path(os.environ.get(\"TOGGL_DATA\", \"./data/toggl-sample.csv\"))\n", "\n", + "PROJECT_INFO_FILE = os.environ.get(\"TOGGL_PROJECT_INFO\")\n", "USER_INFO_FILE = os.environ.get(\"TOGGL_USER_INFO\")\n", "\n", "CLIENT_NAME = os.environ.get(\"HARVEST_CLIENT_NAME\")\n", @@ -29,13 +30,13 @@ " return pd.to_timedelta(pd.to_datetime(td, format=\"%H:%M:%S\").strftime(\"%H:%M:%S\"))\n", "\n", "\n", - "def read_user_info():\n", - " with open(USER_INFO_FILE, \"r\") as ui:\n", + "def read_info_file(file):\n", + " with open(file, \"r\") as ui:\n", " return json.load(ui)\n", "\n", "\n", - "def write_user_info(info):\n", - " with open(USER_INFO_FILE, \"w\") as ui:\n", + "def write_info_file(file, info):\n", + " with open(file, \"w\") as ui:\n", " json.dump(info, ui, indent=2)" ] }, @@ -88,6 +89,31 @@ "source[\"Task\"] = source[\"Task\"].astype(\"category\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# cache of previously seen project information, keyed on Toggl project name\n", + "PROJECT_INFO = {}\n", + "\n", + "if PROJECT_INFO_FILE:\n", + " file_info = read_info_file(PROJECT_INFO_FILE)\n", + " PROJECT_INFO.update(file_info)\n", + " print(f\"Project info: {', '.join(PROJECT_INFO.keys())}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get cached project name if any\n", + "source[\"Project\"] = source[\"Project\"].apply(lambda x: PROJECT_INFO.get(x, x))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -99,7 +125,7 @@ "NOT_FOUND = \"NOT FOUND\"\n", "\n", "if USER_INFO_FILE:\n", - " file_info = read_user_info()\n", + " file_info = read_info_file(USER_INFO_FILE)\n", " USER_INFO.update(file_info)\n", " print(f\"User info: {', '.join(USER_INFO.keys())}\")" ] @@ -158,7 +184,7 @@ "outputs": [], "source": [ "if len(USER_INFO) > 0 and USER_INFO_FILE:\n", - " write_user_info(USER_INFO)" + " write_info_file(USER_INFO_FILE, USER_INFO)" ] }, {