├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── bin ├── color_my_terminal.sh ├── configure_venv_locally.sh ├── predict.sh ├── start_server.sh ├── test.sh ├── test_model_metrics.sh └── train_model.sh ├── ci.gocd.yaml ├── docs ├── FAQs.md └── mlflow.md ├── models └── _keep ├── requirements-dev.txt ├── requirements.txt └── src ├── app.py ├── app_with_logging.py ├── settings.py ├── tests ├── test.py └── test_model_metrics.py └── train.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .venv-local -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv*/ 2 | 3 | **/__pycache__ 4 | models/*.joblib 5 | !models/keep 6 | 7 | # ignoring all json files in project root folder to prevent accidental commits of secrets 8 | /*.json 9 | 10 | # IDE config 11 | .vscode/ 12 | .idea/ 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ================================================================= # 2 | # ------------ First stage in our multistage Dockerfile ----------- # 3 | # ================================================================= # 4 | FROM python:3.6-slim as Base 5 | 6 | RUN apt-get update \ 7 | && apt-get install -y curl git 8 | 9 | RUN pip install pipenv 10 | 11 | WORKDIR /home/ml-app-template 12 | 13 | # COPY requirements.txt /home/ml-app-template/requirements.txt 14 | # RUN pip install -r requirements.txt 15 | 16 | ADD Pipfile.lock /home/ml-app-template/Pipfile.lock 17 | ADD Pipfile /home/ml-app-template/Pipfile 18 | RUN pipenv install --deploy 19 | 20 | COPY . /home/ml-app-template 21 | 22 | # ================================================================= # 23 | # ------------ Second stage in our multistage Dockerfile ---------- # 24 | # ================================================================= # 25 | 26 | FROM Base as Build 27 | 28 | ARG CI 29 | ENV CI=$CI 30 | 31 | RUN pipenv run /home/ml-app-template/bin/train_model.sh 32 | 33 | CMD ["pipenv run /home/ml-app-template/bin/start_server.sh"] 34 | 35 | # ================================================================= # 36 | # ------------ Third stage in our multistage Dockerfile ----------- # 37 | # ================================================================= # 38 | FROM Build as Dev 39 | 40 | # COPY requirements-dev.txt /home/ml-app-template/requirements-dev.txt 41 | # RUN pip install -r /home/ml-app-template/requirements-dev.txt 42 | RUN pipenv install --dev 43 | 44 | 45 | RUN git config --global credential.helper 'cache --timeout=36000' 46 | 47 | EXPOSE 8080 48 | 49 | CMD ["pipenv run /home/ml-app-template/bin/start_server.sh"] 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ThoughtWorks Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | jupyter = "*" 8 | matplotlib = "*" 9 | nose-watch = "*" 10 | pylint = "*" 11 | rednose = "*" 12 | seaborn = "*" 13 | 14 | [packages] 15 | Flask = "*" 16 | fluent-logger = "*" 17 | gunicorn = "*" 18 | joblib = "*" 19 | lime = "*" 20 | mlflow = "*" 21 | nose = "*" 22 | numpy = "*" 23 | pandas = "*" 24 | scikit-learn = "*" 25 | 26 | [requires] 27 | python_version = "3.6" 28 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "6d4c3c3b4215a672dad487d9c2cfd674c7e91efbf7a649f92a9d8dcfa00b66ac" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "argparse": { 20 | "hashes": [ 21 | "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4", 22 | "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314" 23 | ], 24 | "version": "==1.4.0" 25 | }, 26 | "boto3": { 27 | "hashes": [ 28 | "sha256:59782c178af2d5acf66315fe96b3cd1dc075109c0296c384e18a6c4143c0745d", 29 | "sha256:758787b5ad7c5e2aa2979b0671129491fbab00a7b84a26532cd6b9d073ed862b" 30 | ], 31 | "version": "==1.9.156" 32 | }, 33 | "botocore": { 34 | "hashes": [ 35 | "sha256:00b72bc2104a2f56513bc40ce380d0605262decc9fe3b2ce840da48f257598d7", 36 | "sha256:a12a817bf1faf36837bc2d371aacfb5c7c324e0e9f0b3af94b9930cfcd8d62ea" 37 | ], 38 | "version": "==1.12.156" 39 | }, 40 | "certifi": { 41 | "hashes": [ 42 | "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", 43 | "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" 44 | ], 45 | "version": "==2019.3.9" 46 | }, 47 | "chardet": { 48 | "hashes": [ 49 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 50 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 51 | ], 52 | "version": "==3.0.4" 53 | }, 54 | "click": { 55 | "hashes": [ 56 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 57 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 58 | ], 59 | "version": "==7.0" 60 | }, 61 | "cloudpickle": { 62 | "hashes": [ 63 | "sha256:7d43c4d0c7e9735ee8a352c96f84031dabd6676170c4e5e0585a469cc4769f22", 64 | "sha256:d119fb6627e65d43541bdf927975a0f2a5d40074a30d691c8585f761d721bf49" 65 | ], 66 | "version": "==1.1.1" 67 | }, 68 | "configparser": { 69 | "hashes": [ 70 | "sha256:8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32", 71 | "sha256:da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75" 72 | ], 73 | "version": "==3.7.4" 74 | }, 75 | "cycler": { 76 | "hashes": [ 77 | "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", 78 | "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" 79 | ], 80 | "version": "==0.10.0" 81 | }, 82 | "databricks-cli": { 83 | "hashes": [ 84 | "sha256:9db04827ded11313892b7f190f95af00677b91743c4b7009e1094f7f1ffc6bc7", 85 | "sha256:e61f441e6ed74440cc320db4c5c84b866298f4ffc6450c16f2af2bee72b44882" 86 | ], 87 | "version": "==0.8.6" 88 | }, 89 | "decorator": { 90 | "hashes": [ 91 | "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", 92 | "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" 93 | ], 94 | "version": "==4.4.0" 95 | }, 96 | "docker": { 97 | "hashes": [ 98 | "sha256:3db499d4d25847fed86acf8e100c989f7bc0f75a6fff6c52855726ada1d124f6", 99 | "sha256:f61c37d721b489b7d55ef631b241be2d6a5884c3ffe63dc8f7dd9a3c3cd60489" 100 | ], 101 | "version": "==4.0.1" 102 | }, 103 | "docutils": { 104 | "hashes": [ 105 | "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", 106 | "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", 107 | "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" 108 | ], 109 | "version": "==0.14" 110 | }, 111 | "entrypoints": { 112 | "hashes": [ 113 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 114 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 115 | ], 116 | "version": "==0.3" 117 | }, 118 | "flask": { 119 | "hashes": [ 120 | "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3", 121 | "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61" 122 | ], 123 | "index": "pypi", 124 | "version": "==1.0.3" 125 | }, 126 | "fluent-logger": { 127 | "hashes": [ 128 | "sha256:afa2f46616eed226be06e49f3c211a65d484f55cdef7333edc063bca22887127", 129 | "sha256:d953cc20a445812ba79e624d4d8662427cb386eaf7033ab4143e947a30ff3788" 130 | ], 131 | "index": "pypi", 132 | "version": "==0.9.3" 133 | }, 134 | "gitdb2": { 135 | "hashes": [ 136 | "sha256:83361131a1836661a155172932a13c08bda2db3674e4caa32368aa6eb02f38c2", 137 | "sha256:e3a0141c5f2a3f635c7209d56c496ebe1ad35da82fe4d3ec4aaa36278d70648a" 138 | ], 139 | "version": "==2.0.5" 140 | }, 141 | "gitpython": { 142 | "hashes": [ 143 | "sha256:563221e5a44369c6b79172f455584c9ebbb122a13368cc82cb4b5addff788f82", 144 | "sha256:8237dc5bfd6f1366abeee5624111b9d6879393d84745a507de0fda86043b65a8" 145 | ], 146 | "version": "==2.1.11" 147 | }, 148 | "gunicorn": { 149 | "hashes": [ 150 | "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", 151 | "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" 152 | ], 153 | "index": "pypi", 154 | "version": "==19.9.0" 155 | }, 156 | "idna": { 157 | "hashes": [ 158 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 159 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 160 | ], 161 | "version": "==2.8" 162 | }, 163 | "imageio": { 164 | "hashes": [ 165 | "sha256:1a2bbbb7cd38161340fa3b14d806dfbf914abf3ee6fd4592af2afb87d049f209", 166 | "sha256:42e65aadfc3d57a1043615c92bdf6319b67589e49a0aae2b985b82144aceacad" 167 | ], 168 | "version": "==2.5.0" 169 | }, 170 | "itsdangerous": { 171 | "hashes": [ 172 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 173 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 174 | ], 175 | "version": "==1.1.0" 176 | }, 177 | "jinja2": { 178 | "hashes": [ 179 | "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", 180 | "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" 181 | ], 182 | "version": "==2.10.1" 183 | }, 184 | "jmespath": { 185 | "hashes": [ 186 | "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", 187 | "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" 188 | ], 189 | "version": "==0.9.4" 190 | }, 191 | "joblib": { 192 | "hashes": [ 193 | "sha256:21e0c34a69ad7fde4f2b1f3402290e9ec46f545f15f1541c582edfe05d87b63a", 194 | "sha256:315d6b19643ec4afd4c41c671f9f2d65ea9d787da093487a81ead7b0bac94524" 195 | ], 196 | "index": "pypi", 197 | "version": "==0.13.2" 198 | }, 199 | "kiwisolver": { 200 | "hashes": [ 201 | "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", 202 | "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", 203 | "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", 204 | "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", 205 | "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", 206 | "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", 207 | "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", 208 | "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", 209 | "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", 210 | "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", 211 | "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", 212 | "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", 213 | "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", 214 | "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", 215 | "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", 216 | "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", 217 | "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", 218 | "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", 219 | "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", 220 | "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", 221 | "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", 222 | "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", 223 | "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", 224 | "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", 225 | "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", 226 | "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", 227 | "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", 228 | "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f" 229 | ], 230 | "version": "==1.1.0" 231 | }, 232 | "lime": { 233 | "hashes": [ 234 | "sha256:ab386beef8cfe95633ed738c64f72b3b6718af26a7169788ce496f29e468ae6d" 235 | ], 236 | "index": "pypi", 237 | "version": "==0.1.1.34" 238 | }, 239 | "markupsafe": { 240 | "hashes": [ 241 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 242 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 243 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 244 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 245 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 246 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 247 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 248 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 249 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 250 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 251 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 252 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 253 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 254 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 255 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 256 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 257 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 258 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 259 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 260 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 261 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 262 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 263 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 264 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 265 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 266 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 267 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 268 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 269 | ], 270 | "version": "==1.1.1" 271 | }, 272 | "matplotlib": { 273 | "hashes": [ 274 | "sha256:08d9bc2e2acef42965256acd5015dc2c899cbd53e01bf4214c5510c7ea0efd2d", 275 | "sha256:1e0213f87cc0076f7b0c4c251d7e23601e2419cd98691df79edb95517ba06f0c", 276 | "sha256:1f31053f660df5f0310118d7f5bd1e8025170e9773f0bebe8fec486d0926adf6", 277 | "sha256:399bf6352633aeeb45ca55c6c943fa2738022fb17ae498c32a142ced0b41528d", 278 | "sha256:409a5894efb810d630d2512449c7a4394de9a4d15fc6394e26a409b17d9cc18c", 279 | "sha256:5c5ef5cf1bc8f483123102e2615644937af7d4c01d100acc72bf74a044a78717", 280 | "sha256:d0052be5cdfa27018bb08194b8812c47cb985d60eb682e1809c76e9600839516", 281 | "sha256:e7d6620d145ca9f6c3e88248e5734b6fda430e75e70755b887e48f8e9bc1de2a", 282 | "sha256:f3d8b6bccc577e4e5ecbd58fdd63cacb8e58f0ed1e97616a7f7a7baaf4b8d036" 283 | ], 284 | "version": "==3.1.0" 285 | }, 286 | "mleap": { 287 | "hashes": [ 288 | "sha256:eb384a239747f319297a0b4f2bb5c076f01cfd942d7f72a173494606f402fdad" 289 | ], 290 | "version": "==0.8.1" 291 | }, 292 | "mlflow": { 293 | "hashes": [ 294 | "sha256:003480d446d43d7ccb4e0639d57c4875df0a2c392c97e3189750395cf3406389", 295 | "sha256:bcd8518e4269c058792caf0419b45346f9bf8f92d48ac66ef136e3bb87e8ef72" 296 | ], 297 | "index": "pypi", 298 | "version": "==0.9.1" 299 | }, 300 | "msgpack": { 301 | "hashes": [ 302 | "sha256:26cb40116111c232bc235ce131cc3b4e76549088cb154e66a2eb8ff6fcc907ec", 303 | "sha256:300fd3f2c664a3bf473d6a952f843b4a71454f4c592ed7e74a36b205c1782d28", 304 | "sha256:3129c355342853007de4a2a86e75eab966119733eb15748819b6554363d4e85c", 305 | "sha256:31f6d645ee5a97d59d3263fab9e6be76f69fa131cddc0d94091a3c8aca30d67a", 306 | "sha256:3ce7ef7ee2546c3903ca8c934d09250531b80c6127e6478781ae31ed835aac4c", 307 | "sha256:4008c72f5ef2b7936447dcb83db41d97e9791c83221be13d5e19db0796df1972", 308 | "sha256:62bd8e43d204580308d477a157b78d3fee2fb4c15d32578108dc5d89866036c8", 309 | "sha256:70cebfe08fb32f83051971264466eadf183101e335d8107b80002e632f425511", 310 | "sha256:72cb7cf85e9df5251abd7b61a1af1fb77add15f40fa7328e924a9c0b6bc7a533", 311 | "sha256:7c55649965c35eb32c499d17dadfb8f53358b961582846e1bc06f66b9bccc556", 312 | "sha256:86b963a5de11336ec26bc4f839327673c9796b398b9f1fe6bb6150c2a5d00f0f", 313 | "sha256:8c73c9bcdfb526247c5e4f4f6cf581b9bb86b388df82cfcaffde0a6e7bf3b43a", 314 | "sha256:8e68c76c6aff4849089962d25346d6784d38e02baa23ffa513cf46be72e3a540", 315 | "sha256:97ac6b867a8f63debc64f44efdc695109d541ecc361ee2dce2c8884ab37360a1", 316 | "sha256:9d4f546af72aa001241d74a79caec278bcc007b4bcde4099994732e98012c858", 317 | "sha256:a28e69fe5468c9f5251c7e4e7232286d71b7dfadc74f312006ebe984433e9746", 318 | "sha256:fd509d4aa95404ce8d86b4e32ce66d5d706fd6646c205e1c2a715d87078683a2" 319 | ], 320 | "version": "==0.6.1" 321 | }, 322 | "networkx": { 323 | "hashes": [ 324 | "sha256:8311ddef63cf5c5c5e7c1d0212dd141d9a1fe3f474915281b73597ed5f1d4e3d" 325 | ], 326 | "version": "==2.3" 327 | }, 328 | "nose": { 329 | "hashes": [ 330 | "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", 331 | "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", 332 | "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" 333 | ], 334 | "index": "pypi", 335 | "version": "==1.3.7" 336 | }, 337 | "nose-exclude": { 338 | "hashes": [ 339 | "sha256:f78fa8b41eeb815f0486414f710f1eea0949e346cfb11d59ba6295ed69e84304" 340 | ], 341 | "version": "==0.5.0" 342 | }, 343 | "numpy": { 344 | "hashes": [ 345 | "sha256:0e2eed77804b2a6a88741f8fcac02c5499bba3953ec9c71e8b217fad4912c56c", 346 | "sha256:1c666f04553ef70fda54adf097dbae7080645435fc273e2397f26bbf1d127bbb", 347 | "sha256:1f46532afa7b2903bfb1b79becca2954c0a04389d19e03dc73f06b039048ac40", 348 | "sha256:315fa1b1dfc16ae0f03f8fd1c55f23fd15368710f641d570236f3d78af55e340", 349 | "sha256:3d5fcea4f5ed40c3280791d54da3ad2ecf896f4c87c877b113576b8280c59441", 350 | "sha256:48241759b99d60aba63b0e590332c600fc4b46ad597c9b0a53f350b871ef0634", 351 | "sha256:4b4f2924b36d857cf302aec369caac61e43500c17eeef0d7baacad1084c0ee84", 352 | "sha256:54fe3b7ed9e7eb928bbc4318f954d133851865f062fa4bbb02ef8940bc67b5d2", 353 | "sha256:5a8f021c70e6206c317974c93eaaf9bc2b56295b6b1cacccf88846e44a1f33fc", 354 | "sha256:754a6be26d938e6ca91942804eb209307b73f806a1721176278a6038869a1686", 355 | "sha256:771147e654e8b95eea1293174a94f34e2e77d5729ad44aefb62fbf8a79747a15", 356 | "sha256:78a6f89da87eeb48014ec652a65c4ffde370c036d780a995edaeb121d3625621", 357 | "sha256:7fde5c2a3a682a9e101e61d97696687ebdba47637611378b4127fe7e47fdf2bf", 358 | "sha256:80d99399c97f646e873dd8ce87c38cfdbb668956bbc39bc1e6cac4b515bba2a0", 359 | "sha256:88a72c1e45a0ae24d1f249a529d9f71fe82e6fa6a3fd61414b829396ec585900", 360 | "sha256:a4f4460877a16ac73302a9c077ca545498d9fe64e6a81398d8e1a67e4695e3df", 361 | "sha256:a61255a765b3ac73ee4b110b28fccfbf758c985677f526c2b4b39c48cc4b509d", 362 | "sha256:ab4896a8c910b9a04c0142871d8800c76c8a2e5ff44763513e1dd9d9631ce897", 363 | "sha256:abbd6b1c2ef6199f4b7ca9f818eb6b31f17b73a6110aadc4e4298c3f00fab24e", 364 | "sha256:b16d88da290334e33ea992c56492326ea3b06233a00a1855414360b77ca72f26", 365 | "sha256:b78a1defedb0e8f6ae1eb55fa6ac74ab42acc4569c3a2eacc2a407ee5d42ebcb", 366 | "sha256:cfef82c43b8b29ca436560d51b2251d5117818a8d1fb74a8384a83c096745dad", 367 | "sha256:d160e57731fcdec2beda807ebcabf39823c47e9409485b5a3a1db3a8c6ce763e" 368 | ], 369 | "index": "pypi", 370 | "version": "==1.16.3" 371 | }, 372 | "pandas": { 373 | "hashes": [ 374 | "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", 375 | "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", 376 | "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", 377 | "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", 378 | "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", 379 | "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", 380 | "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", 381 | "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", 382 | "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", 383 | "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", 384 | "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", 385 | "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", 386 | "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", 387 | "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", 388 | "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", 389 | "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", 390 | "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", 391 | "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", 392 | "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", 393 | "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" 394 | ], 395 | "index": "pypi", 396 | "version": "==0.24.2" 397 | }, 398 | "pillow": { 399 | "hashes": [ 400 | "sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55", 401 | "sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479", 402 | "sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a", 403 | "sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d", 404 | "sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb", 405 | "sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb", 406 | "sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8", 407 | "sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72", 408 | "sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754", 409 | "sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f", 410 | "sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce", 411 | "sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601", 412 | "sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5", 413 | "sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734", 414 | "sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b", 415 | "sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b", 416 | "sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1", 417 | "sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91", 418 | "sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8", 419 | "sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239", 420 | "sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af", 421 | "sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8", 422 | "sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232", 423 | "sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a", 424 | "sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3", 425 | "sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062" 426 | ], 427 | "version": "==6.0.0" 428 | }, 429 | "protobuf": { 430 | "hashes": [ 431 | "sha256:21e395d7959551e759d604940a115c51c6347d90a475c9baf471a1a86b5604a9", 432 | "sha256:57e05e16955aee9e6a0389fcbd58d8289dd2420e47df1a1096b3a232c26eb2dd", 433 | "sha256:67819e8e48a74c68d87f25cad9f40edfe2faf278cdba5ca73173211b9213b8c9", 434 | "sha256:75da7d43a2c8a13b0bc7238ab3c8ae217cbfd5979d33b01e98e1f78defb2d060", 435 | "sha256:78e08371e236f193ce947712c072542ff19d0043ab5318c2ea46bbc2aaebdca6", 436 | "sha256:7ee5b595db5abb0096e8c4755e69c20dfad38b2d0bcc9bc7bafc652d2496b471", 437 | "sha256:86260ecfe7a66c0e9d82d2c61f86a14aa974d340d159b829b26f35f710f615db", 438 | "sha256:92c77db4bd33ea4ee5f15152a835273f2338a5246b2cbb84bab5d0d7f6e9ba94", 439 | "sha256:9c7b90943e0e188394b4f068926a759e3b4f63738190d1ab3d500d53b9ce7614", 440 | "sha256:a77f217ea50b2542bae5b318f7acee50d9fc8c95dd6d3656eaeff646f7cab5ee", 441 | "sha256:ad589ed1d1f83db22df867b10e01fe445516a5a4d7cfa37fe3590a5f6cfc508b", 442 | "sha256:b06a794901bf573f4b2af87e6139e5cd36ac7c91ac85d7ae3fe5b5f6fc317513", 443 | "sha256:bd8592cc5f8b4371d0bad92543370d4658dc41a5ccaaf105597eb5524c616291", 444 | "sha256:be48e5a6248a928ec43adf2bea037073e5da692c0b3c10b34f9904793bd63138", 445 | "sha256:cc5eb13f5ccc4b1b642cc147c2cdd121a34278b341c7a4d79e91182fff425836", 446 | "sha256:cd3b0e0ad69b74ee55e7c321f52a98effed2b4f4cc9a10f3683d869de00590d5", 447 | "sha256:d6e88c4920660aa75c0c2c4b53407aef5efd9a6e0ca7d2fc84d79aba2ccbda3a", 448 | "sha256:ec3c49b6d247152e19110c3a53d9bb4cf917747882017f70796460728b02722e" 449 | ], 450 | "version": "==3.7.1" 451 | }, 452 | "pyparsing": { 453 | "hashes": [ 454 | "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", 455 | "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" 456 | ], 457 | "version": "==2.4.0" 458 | }, 459 | "python-dateutil": { 460 | "hashes": [ 461 | "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", 462 | "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" 463 | ], 464 | "markers": "python_version >= '2.7'", 465 | "version": "==2.8.0" 466 | }, 467 | "pytz": { 468 | "hashes": [ 469 | "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", 470 | "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" 471 | ], 472 | "version": "==2019.1" 473 | }, 474 | "pywavelets": { 475 | "hashes": [ 476 | "sha256:18b193b67937e805a8e79c036bd2aa4ea3a357737256efeefdabd19c95083c3b", 477 | "sha256:1d7ba03baa81938b17d4819db36f018e680d929af329062af4d4b6d6236d02ba", 478 | "sha256:21f39d86cc35e003576fc1400b15534e2999570418fcdb17ea62d1ff8773b076", 479 | "sha256:250412f482d5cb358b7ec323b2a783d91e5cfc337fdf8fde3c59bf2c35d6366f", 480 | "sha256:25c0c592bf43eaffb4d3c6b6444b14c7407db750b6f2d344d809d4af934319d9", 481 | "sha256:2f2cdd96e4882b0c18e75cc90c4710de429ac226ce53b58a90c7420e3e307631", 482 | "sha256:3abed8dcd3e94ead72ee8010b494df5a9bdbdd5e39129d52fbf8066efa323a51", 483 | "sha256:3e99ab8feeb47755738fb8deb8154c9604c4a7996b1b7db6b090475105ca7c92", 484 | "sha256:64926c4c78dd690ec0d61be20f7c27cfbde6de9fa66ab8205eb079d9db6927fc", 485 | "sha256:687ec8877c10e3a03595ad167d1ea2662bc1ab13ef43d63a6e207a53b2ee4c26", 486 | "sha256:6af0077c7a4c9935aa64301f6942468b494656b8812e801d4a635cf42088f96c", 487 | "sha256:7215856a5d2e1a2dccca1f71d912ee6a7387086f3b3adcb55d7c41314c6abb0c", 488 | "sha256:9e47b241533add77961093b1e40cfff031597d429e91ad7c675838be0f7cc0df", 489 | "sha256:9e782f49dca57bc0fd2a40c0917949d77be2ecc999ccd44fff57fb10aa214135", 490 | "sha256:a12c7a6258c0015d2c75d88b87393ee015494551f049009e8b63eafed2d78efc", 491 | "sha256:a5027d47484498e70391b311a2688c4f74294de629b982ed17be57be4c77ade7", 492 | "sha256:ab02363467ee3cb222c5b425bc53453270ddae72bce313e72fd14616692d725a", 493 | "sha256:adc79308c65a2007bdbd5846fda70088e7ead0ef0a5a6f44d08829e9478a907f", 494 | "sha256:afaaee392450785a346d9e5e5f6e5307b13958d8b0633818632cb38972a7752a", 495 | "sha256:b26b836c7f71df7b2779e62d1338367cfe37b98324e9b0d54b428ac030e3d1f0", 496 | "sha256:b9adbc27d70a2626c235a18b41315de2832c384651d03383db7a58c2a2bccc6f", 497 | "sha256:bd6e62efb7839fd6bf894b7b9aec6d58be0c44a38ea0c9f3c5bea834d84d05eb", 498 | "sha256:de083a3a71576a9c3d8ba73b6f0425e4690d6ac6e480562f30bec5cb20667324", 499 | "sha256:e89551257233a3da717a9e6e2e303243df75faffe0b6781d21c15eb9d682ec6d", 500 | "sha256:eafb7d609c41a04028144f4b6697792f448554960ef353244aaf0a5883263543", 501 | "sha256:fb3ee9f65d25ee5c89104e533d5f341c253cdb9543ef6fcd6dfa599b12e84f1c" 502 | ], 503 | "version": "==1.0.3" 504 | }, 505 | "pyyaml": { 506 | "hashes": [ 507 | "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", 508 | "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", 509 | "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", 510 | "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", 511 | "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", 512 | "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", 513 | "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", 514 | "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", 515 | "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", 516 | "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", 517 | "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" 518 | ], 519 | "version": "==5.1" 520 | }, 521 | "querystring-parser": { 522 | "hashes": [ 523 | "sha256:4c31b547c77b927a8aceccd6fa37f8152aa92fe5654a43d7363fcee8cf6d8aea" 524 | ], 525 | "version": "==1.2.3" 526 | }, 527 | "requests": { 528 | "hashes": [ 529 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 530 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 531 | ], 532 | "version": "==2.22.0" 533 | }, 534 | "s3transfer": { 535 | "hashes": [ 536 | "sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e", 537 | "sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021" 538 | ], 539 | "version": "==0.2.0" 540 | }, 541 | "scikit-image": { 542 | "hashes": [ 543 | "sha256:0fb4f61ba28e7034dc490b7fd37a35428493cc1180205991aea41ab78c1a3b2a", 544 | "sha256:117244d206f014fcdc7fd3b03dc25ffd3c32be49c5103b916dadcf3c268ce629", 545 | "sha256:2dee5c19893bbfebeb30c2edd47e3092d84fe671e1278c8e1b8ab0b541b88590", 546 | "sha256:2edf2189a89d68e7909bfe939f6f9978d4807c4c2c6464b9d23944171272efe5", 547 | "sha256:56f8ec5106c1b6037f25395eb6a2c29cbbb1baa9d8cedb48a6488a697f9e0c02", 548 | "sha256:5c24501fbe0000bf215418ced56fc718ae5bbd004df7dc460fd3fb095385dec5", 549 | "sha256:6665b00b649237171eb0c34ec7d2c5ee96f51cc826edf1b70654fc05fddec7a8", 550 | "sha256:676838be922fdc7c65a1bfa71409b382874d60e2a7a0c968fe832f63b485842d", 551 | "sha256:6b8697a52e1e3c2663f74a0a972cc601d2ceb5b3299cc9be3c3b6ac41d76d238", 552 | "sha256:91208ff13381ccbacce8a38a771e9d9d8982dce146581bcf6656aa8fd05d06ea", 553 | "sha256:a8fb7dc56b14656768fdf21d4325bf82f543fad51dc83c24aa6a27456a946a2d", 554 | "sha256:bea86976ab9b7bebca43721b3ff8fd74a002e1fd95e7600987cd42664fde8a39", 555 | "sha256:ce9354571e1323ed98251723a7e5ffb3526ddd3b533a27aaca4ebd13ed479c56", 556 | "sha256:df111e654b47e5ea456c50553debe4c5ddd97258894c7ad3b7f2f9f10798e053", 557 | "sha256:ed6d2dbb54a68b200df8783fcb17c406ece20420c8c86508ab0f5966e8631da5", 558 | "sha256:f0491621a6b1d2828d47acaf41d3167a647eaae44ef8fcf83b72eb3e1cc7ac51" 559 | ], 560 | "version": "==0.15.0" 561 | }, 562 | "scikit-learn": { 563 | "hashes": [ 564 | "sha256:051c53f9e900b0e9eccff2391f5317d1673d72e842bcbcd3e5d0b132459086ed", 565 | "sha256:0aafc312a55ebf58073151b9308761a5fcfa45b7f7730cea4b1f066f824c72db", 566 | "sha256:185d88ee4955cd68d7ff57356d1dd99cfc2de4b6aa5e5d679cafbc9df54716ff", 567 | "sha256:195465c39daded4f3ef8759291ffde81365486d4293e63dd9e32de0f569ecbbf", 568 | "sha256:4a6398500d035a4402476a2e3ae9f65a7a3f1b366ec6a7f6dd45c289f72dc954", 569 | "sha256:56f14e98632fb9237e7d005c6d8e346d01fa67f7b92f5f5d57a0bd06c741f9f6", 570 | "sha256:77092513dd780e12affde46a6394b52947db3fc00cf1d8c1c8eede52b37591d1", 571 | "sha256:7d2cdfe16b1ae6f9a1760b69be27c2004a84fc362984f930df135c847c47b765", 572 | "sha256:82c3450fc375f27e3529fa05fec627c9fc75915e05fcd55de43f193b3aa907af", 573 | "sha256:a5fba00d9037b62b0e0906f64efe9e4a754e556bc091cc12f84bc81655b4a414", 574 | "sha256:acba6bf5928e415b6296799a7aa069b66254c9440bce88ed2e5915865a317093", 575 | "sha256:b474f00d2533f18761fb17fb0950b27e72baf0796176247b5a7cf0ee369790ee", 576 | "sha256:ca45e0def97f73a828cee417174fafa0ab35a41f8bdca4424120a29c5589c548", 577 | "sha256:f09e544a6756afbd9d31e1d8ddfde5a2c9c17f6d4274104c988fceb611e2d5c5", 578 | "sha256:f979bb85cbfd9ed4d54709d86ab8893b316726abd1c9ab04abe7e6414b71b753", 579 | "sha256:fb4c7a2294447515fffec33c1f5eedbe942e9be56edb8c6619607e7882531d40" 580 | ], 581 | "index": "pypi", 582 | "version": "==0.21.2" 583 | }, 584 | "scipy": { 585 | "hashes": [ 586 | "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", 587 | "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", 588 | "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", 589 | "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", 590 | "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", 591 | "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", 592 | "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", 593 | "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", 594 | "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", 595 | "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", 596 | "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", 597 | "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", 598 | "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", 599 | "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", 600 | "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", 601 | "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee" 602 | ], 603 | "version": "==1.3.0" 604 | }, 605 | "simplejson": { 606 | "hashes": [ 607 | "sha256:067a7177ddfa32e1483ba5169ebea1bc2ea27f224853211ca669325648ca5642", 608 | "sha256:2fc546e6af49fb45b93bbe878dea4c48edc34083729c0abd09981fe55bdf7f91", 609 | "sha256:354fa32b02885e6dae925f1b5bbf842c333c1e11ea5453ddd67309dc31fdb40a", 610 | "sha256:37e685986cf6f8144607f90340cff72d36acf654f3653a6c47b84c5c38d00df7", 611 | "sha256:3af610ee72efbe644e19d5eaad575c73fb83026192114e5f6719f4901097fce2", 612 | "sha256:3b919fc9cf508f13b929a9b274c40786036b31ad28657819b3b9ba44ba651f50", 613 | "sha256:3dd289368bbd064974d9a5961101f080e939cbe051e6689a193c99fb6e9ac89b", 614 | "sha256:6c3258ffff58712818a233b9737fe4be943d306c40cf63d14ddc82ba563f483a", 615 | "sha256:75e3f0b12c28945c08f54350d91e624f8dd580ab74fd4f1bbea54bc6b0165610", 616 | "sha256:b1f329139ba647a9548aa05fb95d046b4a677643070dc2afc05fa2e975d09ca5", 617 | "sha256:ee9625fc8ee164902dfbb0ff932b26df112da9f871c32f0f9c1bcf20c350fe2a", 618 | "sha256:fb2530b53c28f0d4d84990e945c2ebb470edb469d63e389bf02ff409012fe7c5" 619 | ], 620 | "version": "==3.16.0" 621 | }, 622 | "six": { 623 | "hashes": [ 624 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 625 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 626 | ], 627 | "version": "==1.12.0" 628 | }, 629 | "smmap2": { 630 | "hashes": [ 631 | "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde", 632 | "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a" 633 | ], 634 | "version": "==2.0.5" 635 | }, 636 | "sqlparse": { 637 | "hashes": [ 638 | "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", 639 | "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" 640 | ], 641 | "version": "==0.3.0" 642 | }, 643 | "tabulate": { 644 | "hashes": [ 645 | "sha256:8af07a39377cee1103a5c8b3330a421c2d99b9141e9cc5ddd2e3263fea416943" 646 | ], 647 | "version": "==0.8.3" 648 | }, 649 | "urllib3": { 650 | "hashes": [ 651 | "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", 652 | "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" 653 | ], 654 | "markers": "python_version >= '3.4'", 655 | "version": "==1.25.3" 656 | }, 657 | "websocket-client": { 658 | "hashes": [ 659 | "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", 660 | "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" 661 | ], 662 | "version": "==0.56.0" 663 | }, 664 | "werkzeug": { 665 | "hashes": [ 666 | "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", 667 | "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" 668 | ], 669 | "version": "==0.15.4" 670 | } 671 | }, 672 | "develop": { 673 | "appnope": { 674 | "hashes": [ 675 | "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", 676 | "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" 677 | ], 678 | "markers": "sys_platform == 'darwin'", 679 | "version": "==0.1.0" 680 | }, 681 | "argh": { 682 | "hashes": [ 683 | "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", 684 | "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65" 685 | ], 686 | "version": "==0.26.2" 687 | }, 688 | "astroid": { 689 | "hashes": [ 690 | "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", 691 | "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" 692 | ], 693 | "version": "==2.2.5" 694 | }, 695 | "attrs": { 696 | "hashes": [ 697 | "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", 698 | "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" 699 | ], 700 | "version": "==19.1.0" 701 | }, 702 | "backcall": { 703 | "hashes": [ 704 | "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", 705 | "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" 706 | ], 707 | "version": "==0.1.0" 708 | }, 709 | "bleach": { 710 | "hashes": [ 711 | "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", 712 | "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" 713 | ], 714 | "version": "==3.1.0" 715 | }, 716 | "colorama": { 717 | "hashes": [ 718 | "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", 719 | "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" 720 | ], 721 | "version": "==0.4.1" 722 | }, 723 | "cycler": { 724 | "hashes": [ 725 | "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", 726 | "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" 727 | ], 728 | "version": "==0.10.0" 729 | }, 730 | "decorator": { 731 | "hashes": [ 732 | "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", 733 | "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" 734 | ], 735 | "version": "==4.4.0" 736 | }, 737 | "defusedxml": { 738 | "hashes": [ 739 | "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", 740 | "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" 741 | ], 742 | "version": "==0.6.0" 743 | }, 744 | "entrypoints": { 745 | "hashes": [ 746 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 747 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 748 | ], 749 | "version": "==0.3" 750 | }, 751 | "ipykernel": { 752 | "hashes": [ 753 | "sha256:346189536b88859937b5f4848a6fd85d1ad0729f01724a411de5cae9b618819c", 754 | "sha256:f0e962052718068ad3b1d8bcc703794660858f58803c3798628817f492a8769c" 755 | ], 756 | "version": "==5.1.1" 757 | }, 758 | "ipython": { 759 | "hashes": [ 760 | "sha256:54c5a8aa1eadd269ac210b96923688ccf01ebb2d0f21c18c3c717909583579a8", 761 | "sha256:e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26" 762 | ], 763 | "markers": "python_version >= '3.3'", 764 | "version": "==7.5.0" 765 | }, 766 | "ipython-genutils": { 767 | "hashes": [ 768 | "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", 769 | "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" 770 | ], 771 | "version": "==0.2.0" 772 | }, 773 | "ipywidgets": { 774 | "hashes": [ 775 | "sha256:0f2b5cde9f272cb49d52f3f0889fdd1a7ae1e74f37b48dac35a83152780d2b7b", 776 | "sha256:a3e224f430163f767047ab9a042fc55adbcab0c24bbe6cf9f306c4f89fdf0ba3" 777 | ], 778 | "version": "==7.4.2" 779 | }, 780 | "isort": { 781 | "hashes": [ 782 | "sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a", 783 | "sha256:f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d" 784 | ], 785 | "version": "==4.3.20" 786 | }, 787 | "jedi": { 788 | "hashes": [ 789 | "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b", 790 | "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c" 791 | ], 792 | "version": "==0.13.3" 793 | }, 794 | "jinja2": { 795 | "hashes": [ 796 | "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", 797 | "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" 798 | ], 799 | "version": "==2.10.1" 800 | }, 801 | "jsonschema": { 802 | "hashes": [ 803 | "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", 804 | "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" 805 | ], 806 | "version": "==3.0.1" 807 | }, 808 | "jupyter": { 809 | "hashes": [ 810 | "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", 811 | "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", 812 | "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f" 813 | ], 814 | "index": "pypi", 815 | "version": "==1.0.0" 816 | }, 817 | "jupyter-client": { 818 | "hashes": [ 819 | "sha256:b5f9cb06105c1d2d30719db5ffb3ea67da60919fb68deaefa583deccd8813551", 820 | "sha256:c44411eb1463ed77548bc2d5ec0d744c9b81c4a542d9637c7a52824e2121b987" 821 | ], 822 | "version": "==5.2.4" 823 | }, 824 | "jupyter-console": { 825 | "hashes": [ 826 | "sha256:308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5", 827 | "sha256:cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568" 828 | ], 829 | "version": "==6.0.0" 830 | }, 831 | "jupyter-core": { 832 | "hashes": [ 833 | "sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970", 834 | "sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7" 835 | ], 836 | "version": "==4.4.0" 837 | }, 838 | "kiwisolver": { 839 | "hashes": [ 840 | "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", 841 | "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", 842 | "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", 843 | "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", 844 | "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", 845 | "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", 846 | "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", 847 | "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", 848 | "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", 849 | "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", 850 | "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", 851 | "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", 852 | "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", 853 | "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", 854 | "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", 855 | "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", 856 | "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", 857 | "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", 858 | "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", 859 | "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", 860 | "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", 861 | "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", 862 | "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", 863 | "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", 864 | "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", 865 | "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", 866 | "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", 867 | "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f" 868 | ], 869 | "version": "==1.1.0" 870 | }, 871 | "lazy-object-proxy": { 872 | "hashes": [ 873 | "sha256:159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", 874 | "sha256:23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", 875 | "sha256:3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", 876 | "sha256:3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", 877 | "sha256:4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", 878 | "sha256:4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", 879 | "sha256:64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", 880 | "sha256:6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", 881 | "sha256:7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", 882 | "sha256:7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", 883 | "sha256:8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", 884 | "sha256:a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", 885 | "sha256:acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", 886 | "sha256:be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", 887 | "sha256:bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", 888 | "sha256:c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", 889 | "sha256:dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", 890 | "sha256:e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1" 891 | ], 892 | "version": "==1.4.1" 893 | }, 894 | "markupsafe": { 895 | "hashes": [ 896 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 897 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 898 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 899 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 900 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 901 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 902 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 903 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 904 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 905 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 906 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 907 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 908 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 909 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 910 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 911 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 912 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 913 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 914 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 915 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 916 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 917 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 918 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 919 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 920 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 921 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 922 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 923 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 924 | ], 925 | "version": "==1.1.1" 926 | }, 927 | "matplotlib": { 928 | "hashes": [ 929 | "sha256:08d9bc2e2acef42965256acd5015dc2c899cbd53e01bf4214c5510c7ea0efd2d", 930 | "sha256:1e0213f87cc0076f7b0c4c251d7e23601e2419cd98691df79edb95517ba06f0c", 931 | "sha256:1f31053f660df5f0310118d7f5bd1e8025170e9773f0bebe8fec486d0926adf6", 932 | "sha256:399bf6352633aeeb45ca55c6c943fa2738022fb17ae498c32a142ced0b41528d", 933 | "sha256:409a5894efb810d630d2512449c7a4394de9a4d15fc6394e26a409b17d9cc18c", 934 | "sha256:5c5ef5cf1bc8f483123102e2615644937af7d4c01d100acc72bf74a044a78717", 935 | "sha256:d0052be5cdfa27018bb08194b8812c47cb985d60eb682e1809c76e9600839516", 936 | "sha256:e7d6620d145ca9f6c3e88248e5734b6fda430e75e70755b887e48f8e9bc1de2a", 937 | "sha256:f3d8b6bccc577e4e5ecbd58fdd63cacb8e58f0ed1e97616a7f7a7baaf4b8d036" 938 | ], 939 | "version": "==3.1.0" 940 | }, 941 | "mccabe": { 942 | "hashes": [ 943 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 944 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 945 | ], 946 | "version": "==0.6.1" 947 | }, 948 | "mistune": { 949 | "hashes": [ 950 | "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", 951 | "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" 952 | ], 953 | "version": "==0.8.4" 954 | }, 955 | "nbconvert": { 956 | "hashes": [ 957 | "sha256:138381baa41d83584459b5cfecfc38c800ccf1f37d9ddd0bd440783346a4c39c", 958 | "sha256:4a978548d8383f6b2cfca4a3b0543afb77bc7cb5a96e8b424337ab58c12da9bc" 959 | ], 960 | "version": "==5.5.0" 961 | }, 962 | "nbformat": { 963 | "hashes": [ 964 | "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", 965 | "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" 966 | ], 967 | "version": "==4.4.0" 968 | }, 969 | "nose-watch": { 970 | "hashes": [ 971 | "sha256:1a50deb7f39e3c7091aa7ac5e19bf7bddd65d6db41727ab1fe563450645a0088" 972 | ], 973 | "index": "pypi", 974 | "version": "==0.9.2" 975 | }, 976 | "notebook": { 977 | "hashes": [ 978 | "sha256:573e0ae650c5d76b18b6e564ba6d21bf321d00847de1d215b418acb64f056eb8", 979 | "sha256:f64fa6624d2323fbef6210a621817d6505a45d0d4a9367f1843b20a38a4666ee" 980 | ], 981 | "version": "==5.7.8" 982 | }, 983 | "numpy": { 984 | "hashes": [ 985 | "sha256:0e2eed77804b2a6a88741f8fcac02c5499bba3953ec9c71e8b217fad4912c56c", 986 | "sha256:1c666f04553ef70fda54adf097dbae7080645435fc273e2397f26bbf1d127bbb", 987 | "sha256:1f46532afa7b2903bfb1b79becca2954c0a04389d19e03dc73f06b039048ac40", 988 | "sha256:315fa1b1dfc16ae0f03f8fd1c55f23fd15368710f641d570236f3d78af55e340", 989 | "sha256:3d5fcea4f5ed40c3280791d54da3ad2ecf896f4c87c877b113576b8280c59441", 990 | "sha256:48241759b99d60aba63b0e590332c600fc4b46ad597c9b0a53f350b871ef0634", 991 | "sha256:4b4f2924b36d857cf302aec369caac61e43500c17eeef0d7baacad1084c0ee84", 992 | "sha256:54fe3b7ed9e7eb928bbc4318f954d133851865f062fa4bbb02ef8940bc67b5d2", 993 | "sha256:5a8f021c70e6206c317974c93eaaf9bc2b56295b6b1cacccf88846e44a1f33fc", 994 | "sha256:754a6be26d938e6ca91942804eb209307b73f806a1721176278a6038869a1686", 995 | "sha256:771147e654e8b95eea1293174a94f34e2e77d5729ad44aefb62fbf8a79747a15", 996 | "sha256:78a6f89da87eeb48014ec652a65c4ffde370c036d780a995edaeb121d3625621", 997 | "sha256:7fde5c2a3a682a9e101e61d97696687ebdba47637611378b4127fe7e47fdf2bf", 998 | "sha256:80d99399c97f646e873dd8ce87c38cfdbb668956bbc39bc1e6cac4b515bba2a0", 999 | "sha256:88a72c1e45a0ae24d1f249a529d9f71fe82e6fa6a3fd61414b829396ec585900", 1000 | "sha256:a4f4460877a16ac73302a9c077ca545498d9fe64e6a81398d8e1a67e4695e3df", 1001 | "sha256:a61255a765b3ac73ee4b110b28fccfbf758c985677f526c2b4b39c48cc4b509d", 1002 | "sha256:ab4896a8c910b9a04c0142871d8800c76c8a2e5ff44763513e1dd9d9631ce897", 1003 | "sha256:abbd6b1c2ef6199f4b7ca9f818eb6b31f17b73a6110aadc4e4298c3f00fab24e", 1004 | "sha256:b16d88da290334e33ea992c56492326ea3b06233a00a1855414360b77ca72f26", 1005 | "sha256:b78a1defedb0e8f6ae1eb55fa6ac74ab42acc4569c3a2eacc2a407ee5d42ebcb", 1006 | "sha256:cfef82c43b8b29ca436560d51b2251d5117818a8d1fb74a8384a83c096745dad", 1007 | "sha256:d160e57731fcdec2beda807ebcabf39823c47e9409485b5a3a1db3a8c6ce763e" 1008 | ], 1009 | "index": "pypi", 1010 | "version": "==1.16.3" 1011 | }, 1012 | "pandas": { 1013 | "hashes": [ 1014 | "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", 1015 | "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", 1016 | "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", 1017 | "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", 1018 | "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", 1019 | "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", 1020 | "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", 1021 | "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", 1022 | "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", 1023 | "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", 1024 | "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", 1025 | "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", 1026 | "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", 1027 | "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", 1028 | "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", 1029 | "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", 1030 | "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", 1031 | "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", 1032 | "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", 1033 | "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" 1034 | ], 1035 | "index": "pypi", 1036 | "version": "==0.24.2" 1037 | }, 1038 | "pandocfilters": { 1039 | "hashes": [ 1040 | "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9" 1041 | ], 1042 | "version": "==1.4.2" 1043 | }, 1044 | "parso": { 1045 | "hashes": [ 1046 | "sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33", 1047 | "sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376" 1048 | ], 1049 | "version": "==0.4.0" 1050 | }, 1051 | "pathtools": { 1052 | "hashes": [ 1053 | "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" 1054 | ], 1055 | "version": "==0.1.2" 1056 | }, 1057 | "pexpect": { 1058 | "hashes": [ 1059 | "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", 1060 | "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" 1061 | ], 1062 | "markers": "sys_platform != 'win32'", 1063 | "version": "==4.7.0" 1064 | }, 1065 | "pickleshare": { 1066 | "hashes": [ 1067 | "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", 1068 | "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" 1069 | ], 1070 | "version": "==0.7.5" 1071 | }, 1072 | "prometheus-client": { 1073 | "hashes": [ 1074 | "sha256:1b38b958750f66f208bcd9ab92a633c0c994d8859c831f7abc1f46724fcee490" 1075 | ], 1076 | "version": "==0.6.0" 1077 | }, 1078 | "prompt-toolkit": { 1079 | "hashes": [ 1080 | "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", 1081 | "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", 1082 | "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55" 1083 | ], 1084 | "version": "==2.0.9" 1085 | }, 1086 | "ptyprocess": { 1087 | "hashes": [ 1088 | "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", 1089 | "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" 1090 | ], 1091 | "markers": "os_name != 'nt'", 1092 | "version": "==0.6.0" 1093 | }, 1094 | "pygments": { 1095 | "hashes": [ 1096 | "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", 1097 | "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" 1098 | ], 1099 | "version": "==2.4.2" 1100 | }, 1101 | "pylint": { 1102 | "hashes": [ 1103 | "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", 1104 | "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1" 1105 | ], 1106 | "index": "pypi", 1107 | "version": "==2.3.1" 1108 | }, 1109 | "pyparsing": { 1110 | "hashes": [ 1111 | "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", 1112 | "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" 1113 | ], 1114 | "version": "==2.4.0" 1115 | }, 1116 | "pyrsistent": { 1117 | "hashes": [ 1118 | "sha256:16692ee739d42cf5e39cef8d27649a8c1fdb7aa99887098f1460057c5eb75c3a" 1119 | ], 1120 | "version": "==0.15.2" 1121 | }, 1122 | "python-dateutil": { 1123 | "hashes": [ 1124 | "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", 1125 | "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" 1126 | ], 1127 | "markers": "python_version >= '2.7'", 1128 | "version": "==2.8.0" 1129 | }, 1130 | "pytz": { 1131 | "hashes": [ 1132 | "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", 1133 | "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" 1134 | ], 1135 | "version": "==2019.1" 1136 | }, 1137 | "pyyaml": { 1138 | "hashes": [ 1139 | "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", 1140 | "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", 1141 | "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", 1142 | "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", 1143 | "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", 1144 | "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", 1145 | "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", 1146 | "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", 1147 | "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", 1148 | "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", 1149 | "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" 1150 | ], 1151 | "version": "==5.1" 1152 | }, 1153 | "pyzmq": { 1154 | "hashes": [ 1155 | "sha256:1651e52ed91f0736afd6d94ef9f3259b5534ce8beddb054f3d5ca989c4ef7c4f", 1156 | "sha256:5ccb9b3d4cd20c000a9b75689d5add8cd3bce67fcbd0f8ae1b59345247d803af", 1157 | "sha256:5e120c4cd3872e332fb35d255ad5998ebcee32ace4387b1b337416b6b90436c7", 1158 | "sha256:5e2a3707c69a7281a9957f83718815fd74698cba31f6d69f9ed359921f662221", 1159 | "sha256:63d51add9af8d0442dc90f916baf98fdc04e3b0a32afec4bfc83f8d85e72959f", 1160 | "sha256:65c5a0bdc49e20f7d6b03a661f71e2fda7a99c51270cafe71598146d09810d0d", 1161 | "sha256:66828fabe911aa545d919028441a585edb7c9c77969a5fea6722ef6e6ece38ab", 1162 | "sha256:7d79427e82d9dad6e9b47c0b3e7ae5f9d489b1601e3a36ea629bb49501a4daf3", 1163 | "sha256:824ee5d3078c4eae737ffc500fbf32f2b14e6ec89b26b435b7834febd70120cf", 1164 | "sha256:89dc0a83cccec19ff3c62c091e43e66e0183d1e6b4658c16ee4e659518131494", 1165 | "sha256:8b319805f6f7c907b101c864c3ca6cefc9db8ce0791356f180b1b644c7347e4c", 1166 | "sha256:90facfb379ab47f94b19519c1ecc8ec8d10813b69d9c163117944948bdec5d15", 1167 | "sha256:a0a178c7420021fc0730180a914a4b4b3092ce9696ceb8e72d0f60f8ce1655dd", 1168 | "sha256:a7a89591ae315baccb8072f216614b3e59aed7385aef4393a6c741783d6ee9cf", 1169 | "sha256:ba2578f0ae582452c02ed9fac2dc477b08e80ce05d2c0885becf5fff6651ccb0", 1170 | "sha256:c69b0055c55702f5b0b6b354133e8325b9a56dbc80e1be2d240bead253fb9825", 1171 | "sha256:ca434e1858fe222380221ddeb81e86f45522773344c9da63c311d17161df5e06", 1172 | "sha256:d4b8ecfc3d92f114f04d5c40f60a65e5196198b827503341521dda12d8b14939", 1173 | "sha256:d706025c47b09a54f005953ebe206f6d07a22516776faa4f509aaff681cc5468", 1174 | "sha256:d8f27e958f8a2c0c8ffd4d8855c3ce8ac3fa1e105f0491ce31729aa2b3229740", 1175 | "sha256:dbd264298f76b9060ce537008eb989317ca787c857e23cbd1b3ddf89f190a9b1", 1176 | "sha256:e926d66f0df8fdbf03ba20583af0f215e475c667fb033d45fd031c66c63e34c9", 1177 | "sha256:efc3bd48237f973a749f7312f68062f1b4ca5c2032a0673ca3ea8e46aa77187b", 1178 | "sha256:f59bc782228777cbfe04555707a9c56d269c787ed25d6d28ed9d0fbb41cb1ad2", 1179 | "sha256:f8da5322f4ff5f667a0d5a27e871b560c6637153c81e318b35cb012b2a98835c" 1180 | ], 1181 | "version": "==18.0.1" 1182 | }, 1183 | "qtconsole": { 1184 | "hashes": [ 1185 | "sha256:4af84facdd6f00a6b9b2927255f717bb23ae4b7a20ba1d9ef0a5a5a8dbe01ae2", 1186 | "sha256:60d61d93f7d67ba2b265c6d599d413ffec21202fec999a952f658ff3a73d252b" 1187 | ], 1188 | "version": "==4.5.1" 1189 | }, 1190 | "rednose": { 1191 | "hashes": [ 1192 | "sha256:6da77917788be277b70259edc0bb92fc6f28fe268b765b4ea88206cc3543a3e1" 1193 | ], 1194 | "index": "pypi", 1195 | "version": "==1.3.0" 1196 | }, 1197 | "scipy": { 1198 | "hashes": [ 1199 | "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", 1200 | "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", 1201 | "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", 1202 | "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", 1203 | "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", 1204 | "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", 1205 | "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", 1206 | "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", 1207 | "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", 1208 | "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", 1209 | "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", 1210 | "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", 1211 | "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", 1212 | "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", 1213 | "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", 1214 | "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee" 1215 | ], 1216 | "version": "==1.3.0" 1217 | }, 1218 | "seaborn": { 1219 | "hashes": [ 1220 | "sha256:42e627b24e849c2d3bbfd059e00005f6afbc4a76e4895baf44ae23fe8a4b09a5", 1221 | "sha256:76c83f794ca320fb6b23a7c6192d5e185a5fcf4758966a0c0a54baee46d41e2f" 1222 | ], 1223 | "index": "pypi", 1224 | "version": "==0.9.0" 1225 | }, 1226 | "send2trash": { 1227 | "hashes": [ 1228 | "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", 1229 | "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b" 1230 | ], 1231 | "version": "==1.5.0" 1232 | }, 1233 | "six": { 1234 | "hashes": [ 1235 | "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", 1236 | "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" 1237 | ], 1238 | "version": "==1.12.0" 1239 | }, 1240 | "terminado": { 1241 | "hashes": [ 1242 | "sha256:d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460", 1243 | "sha256:de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2" 1244 | ], 1245 | "version": "==0.8.2" 1246 | }, 1247 | "termstyle": { 1248 | "hashes": [ 1249 | "sha256:ef74b83698ea014112040cf32b1a093c1ab3d91c4dd18ecc03ec178fd99c9f9f" 1250 | ], 1251 | "version": "==0.1.11" 1252 | }, 1253 | "testpath": { 1254 | "hashes": [ 1255 | "sha256:46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", 1256 | "sha256:b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8" 1257 | ], 1258 | "version": "==0.4.2" 1259 | }, 1260 | "tornado": { 1261 | "hashes": [ 1262 | "sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b", 1263 | "sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec", 1264 | "sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2", 1265 | "sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8", 1266 | "sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d", 1267 | "sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0", 1268 | "sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa" 1269 | ], 1270 | "version": "==6.0.2" 1271 | }, 1272 | "traitlets": { 1273 | "hashes": [ 1274 | "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", 1275 | "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" 1276 | ], 1277 | "version": "==4.3.2" 1278 | }, 1279 | "typed-ast": { 1280 | "hashes": [ 1281 | "sha256:132eae51d6ef3ff4a8c47c393a4ef5ebf0d1aecc96880eb5d6c8ceab7017cc9b", 1282 | "sha256:18141c1484ab8784006c839be8b985cfc82a2e9725837b0ecfa0203f71c4e39d", 1283 | "sha256:2baf617f5bbbfe73fd8846463f5aeafc912b5ee247f410700245d68525ec584a", 1284 | "sha256:3d90063f2cbbe39177e9b4d888e45777012652d6110156845b828908c51ae462", 1285 | "sha256:4304b2218b842d610aa1a1d87e1dc9559597969acc62ce717ee4dfeaa44d7eee", 1286 | "sha256:4983ede548ffc3541bae49a82675996497348e55bafd1554dc4e4a5d6eda541a", 1287 | "sha256:5315f4509c1476718a4825f45a203b82d7fdf2a6f5f0c8f166435975b1c9f7d4", 1288 | "sha256:6cdfb1b49d5345f7c2b90d638822d16ba62dc82f7616e9b4caa10b72f3f16649", 1289 | "sha256:7b325f12635598c604690efd7a0197d0b94b7d7778498e76e0710cd582fd1c7a", 1290 | "sha256:8d3b0e3b8626615826f9a626548057c5275a9733512b137984a68ba1598d3d2f", 1291 | "sha256:8f8631160c79f53081bd23446525db0bc4c5616f78d04021e6e434b286493fd7", 1292 | "sha256:912de10965f3dc89da23936f1cc4ed60764f712e5fa603a09dd904f88c996760", 1293 | "sha256:b010c07b975fe853c65d7bbe9d4ac62f1c69086750a574f6292597763781ba18", 1294 | "sha256:c908c10505904c48081a5415a1e295d8403e353e0c14c42b6d67f8f97fae6616", 1295 | "sha256:c94dd3807c0c0610f7c76f078119f4ea48235a953512752b9175f9f98f5ae2bd", 1296 | "sha256:ce65dee7594a84c466e79d7fb7d3303e7295d16a83c22c7c4037071b059e2c21", 1297 | "sha256:eaa9cfcb221a8a4c2889be6f93da141ac777eb8819f077e1d09fb12d00a09a93", 1298 | "sha256:f3376bc31bad66d46d44b4e6522c5c21976bf9bca4ef5987bb2bf727f4506cbb", 1299 | "sha256:f9202fa138544e13a4ec1a6792c35834250a85958fde1251b6a22e07d1260ae7" 1300 | ], 1301 | "markers": "implementation_name == 'cpython'", 1302 | "version": "==1.3.5" 1303 | }, 1304 | "watchdog": { 1305 | "hashes": [ 1306 | "sha256:965f658d0732de3188211932aeb0bb457587f04f63ab4c1e33eab878e9de961d" 1307 | ], 1308 | "version": "==0.9.0" 1309 | }, 1310 | "wcwidth": { 1311 | "hashes": [ 1312 | "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", 1313 | "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" 1314 | ], 1315 | "version": "==0.1.7" 1316 | }, 1317 | "webencodings": { 1318 | "hashes": [ 1319 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 1320 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 1321 | ], 1322 | "version": "==0.5.1" 1323 | }, 1324 | "widgetsnbextension": { 1325 | "hashes": [ 1326 | "sha256:14b2c65f9940c9a7d3b70adbe713dbd38b5ec69724eebaba034d1036cf3d4740", 1327 | "sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265" 1328 | ], 1329 | "version": "==3.4.2" 1330 | }, 1331 | "wrapt": { 1332 | "hashes": [ 1333 | "sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533" 1334 | ], 1335 | "version": "==1.11.1" 1336 | } 1337 | } 1338 | } 1339 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ml-app-template 2 | 3 | An ML project template with sensible defaults: 4 | - Dockerised dev setup 5 | - Unit test setup 6 | - Automated tests for model metrics 7 | - CI pipeline as code 8 | 9 | For infrastructure-related stuff (e.g. provisioning of CI server, deployments, etc.), please refer to https://github.com/ThoughtWorksInc/ml-cd-starter-kit. 10 | 11 | ## Getting started 12 | 13 | 1. Fork repository: https://github.com/ThoughtWorksInc/ml-app-template 14 | 2. Clone repository: `git clone https://github.com/YOUR_USERNAME/ml-app-template` 15 | 3. To develop on local environment with installed Python packages, run: `pipenv install` then activate environment with `pipenv shell` 16 | 3.b. to run anything without activating the virtual environment, for example, nosetests, try `pipenv run nosetests` 17 | 4. Install Docker ([Mac](https://docs.docker.com/docker-for-mac/install/), [Linux](https://docs.docker.com/install/linux/docker-ce/ubuntu/)) 18 | 5. Start Docker on your desktop 19 | 6. Build image and start container: 20 | 21 | ```shell 22 | # build docker image [Mac/Linux users] 23 | docker build . -t ml-app-template 24 | 25 | # build docker image [Windows users] 26 | MSYS_NO_PATHCONV=1 docker build . -t ml-app-template 27 | 28 | # start docker container [Mac/Linux users] 29 | docker run -it -v $(pwd):/home/ml-app-template \ 30 | -p 8080:8080 \ 31 | -p 8888:8888 \ 32 | ml-app-template bash 33 | 34 | # start docker container [Windows users] 35 | winpty docker run -it -v C:\\Users\\path\\to\\your\\ml-app-template:/home/ml-app-template -p 8080:8080 -p 8888:8888 ml-app-template bash 36 | # Note: to find the path, you can run `pwd` in git bash, and manually replace forward slashes (/) with double backslashes (\\) 37 | ``` 38 | 39 | You're ready to roll! Here are some common commands that you can run in your dev workflow. Run these in the container. 40 | 41 | ```shell 42 | # add some color to your terminal 43 | source bin/color_my_terminal.sh 44 | 45 | # activate virtual environment for python 46 | pipenv shell 47 | 48 | # run unit tests 49 | nosetests 50 | 51 | # run unit tests in watch mode and color output 52 | nosetests --with-watch --rednose --nologcapture 53 | 54 | # train model 55 | SHOULD_USE_MLFLOW=false python src/train.py 56 | 57 | # start flask app in development mode 58 | python src/app.py 59 | 60 | # make requests to your app 61 | # 1. In your browser, visit http://localhost:8080 62 | # 2. Open another terminal in the running container (detailed instructions below) and run: 63 | bin/predict.sh http://localhost:8080 64 | 65 | # You can also use this script to test your deployed application later: 66 | bin/predict.sh http://my-app.com 67 | ``` 68 | 69 | Here are some other commands that you may find useful 70 | ```shell 71 | # see list of running containers 72 | docker ps 73 | 74 | # start a bash shell in a running container 75 | docker exec -it bash 76 | 77 | # starting jupyter notebook server on http://localhost:8888 78 | jupyter notebook --ip 0.0.0.0 --no-browser --allow-root 79 | ``` 80 | 81 | ## What's in this repo? 82 | 83 | We've created a project template to help you with the boilerplate code that we usually have to write in any typical project. 84 | 85 | To reduce incidental complexity, we used a simple dataset (boston housing prices) to train a simple linear regression model. Replace the (i) data, (ii) data preprocessing code and (iii) model specification for your use case. 86 | 87 | This is the project structure: 88 | 89 | ```sh 90 | . 91 | ├── Dockerfile 92 | ├── README.md 93 | ├── requirements-dev.txt # specify dev dependencies (e.g. jupyter) here 94 | ├── requirements.txt # specify app dependencies here 95 | ├── ci.gocd.yaml # specify your CI pipeline here 96 | └── src # place your code here 97 | ├── app.py 98 | ├── app_with_logging.py 99 | ├── tests # place your tests here 100 | │   ├── test.py 101 | │   └── test_model_metrics.py 102 | └── settings.py # define environment variables here 103 | └── train.py 104 | ├── bin # store shell scripts here 105 | │   ├── color_my_terminal.sh 106 | │   ├── configure_venv_locally.sh 107 | │   ├── predict.sh 108 | │   ├── start_server.sh 109 | │   ├── test.sh 110 | │   ├── test_model_metrics.sh 111 | │   └── train_model.sh 112 | ├── docs 113 | │   ├── FAQs.md 114 | │   └── mlflow.md 115 | ├── models # serialize stuff here 116 | │   ├── _keep 117 | │   ├── column_order.joblib 118 | │   └── model.joblib 119 | 120 | ``` 121 | 122 | For logging, `app_with_logging.py` contains the code for logging (i) inputs to the model, (ii) model outputs and (iii) [LIME](https://github.com/marcotcr/lime) metrics. You can refer to this file to send logs to elasticsearch using fluentd. To keep the main app simple to accessible to people who may not be familiar with these technologies, we've kept it in a separate file `app_with_logging.py` for reference. 123 | 124 | ## IDE configuration 125 | 126 | Please refer to [FAQs](./FAQs.md) for instructions on how to configure VS Code or PyCharm to give you intellisense and auto-complete suggestions as you code. 127 | 128 | ## Infrastructure 129 | 130 | To provision the infrastructure used in this repo (e.g. GoCD, MLFlow, EFK), please check out the [`ml-cd-starter-kit`](https://github.com/ThoughtWorksInc/ml-cd-starter-kit) repo and follow the instrutions in the README. 131 | 132 | When you're done setting up the infrastructure, do the following: 133 | - in `src/settings.py`, update the ip addresses with that of your own infrastructure. 134 | - in `ci.gocd.yaml`, replace `davified/ml-app-template` with `YOUR_USERNAME/YOUR_IMAGE_NAME` 135 | 136 | ## Troubleshooting 137 | 138 | If you encounter any errors, please refer to [FAQs](./docs/FAQs.md) for a list of common errors and how to fix them. 139 | -------------------------------------------------------------------------------- /bin/color_my_terminal.sh: -------------------------------------------------------------------------------- 1 | export "PS1=${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\] \$ " -------------------------------------------------------------------------------- /bin/configure_venv_locally.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | which python3 5 | if [ $? -ne 0 ]; then 6 | if [[ $(uname) == 'Darwin' ]]; then 7 | # mac users 8 | which brew 9 | if [ $? -ne 0 ]; then 10 | echo "INFO: Installing homebrew" 11 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 12 | fi 13 | 14 | echo "INFO: Installing python3" 15 | brew install python3 16 | else 17 | echo "Please install Python 3 before using this script" 18 | echo "Exiting..." 19 | 20 | exit 1 21 | fi 22 | fi 23 | 24 | python3 -m venv .venv-local 25 | 26 | source .venv-local/bin/activate 27 | pip install --upgrade pip 28 | pip install -r requirements-dev.txt 29 | -------------------------------------------------------------------------------- /bin/predict.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ $1 == '' ]]; then 5 | echo "[ERROR] Usage: $0 " 6 | echo "[ERROR] Example: $0 http://localhost:8080" 7 | echo "[ERROR] Example: $0 http://my.app.ip.com:SOME_PORT" 8 | echo "[ERROR] Exiting..." 9 | exit 1 10 | else 11 | base_url=$1 12 | fi 13 | 14 | curl --request POST "$base_url/predict" \ 15 | --header "Content-Type: application/json" \ 16 | --data \ 17 | '{ 18 | "AGE": 65.2, 19 | "B": 396.9, 20 | "CHAS": 0, 21 | "CRIM": 0.00632, 22 | "DIS": 4.09, 23 | "INDUS": 2.31, 24 | "LSTAT": 4.98, 25 | "NOX": 0.538, 26 | "PTRATIO": 15.3, 27 | "RAD": 1.0, 28 | "RM": 16.575, 29 | "TAX": 296, 30 | "ZN": 18 31 | }' -------------------------------------------------------------------------------- /bin/start_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use PORT environment variable if defined. Otherwise, default to 8080 4 | PORT="${PORT:-8080}" 5 | 6 | gunicorn -b 0.0.0.0:$PORT src.app:app 7 | -------------------------------------------------------------------------------- /bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nosetests -------------------------------------------------------------------------------- /bin/test_model_metrics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | export RUN_METRICS_TEST='true' 5 | python -m unittest src/tests/test_model_metrics.py -------------------------------------------------------------------------------- /bin/train_model.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | python src/train.py 5 | echo "Model training complete." -------------------------------------------------------------------------------- /ci.gocd.yaml: -------------------------------------------------------------------------------- 1 | pipelines: 2 | demo_ci_pipeline: 3 | group: demo_group 4 | materials: 5 | demo_git: # name of material 6 | git: https://github.com/ThoughtWorksInc/ml-app-template.git 7 | stages: 8 | - commit: # name of stage 9 | clean_workspace: true 10 | jobs: 11 | build_and_test: # name of the job 12 | elastic_profile_id: demo-app 13 | tasks: 14 | - exec: # indicates type of task 15 | command: bash 16 | arguments: 17 | - -c 18 | - docker build . -t davified/ml-app-template:v${GO_PIPELINE_LABEL} --target Base 19 | - exec: 20 | command: bash 21 | arguments: 22 | - -c 23 | - docker run davified/ml-app-template:v${GO_PIPELINE_LABEL} bin/test.sh 24 | - exec: 25 | command: bash 26 | arguments: 27 | - -c 28 | - docker build . -t davified/ml-app-template:v${GO_PIPELINE_LABEL} --target Build --build-arg CI=true 29 | - exec: 30 | command: bash 31 | arguments: 32 | - -c 33 | - docker run davified/ml-app-template:v${GO_PIPELINE_LABEL} bin/test_model_metrics.sh 34 | - exec: 35 | command: bash 36 | arguments: 37 | - -c 38 | - echo "v${GO_PIPELINE_LABEL}" > docker_image_tag.txt 39 | artifacts: 40 | - external: 41 | id: docker-release-candidate 42 | store_id: dockerhub # artifact store id (configured on gocd UI) 43 | configuration: 44 | options: 45 | Image: davified/ml-app-template 46 | Tag: v${GO_PIPELINE_LABEL} 47 | - build: 48 | source: docker_image_tag.txt 49 | destination: metadata # destination directory is relative to the artifacts folder on go server 50 | - deploy_staging: 51 | clean_workspace: true 52 | jobs: 53 | deploy_staging: 54 | elastic_profile_id: docker-dind-kubectl 55 | tasks: 56 | - exec: 57 | command: bash 58 | arguments: 59 | - -c 60 | - kubectl set image deployment/ml-cd-starter-kit-ml-app-template ml-app-template=davified/ml-app-template:v${GO_PIPELINE_LABEL} # ml-app-template is the name of the chart in ml-cd-starter-kit 61 | - deploy_prod: 62 | clean_workspace: true 63 | approval: 64 | type: manual 65 | jobs: 66 | deploy_staging: 67 | elastic_profile_id: docker-dind-kubectl 68 | tasks: 69 | - exec: 70 | command: bash 71 | arguments: 72 | - -c 73 | - kubectl set image deployment/ml-cd-starter-kit-prod-ml-app-template ml-app-template=davified/ml-app-template:v${GO_PIPELINE_LABEL} # update the container used by deployment/ml-cd-starter-kit-PROD-ml-app-template 74 | # second pipeline 75 | evaluate_model: 76 | group: demo_group 77 | materials: 78 | myupstream: 79 | pipeline: demo_ci_pipeline 80 | stage: commit 81 | stages: 82 | - default_stage: 83 | clean_workspace: true 84 | jobs: 85 | default_job: 86 | elastic_profile_id: demo-app 87 | tasks: 88 | - fetch: 89 | pipeline: demo_ci_pipeline 90 | stage: commit 91 | job: build_and_test 92 | source: metadata 93 | destination: . 94 | - exec: 95 | command: bash 96 | arguments: 97 | - -c 98 | - docker run davified/ml-app-template:$(cat metadata/docker_image_tag.txt) bin/test_model_metrics.sh -------------------------------------------------------------------------------- /docs/FAQs.md: -------------------------------------------------------------------------------- 1 | # FAQs 2 | 3 | ### IDE configuration 4 | To get the optimal coding workflow, we often rely on intellisense and code completion provided by our code editors. Unfortunately, this becomes [hard](https://github.com/Microsoft/vscode-python/issues/79#issuecomment-348193800) when our python virtual environment is contained within the docker container. As a workaround, you can: 5 | - Run `bin/configure_venv_locally.sh`. This will create a duplicate python virtual environment (by the name of `.venv-local`) on your host (i.e. your computer) 6 | - Configure your IDE with the python path of this virtual environment: 7 | - [VS Code](https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment) 8 | - [PyCharm (community edition)](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html) 9 | - PyCharm (professional edition) users: you don't need this workaround. You can follow set up your IDE to use the virtual environment in the Docker container (see [instructions])(https://www.jetbrains.com/help/pycharm/using-docker-as-a-remote-interpreter.html) 10 | - configure autosave 11 | 12 | 13 | ### Common errors and how to fix them 14 | 15 | 1. `docker run` causes the following error: 16 | ```shell 17 | docker: Error response from daemon: driver failed programming external connectivity on endpoint elated_brown (a26aea6b1fcd5f286dd7164b42 18 | 47de2f958f8280140b51ec39eed13e3801037b): Bind for 0.0.0.0:8080 failed: port is already allocated. 19 | 20 | # Reason: some container is already running and taken port 8080 21 | # Solution: 22 | # 1. get id of running container 23 | docker ps 24 | 25 | # 2 stop container 26 | docker stop 27 | # e.g. docker stop 9d57a1f8f49a 28 | 29 | # Now you can run `docker run` again 30 | ``` 31 | 32 | ### [Windows users] Common errors and how to fix them 33 | 34 | 1. If you encounter the following error, when running `docker run ... -p 8080:8080 ...`: 35 | ```shell 36 | docker: Error response from daemon: driver failed programming external connectivity on endpoint zealous_rubin (f70ddf46807daed2b1a24e3f897af1dd587b97b30ef676c8fcdba40598756 37 | c49): Error starting userland proxy: mkdir /port/tcp:0.0.0.0:8080:tcp:172.17.0.2:8080: input/output error. 38 | 39 | # Solution: 40 | # 1. Right click docker icon --> Settings --> Daemon --> Ensure 'Experimental Features' is unchecked 41 | # 2. Restart docker 42 | ``` 43 | 44 | 2. You mounted a volume (e.g. `docker run -v /$(pwd):/home/`) but you don't see the mounted directory: 45 | ```shell 46 | # solution: replace /$(pwd) with the full path to the directory that you wish to mount: 47 | winpty docker run -it -v C:\\Users\\path\\to\\your\\ml-app-template:/home/ml-app-template -p 8080:8080 ml-app-template bash 48 | 49 | # Note: to find the full path, you can run `pwd` in the directory that you wish to mount, and manually replace forward slashes (/) with double backslashes (\\) 50 | ``` 51 | This is an open issue in Docker for Windows that has to do with how Git Bash converts filepaths: https://github.com/docker/toolbox/issues/673 52 | 53 | 3. You edited a shell script and tried to run it but got some error about invalid characters (^M) 54 | ```shell 55 | # on git bash, convert line endings to unix endings 56 | dos2unix bin/my_file.sh 57 | 58 | # now you can execute your script 59 | bin/my_file.sh 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/mlflow.md: -------------------------------------------------------------------------------- 1 | # Provisioning MLFlow 2 | 3 | [For workshop facilitators] 4 | 5 | Instructions for provisioning MLFlow on kubernetes are in the README of: https://github.com/ThoughtWorksInc/ml-cd-starter-kit 6 | 7 | Once MLFlow is provisioned, remember to: 8 | - Replace the mlflow tracking server URL in `src/train.py` 9 | - search for `SHOULD_USE_MLFLOW=false` in the codebase and remove it. -------------------------------------------------------------------------------- /models/_keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/ml-app-template/692b205909f1ecd8cdca5bdef37b05a5aa1397c6/models/_keep -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | jupyter==1.0.0 4 | matplotlib==3.0.2 5 | nose-watch==0.9.2 6 | pylint 7 | rednose==1.3.0 8 | seaborn==0.9.0 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 2 | fluent-logger 3 | gunicorn==19.9.0 4 | joblib==0.13.1 5 | lime==0.1.1.33 6 | mlflow==0.8.2 7 | nose==1.3.7 8 | numpy==1.15.4 9 | pandas==0.23.4 10 | scikit-learn==0.20.2 11 | -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | import os, json, re 2 | 3 | import joblib 4 | import pandas as pd 5 | from flask import Flask, jsonify, request 6 | 7 | app = Flask(__name__) 8 | column_order = joblib.load('models/column_order.joblib') 9 | model = joblib.load('models/model.joblib') 10 | 11 | @app.route('/', methods=['GET']) 12 | def hello_world(): 13 | return jsonify({"response": "hello ml-app-template!"}) 14 | 15 | @app.route('/predict', methods=['POST']) 16 | def predict(): 17 | request_payload = request.json 18 | input_features = pd.DataFrame([], columns=column_order) 19 | input_features = input_features.append(request_payload, ignore_index=True) 20 | input_features = input_features.fillna(0) 21 | 22 | prediction = model.predict(input_features.values.tolist()).tolist()[0] 23 | 24 | return jsonify({'predicted price (thousands)': prediction}) 25 | 26 | if __name__ == '__main__': 27 | import settings 28 | if settings.PORT == 8080: 29 | app.run(port=settings.PORT, host='0.0.0.0', debug=True) 30 | else: 31 | app.run() 32 | -------------------------------------------------------------------------------- /src/app_with_logging.py: -------------------------------------------------------------------------------- 1 | import os, json, re 2 | 3 | import joblib 4 | import pandas as pd 5 | from flask import Flask, jsonify, request 6 | from fluent import sender 7 | from fluent import event 8 | from sklearn import datasets 9 | import numpy as np 10 | import lime 11 | import lime.lime_tabular 12 | 13 | from src import settings 14 | 15 | app = Flask(__name__) 16 | column_order = joblib.load('models/column_order.joblib') 17 | model = joblib.load('models/model.joblib') 18 | 19 | @app.route('/', methods=['GET']) 20 | def hello_world(): 21 | return jsonify({"response": "hello world!"}) 22 | 23 | def lime_explain(input): 24 | boston = datasets.load_boston() 25 | categorical_features = np.argwhere(np.array([len(set(boston.data[:,x])) for x in range(boston.data.shape[1])]) <= 10).flatten() 26 | explainer = lime.lime_tabular.LimeTabularExplainer(boston.data, feature_names=boston.feature_names, class_names=['price'], categorical_features=categorical_features, verbose=True, mode='regression') 27 | exp = explainer.explain_instance(np.array(input), model.predict, num_features=5).as_list() 28 | 29 | lime_feature_contributions = {} 30 | for feature, contribution in exp: 31 | feature_name = re.findall("[a-zA-Z]+", feature)[0] 32 | lime_feature_contributions[f'LIME_{feature_name}'] = contribution 33 | return lime_feature_contributions 34 | 35 | 36 | @app.route('/predict', methods=['POST']) 37 | def predict(): 38 | request_payload = request.json 39 | input_features = pd.DataFrame([], columns=column_order) 40 | input_features = input_features.append(request_payload, ignore_index=True) 41 | input_features = input_features.fillna(0) 42 | 43 | prediction = model.predict(input_features.values.tolist()).tolist()[0] 44 | 45 | logger = sender.FluentSender('app', host=settings.FLUENTD_IP, port=24220) 46 | feature_names = column_order.tolist() 47 | feature_values = input_features.values.tolist()[0] 48 | lime_feature_contributions = lime_explain(feature_values) 49 | 50 | log_payload = {'prediction': prediction, **dict(zip(feature_names, feature_values)), **lime_feature_contributions} 51 | if not logger.emit('prediction', log_payload): 52 | print('logger error') 53 | print(logger.last_error) 54 | logger.clear_last_error() # clear stored error after handled errors 55 | 56 | return jsonify({'predicted price (thousands)': prediction}) 57 | 58 | if __name__ == '__main__': 59 | import settings 60 | if settings.PORT == 8080: 61 | app.run(port=settings.PORT, host='0.0.0.0', debug=True) 62 | else: 63 | app.run() 64 | -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | SHOULD_USE_MLFLOW='false' 3 | PORT=8080 4 | # CI is defined as 'true' in ci.gocd.yaml 5 | CI=os.environ.get('CI', '') 6 | 7 | # replace the following with the external IP of your k8s services 8 | MLFLOW_IP='35.185.191.70' 9 | FLUENTD_IP='35.198.222.225' 10 | -------------------------------------------------------------------------------- /src/tests/test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class TestSimpleExample(TestCase): 5 | def test_1_should_equal_1(self): 6 | self.assertEqual(1, 1) 7 | 8 | def test_1_plus_1_should_equal_2(self): 9 | self.assertEqual(1 + 1, 2) 10 | -------------------------------------------------------------------------------- /src/tests/test_model_metrics.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from math import sqrt 4 | 5 | import joblib 6 | import pandas as pd 7 | from sklearn import datasets, metrics 8 | 9 | 10 | @unittest.skipUnless(os.environ.get('RUN_METRICS_TEST', '') == 'true', 'skip metrics tests when running unit tests') 11 | class TestSimpleExample(unittest.TestCase): 12 | def setUp(self): 13 | import warnings 14 | with warnings.catch_warnings(): 15 | warnings.simplefilter("ignore") 16 | model = joblib.load(f'./models/model.joblib') 17 | 18 | data = datasets.load_boston() 19 | x = pd.DataFrame(data.data, columns=data.feature_names) 20 | self.y = pd.DataFrame(data.target, columns=["MEDV"]) 21 | self.y_pred = model.predict(x) 22 | 23 | 24 | def test_rmse_should_be_below_5(self): 25 | rmse = sqrt(metrics.mean_squared_error(y_true=self.y, y_pred=self.y_pred)) 26 | self.assertLessEqual(rmse, 6) 27 | 28 | def test_r2_score_should_be_above_0_point_8(self): 29 | r2 = metrics.r2_score(y_true=self.y, y_pred=self.y_pred) 30 | self.assertGreaterEqual(r2, 0.5) -------------------------------------------------------------------------------- /src/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | from math import sqrt 3 | 4 | import joblib 5 | import mlflow 6 | import numpy as np 7 | import pandas as pd 8 | from sklearn import datasets, metrics 9 | from sklearn.ensemble import RandomForestRegressor 10 | from sklearn.model_selection import train_test_split 11 | 12 | import settings 13 | 14 | # load data 15 | data = datasets.load_boston() 16 | 17 | # preprocess data 18 | x = pd.DataFrame(data.data, columns=data.feature_names) 19 | y = pd.DataFrame(data.target, columns=["MEDV"]) 20 | column_order = x.columns 21 | x_train, x_test, y_train, y_test = train_test_split(x, y) 22 | 23 | # train model 24 | print('Training ML model...') 25 | N_ESTIMATORS = 2 26 | MAX_DEPTH = 2 27 | model = RandomForestRegressor(n_estimators=N_ESTIMATORS, max_depth=MAX_DEPTH) 28 | model = model.fit(x_train, y_train.values.ravel()) 29 | 30 | # save model 31 | joblib.dump(model, 'models/model.joblib') 32 | joblib.dump(column_order, 'models/column_order.joblib') 33 | 34 | if settings.SHOULD_USE_MLFLOW == 'true': 35 | # log training run to mlflow 36 | mlflow.set_tracking_uri(uri=f'http://{settings.MLFLOW_IP}:5000') 37 | if settings.CI == 'true': 38 | mlflow.set_experiment('CI') 39 | else: 40 | mlflow.set_experiment('dev') 41 | 42 | with mlflow.start_run() as run: 43 | # calculate evaluation metrics 44 | y_test_pred = model.predict(x_test) 45 | rmse = sqrt(metrics.mean_squared_error(y_true=y_test, y_pred=y_test_pred)) 46 | r2_score = metrics.r2_score(y_true=y_test, y_pred=y_test_pred) 47 | 48 | # log hyperparameters to mlflow 49 | mlflow.log_param('n_estimators', N_ESTIMATORS) 50 | mlflow.log_param('max_depth', MAX_DEPTH) 51 | 52 | # log metrics to mlflow 53 | mlflow.log_metric("rmse_validation_data", rmse) 54 | mlflow.log_metric("r2_score_validation_data", r2_score) 55 | else: 56 | print('Not logging training run because MLFlow tracking server is not up, or its URL is not set in train.py') --------------------------------------------------------------------------------