From 83d859fd3a831e10cdbe61456833093acd7a0a31 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Wed, 18 Mar 2020 17:03:36 -0400 Subject: [PATCH] Just merging stuff real quick. --- Pipfile | 1 + Pipfile.lock | 234 +++++++++++------- crc/models/irb_document.py | 17 ++ crc/scripts/script.py | 20 +- crc/scripts/study_info.py | 66 ++++- crc/services/file_service.py | 1 + .../versions/2b787cc265d8_merge_d_and_f.py | 24 ++ .../two_forms/mods/two_forms_struc_mod.bpmn | 1 + tests/test_study_info_service.py | 61 +++++ 9 files changed, 320 insertions(+), 105 deletions(-) create mode 100644 crc/models/irb_document.py create mode 100644 migrations/versions/2b787cc265d8_merge_d_and_f.py create mode 100644 tests/test_study_info_service.py diff --git a/Pipfile b/Pipfile index 9caaaeee..ffb7a4ad 100644 --- a/Pipfile +++ b/Pipfile @@ -34,6 +34,7 @@ psycopg2-binary = "*" docxtpl = "*" flask-sso = "*" python-dateutil = "*" +pandas = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 6898271f..c0185212 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "60e48d05048f627878a5c81377318bc2ff2a94b2574441c166fda3a523c789df" + "sha256": "0e22a6c7b84b96d9d8677790cc90cc7f0a894f5742d7ecbcbd0fdcbe59587eda" }, "pipfile-spec": 6, "requires": { @@ -25,10 +25,10 @@ }, "alembic": { "hashes": [ - "sha256:2df2519a5b002f881517693b95626905a39c5faf4b5a1f94de4f1441095d1d26" + "sha256:791a5686953c4b366d3228c5377196db2f534475bb38d26f70eb69668efd9028" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.4.1" }, "amqp": { "hashes": [ @@ -104,10 +104,10 @@ }, "celery": { "hashes": [ - "sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f", - "sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2" + "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f", + "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a" ], - "version": "==4.4.0" + "version": "==4.4.2" }, "certifi": { "hashes": [ @@ -158,10 +158,10 @@ }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" ], - "version": "==7.0" + "version": "==7.1.1" }, "clickclick": { "hashes": [ @@ -197,40 +197,40 @@ }, "coverage": { "hashes": [ - "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", - "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", - "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", - "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", - "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", - "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", - "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", - "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", - "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", - "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", - "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", - "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", - "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", - "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", - "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", - "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", - "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", - "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", - "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", - "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", - "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", - "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", - "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", - "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", - "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", - "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", - "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", - "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", - "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", - "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", - "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" + "sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0", + "sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30", + "sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b", + "sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0", + "sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823", + "sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe", + "sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037", + "sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6", + "sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31", + "sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd", + "sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892", + "sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1", + "sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78", + "sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac", + "sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006", + "sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014", + "sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2", + "sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7", + "sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8", + "sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7", + "sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9", + "sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1", + "sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307", + "sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a", + "sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435", + "sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0", + "sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5", + "sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441", + "sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732", + "sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de", + "sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1" ], "index": "pypi", - "version": "==5.0.3" + "version": "==5.0.4" }, "docutils": { "hashes": [ @@ -294,11 +294,11 @@ }, "flask-migrate": { "hashes": [ - "sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1", - "sha256:a96ff1875a49a40bd3e8ac04fce73fdb0870b9211e6168608cbafa4eb839d502" + "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", + "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" ], "index": "pypi", - "version": "==2.5.2" + "version": "==2.5.3" }, "flask-restful": { "hashes": [ @@ -393,10 +393,10 @@ }, "kombu": { "hashes": [ - "sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac", - "sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1" + "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", + "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" ], - "version": "==4.6.7" + "version": "==4.6.8" }, "lxml": { "hashes": [ @@ -432,9 +432,10 @@ }, "mako": { "hashes": [ - "sha256:2984a6733e1d472796ceef37ad48c26f4a984bb18119bb2dbc37a44d8f6e75a4" + "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", + "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" ], - "version": "==1.1.1" + "version": "==1.1.2" }, "markupsafe": { "hashes": [ @@ -476,11 +477,11 @@ }, "marshmallow": { "hashes": [ - "sha256:3a94945a7461f2ab4df9576e51c97d66bee2c86155d3d3933fab752b31effab8", - "sha256:4b95c7735f93eb781dfdc4dded028108998cad759dda8dd9d4b5b4ac574cbf13" + "sha256:90854221bbb1498d003a0c3cc9d8390259137551917961c8b5258c64026b2f85", + "sha256:ac2e13b30165501b7d41fc0371b8df35944f5849769d136f20e2c5f6cdc6e665" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.5.1" }, "marshmallow-enum": { "hashes": [ @@ -492,11 +493,37 @@ }, "marshmallow-sqlalchemy": { "hashes": [ - "sha256:a370e247216e1a005277d92079d2f0d8d5b0a70fba68ee645730f6a1200991d1", - "sha256:f3155e87717e3a52def3a177b4022fd0500e71f626cbb0672adcb95588a99aa3" + "sha256:9301c6fd197bd97337820ea1417aa1233d0ee3e22748ebd5821799bc841a57e8", + "sha256:dde9e20bcb710e9e59f765a38e3d6d17f1b2d6b4320cbdc2cea0f6b57f70d08c" ], "index": "pypi", - "version": "==0.22.2" + "version": "==0.22.3" + }, + "numpy": { + "hashes": [ + "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448", + "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c", + "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5", + "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed", + "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5", + "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3", + "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c", + "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8", + "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b", + "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963", + "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef", + "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa", + "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286", + "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61", + "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5", + "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5", + "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da", + "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b", + "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c", + "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0", + "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d" + ], + "version": "==1.18.2" }, "openapi-spec-validator": { "hashes": [ @@ -515,10 +542,32 @@ }, "packaging": { "hashes": [ - "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", - "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==20.1" + "version": "==20.3" + }, + "pandas": { + "hashes": [ + "sha256:07c1b58936b80eafdfe694ce964ac21567b80a48d972879a359b3ebb2ea76835", + "sha256:0ebe327fb088df4d06145227a4aa0998e4f80a9e6aed4b61c1f303bdfdf7c722", + "sha256:11c7cb654cd3a0e9c54d81761b5920cdc86b373510d829461d8f2ed6d5905266", + "sha256:12f492dd840e9db1688126216706aa2d1fcd3f4df68a195f9479272d50054645", + "sha256:167a1315367cea6ec6a5e11e791d9604f8e03f95b57ad227409de35cf850c9c5", + "sha256:1a7c56f1df8d5ad8571fa251b864231f26b47b59cbe41aa5c0983d17dbb7a8e4", + "sha256:1fa4bae1a6784aa550a1c9e168422798104a85bf9c77a1063ea77ee6f8452e3a", + "sha256:32f42e322fb903d0e189a4c10b75ba70d90958cc4f66a1781ed027f1a1d14586", + "sha256:387dc7b3c0424327fe3218f81e05fc27832772a5dffbed385013161be58df90b", + "sha256:6597df07ea361231e60c00692d8a8099b519ed741c04e65821e632bc9ccb924c", + "sha256:743bba36e99d4440403beb45a6f4f3a667c090c00394c176092b0b910666189b", + "sha256:858a0d890d957ae62338624e4aeaf1de436dba2c2c0772570a686eaca8b4fc85", + "sha256:863c3e4b7ae550749a0bb77fa22e601a36df9d2905afef34a6965bed092ba9e5", + "sha256:a210c91a02ec5ff05617a298ad6f137b9f6f5771bf31f2d6b6367d7f71486639", + "sha256:ca84a44cf727f211752e91eab2d1c6c1ab0f0540d5636a8382a3af428542826e", + "sha256:d234bcf669e8b4d6cbcd99e3ce7a8918414520aeb113e2a81aeb02d0a533d7f7" + ], + "index": "pypi", + "version": "==1.0.3" }, "psycopg2-binary": { "hashes": [ @@ -560,16 +609,17 @@ }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "version": "==2.19" + "version": "==2.20" }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pyjwt": { "hashes": [ @@ -676,25 +726,25 @@ }, "sphinx": { "hashes": [ - "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88", - "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709" + "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", + "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" ], "index": "pypi", - "version": "==2.4.3" + "version": "==2.4.4" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", - "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", - "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-htmlhelp": { "hashes": [ @@ -712,17 +762,17 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", - "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", + "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", - "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", + "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" ], - "version": "==1.1.3" + "version": "==1.1.4" }, "spiffworkflow": { "editable": true, @@ -731,9 +781,9 @@ }, "sqlalchemy": { "hashes": [ - "sha256:64a7b71846db6423807e96820993fa12a03b89127d278290ca25c0b11ed7b4fb" + "sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" ], - "version": "==1.3.13" + "version": "==1.3.15" }, "swagger-ui-bundle": { "hashes": [ @@ -797,10 +847,10 @@ }, "zipp": { "hashes": [ - "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2", - "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==3.0.0" + "version": "==3.1.0" } }, "develop": { @@ -828,10 +878,10 @@ }, "packaging": { "hashes": [ - "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", - "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==20.1" + "version": "==20.3" }, "pluggy": { "hashes": [ @@ -856,11 +906,11 @@ }, "pytest": { "hashes": [ - "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", - "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" + "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", + "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" ], "index": "pypi", - "version": "==5.3.5" + "version": "==5.4.1" }, "six": { "hashes": [ @@ -878,10 +928,10 @@ }, "zipp": { "hashes": [ - "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2", - "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==3.0.0" + "version": "==3.1.0" } } } diff --git a/crc/models/irb_document.py b/crc/models/irb_document.py new file mode 100644 index 00000000..c82331ec --- /dev/null +++ b/crc/models/irb_document.py @@ -0,0 +1,17 @@ +from marshmallow_sqlalchemy import ModelSchema +from crc import db + + +class IRBDocumentModel(db.Model): + """Provides a lookup table for information about how to submit IRB documents, + which are named according to category levels. These may be linked + to a ProtocolBuilder required document, which is the irb_required_doc_id """ + __tablename__ = 'irb_document' + id = db.Column(db.Integer, primary_key=True) + irb_required_doc_id = db.Column(db.Integer) + category1 = db.Column(db.String) + category2 = db.Column(db.String) + category3 = db.Column(db.String) + who_uploads = db.Column(db.String) + + diff --git a/crc/scripts/script.py b/crc/scripts/script.py index ee5a3dd9..4a4c012f 100644 --- a/crc/scripts/script.py +++ b/crc/scripts/script.py @@ -18,6 +18,13 @@ class Script: "This is an internal error. The script you are trying to execute " + "does not properly implement the do_task function.") + def validate(self): + """Override this method to perform an early check that the script has access to + everything it needs to properly process requests. + Should return an array of ScriptValidationErrors. + """ + return [] + @staticmethod def get_all_subclasses(): return Script._get_all_subclasses(Script) @@ -38,4 +45,15 @@ class Script: all_subclasses.append(subclass) all_subclasses.extend(Script._get_all_subclasses(subclass)) - return all_subclasses \ No newline at end of file + return all_subclasses + + +class ScriptValidationError: + + def __init__(self, code, message): + self.code = code + self.message = message + + @classmethod + def from_api_error(cls, api_error: ApiError): + return cls(api_error.code, api_error.message) diff --git a/crc/scripts/study_info.py b/crc/scripts/study_info.py index 6d168af5..1836ac57 100644 --- a/crc/scripts/study_info.py +++ b/crc/scripts/study_info.py @@ -1,7 +1,10 @@ -from crc import session +from pandas import ExcelFile + +from crc import session, ma from crc.api.common import ApiError from crc.models.study import StudyModel, StudyModelSchema -from crc.scripts.script import Script +from crc.scripts.script import Script, ScriptValidationError +from crc.services.file_service import FileService from crc.services.protocol_builder import ProtocolBuilderService @@ -9,6 +12,7 @@ class StudyInfo(Script): """Just your basic class that can pull in data from a few api endpoints and do a basic task.""" pb = ProtocolBuilderService() type_options = ['info', 'investigators', 'required_docs', 'details'] + IRB_PRO_CATEGORIES_FILE = "irb_pro_categories.xls" def get_description(self): return """StudyInfo [TYPE], where TYPE is one of 'info', 'investigators','required_docs', or 'details' @@ -37,22 +41,60 @@ class StudyInfo(Script): if cmd == 'investigators': study_info["investigators"] = self.pb.get_investigators(study_id) if cmd == 'required_docs': - study_info["required_docs"] = self.pb.get_required_docs(study_id) + study_info["required_docs"] = self.get_required_docs(study_id) if cmd == 'details': study_info["details"] = self.pb.get_study_details(study_id) task.data["study"] = study_info - def get_required_docs(self, study_id): - required_docs = self.pb.get_required_docs(study_id) + """Takes data from the protocol builder, and merges it with data from the IRB Pro Categories spreadsheet to return + pertinant details about the required documents.""" + pb_docs = self.pb.get_required_docs(study_id) + data_frame = self.get_file_reference_dictionary() + required_docs = [] + for doc in pb_docs: + required_docs.append(RequiredDocument.form_pb_and_spread_sheet(doc, data_frame)) return required_docs + def get_file_reference_dictionary(self): + """Loads up the xsl file that contains the IRB Pro Categories and converts it to a Panda's data frame for processing.""" + data_model = FileService.get_reference_file_data(StudyInfo.IRB_PRO_CATEGORIES_FILE) + xls = ExcelFile(data_model.data) + df = xls.parse(xls.sheet_names[0]) + return df + + # Verifies that information is available for this script task to function + # correctly. Returns a list of validation errors. + @staticmethod + def validate(): + errors = [] + try: + FileService.get_reference_file_data(StudyInfo.IRB_PRO_CATEGORIES_FILE) + except ApiError as ae: + errors.append(ScriptValidationError.from_api_error(ae)) + return errors + +class RequiredDocument(object): + def __init__(self, pb_id, pb_name, category1, category2, category3, who_uploads, required, total_uploaded): + self.protocol_builder_id = pb_id + self.protocol_builder_name = pb_name + self.category1 = category1 + self.category2 = category2 + self.category3 = category3 + self.who_uploads = who_uploads + self.required = required + self.total_uploaded = total_uploaded + + @classmethod + def form_pb_and_spread_sheet(cls, pb_data, sheet_data): + """Generates a Required Document record from protobol builder record and a Panda's data sheet""" + return cls(pb_id=pb_data['AUXDOCID'], + pb_name=pb_data['AUXDOC'], + category1="") - - - - - - - +class RequiredDocumentSchema(ma.Schema): + class Meta: + model = RequiredDocument + fields = ["pb_id", "pb_name", "category1", "category2", "category3", + "who_uploads", "required", "total_uploaded"] diff --git a/crc/services/file_service.py b/crc/services/file_service.py index 3042c12e..bed4072b 100644 --- a/crc/services/file_service.py +++ b/crc/services/file_service.py @@ -145,3 +145,4 @@ class FileService(object): if not file_model: raise ApiError("file_not_found", "There is no reference file with the name '%s'" % file_name) return FileService.get_file_data(file_model.id, file_model) + diff --git a/migrations/versions/2b787cc265d8_merge_d_and_f.py b/migrations/versions/2b787cc265d8_merge_d_and_f.py new file mode 100644 index 00000000..2b676ae6 --- /dev/null +++ b/migrations/versions/2b787cc265d8_merge_d_and_f.py @@ -0,0 +1,24 @@ +"""merge D and F + +Revision ID: 2b787cc265d8 +Revises: 65f3fce6031a, 0c8a2f8db28c +Create Date: 2020-03-18 16:02:50.126232 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2b787cc265d8' +down_revision = ('65f3fce6031a', '0c8a2f8db28c') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/tests/data/two_forms/mods/two_forms_struc_mod.bpmn b/tests/data/two_forms/mods/two_forms_struc_mod.bpmn index c726289a..c3f73610 100644 --- a/tests/data/two_forms/mods/two_forms_struc_mod.bpmn +++ b/tests/data/two_forms/mods/two_forms_struc_mod.bpmn @@ -27,6 +27,7 @@ SequenceFlow_00p5po6 SequenceFlow_17ggqu2 + We have a test that replaces tow_forms with this file, which adds a new step to the process.  A breaking change. diff --git a/tests/test_study_info_service.py b/tests/test_study_info_service.py new file mode 100644 index 00000000..cb22179c --- /dev/null +++ b/tests/test_study_info_service.py @@ -0,0 +1,61 @@ +import os +from unittest.mock import patch + +from crc import app +from crc.models.file import CONTENT_TYPES +from crc.scripts.study_info import StudyInfo +from crc.services.file_service import FileService +from crc.services.protocol_builder import ProtocolBuilderService +from tests.base_test import BaseTest + + +class TestStudyInfoService(BaseTest): + test_uid = "dhf8r" + test_study_id = 1 + + """ + 1. get a list of only the required documents for the study. + 2. For this study, is this document required accroding to the protocol builder? + 3. For ALL uploaded documents, what the total number of files that were uploaded? per instance of this document naming + convention that we are implementing for the IRB. + """ + + def test_get_required_docs(self): + """The Study info service should re-structure required docs to provide + a flatted view of just the documents that are required. + """ + + def create_reference_document(self): + file_path = os.path.join(app.root_path, '..', 'tests', 'data', 'reference', 'irb_documents.xlsx') + file = open(file_path, "rb") + FileService.add_reference_file(StudyInfo.IRB_PRO_CATEGORIES_FILE, + binary_data=file.read(), + content_type=CONTENT_TYPES['xls']) + + + def test_validate_returns_error_if_reference_files_do_not_exist(self): + errors = StudyInfo.validate() + FileService. + self.assertTrue(len(errors) > 0) + self.assertEquals("file_not_found", errors[0].code) + + def test_no_validation_error_when_correct_file_exists(self): + self.create_reference_document() + errors = StudyInfo.validate() + self.assertTrue(len(errors) == 0) + + def test_load_lookup_data(self): + self.create_reference_document() + dict = StudyInfo().get_file_reference_dictionary() + self.assertIsNotNone(dict) + + @patch('crc.services.protocol_builder.requests.get') + def test_get_required_docs(self, mock_get): + mock_get.return_value.ok = True + mock_get.return_value.text = self.protocol_builder_response('required_docs.json') + self.create_reference_document() + studyInfo = StudyInfo() + required_docs = studyInfo.get_required_docs(12) + self.assertIsNotNone(required_docs) + self.assertTrue(len(required_docs) == 5) + self.assertEquals(6, required_docs[0].protocol_builder_id) \ No newline at end of file