├── .gitignore ├── LICENSE ├── README.md └── ssr-mobile-web └── mweb-jan-2022-v1 ├── README.md ├── models ├── nodejs-saved-model │ ├── saved_model.pb │ └── variables │ │ ├── variables.data-00000-of-00001 │ │ └── variables.index └── py-saved-model │ ├── saved_model.pb │ └── variables │ ├── variables.data-00000-of-00001 │ └── variables.index ├── nodejs-example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── predictors.js ├── server.js └── www │ ├── index.html │ └── predictor-client.js └── python-example ├── README.md ├── ssr_mobile_web_model_demo.ipynb └── ssr_mobile_web_model_demo.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .ipynb_checkpoints 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, LinkedIn 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Personalize Performance with Performance Quality Models 2 | 3 | Performance Quality Models (or PQM in short) are a collection of AI models with a bold goal to predict various performance proxies (e.g. page load time (PLT) class) of any web / mobile page. Just like [netinfo’s effective type attribute](https://wicg.github.io/netinfo/#effectivetype-attribute), they predict, say the PLT class of any HTTP request as good / poor. Their difference is explained in the FAQ section below. As PQMs use both device and network characteristics while predicting the PLT (or any proxy) class, it can be used to build impactful application features that are otherwise tricky to implement. Some examples are: 4 | 1. Dynamically adjust the quality of images based on the current PLT class. Read [this blog](https://www.linkedin.com/pulse/personalizing-performance-adapting-application-real-time-pasumarthy) to learn more about our experience and results. 5 | 2. Disable autoplay of videos on feed or elsewhere for slow devices 6 | 3. Reduce the size of payload for poorer networks [[1](https://www.algolia.com/blog/engineering/netinfo-api-algolia-javascript-client/)] 7 | 8 | These model files are binaries that can be imported into your [Python](/ssr-mobile-web/mweb-jan-2022-v1/python-example) or [node.js](ssr-mobile-web/mweb-jan-2022-v1/nodejs-example) backends. Theoretically, this model file can also be imported in your client side JavaScript / Android / iOS code just like any other assets. We do not show that approach here, but please reach out to us if that suits your use case better. 9 | 10 | This is our first attempt to open source a ML model trained on LinkedIn’s performance data of millions of page views around the world. So, please share your experiences using these models in the [discussion forum](https://github.com/linkedin/performance-quality-models/discussions). If you have ideas around how to distribute these models better or how to improve their accuracy, don't hesistate to reach out! 11 | 12 | ## Currently Supported Models 13 | 14 | Go to the corresponding folders of each model to see how to use them, how they were built and other useful information. We will continue to update this list as we build and test new models thoroughly. 15 | 16 | ### SSR / Static Files on Mobile Web 17 | 18 | If you use server side rendering or static file server techniques to serve your mobile web traffic, you can use these models. 19 | 20 | - [mweb-jan-2022-v1](ssr-mobile-web/mweb-jan-2022-v1/) 21 | 22 | ## Tech Talks & Blogs 23 | - [How LinkedIn Personalized Performance for Millions of Members using TensorFlow.js](https://blog.tensorflow.org/2022/03/how-linkedin-personalized-performance.html?linkId=8049742) published in March 2022 by Google talks about a clever way to deploy these models into production without any complex AI stack 24 | - [Understanding network quality: The rise of customized content delivery](https://engineering.linkedin.com/blog/2019/06/understanding-network-quality--the-rise-of-customized-content-de) 25 | - [Personalizing Performance: Adapting Application in real time to member environments](https://www.linkedin.com/pulse/personalizing-performance-adapting-application-real-time-pasumarthy/) 26 | - [Discussions with W3C on the future of Netinfo](https://w3c.github.io/web-performance/meetings/2021/2021-10-TPAC/index.html#h.rudaey4ntcqb) 27 | - How LinkedIn uses AI to optimize performance for every member​ at Facebook Performance Summit in 2019 - [slides](https://microsoft-my.sharepoint.com/:p:/g/personal/pvijayan_linkedin_biz/Ebt_xi0Yf7NBszIUAj9RmGkBKnssSp2qAQF0Qy0qdLJvPw?e=dw1Eov) | [recording](https://www.youtube.com/watch?v=4A13Pzal8Hg) 28 | - AutoML to find the best PQMs at Ray Summit in 2020 - [slides](http://bit.ly/ray-at-linkedin) | [recording](https://youtu.be/0Z0Th9ySIfs?t=761) 29 | 30 | 31 | ## FAQs 32 | 33 | **1. Aren't on-device realtime measurements calculated by the NetInfo API going to be more accurate than a prediction generated by a model that was trained with historical data? And if so, what are the advantages of using a predictive model instead? Also, it looks like there are some other options besides NetInfo like https://github.com/bluesmoon/boomerang/ or https://github.com/sbstjn/latenz** 34 | 35 | NetInfo or similar only consider network aspects, whereas PQMs include network, server, client and CDNs' performance as a whole. 36 | 37 | NetInfo has privacy concerns and not a standard web API yet. It is not available on [Safari](https://caniuse.com/netinfo) and many other browsers, which can be a huge drawback in countries where iOS is a big market. 38 | 39 | Having said that, we plan to include real time network measurements as inputs to the model in the future to further improve its accuracy. 40 | 41 | **2. I’m new to ML and not sure if Linkedin’s PQMs would work for my company’s problems. How can I quickly verify this?** 42 | 43 | PQMs, though trained on LinkedIn data, capture users’ devices and network conditions all around the world. As we did not use any crucial and private information to LinkedIn to either train or test them, we believe the models captured the general network and device characteristics of users. These characteristics would not change much whether a user visits linkedin.com or medium.com on the same device and from the same place. Just like any 3rd party software, give it a try on your own data, check its performance and report any problems in the issue tab. It is very simple to try these models. Please see example scripts of each model for more information. 44 | 45 | If you wish to customize the model, techniques like transfer learning can be used. With transfer learning, you can further fine tune and retrain the model on your own performance datasets. As the model already has a general sense of performance, fine tuning wouldn’t need a lot of data or training infrastructure. 46 | 47 | Unlike general programming, ML models tend to lose their predictive power with time. As we also use them inside LinkedIn, we constantly monitor their accuracy. When we see it fall, we will continue to release newer versions of the models. 48 | 49 | Feel free to file an issue if you want more advice or help on any of the above steps to try. We are happy to collaborate :) 50 | 51 | **3. How does the accuracy of a predictive model compare to on-device real-time measurements?** 52 | 53 | NetInfo, an experimental API available on Chromium based browsers uses on-device measurements to estimate the future throughput and RTT of network requests. Page Speed Predictor on the other hand estimates the overall page load performance which includes network, server and client latencies. So there is no apples to apples comparison and depends on the use case of how the prediction is used. 54 | 55 | **4. Is this model currently being used at LinkedIn? If so, what is it being used for? If not, why not?** 56 | 57 | [Lite, the global mobile web offering of LinkedIn](https://engineering.linkedin.com/blog/2018/03/linkedin-lite--a-lightweight-mobile-web-experience), uses PQM to decide the resolution of images on feed today in realtime for every request. We shared our experiences and early results at multiple venues like Facebook's Performance Summit in 2019 and Ray Summit in 2020. 58 | 59 | **5. The model in the git repository was last updated in Jan 2022. When will it next be updated?** 60 | 61 | We actively monitor the accuracy of the model on a daily basis and will continue to publish new versions whenever we see a drop in those metrics. We can encourage you to open an issue if you see a drop in model's performance. 62 | 63 | ## License 64 | [BSD-2 Clause](https://github.com/linkedin/performance-quality-models/blob/main/LICENSE) 65 | 66 | ## Open Source Software Used 67 | ``` 68 | Tensorflow 69 | https://github.com/tensorflow/tensorflow 70 | Copyright 2019 The TensorFlow Authors 71 | License: Apache License 2.0 72 | ``` 73 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/README.md: -------------------------------------------------------------------------------- 1 | # Mobile Web Jan 2022 V1 2 | 3 | ## About the Model 4 | 5 | The model was built and trained from around 25M random samples of LinkedIn Lite’s [RUM](https://developer.mozilla.org/en-US/docs/Web/Performance/Rum-vs-Synthetic#Real_User_Monitoring) data from the month of January 2022. Lite is LinkedIn's main mobile web server application. 6 | 7 | ### Model Input 8 | 9 | | Model Input | Description | Some Examples | 10 | | ----------- | ----------- | ------------- | 11 | | Country code | ISO 3166-1 alpha-2 country code ([more details](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)) | us, in, br | 12 | | OS Family | Name of the operating system | iOS, Android, Windows | 13 | | OS version | Major version number of the OS | 14, 8 | 14 | | Browser | Name of browser | Chrome, Safari | 15 | | Browser version | Major version number of the browser | 14, 74 | 16 | | ASN number | [Autonomous System Number](https://en.wikipedia.org/wiki/Autonomous_system_(Internet)), like your Internet Service Provider | “7922” | 17 | 18 | OS and browser info is extracted from the user agent using the [UA Parser library](https://mvnrepository.com/artifact/com.github.ua-parser/uap-java/1.4.3). Using any other way to extract this information may work but it may lead to [train-serve data skew](https://www.tensorflow.org/tfx/data_validation/get_started#checking_data_skew_and_drift). 19 | 20 | Using [Digital Element](https://www.digitalelement.com/resources/faq/)'s Jan 2022 database, we extracted ASN numbers from IP Addresses. You may have to purchase their license to be absolutely match to what we did. However, any such accurate translation service should work. While trying out our demos, use any free website like [this one](https://hackertarget.com/as-ip-lookup/) or [this one](https://mxtoolbox.com/asn.aspx) to obtain the ASN number for the IP Address of your interest. 21 | 22 | ### Model Output 23 | 24 | The model is trained to return the page load time class. For now, we have two classes: 25 | 26 | 1. Less than 1300ms and 27 | 2. Greater than or equal to 1300ms, 28 | 29 | but we may expand them to more classes in the future based on the use cases. As shown in the [ssr-mobile-web-modile-demo.pynb](python-example/ssr_mobile_web_model_demo.ipynb) example, the [TF predictor](https://github.com/tensorflow/tensorflow/blob/63f17d0fe1192eff0aa47faae5d15ec7aa02490a/tensorflow/python/saved_model/load.py#L850) returns a probability distribution of these PLT classes. We could simply pick the class with the highest probability as the model’s prediction. SavedModels from Estimators section from this [guide](https://github.com/tensorflow/docs/blob/e9f1ce05852b13e9335860d93aa28f0782b60ddc/site/en/guide/estimator.ipynb) is another end to end example of this pattern. 30 | 31 | ## How to use it 32 | 33 | We currently have examples of how to use this model in Python and Node.js: 34 | 35 | - Python: [README.md](python-example/README.md) 36 | - Node.js: [README.md](nodejs-example/README.md) 37 | 38 | ## FAQs 39 | 40 | **How was the model built?** 41 | 42 | A deep neural network model is trained on historical [RUM](https://developer.mozilla.org/en-US/docs/Web/Performance/Rum-vs-Synthetic#Real_User_Monitoring) data of LinkedIn Lite. Lite is a [server side rendered](https://engineering.linkedin.com/blog/2018/03/linkedin-lite--a-lightweight-mobile-web-experience) application whose [onLoad](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload) event is used as a proxy for page load time (PLT). Standard features like browser, OS, ASN etc. as inputs and bucketed PLT as target are fed to a [tf.estimator.DNNClassifier](https://www.tensorflow.org/api_docs/python/tf/estimator/DNNClassifier) for [training](https://developers.google.com/machine-learning/glossary/#training). The trained model is exported to disk in [saved model](https://www.tensorflow.org/guide/saved_model#the_savedmodel_format_on_disk) format, which we are distributing as part of this repo. We are working on a detailed blog on how we trained 100s of models in an automated manner to find the best one on our [Engineering Blog](https://engineering.linkedin.com/blog). In the meantime, you can checkout this [presentation](http://bit.ly/ray-at-linkedin) and [video](https://youtu.be/0Z0Th9ySIfs?t=761) which contains much of the same content. 43 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/saved_model.pb -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/nodejs-saved-model/variables/variables.index -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/saved_model.pb -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linkedin/performance-quality-models/307a92325f5fffd28ba5de9bbe872fc40be6d63a/ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model/variables/variables.index -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/README.md: -------------------------------------------------------------------------------- 1 | # Nodejs with SSR Mobile Web Jan 2022 Model V1 2 | 3 | This is a simple example to show how to create an API that makes 4 | performance quality predictions using Expressjs with Nodejs. It 5 | uses the SSR Mobile Web Jan 2022 V1 model. 6 | 7 | ## How to Run the Example 8 | 9 | 1. `npm install` 10 | 2. `node server.js` 11 | 3. In your browser, visit `http://localhost:3001` -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "performance-quality-models-nodejs", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/node-pre-gyp": { 8 | "version": "1.0.4", 9 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.4.tgz", 10 | "integrity": "sha512-M669Qo4nRT7iDmQEjQYC7RU8Z6dpz9UmSbkJ1OFEja3uevCdLKh7IZZki7L1TZj02kRyl82snXFY8QqkyfowrQ==", 11 | "requires": { 12 | "detect-libc": "^1.0.3", 13 | "https-proxy-agent": "^5.0.0", 14 | "make-dir": "^3.1.0", 15 | "node-fetch": "^2.6.1", 16 | "nopt": "^5.0.0", 17 | "npmlog": "^4.1.2", 18 | "rimraf": "^3.0.2", 19 | "semver": "^7.3.4", 20 | "tar": "^6.1.0" 21 | }, 22 | "dependencies": { 23 | "debug": { 24 | "version": "4.3.3", 25 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 26 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 27 | "requires": { 28 | "ms": "2.1.2" 29 | } 30 | }, 31 | "https-proxy-agent": { 32 | "version": "5.0.0", 33 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", 34 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 35 | "requires": { 36 | "agent-base": "6", 37 | "debug": "4" 38 | } 39 | }, 40 | "ms": { 41 | "version": "2.1.2", 42 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 43 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 44 | }, 45 | "rimraf": { 46 | "version": "3.0.2", 47 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 48 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 49 | "requires": { 50 | "glob": "^7.1.3" 51 | } 52 | }, 53 | "tar": { 54 | "version": "6.1.11", 55 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 56 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 57 | "requires": { 58 | "chownr": "^2.0.0", 59 | "fs-minipass": "^2.0.0", 60 | "minipass": "^3.0.0", 61 | "minizlib": "^2.1.1", 62 | "mkdirp": "^1.0.3", 63 | "yallist": "^4.0.0" 64 | } 65 | } 66 | } 67 | }, 68 | "@tensorflow/tfjs": { 69 | "version": "3.12.0", 70 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-3.12.0.tgz", 71 | "integrity": "sha512-Gpala8Lf2DJ3j2hY43gPQ8TIJzw5sYrxfpQF0SBX26/zuHUMaF/DQz4koeRqf9s1t6MAjVbbkXdYKtGeEz4Jrg==", 72 | "requires": { 73 | "@tensorflow/tfjs-backend-cpu": "3.12.0", 74 | "@tensorflow/tfjs-backend-webgl": "3.12.0", 75 | "@tensorflow/tfjs-converter": "3.12.0", 76 | "@tensorflow/tfjs-core": "3.12.0", 77 | "@tensorflow/tfjs-data": "3.12.0", 78 | "@tensorflow/tfjs-layers": "3.12.0", 79 | "argparse": "^1.0.10", 80 | "chalk": "^4.1.0", 81 | "core-js": "3", 82 | "regenerator-runtime": "^0.13.5", 83 | "yargs": "^16.0.3" 84 | } 85 | }, 86 | "@tensorflow/tfjs-backend-cpu": { 87 | "version": "3.12.0", 88 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-3.12.0.tgz", 89 | "integrity": "sha512-UMf/OHHCzm/+oKXtTDnanlyYFNNKKU5u2hJooKak1JqSOEKChBOPYKmxf9VWmY7Pos4GbbKH/V1pLzfLnLq+Bg==", 90 | "requires": { 91 | "@types/seedrandom": "2.4.27", 92 | "seedrandom": "2.4.3" 93 | } 94 | }, 95 | "@tensorflow/tfjs-backend-webgl": { 96 | "version": "3.12.0", 97 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-3.12.0.tgz", 98 | "integrity": "sha512-nFiPS7AiDZ+H4RXpx8dSYPWzy3B1zC/aRKwH9uz9MUp1WiHHbYr90+E0ut9HFrIz0cMb0sR7vGU+zkjSZ1rLsA==", 99 | "requires": { 100 | "@tensorflow/tfjs-backend-cpu": "3.12.0", 101 | "@types/offscreencanvas": "~2019.3.0", 102 | "@types/seedrandom": "2.4.27", 103 | "@types/webgl-ext": "0.0.30", 104 | "@types/webgl2": "0.0.6", 105 | "seedrandom": "2.4.3" 106 | } 107 | }, 108 | "@tensorflow/tfjs-converter": { 109 | "version": "3.12.0", 110 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-3.12.0.tgz", 111 | "integrity": "sha512-WO6FreEB83CgFwvcyrzyaWG5WshanfyhTI6rOqSbjY86+MlJprTn4fuY9qbnYk/gDLhxEaUpYgubamY4g4L76g==" 112 | }, 113 | "@tensorflow/tfjs-core": { 114 | "version": "3.12.0", 115 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-3.12.0.tgz", 116 | "integrity": "sha512-0rXf+aMjfkFLan7qna+L7DjfSZaaRibKgq9XuorjSy9rZcdZLE2IHFTzYR7B8mdlnq2szArKzm+JXqa9grTl7w==", 117 | "requires": { 118 | "@types/long": "^4.0.1", 119 | "@types/offscreencanvas": "~2019.3.0", 120 | "@types/seedrandom": "2.4.27", 121 | "@types/webgl-ext": "0.0.30", 122 | "long": "4.0.0", 123 | "node-fetch": "~2.6.1", 124 | "seedrandom": "2.4.3" 125 | } 126 | }, 127 | "@tensorflow/tfjs-data": { 128 | "version": "3.12.0", 129 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-3.12.0.tgz", 130 | "integrity": "sha512-Q5S0pYgjqqLeFPaoHaEt9KeNFf5BrobcYmLZ9bsUnSowY0Kxz6RvnPWNNaOI3FhgqCF/e+RmGX20J0wx3Ty04Q==", 131 | "requires": { 132 | "@types/node-fetch": "^2.1.2", 133 | "node-fetch": "~2.6.1" 134 | } 135 | }, 136 | "@tensorflow/tfjs-layers": { 137 | "version": "3.12.0", 138 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-3.12.0.tgz", 139 | "integrity": "sha512-GlU9LxDnPg3I3p+MMOYzKgr2NZK2vVmT4/EotwMVIAyhR2WbVFTRP0AXkIrWSoNIJ9v/ryvFEvNNPyNfkXM/xQ==" 140 | }, 141 | "@tensorflow/tfjs-node": { 142 | "version": "3.12.0", 143 | "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-3.12.0.tgz", 144 | "integrity": "sha512-Tl4dil1k9Mjj8A4vLxZJqwOhcV8zo2ZE8XCbKCF+B5i0sBJNqej4WzxJvdA5v3xu36D0UrEaWC5miqaX7ODQFw==", 145 | "requires": { 146 | "@mapbox/node-pre-gyp": "1.0.4", 147 | "@tensorflow/tfjs": "3.12.0", 148 | "adm-zip": "^0.5.2", 149 | "google-protobuf": "^3.9.2", 150 | "https-proxy-agent": "^2.2.1", 151 | "progress": "^2.0.0", 152 | "rimraf": "^2.6.2", 153 | "tar": "^4.4.6" 154 | } 155 | }, 156 | "@types/long": { 157 | "version": "4.0.1", 158 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 159 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 160 | }, 161 | "@types/node": { 162 | "version": "17.0.2", 163 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.2.tgz", 164 | "integrity": "sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA==" 165 | }, 166 | "@types/node-fetch": { 167 | "version": "2.5.12", 168 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", 169 | "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", 170 | "requires": { 171 | "@types/node": "*", 172 | "form-data": "^3.0.0" 173 | } 174 | }, 175 | "@types/offscreencanvas": { 176 | "version": "2019.3.0", 177 | "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", 178 | "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" 179 | }, 180 | "@types/seedrandom": { 181 | "version": "2.4.27", 182 | "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz", 183 | "integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE=" 184 | }, 185 | "@types/webgl-ext": { 186 | "version": "0.0.30", 187 | "resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz", 188 | "integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==" 189 | }, 190 | "@types/webgl2": { 191 | "version": "0.0.6", 192 | "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.6.tgz", 193 | "integrity": "sha512-50GQhDVTq/herLMiqSQkdtRu+d5q/cWHn4VvKJtrj4DJAjo1MNkWYa2MA41BaBO1q1HgsUjuQvEOk0QHvlnAaQ==" 194 | }, 195 | "abbrev": { 196 | "version": "1.1.1", 197 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 198 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 199 | }, 200 | "accepts": { 201 | "version": "1.3.7", 202 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 203 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 204 | "requires": { 205 | "mime-types": "~2.1.24", 206 | "negotiator": "0.6.2" 207 | } 208 | }, 209 | "adm-zip": { 210 | "version": "0.5.9", 211 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", 212 | "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==" 213 | }, 214 | "agent-base": { 215 | "version": "6.0.2", 216 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 217 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 218 | "requires": { 219 | "debug": "4" 220 | }, 221 | "dependencies": { 222 | "debug": { 223 | "version": "4.3.3", 224 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 225 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 226 | "requires": { 227 | "ms": "2.1.2" 228 | } 229 | }, 230 | "ms": { 231 | "version": "2.1.2", 232 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 233 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 234 | } 235 | } 236 | }, 237 | "ansi-regex": { 238 | "version": "2.1.1", 239 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 240 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 241 | }, 242 | "ansi-styles": { 243 | "version": "4.3.0", 244 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 245 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 246 | "requires": { 247 | "color-convert": "^2.0.1" 248 | } 249 | }, 250 | "aproba": { 251 | "version": "1.2.0", 252 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 253 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 254 | }, 255 | "are-we-there-yet": { 256 | "version": "1.1.7", 257 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", 258 | "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", 259 | "requires": { 260 | "delegates": "^1.0.0", 261 | "readable-stream": "^2.0.6" 262 | } 263 | }, 264 | "argparse": { 265 | "version": "1.0.10", 266 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 267 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 268 | "requires": { 269 | "sprintf-js": "~1.0.2" 270 | } 271 | }, 272 | "array-flatten": { 273 | "version": "1.1.1", 274 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 275 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 276 | }, 277 | "asynckit": { 278 | "version": "0.4.0", 279 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 280 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 281 | }, 282 | "balanced-match": { 283 | "version": "1.0.2", 284 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 285 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 286 | }, 287 | "body-parser": { 288 | "version": "1.19.1", 289 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", 290 | "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", 291 | "requires": { 292 | "bytes": "3.1.1", 293 | "content-type": "~1.0.4", 294 | "debug": "2.6.9", 295 | "depd": "~1.1.2", 296 | "http-errors": "1.8.1", 297 | "iconv-lite": "0.4.24", 298 | "on-finished": "~2.3.0", 299 | "qs": "6.9.6", 300 | "raw-body": "2.4.2", 301 | "type-is": "~1.6.18" 302 | } 303 | }, 304 | "brace-expansion": { 305 | "version": "1.1.11", 306 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 307 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 308 | "requires": { 309 | "balanced-match": "^1.0.0", 310 | "concat-map": "0.0.1" 311 | } 312 | }, 313 | "bytes": { 314 | "version": "3.1.1", 315 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", 316 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" 317 | }, 318 | "chalk": { 319 | "version": "4.1.2", 320 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 321 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 322 | "requires": { 323 | "ansi-styles": "^4.1.0", 324 | "supports-color": "^7.1.0" 325 | } 326 | }, 327 | "chownr": { 328 | "version": "2.0.0", 329 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 330 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 331 | }, 332 | "cliui": { 333 | "version": "7.0.4", 334 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 335 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 336 | "requires": { 337 | "string-width": "^4.2.0", 338 | "strip-ansi": "^6.0.0", 339 | "wrap-ansi": "^7.0.0" 340 | }, 341 | "dependencies": { 342 | "ansi-regex": { 343 | "version": "5.0.1", 344 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 345 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 346 | }, 347 | "is-fullwidth-code-point": { 348 | "version": "3.0.0", 349 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 350 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 351 | }, 352 | "string-width": { 353 | "version": "4.2.3", 354 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 355 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 356 | "requires": { 357 | "emoji-regex": "^8.0.0", 358 | "is-fullwidth-code-point": "^3.0.0", 359 | "strip-ansi": "^6.0.1" 360 | } 361 | }, 362 | "strip-ansi": { 363 | "version": "6.0.1", 364 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 365 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 366 | "requires": { 367 | "ansi-regex": "^5.0.1" 368 | } 369 | } 370 | } 371 | }, 372 | "code-point-at": { 373 | "version": "1.1.0", 374 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 375 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 376 | }, 377 | "color-convert": { 378 | "version": "2.0.1", 379 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 380 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 381 | "requires": { 382 | "color-name": "~1.1.4" 383 | } 384 | }, 385 | "color-name": { 386 | "version": "1.1.4", 387 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 388 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 389 | }, 390 | "combined-stream": { 391 | "version": "1.0.8", 392 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 393 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 394 | "requires": { 395 | "delayed-stream": "~1.0.0" 396 | } 397 | }, 398 | "concat-map": { 399 | "version": "0.0.1", 400 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 401 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 402 | }, 403 | "console-control-strings": { 404 | "version": "1.1.0", 405 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 406 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 407 | }, 408 | "content-disposition": { 409 | "version": "0.5.4", 410 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 411 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 412 | "requires": { 413 | "safe-buffer": "5.2.1" 414 | } 415 | }, 416 | "content-type": { 417 | "version": "1.0.4", 418 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 419 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 420 | }, 421 | "cookie": { 422 | "version": "0.4.1", 423 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 424 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 425 | }, 426 | "cookie-signature": { 427 | "version": "1.0.6", 428 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 429 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 430 | }, 431 | "core-js": { 432 | "version": "3.20.0", 433 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.0.tgz", 434 | "integrity": "sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ==" 435 | }, 436 | "core-util-is": { 437 | "version": "1.0.3", 438 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 439 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 440 | }, 441 | "debug": { 442 | "version": "2.6.9", 443 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 444 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 445 | "requires": { 446 | "ms": "2.0.0" 447 | } 448 | }, 449 | "delayed-stream": { 450 | "version": "1.0.0", 451 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 452 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 453 | }, 454 | "delegates": { 455 | "version": "1.0.0", 456 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 457 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 458 | }, 459 | "depd": { 460 | "version": "1.1.2", 461 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 462 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 463 | }, 464 | "destroy": { 465 | "version": "1.0.4", 466 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 467 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 468 | }, 469 | "detect-libc": { 470 | "version": "1.0.3", 471 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 472 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 473 | }, 474 | "ee-first": { 475 | "version": "1.1.1", 476 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 477 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 478 | }, 479 | "emoji-regex": { 480 | "version": "8.0.0", 481 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 482 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 483 | }, 484 | "encodeurl": { 485 | "version": "1.0.2", 486 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 487 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 488 | }, 489 | "es6-promise": { 490 | "version": "4.2.8", 491 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 492 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 493 | }, 494 | "es6-promisify": { 495 | "version": "5.0.0", 496 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 497 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 498 | "requires": { 499 | "es6-promise": "^4.0.3" 500 | } 501 | }, 502 | "escalade": { 503 | "version": "3.1.1", 504 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 505 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 506 | }, 507 | "escape-html": { 508 | "version": "1.0.3", 509 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 510 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 511 | }, 512 | "etag": { 513 | "version": "1.8.1", 514 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 515 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 516 | }, 517 | "express": { 518 | "version": "4.17.2", 519 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", 520 | "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", 521 | "requires": { 522 | "accepts": "~1.3.7", 523 | "array-flatten": "1.1.1", 524 | "body-parser": "1.19.1", 525 | "content-disposition": "0.5.4", 526 | "content-type": "~1.0.4", 527 | "cookie": "0.4.1", 528 | "cookie-signature": "1.0.6", 529 | "debug": "2.6.9", 530 | "depd": "~1.1.2", 531 | "encodeurl": "~1.0.2", 532 | "escape-html": "~1.0.3", 533 | "etag": "~1.8.1", 534 | "finalhandler": "~1.1.2", 535 | "fresh": "0.5.2", 536 | "merge-descriptors": "1.0.1", 537 | "methods": "~1.1.2", 538 | "on-finished": "~2.3.0", 539 | "parseurl": "~1.3.3", 540 | "path-to-regexp": "0.1.7", 541 | "proxy-addr": "~2.0.7", 542 | "qs": "6.9.6", 543 | "range-parser": "~1.2.1", 544 | "safe-buffer": "5.2.1", 545 | "send": "0.17.2", 546 | "serve-static": "1.14.2", 547 | "setprototypeof": "1.2.0", 548 | "statuses": "~1.5.0", 549 | "type-is": "~1.6.18", 550 | "utils-merge": "1.0.1", 551 | "vary": "~1.1.2" 552 | } 553 | }, 554 | "finalhandler": { 555 | "version": "1.1.2", 556 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 557 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 558 | "requires": { 559 | "debug": "2.6.9", 560 | "encodeurl": "~1.0.2", 561 | "escape-html": "~1.0.3", 562 | "on-finished": "~2.3.0", 563 | "parseurl": "~1.3.3", 564 | "statuses": "~1.5.0", 565 | "unpipe": "~1.0.0" 566 | } 567 | }, 568 | "form-data": { 569 | "version": "3.0.1", 570 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 571 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 572 | "requires": { 573 | "asynckit": "^0.4.0", 574 | "combined-stream": "^1.0.8", 575 | "mime-types": "^2.1.12" 576 | } 577 | }, 578 | "forwarded": { 579 | "version": "0.2.0", 580 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 581 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 582 | }, 583 | "fresh": { 584 | "version": "0.5.2", 585 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 586 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 587 | }, 588 | "fs-minipass": { 589 | "version": "2.1.0", 590 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 591 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 592 | "requires": { 593 | "minipass": "^3.0.0" 594 | } 595 | }, 596 | "fs.realpath": { 597 | "version": "1.0.0", 598 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 599 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 600 | }, 601 | "gauge": { 602 | "version": "2.7.4", 603 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 604 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 605 | "requires": { 606 | "aproba": "^1.0.3", 607 | "console-control-strings": "^1.0.0", 608 | "has-unicode": "^2.0.0", 609 | "object-assign": "^4.1.0", 610 | "signal-exit": "^3.0.0", 611 | "string-width": "^1.0.1", 612 | "strip-ansi": "^3.0.1", 613 | "wide-align": "^1.1.0" 614 | } 615 | }, 616 | "get-caller-file": { 617 | "version": "2.0.5", 618 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 619 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 620 | }, 621 | "glob": { 622 | "version": "7.2.0", 623 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 624 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 625 | "requires": { 626 | "fs.realpath": "^1.0.0", 627 | "inflight": "^1.0.4", 628 | "inherits": "2", 629 | "minimatch": "^3.0.4", 630 | "once": "^1.3.0", 631 | "path-is-absolute": "^1.0.0" 632 | } 633 | }, 634 | "google-protobuf": { 635 | "version": "3.19.1", 636 | "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.1.tgz", 637 | "integrity": "sha512-Isv1RlNC+IzZzilcxnlVSf+JvuhxmY7DaxYCBy+zPS9XVuJRtlTTIXR9hnZ1YL1MMusJn/7eSy2swCzZIomQSg==" 638 | }, 639 | "has-flag": { 640 | "version": "4.0.0", 641 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 642 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 643 | }, 644 | "has-unicode": { 645 | "version": "2.0.1", 646 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 647 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 648 | }, 649 | "http-errors": { 650 | "version": "1.8.1", 651 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 652 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 653 | "requires": { 654 | "depd": "~1.1.2", 655 | "inherits": "2.0.4", 656 | "setprototypeof": "1.2.0", 657 | "statuses": ">= 1.5.0 < 2", 658 | "toidentifier": "1.0.1" 659 | } 660 | }, 661 | "https-proxy-agent": { 662 | "version": "2.2.4", 663 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 664 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 665 | "requires": { 666 | "agent-base": "^4.3.0", 667 | "debug": "^3.1.0" 668 | }, 669 | "dependencies": { 670 | "agent-base": { 671 | "version": "4.3.0", 672 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 673 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 674 | "requires": { 675 | "es6-promisify": "^5.0.0" 676 | } 677 | }, 678 | "debug": { 679 | "version": "3.2.7", 680 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 681 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 682 | "requires": { 683 | "ms": "^2.1.1" 684 | } 685 | }, 686 | "ms": { 687 | "version": "2.1.3", 688 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 689 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 690 | } 691 | } 692 | }, 693 | "iconv-lite": { 694 | "version": "0.4.24", 695 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 696 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 697 | "requires": { 698 | "safer-buffer": ">= 2.1.2 < 3" 699 | } 700 | }, 701 | "inflight": { 702 | "version": "1.0.6", 703 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 704 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 705 | "requires": { 706 | "once": "^1.3.0", 707 | "wrappy": "1" 708 | } 709 | }, 710 | "inherits": { 711 | "version": "2.0.4", 712 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 713 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 714 | }, 715 | "ipaddr.js": { 716 | "version": "1.9.1", 717 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 718 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 719 | }, 720 | "is-fullwidth-code-point": { 721 | "version": "1.0.0", 722 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 723 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 724 | "requires": { 725 | "number-is-nan": "^1.0.0" 726 | } 727 | }, 728 | "isarray": { 729 | "version": "1.0.0", 730 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 731 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 732 | }, 733 | "long": { 734 | "version": "4.0.0", 735 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 736 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 737 | }, 738 | "lru-cache": { 739 | "version": "6.0.0", 740 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 741 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 742 | "requires": { 743 | "yallist": "^4.0.0" 744 | } 745 | }, 746 | "make-dir": { 747 | "version": "3.1.0", 748 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 749 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 750 | "requires": { 751 | "semver": "^6.0.0" 752 | }, 753 | "dependencies": { 754 | "semver": { 755 | "version": "6.3.0", 756 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 757 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 758 | } 759 | } 760 | }, 761 | "media-typer": { 762 | "version": "0.3.0", 763 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 764 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 765 | }, 766 | "merge-descriptors": { 767 | "version": "1.0.1", 768 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 769 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 770 | }, 771 | "methods": { 772 | "version": "1.1.2", 773 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 774 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 775 | }, 776 | "mime": { 777 | "version": "1.6.0", 778 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 779 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 780 | }, 781 | "mime-db": { 782 | "version": "1.51.0", 783 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 784 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 785 | }, 786 | "mime-types": { 787 | "version": "2.1.34", 788 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 789 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 790 | "requires": { 791 | "mime-db": "1.51.0" 792 | } 793 | }, 794 | "minimatch": { 795 | "version": "3.0.4", 796 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 797 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 798 | "requires": { 799 | "brace-expansion": "^1.1.7" 800 | } 801 | }, 802 | "minimist": { 803 | "version": "1.2.5", 804 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 805 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 806 | }, 807 | "minipass": { 808 | "version": "3.1.6", 809 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", 810 | "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", 811 | "requires": { 812 | "yallist": "^4.0.0" 813 | } 814 | }, 815 | "minizlib": { 816 | "version": "2.1.2", 817 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 818 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 819 | "requires": { 820 | "minipass": "^3.0.0", 821 | "yallist": "^4.0.0" 822 | } 823 | }, 824 | "mkdirp": { 825 | "version": "1.0.4", 826 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 827 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 828 | }, 829 | "ms": { 830 | "version": "2.0.0", 831 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 832 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 833 | }, 834 | "negotiator": { 835 | "version": "0.6.2", 836 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 837 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 838 | }, 839 | "node-fetch": { 840 | "version": "2.6.6", 841 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", 842 | "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", 843 | "requires": { 844 | "whatwg-url": "^5.0.0" 845 | } 846 | }, 847 | "nopt": { 848 | "version": "5.0.0", 849 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 850 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 851 | "requires": { 852 | "abbrev": "1" 853 | } 854 | }, 855 | "npmlog": { 856 | "version": "4.1.2", 857 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 858 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 859 | "requires": { 860 | "are-we-there-yet": "~1.1.2", 861 | "console-control-strings": "~1.1.0", 862 | "gauge": "~2.7.3", 863 | "set-blocking": "~2.0.0" 864 | } 865 | }, 866 | "number-is-nan": { 867 | "version": "1.0.1", 868 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 869 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 870 | }, 871 | "object-assign": { 872 | "version": "4.1.1", 873 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 874 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 875 | }, 876 | "on-finished": { 877 | "version": "2.3.0", 878 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 879 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 880 | "requires": { 881 | "ee-first": "1.1.1" 882 | } 883 | }, 884 | "once": { 885 | "version": "1.4.0", 886 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 887 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 888 | "requires": { 889 | "wrappy": "1" 890 | } 891 | }, 892 | "parseurl": { 893 | "version": "1.3.3", 894 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 895 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 896 | }, 897 | "path-is-absolute": { 898 | "version": "1.0.1", 899 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 900 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 901 | }, 902 | "path-to-regexp": { 903 | "version": "0.1.7", 904 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 905 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 906 | }, 907 | "process-nextick-args": { 908 | "version": "2.0.1", 909 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 910 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 911 | }, 912 | "progress": { 913 | "version": "2.0.3", 914 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 915 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 916 | }, 917 | "proxy-addr": { 918 | "version": "2.0.7", 919 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 920 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 921 | "requires": { 922 | "forwarded": "0.2.0", 923 | "ipaddr.js": "1.9.1" 924 | } 925 | }, 926 | "qs": { 927 | "version": "6.9.6", 928 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", 929 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" 930 | }, 931 | "range-parser": { 932 | "version": "1.2.1", 933 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 934 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 935 | }, 936 | "raw-body": { 937 | "version": "2.4.2", 938 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", 939 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", 940 | "requires": { 941 | "bytes": "3.1.1", 942 | "http-errors": "1.8.1", 943 | "iconv-lite": "0.4.24", 944 | "unpipe": "1.0.0" 945 | } 946 | }, 947 | "readable-stream": { 948 | "version": "2.3.7", 949 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 950 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 951 | "requires": { 952 | "core-util-is": "~1.0.0", 953 | "inherits": "~2.0.3", 954 | "isarray": "~1.0.0", 955 | "process-nextick-args": "~2.0.0", 956 | "safe-buffer": "~5.1.1", 957 | "string_decoder": "~1.1.1", 958 | "util-deprecate": "~1.0.1" 959 | }, 960 | "dependencies": { 961 | "safe-buffer": { 962 | "version": "5.1.2", 963 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 964 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 965 | } 966 | } 967 | }, 968 | "regenerator-runtime": { 969 | "version": "0.13.9", 970 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", 971 | "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" 972 | }, 973 | "require-directory": { 974 | "version": "2.1.1", 975 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 976 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 977 | }, 978 | "rimraf": { 979 | "version": "2.7.1", 980 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 981 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 982 | "requires": { 983 | "glob": "^7.1.3" 984 | } 985 | }, 986 | "safe-buffer": { 987 | "version": "5.2.1", 988 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 989 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 990 | }, 991 | "safer-buffer": { 992 | "version": "2.1.2", 993 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 994 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 995 | }, 996 | "seedrandom": { 997 | "version": "2.4.3", 998 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", 999 | "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" 1000 | }, 1001 | "semver": { 1002 | "version": "7.3.5", 1003 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 1004 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1005 | "requires": { 1006 | "lru-cache": "^6.0.0" 1007 | } 1008 | }, 1009 | "send": { 1010 | "version": "0.17.2", 1011 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 1012 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 1013 | "requires": { 1014 | "debug": "2.6.9", 1015 | "depd": "~1.1.2", 1016 | "destroy": "~1.0.4", 1017 | "encodeurl": "~1.0.2", 1018 | "escape-html": "~1.0.3", 1019 | "etag": "~1.8.1", 1020 | "fresh": "0.5.2", 1021 | "http-errors": "1.8.1", 1022 | "mime": "1.6.0", 1023 | "ms": "2.1.3", 1024 | "on-finished": "~2.3.0", 1025 | "range-parser": "~1.2.1", 1026 | "statuses": "~1.5.0" 1027 | }, 1028 | "dependencies": { 1029 | "ms": { 1030 | "version": "2.1.3", 1031 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1032 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1033 | } 1034 | } 1035 | }, 1036 | "serve-static": { 1037 | "version": "1.14.2", 1038 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 1039 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 1040 | "requires": { 1041 | "encodeurl": "~1.0.2", 1042 | "escape-html": "~1.0.3", 1043 | "parseurl": "~1.3.3", 1044 | "send": "0.17.2" 1045 | } 1046 | }, 1047 | "set-blocking": { 1048 | "version": "2.0.0", 1049 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1050 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1051 | }, 1052 | "setprototypeof": { 1053 | "version": "1.2.0", 1054 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1055 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1056 | }, 1057 | "signal-exit": { 1058 | "version": "3.0.6", 1059 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", 1060 | "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" 1061 | }, 1062 | "sprintf-js": { 1063 | "version": "1.0.3", 1064 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1065 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 1066 | }, 1067 | "statuses": { 1068 | "version": "1.5.0", 1069 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1070 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1071 | }, 1072 | "string-width": { 1073 | "version": "1.0.2", 1074 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1075 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1076 | "requires": { 1077 | "code-point-at": "^1.0.0", 1078 | "is-fullwidth-code-point": "^1.0.0", 1079 | "strip-ansi": "^3.0.0" 1080 | } 1081 | }, 1082 | "string_decoder": { 1083 | "version": "1.1.1", 1084 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1085 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1086 | "requires": { 1087 | "safe-buffer": "~5.1.0" 1088 | }, 1089 | "dependencies": { 1090 | "safe-buffer": { 1091 | "version": "5.1.2", 1092 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1093 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1094 | } 1095 | } 1096 | }, 1097 | "strip-ansi": { 1098 | "version": "3.0.1", 1099 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1100 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1101 | "requires": { 1102 | "ansi-regex": "^2.0.0" 1103 | } 1104 | }, 1105 | "supports-color": { 1106 | "version": "7.2.0", 1107 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1108 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1109 | "requires": { 1110 | "has-flag": "^4.0.0" 1111 | } 1112 | }, 1113 | "tar": { 1114 | "version": "4.4.19", 1115 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", 1116 | "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", 1117 | "requires": { 1118 | "chownr": "^1.1.4", 1119 | "fs-minipass": "^1.2.7", 1120 | "minipass": "^2.9.0", 1121 | "minizlib": "^1.3.3", 1122 | "mkdirp": "^0.5.5", 1123 | "safe-buffer": "^5.2.1", 1124 | "yallist": "^3.1.1" 1125 | }, 1126 | "dependencies": { 1127 | "chownr": { 1128 | "version": "1.1.4", 1129 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 1130 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 1131 | }, 1132 | "fs-minipass": { 1133 | "version": "1.2.7", 1134 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 1135 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 1136 | "requires": { 1137 | "minipass": "^2.6.0" 1138 | } 1139 | }, 1140 | "minipass": { 1141 | "version": "2.9.0", 1142 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 1143 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 1144 | "requires": { 1145 | "safe-buffer": "^5.1.2", 1146 | "yallist": "^3.0.0" 1147 | } 1148 | }, 1149 | "minizlib": { 1150 | "version": "1.3.3", 1151 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 1152 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 1153 | "requires": { 1154 | "minipass": "^2.9.0" 1155 | } 1156 | }, 1157 | "mkdirp": { 1158 | "version": "0.5.5", 1159 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1160 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1161 | "requires": { 1162 | "minimist": "^1.2.5" 1163 | } 1164 | }, 1165 | "yallist": { 1166 | "version": "3.1.1", 1167 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1168 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1169 | } 1170 | } 1171 | }, 1172 | "toidentifier": { 1173 | "version": "1.0.1", 1174 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1175 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 1176 | }, 1177 | "tr46": { 1178 | "version": "0.0.3", 1179 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1180 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 1181 | }, 1182 | "type-is": { 1183 | "version": "1.6.18", 1184 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1185 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1186 | "requires": { 1187 | "media-typer": "0.3.0", 1188 | "mime-types": "~2.1.24" 1189 | } 1190 | }, 1191 | "unpipe": { 1192 | "version": "1.0.0", 1193 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1194 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1195 | }, 1196 | "util-deprecate": { 1197 | "version": "1.0.2", 1198 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1199 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1200 | }, 1201 | "utils-merge": { 1202 | "version": "1.0.1", 1203 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1204 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1205 | }, 1206 | "vary": { 1207 | "version": "1.1.2", 1208 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1209 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1210 | }, 1211 | "webidl-conversions": { 1212 | "version": "3.0.1", 1213 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1214 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1215 | }, 1216 | "whatwg-url": { 1217 | "version": "5.0.0", 1218 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1219 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1220 | "requires": { 1221 | "tr46": "~0.0.3", 1222 | "webidl-conversions": "^3.0.0" 1223 | } 1224 | }, 1225 | "wide-align": { 1226 | "version": "1.1.5", 1227 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 1228 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 1229 | "requires": { 1230 | "string-width": "^1.0.2 || 2 || 3 || 4" 1231 | } 1232 | }, 1233 | "wrap-ansi": { 1234 | "version": "7.0.0", 1235 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1236 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1237 | "requires": { 1238 | "ansi-styles": "^4.0.0", 1239 | "string-width": "^4.1.0", 1240 | "strip-ansi": "^6.0.0" 1241 | }, 1242 | "dependencies": { 1243 | "ansi-regex": { 1244 | "version": "5.0.1", 1245 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1246 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 1247 | }, 1248 | "is-fullwidth-code-point": { 1249 | "version": "3.0.0", 1250 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1251 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 1252 | }, 1253 | "string-width": { 1254 | "version": "4.2.3", 1255 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1256 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1257 | "requires": { 1258 | "emoji-regex": "^8.0.0", 1259 | "is-fullwidth-code-point": "^3.0.0", 1260 | "strip-ansi": "^6.0.1" 1261 | } 1262 | }, 1263 | "strip-ansi": { 1264 | "version": "6.0.1", 1265 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1266 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1267 | "requires": { 1268 | "ansi-regex": "^5.0.1" 1269 | } 1270 | } 1271 | } 1272 | }, 1273 | "wrappy": { 1274 | "version": "1.0.2", 1275 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1276 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1277 | }, 1278 | "y18n": { 1279 | "version": "5.0.8", 1280 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1281 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 1282 | }, 1283 | "yallist": { 1284 | "version": "4.0.0", 1285 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1286 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1287 | }, 1288 | "yargs": { 1289 | "version": "16.2.0", 1290 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1291 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1292 | "requires": { 1293 | "cliui": "^7.0.2", 1294 | "escalade": "^3.1.1", 1295 | "get-caller-file": "^2.0.5", 1296 | "require-directory": "^2.1.1", 1297 | "string-width": "^4.2.0", 1298 | "y18n": "^5.0.5", 1299 | "yargs-parser": "^20.2.2" 1300 | }, 1301 | "dependencies": { 1302 | "ansi-regex": { 1303 | "version": "5.0.1", 1304 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1305 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 1306 | }, 1307 | "is-fullwidth-code-point": { 1308 | "version": "3.0.0", 1309 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1310 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 1311 | }, 1312 | "string-width": { 1313 | "version": "4.2.3", 1314 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1315 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1316 | "requires": { 1317 | "emoji-regex": "^8.0.0", 1318 | "is-fullwidth-code-point": "^3.0.0", 1319 | "strip-ansi": "^6.0.1" 1320 | } 1321 | }, 1322 | "strip-ansi": { 1323 | "version": "6.0.1", 1324 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1325 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1326 | "requires": { 1327 | "ansi-regex": "^5.0.1" 1328 | } 1329 | } 1330 | } 1331 | }, 1332 | "yargs-parser": { 1333 | "version": "20.2.9", 1334 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 1335 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" 1336 | } 1337 | } 1338 | } 1339 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "performance-quality-models-nodejs", 3 | "version": "0.0.1", 4 | "description": "An example of using the performance quality models on Nodejs using Express.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Mark E. Pascual ", 11 | "dependencies": { 12 | "@tensorflow/tfjs-node": "^3.12.0", 13 | "express": "^4.17.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/predictors.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const tf = require("@tensorflow/tfjs-node"); 3 | 4 | class PredictorBase { 5 | constructor(modelDir) { 6 | this.modelDir = modelDir; 7 | this.modelName = path.basename(modelDir); 8 | this.model = null; 9 | } 10 | 11 | async loadModel() { 12 | this.model = await tf.node.loadSavedModel(this.modelDir); 13 | } 14 | 15 | preProcessInput(input) { 16 | throw new Error("Not Implemented"); 17 | } 18 | 19 | /** 20 | * Wraps the feature values as tensor arrays 21 | * Types are inferred by tfjs 22 | * Keys to input should match with what the model expects 23 | * @param {object} input final input dictionary ready to be passed to the model 24 | * @returns a dictionary of tensor arrays as required by the model 25 | */ 26 | prepareX(input) { 27 | const x = {}; 28 | for (const feature of Object.keys(input)) { 29 | const value = input[feature]; 30 | x[feature] = tf.tensor([value], [1, 1]); // explicitly ensure it is not a rank 0 tensor 31 | } 32 | return x; 33 | } 34 | 35 | /** 36 | * Process the input and make predictions on it 37 | * @param {object} rawInput {[name: string]: tf.Tensor} dictionary 38 | * @returns {class1: probability1, class2: probability2, ...} 39 | */ 40 | predict(rawInput) { 41 | if (!this.model) { 42 | throw new Error( 43 | `Model '${this.modelName}' is not loaded. Please load it first.` 44 | ); 45 | } 46 | const result = tf.tidy(() => { 47 | const input = this.preProcessInput(rawInput); 48 | const x = this.prepareX(input); 49 | const output = this.model.predict(x, {}); 50 | const probs = Array.from(output.probabilities.dataSync()); 51 | const classes = Array.from(output.all_class_ids.dataSync()); 52 | const result = Object.fromEntries( 53 | classes.map((classId, i) => [classId, probs[i]]) 54 | ); 55 | return result; 56 | }); 57 | 58 | for (const [key, value] of Object.entries(result)) { 59 | if (typeof value !== "number" || isNaN(value)) { 60 | throw new Error( 61 | `Invalid confidence score (${value}) causing bad prediction result: ${JSON.stringify( 62 | result 63 | )}` 64 | ); 65 | } 66 | } 67 | 68 | return result; 69 | } 70 | } 71 | 72 | class MWebJan2022Predictor extends PredictorBase { 73 | constructor(modelDir) { 74 | super(modelDir); 75 | this._features = [ 76 | "asn_number", 77 | "browser_major_version", 78 | "browser_major_version_na", 79 | "browser_name", 80 | "country_code", 81 | "osfamily", 82 | "osmajor", 83 | "osmajor_na" 84 | ]; 85 | this._defaults = { 86 | "browser_major_version": 15.0, 87 | "osmajor": 14.0, 88 | "asn_number": '**', 89 | "country_code": '**', 90 | "browser_name": '**', 91 | "osfamily": '**' 92 | }; 93 | this._normalizer = { 94 | "means": { "browser_major_version": 52.65782220933843, "osmajor": 13.372263709715911 }, 95 | "stds": { "browser_major_version": 41.48294747389074, "osmajor": 2.376855002582524 } 96 | }; 97 | } 98 | 99 | async loadModel() { 100 | this.model = await tf.node.loadSavedModel( 101 | this.modelDir, 102 | ["serve"], 103 | "predict" 104 | ); 105 | } 106 | 107 | _normalizeNumericalFetaures(x) { 108 | const { means, stds } = this._normalizer; 109 | for (const feature in means) { 110 | x[feature] = (parseFloat(x[feature]) - means[feature]) / stds[feature]; 111 | } 112 | return x; 113 | } 114 | 115 | _checkNA(value) { 116 | return ( 117 | value === null || 118 | value === undefined || 119 | value < 0 || 120 | value === "" || 121 | value === "unknown" 122 | ); 123 | } 124 | 125 | _fillNA(x) { 126 | for (const feature of Object.keys(x)) { 127 | if (this._checkNA(x[feature])) { 128 | x[feature] = this._defaults[feature]; 129 | } 130 | } 131 | return x; 132 | } 133 | 134 | _addNAFetaures(x) { 135 | x.browser_major_version_na = "False"; 136 | x.osmajor_na = "False"; 137 | 138 | if (this._checkNA(x.browser_major_version)) { 139 | x.browser_major_version_na = "True"; 140 | } 141 | 142 | if (this._checkNA(x.osmajor)) { 143 | x.osmajor_na = "True"; 144 | } 145 | return x; 146 | } 147 | 148 | preProcessInput(input) { 149 | let x = {}; 150 | for (const feature of this._features) { 151 | x[feature] = input[feature]; 152 | } 153 | x = this._addNAFetaures(x); 154 | x = this._fillNA(x); 155 | x = this._normalizeNumericalFetaures(x); 156 | return x; 157 | } 158 | } 159 | 160 | module.exports = { MWebJan2022Predictor }; 161 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const { MWebJan2022Predictor } = require('./predictors'); 4 | 5 | async function main() { 6 | // load the model so it's ready 7 | const predictor = new MWebJan2022Predictor(__dirname + '/../models/nodejs-saved-model'); 8 | await predictor.loadModel(); 9 | 10 | // Make all the files in 'www' available. 11 | app.use(express.static('www')); 12 | 13 | app.get('/api/performanceQuality', (req, res) => { 14 | const { 15 | asn, 16 | country_code, 17 | browser_name, 18 | browser_major_version, 19 | osFamily, 20 | osMajor 21 | } = req.query; 22 | const result = predictor.predict({asn, country_code, browser_name, browser_major_version, osFamily, osMajor}); 23 | res.setHeader('Content-Type', 'application/json'); 24 | res.send(result); 25 | }); 26 | 27 | app.get('/', (req, res) => { 28 | res.sendFile(__dirname + '/www/index.html'); 29 | }); 30 | 31 | // Listen for requests. 32 | const listener = app.listen(3001, () => { 33 | console.log('Your app is listening on port ' + listener.address().port); 34 | }); 35 | } 36 | 37 | main(); 38 | 39 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Performance Quality Predictor 12 | 13 | 14 |
15 |

Performance Quality Predictor

16 |
17 |
18 |
19 | ASN 20 |
21 | 22 |
23 |
24 |
25 | Country Code 26 |
27 | 28 |
29 |
30 |
31 | Browser Name 32 |
33 | 34 |
35 |
36 |
37 | Browser Major Version 38 |
39 | 40 |
41 |
42 |
43 | OS Family 44 |
45 | 46 |
47 |
48 |
49 | OS Major Version 50 |
51 | 52 |
53 | 54 |
55 |
56 |
57 |

Prediction Result

58 |
59 |

API Url:

60 |
61 |
62 |

JSON:

63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/nodejs-example/www/predictor-client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function getFeatures() { 4 | const asn_number = $('#asn-input').val().trim(); 5 | const country_code = $('#country-code-input').val().trim(); 6 | const browser_name = $('#browser-name-input').val().trim(); 7 | const browser_major_version = $('#browser-major-version-input').val().trim(); 8 | const osFamily = $('#os-family-input').val().trim(); 9 | const osMajor = $('#os-major-version-input').val().trim(); 10 | 11 | const features = { asn_number, country_code, browser_name, browser_major_version, osFamily, osMajor }; 12 | for (const k in features) { 13 | if (features[k] === '') { 14 | delete features[k]; 15 | } 16 | } 17 | return features; 18 | } 19 | 20 | $('#api-form').submit(event => { 21 | $('#api-url').text(''); 22 | $('#api-result').text(''); 23 | 24 | const features = getFeatures(); 25 | const queryString = new URLSearchParams(features); 26 | const apiUrl = `${window.location.origin}/api/performanceQuality?${queryString}`; 27 | $('#api-url').text(apiUrl); 28 | 29 | $.getJSON(apiUrl, data => { 30 | $('#api-result').text(JSON.stringify(data)); 31 | }); 32 | 33 | event.preventDefault(); 34 | }); 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/python-example/README.md: -------------------------------------------------------------------------------- 1 | # Serve PQMs using Python 2 | 3 | This folder contains a simple script to load the SSR PQM and make predictions with it in production. This is the same model as in the node.js case. Simply paste this code in your flask or any other Python web server backends. 4 | 5 | ## Try on Google Colab 6 | - Visit https://colab.research.google.com/github/linkedin/performance-quality-models/blob/main/ssr-mobile-web/mweb-jan-2022-v1/python-example/ssr_mobile_web_model_demo.ipynb 7 | - Follow the code step by step with inline documentation. The behavior of the model can be experienced interactively using a form based UI at the end. 8 | 9 | ## Try in your own environment 10 | 11 | We recommend using Google colab to try the models as it requires no additional setup. To run this on your local laptop 12 | 13 | - Clone the repo 14 | - Install Python 3 15 | - `pip install jupyter` 16 | - `jupyter notebook` 17 | - Open ssr-mobile-web/mweb-jan-2022-v1/python-example/ssr_mobile_web_model_demo.ipynb in the Jupyter UI 18 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/python-example/ssr_mobile_web_model_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "adb88842", 6 | "metadata": { 7 | "id": "adb88842" 8 | }, 9 | "source": [ 10 | "# SSR Mobile Web Model Python Demo" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "6524c6d0", 16 | "metadata": { 17 | "id": "6524c6d0" 18 | }, 19 | "source": [ 20 | "We'll see how to load the mweb-jan-2022-v1 predictor and make predictions with it in Python. As a bonus, we also share a playground to play with the model and get a feel for its performance. The interactive UI demo only works in a notebook interface.\n", 21 | "\n", 22 | "Simply run all the cells below to get started. " 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 1, 28 | "id": "f61621f3", 29 | "metadata": { 30 | "colab": { 31 | "base_uri": "https://localhost:8080/" 32 | }, 33 | "id": "f61621f3", 34 | "outputId": "896e0836-c6dc-46e3-aa14-fc5b803799ac", 35 | "scrolled": true 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "! pip install -U -q pip && pip install -U -q tensorflow==2.5 pandas" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "id": "71620998", 46 | "metadata": { 47 | "colab": { 48 | "base_uri": "https://localhost:8080/", 49 | "height": 35 50 | }, 51 | "id": "71620998", 52 | "outputId": "6129e017-f168-4fa0-a842-0d0749de131b" 53 | }, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "text/plain": [ 58 | "'2.5.0'" 59 | ] 60 | }, 61 | "execution_count": 2, 62 | "metadata": {}, 63 | "output_type": "execute_result" 64 | } 65 | ], 66 | "source": [ 67 | "import re\n", 68 | "import logging\n", 69 | "from pathlib import Path\n", 70 | "\n", 71 | "import tensorflow as tf\n", 72 | "\n", 73 | "tf.__version__" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 3, 79 | "id": "p-fchD8vNZNJ", 80 | "metadata": { 81 | "id": "p-fchD8vNZNJ" 82 | }, 83 | "outputs": [], 84 | "source": [ 85 | "logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(message)s')" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 11, 91 | "id": "dc69496b", 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "# the below model is the TF Python equivalent of JS' saved model\n", 96 | "MODEL_PATH = \"../models/py-saved-model\"" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "id": "Hr_DmNjkNHAe", 102 | "metadata": { 103 | "id": "Hr_DmNjkNHAe" 104 | }, 105 | "source": [ 106 | "Setup the notebook for Google Colab. This cell can be ignored if not on colab.google.com" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 6, 112 | "id": "Fr1fjodASSi8", 113 | "metadata": { 114 | "colab": { 115 | "base_uri": "https://localhost:8080/" 116 | }, 117 | "id": "Fr1fjodASSi8", 118 | "outputId": "e9c7b7b2-8cf0-475d-95e0-d4c946e14e63" 119 | }, 120 | "outputs": [ 121 | { 122 | "name": "stderr", 123 | "output_type": "stream", 124 | "text": [ 125 | "2022-02-18 17:26:42,503 Ignore this warning if not on colab.google.com\n", 126 | "Traceback (most recent call last):\n", 127 | " File \"/var/folders/mv/b0h9w4y51vg9rzqvklk87z1h000vmf/T/ipykernel_5689/607255814.py\", line 2, in \n", 128 | " import google.colab\n", 129 | "ModuleNotFoundError: No module named 'google.colab'\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "try:\n", 135 | " import google.colab\n", 136 | " import subprocess\n", 137 | " \n", 138 | " clone_cmd_res = subprocess.run(\n", 139 | " \"git clone -l -s https://github.com/linkedin/performance-quality-models.git performance-quality-models\",\n", 140 | " shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True\n", 141 | " )\n", 142 | " \n", 143 | " if clone_cmd_res.returncode != 0:\n", 144 | " raise Exception(clone_cmd_res.stderr)\n", 145 | " \n", 146 | " %cd performance-quality-models\n", 147 | " \n", 148 | " MODEL_PATH = \"./ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model\"\n", 149 | "except:\n", 150 | " logging.warning(\"Ignore this warning if not on colab.google.com\", exc_info=True)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "id": "qdvGxnk9Nf7k", 156 | "metadata": { 157 | "id": "qdvGxnk9Nf7k" 158 | }, 159 | "source": [ 160 | "Define a Predictor class which loads the model and transforms the data into a form that model can understand." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 7, 166 | "id": "8f9d5505", 167 | "metadata": { 168 | "code_folding": [ 169 | 0 170 | ], 171 | "id": "8f9d5505" 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "class MWebJan2022Predictor:\n", 176 | " def __init__(self, modelDir):\n", 177 | " self.modelDir = modelDir;\n", 178 | " self.modelName = Path(modelDir).name\n", 179 | " self.model = None\n", 180 | " \n", 181 | " self._features = [\n", 182 | " 'asn_number',\n", 183 | " 'browser_major_version',\n", 184 | " 'browser_major_version_na',\n", 185 | " 'browser_name',\n", 186 | " 'country_code',\n", 187 | " 'osfamily',\n", 188 | " 'osmajor',\n", 189 | " 'osmajor_na'\n", 190 | " ]\n", 191 | "\n", 192 | " self._defaults = {\n", 193 | " \"browser_major_version\": 15.0, \n", 194 | " \"osmajor\": 14.0,\n", 195 | " \"asn_number\": '**',\n", 196 | " \"country_code\": '**',\n", 197 | " \"browser_name\": '**',\n", 198 | " \"osfamily\": '**',\n", 199 | " }\n", 200 | "\n", 201 | " self._normalizer = {\n", 202 | " \"means\": {\"browser_major_version\": 52.65782220933843, \"osmajor\": 13.372263709715911}, \n", 203 | " \"stds\": {\"browser_major_version\": 41.48294747389074, \"osmajor\": 2.376855002582524}\n", 204 | " }\n", 205 | "\n", 206 | " def loadModel(self):\n", 207 | " self.model = tf.saved_model.load(self.modelDir).signatures[\"predict\"]\n", 208 | " \n", 209 | " def _normalizeNumericalFetaures(self, x):\n", 210 | " means = self._normalizer['means']\n", 211 | " stds = self._normalizer[\"stds\"] \n", 212 | " for feature in means:\n", 213 | " x[feature] = (float(x[feature]) - means[feature]) / stds[feature];\n", 214 | " return x;\n", 215 | "\n", 216 | " def _checkNA(self, value):\n", 217 | " res = value == None or value == '' or value == 'unknown'\n", 218 | " if isinstance(value, float) or isinstance(value, int):\n", 219 | " res = res or value < 0\n", 220 | " return res\n", 221 | "\n", 222 | " def _fillNA(self, x):\n", 223 | " for feature in x.keys():\n", 224 | " if self._checkNA(x[feature]):\n", 225 | " x[feature] = self._defaults[feature];\n", 226 | " return x;\n", 227 | "\n", 228 | " def _addNAFetaures(self, x):\n", 229 | " x[\"browser_major_version_na\"] = 'False';\n", 230 | " x[\"osmajor_na\"] = 'False';\n", 231 | "\n", 232 | " if self._checkNA(x[\"browser_major_version\"]):\n", 233 | " x[\"browser_major_version_na\"] = 'True';\n", 234 | "\n", 235 | " if self._checkNA(x[\"osmajor\"]):\n", 236 | " x[\"osmajor_na\"] = 'True';\n", 237 | "\n", 238 | " return x;\n", 239 | " \n", 240 | " def _convert_to_bytes(self, x):\n", 241 | " for feat, val in x.items():\n", 242 | " if isinstance(val, str):\n", 243 | " x[feat] = bytes(x[feat], 'utf-8')\n", 244 | " return x\n", 245 | " \n", 246 | " def prepareX(self, inp_example):\n", 247 | " model_input = tf.train.Example(features=tf.train.Features(feature={\n", 248 | " 'country_code': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"country_code\"]])),\n", 249 | " 'osfamily': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"osfamily\"]])),\n", 250 | " 'browser_name': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"browser_name\"]])),\n", 251 | " 'browser_major_version_na': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"browser_major_version_na\"]])),\n", 252 | " 'osmajor_na': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"osmajor_na\"]])),\n", 253 | " 'asn_number': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example[\"asn_number\"]])),\n", 254 | " 'browser_major_version': tf.train.Feature(float_list=tf.train.FloatList(value=[inp_example[\"browser_major_version\"]])),\n", 255 | " 'osmajor': tf.train.Feature(float_list=tf.train.FloatList(value=[inp_example[\"osmajor\"]]))\n", 256 | " }))\n", 257 | " return model_input.SerializeToString()\n", 258 | "\n", 259 | " def preProcessInput(self, inp):\n", 260 | " x = {};\n", 261 | " for feature in self._features:\n", 262 | " x[feature] = inp.get(feature, None);\n", 263 | "\n", 264 | " x = self._addNAFetaures(x);\n", 265 | " x = self._fillNA(x);\n", 266 | " x = self._normalizeNumericalFetaures(x);\n", 267 | " x = self._convert_to_bytes(x)\n", 268 | " return x;\n", 269 | " \n", 270 | " \n", 271 | " def predict(self, rawInput):\n", 272 | " \"\"\"\n", 273 | " * Process the input and make predictions on it\n", 274 | " * @param {object} rawInput {[name: string]: tf.Tensor} dictionary\n", 275 | " * @returns {class1: probability1, class2: probability2, ...}\n", 276 | " \"\"\"\n", 277 | " if not self.model:\n", 278 | " self.loadModel()\n", 279 | "\n", 280 | " inp = self.preProcessInput(rawInput);\n", 281 | " logging.debug(f\"Model input: {inp}\")\n", 282 | " x = self.prepareX(inp)\n", 283 | " logging.debug(f\"Model (x): {x}\")\n", 284 | " output = self.model(examples=tf.constant([x]))\n", 285 | " return output" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 8, 291 | "id": "4918436d", 292 | "metadata": { 293 | "code_folding": [ 294 | 0 295 | ], 296 | "id": "4918436d", 297 | "scrolled": true 298 | }, 299 | "outputs": [], 300 | "source": [ 301 | "def make_prediction(predictor, inp):\n", 302 | " p = predictor.predict(inp)\n", 303 | " scores = p['probabilities'].numpy()[0]\n", 304 | " return {i: score for i, score in enumerate(scores)} # return the probability for each class" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 12, 310 | "id": "c67cec70", 311 | "metadata": { 312 | "id": "c67cec70", 313 | "scrolled": true 314 | }, 315 | "outputs": [ 316 | { 317 | "name": "stderr", 318 | "output_type": "stream", 319 | "text": [ 320 | "2022-02-18 17:30:13.148740: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", 321 | "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", 322 | "2022-02-18 17:30:13.563895: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)\n" 323 | ] 324 | } 325 | ], 326 | "source": [ 327 | "predictor = MWebJan2022Predictor(MODEL_PATH)\n", 328 | "predictor.loadModel()" 329 | ] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "id": "769234f7", 334 | "metadata": { 335 | "id": "769234f7" 336 | }, 337 | "source": [ 338 | "Make some predictions" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": 13, 344 | "id": "22480e27", 345 | "metadata": { 346 | "colab": { 347 | "base_uri": "https://localhost:8080/" 348 | }, 349 | "id": "22480e27", 350 | "outputId": "a9983b68-586e-46d7-a4a4-0ed72a1fc157" 351 | }, 352 | "outputs": [ 353 | { 354 | "data": { 355 | "text/plain": [ 356 | "{0: 0.0106238695, 1: 0.9893762}" 357 | ] 358 | }, 359 | "execution_count": 13, 360 | "metadata": {}, 361 | "output_type": "execute_result" 362 | } 363 | ], 364 | "source": [ 365 | "make_prediction(predictor, \n", 366 | " {\n", 367 | " 'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', \n", 368 | " 'country_code': 'us', 'osfamily': 'Android', 'osmajor': '6'\n", 369 | " }\n", 370 | ")" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "id": "tYIDW4z6Nwok", 376 | "metadata": { 377 | "id": "tYIDW4z6Nwok" 378 | }, 379 | "source": [ 380 | "A result, `{0: 0.0106238695, 1: 0.9893762}` implies that the model is 98.94% sure that the given is input configuration of the device and network will have **poor** performance quality (i.e page load time > 950ms). In this case we disable all aggresive optimizations. \n", 381 | "\n", 382 | "To read it the other way, the model is 1.06% sure (LOL) that the input configuration has a **good** performance, i.e. page load time <= 950ms." 383 | ] 384 | }, 385 | { 386 | "cell_type": "markdown", 387 | "id": "0f3dbc3a", 388 | "metadata": { 389 | "id": "0f3dbc3a" 390 | }, 391 | "source": [ 392 | "Some example inputs to try, while getting started,\n", 393 | "```json\n", 394 | "{'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', 'country_code': 'us', \n", 395 | " 'osfamily': 'Android', 'osmajor': '6'}\n", 396 | "{'asn_number': '3352', 'browser_major_version': '13', 'browser_name': 'safari', 'country_code': 'es', \n", 397 | " 'osfamily': 'iOS', 'osmajor': '13'}\n", 398 | "{'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', 'country_code': 'us', \n", 399 | " 'osfamily': 'Android', 'osmajor': '6'}\n", 400 | "```" 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "id": "235bc71a", 406 | "metadata": { 407 | "id": "235bc71a" 408 | }, 409 | "source": [ 410 | "## Interactive UI\n", 411 | "\n", 412 | "To understand the model's behavior a bit more, use the below interactive UI. The model predicts on every keystroke. We can afford to do it, because it is so fast!" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 14, 418 | "id": "-vmZufQkL_Wg", 419 | "metadata": { 420 | "id": "-vmZufQkL_Wg" 421 | }, 422 | "outputs": [], 423 | "source": [ 424 | "! pip install -U -q pip && pip install -U -q dash==2.0.0 jupyter-dash==0.4.0" 425 | ] 426 | }, 427 | { 428 | "cell_type": "code", 429 | "execution_count": 15, 430 | "id": "6X8Hf_8oL3-W", 431 | "metadata": { 432 | "id": "6X8Hf_8oL3-W" 433 | }, 434 | "outputs": [], 435 | "source": [ 436 | "from jupyter_dash import JupyterDash\n", 437 | "from dash import dcc\n", 438 | "from dash import html\n", 439 | "from dash.dependencies import Input, Output" 440 | ] 441 | }, 442 | { 443 | "cell_type": "code", 444 | "execution_count": 16, 445 | "id": "a4814428", 446 | "metadata": { 447 | "id": "a4814428" 448 | }, 449 | "outputs": [], 450 | "source": [ 451 | "def design_inline_form_control(label:str, input_type:str=\"text\", default_val=\"\", readonly=False):\n", 452 | " input_id = re.sub(r\"\\s+\", \"\", label)\n", 453 | " div = html.Div([\n", 454 | " html.Div([\n", 455 | " html.Label(label, className='col-form-label', htmlFor=input_id)\n", 456 | " ], className=\"col-md-3\"),\n", 457 | " html.Div([ \n", 458 | " dcc.Input(id=input_id, value=default_val, type=input_type, required=True, \n", 459 | " className=\"form-control\", readOnly=readonly)\n", 460 | " ], className=\"col-auto\")\n", 461 | " ], className=\"row g-3 mb-3 align-items-center\")\n", 462 | " return div, input_id" 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": 17, 468 | "id": "89479b7c", 469 | "metadata": { 470 | "colab": { 471 | "base_uri": "https://localhost:8080/", 472 | "height": 651 473 | }, 474 | "id": "89479b7c", 475 | "outputId": "7c2da3b0-5706-4822-9680-cf762ba0053c", 476 | "scrolled": false 477 | }, 478 | "outputs": [ 479 | { 480 | "data": { 481 | "text/html": [ 482 | "\n", 483 | " \n", 491 | " " 492 | ], 493 | "text/plain": [ 494 | "" 495 | ] 496 | }, 497 | "metadata": {}, 498 | "output_type": "display_data" 499 | } 500 | ], 501 | "source": [ 502 | "app = JupyterDash(__name__, external_stylesheets=[\"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css\"])\n", 503 | "\n", 504 | "asn_div, a_id = design_inline_form_control(\"ASN number\", \"number\", 3352)\n", 505 | "browser_version_div, bv_id = design_inline_form_control(\"Browser major version\", \"number\", 14)\n", 506 | "browser_name_div, bn_id = design_inline_form_control(\"Browser name\", default_val='safari')\n", 507 | "country_div, cc_id = design_inline_form_control(\"Country code\", default_val='ca')\n", 508 | "os_family_div, os_id = design_inline_form_control(\"OS Family\", default_val='iOS')\n", 509 | "os_major_div, osm_id = design_inline_form_control(\"OS Major version\", \"number\", 14)\n", 510 | " \n", 511 | "app.layout = html.Div([\n", 512 | " html.H1(\"Performance Quality Predictor\", className=\"mb-5\"),\n", 513 | " html.P(\"The model is live and ready! Try changing any of the values below and see the prediction at the end.\", className=\"text-muted\"),\n", 514 | " html.Div([\n", 515 | " asn_div, browser_version_div, browser_name_div, country_div, os_family_div, os_major_div\n", 516 | " ]),\n", 517 | " html.P([\n", 518 | " \"The model thinks the performance quality for the above request to be, \",\n", 519 | " html.Mark(\"Good\", id=\"result_class\"),\n", 520 | " \" with \",\n", 521 | " html.Mark(\"85%\", id=\"result_prob\"),\n", 522 | " \" confidence.\"\n", 523 | " ], className=\"lead mt-4\")\n", 524 | "])\n", 525 | "\n", 526 | "@app.callback(\n", 527 | " [Output(\"result_class\", 'children'), Output(\"result_prob\", 'children')],\n", 528 | " [Input(a_id, \"value\"), Input(bv_id, \"value\"), \n", 529 | " Input(bn_id, \"value\"), Input(cc_id, \"value\"), \n", 530 | " Input(os_id, \"value\"), Input(osm_id, \"value\")]\n", 531 | ")\n", 532 | "def update_figure(asn_number:int, browser_version:int, browser_name:str, country_code:str, os_family:str, os_major:int):\n", 533 | " inp = {\n", 534 | " 'asn_number': f\"{asn_number}\",\n", 535 | " 'browser_major_version': browser_version,\n", 536 | " 'browser_name': browser_name,\n", 537 | " 'country_code': country_code,\n", 538 | " 'osfamily': os_family,\n", 539 | " 'osmajor': os_major,\n", 540 | " }\n", 541 | " pred = make_prediction(predictor, inp)\n", 542 | " good_prob = pred[0]\n", 543 | " bad_prob = pred[1] # or 1 - good_prob\n", 544 | " if good_prob > bad_prob:\n", 545 | " return \"Good\", f\"{good_prob:.2%}\"\n", 546 | " else:\n", 547 | " return \"Bad\", f\"{bad_prob:.2%}\"\n", 548 | "\n", 549 | "# Run app and display result inline in the notebook\n", 550 | "app.run_server(mode='inline', height=630)\n" 551 | ] 552 | }, 553 | { 554 | "cell_type": "code", 555 | "execution_count": null, 556 | "id": "473cdd5d", 557 | "metadata": { 558 | "id": "473cdd5d" 559 | }, 560 | "outputs": [], 561 | "source": [] 562 | } 563 | ], 564 | "metadata": { 565 | "colab": { 566 | "name": "ssr_mobile_web_model_demo.ipynb", 567 | "provenance": [] 568 | }, 569 | "kernelspec": { 570 | "display_name": "Python 3 (ipykernel)", 571 | "language": "python", 572 | "name": "python3" 573 | }, 574 | "language_info": { 575 | "codemirror_mode": { 576 | "name": "ipython", 577 | "version": 3 578 | }, 579 | "file_extension": ".py", 580 | "mimetype": "text/x-python", 581 | "name": "python", 582 | "nbconvert_exporter": "python", 583 | "pygments_lexer": "ipython3", 584 | "version": "3.9.5" 585 | }, 586 | "toc": { 587 | "base_numbering": 1, 588 | "nav_menu": {}, 589 | "number_sections": false, 590 | "sideBar": true, 591 | "skip_h1_title": false, 592 | "title_cell": "Table of Contents", 593 | "title_sidebar": "Contents", 594 | "toc_cell": false, 595 | "toc_position": {}, 596 | "toc_section_display": true, 597 | "toc_window_display": true 598 | }, 599 | "varInspector": { 600 | "cols": { 601 | "lenName": 16, 602 | "lenType": 16, 603 | "lenVar": 40 604 | }, 605 | "kernels_config": { 606 | "python": { 607 | "delete_cmd_postfix": "", 608 | "delete_cmd_prefix": "del ", 609 | "library": "var_list.py", 610 | "varRefreshCmd": "print(var_dic_list())" 611 | }, 612 | "r": { 613 | "delete_cmd_postfix": ") ", 614 | "delete_cmd_prefix": "rm(", 615 | "library": "var_list.r", 616 | "varRefreshCmd": "cat(var_dic_list()) " 617 | } 618 | }, 619 | "types_to_exclude": [ 620 | "module", 621 | "function", 622 | "builtin_function_or_method", 623 | "instance", 624 | "_Feature" 625 | ], 626 | "window_display": false 627 | } 628 | }, 629 | "nbformat": 4, 630 | "nbformat_minor": 5 631 | } 632 | -------------------------------------------------------------------------------- /ssr-mobile-web/mweb-jan-2022-v1/python-example/ssr_mobile_web_model_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # # SSR Mobile Web Model Python Demo 5 | 6 | # We'll see how to load the mweb-jan-2022-v1 predictor and make predictions with it in Python. As a bonus, we also share a playground to play with the model and get a feel for its performance. The interactive UI demo only works in a notebook interface. 7 | # 8 | # Simply run all the cells below to get started. 9 | 10 | # In[1]: 11 | 12 | 13 | get_ipython().system(' pip install -U -q pip && pip install -U -q tensorflow==2.5 pandas') 14 | 15 | 16 | # In[2]: 17 | 18 | 19 | import re 20 | import logging 21 | from pathlib import Path 22 | 23 | import tensorflow as tf 24 | 25 | tf.__version__ 26 | 27 | 28 | # In[3]: 29 | 30 | 31 | logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(message)s') 32 | 33 | 34 | # In[11]: 35 | 36 | 37 | # the below model is the TF Python equivalent of JS' saved model 38 | MODEL_PATH = "../models/py-saved-model" 39 | 40 | 41 | # Setup the notebook for Google Colab. This cell can be ignored if not on colab.google.com 42 | 43 | # In[6]: 44 | 45 | 46 | try: 47 | import google.colab 48 | import subprocess 49 | 50 | clone_cmd_res = subprocess.run( 51 | "git clone -l -s https://github.com/linkedin/performance-quality-models.git performance-quality-models", 52 | shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True 53 | ) 54 | 55 | if clone_cmd_res.returncode != 0: 56 | raise Exception(clone_cmd_res.stderr) 57 | 58 | get_ipython().run_line_magic('cd', 'performance-quality-models') 59 | 60 | MODEL_PATH = "./ssr-mobile-web/mweb-jan-2022-v1/models/py-saved-model" 61 | except: 62 | logging.warning("Ignore this warning if not on colab.google.com", exc_info=True) 63 | 64 | 65 | # Define a Predictor class which loads the model and transforms the data into a form that model can understand. 66 | 67 | # In[7]: 68 | 69 | 70 | class MWebJan2022Predictor: 71 | def __init__(self, modelDir): 72 | self.modelDir = modelDir; 73 | self.modelName = Path(modelDir).name 74 | self.model = None 75 | 76 | self._features = [ 77 | 'asn_number', 78 | 'browser_major_version', 79 | 'browser_major_version_na', 80 | 'browser_name', 81 | 'country_code', 82 | 'osfamily', 83 | 'osmajor', 84 | 'osmajor_na' 85 | ] 86 | 87 | self._defaults = { 88 | "browser_major_version": 15.0, 89 | "osmajor": 14.0, 90 | "asn_number": '**', 91 | "country_code": '**', 92 | "browser_name": '**', 93 | "osfamily": '**', 94 | } 95 | 96 | self._normalizer = { 97 | "means": {"browser_major_version": 52.65782220933843, "osmajor": 13.372263709715911}, 98 | "stds": {"browser_major_version": 41.48294747389074, "osmajor": 2.376855002582524} 99 | } 100 | 101 | def loadModel(self): 102 | self.model = tf.saved_model.load(self.modelDir).signatures["predict"] 103 | 104 | def _normalizeNumericalFetaures(self, x): 105 | means = self._normalizer['means'] 106 | stds = self._normalizer["stds"] 107 | for feature in means: 108 | x[feature] = (float(x[feature]) - means[feature]) / stds[feature]; 109 | return x; 110 | 111 | def _checkNA(self, value): 112 | res = value == None or value == '' or value == 'unknown' 113 | if isinstance(value, float) or isinstance(value, int): 114 | res = res or value < 0 115 | return res 116 | 117 | def _fillNA(self, x): 118 | for feature in x.keys(): 119 | if self._checkNA(x[feature]): 120 | x[feature] = self._defaults[feature]; 121 | return x; 122 | 123 | def _addNAFetaures(self, x): 124 | x["browser_major_version_na"] = 'False'; 125 | x["osmajor_na"] = 'False'; 126 | 127 | if self._checkNA(x["browser_major_version"]): 128 | x["browser_major_version_na"] = 'True'; 129 | 130 | if self._checkNA(x["osmajor"]): 131 | x["osmajor_na"] = 'True'; 132 | 133 | return x; 134 | 135 | def _convert_to_bytes(self, x): 136 | for feat, val in x.items(): 137 | if isinstance(val, str): 138 | x[feat] = bytes(x[feat], 'utf-8') 139 | return x 140 | 141 | def prepareX(self, inp_example): 142 | model_input = tf.train.Example(features=tf.train.Features(feature={ 143 | 'country_code': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["country_code"]])), 144 | 'osfamily': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["osfamily"]])), 145 | 'browser_name': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["browser_name"]])), 146 | 'browser_major_version_na': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["browser_major_version_na"]])), 147 | 'osmajor_na': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["osmajor_na"]])), 148 | 'asn_number': tf.train.Feature(bytes_list=tf.train.BytesList(value=[inp_example["asn_number"]])), 149 | 'browser_major_version': tf.train.Feature(float_list=tf.train.FloatList(value=[inp_example["browser_major_version"]])), 150 | 'osmajor': tf.train.Feature(float_list=tf.train.FloatList(value=[inp_example["osmajor"]])) 151 | })) 152 | return model_input.SerializeToString() 153 | 154 | def preProcessInput(self, inp): 155 | x = {}; 156 | for feature in self._features: 157 | x[feature] = inp.get(feature, None); 158 | 159 | x = self._addNAFetaures(x); 160 | x = self._fillNA(x); 161 | x = self._normalizeNumericalFetaures(x); 162 | x = self._convert_to_bytes(x) 163 | return x; 164 | 165 | 166 | def predict(self, rawInput): 167 | """ 168 | * Process the input and make predictions on it 169 | * @param {object} rawInput {[name: string]: tf.Tensor} dictionary 170 | * @returns {class1: probability1, class2: probability2, ...} 171 | """ 172 | if not self.model: 173 | self.loadModel() 174 | 175 | inp = self.preProcessInput(rawInput); 176 | logging.debug(f"Model input: {inp}") 177 | x = self.prepareX(inp) 178 | logging.debug(f"Model (x): {x}") 179 | output = self.model(examples=tf.constant([x])) 180 | return output 181 | 182 | 183 | # In[8]: 184 | 185 | 186 | def make_prediction(predictor, inp): 187 | p = predictor.predict(inp) 188 | scores = p['probabilities'].numpy()[0] 189 | return {i: score for i, score in enumerate(scores)} # return the probability for each class 190 | 191 | 192 | # In[12]: 193 | 194 | 195 | predictor = MWebJan2022Predictor(MODEL_PATH) 196 | predictor.loadModel() 197 | 198 | 199 | # Make some predictions 200 | 201 | # In[13]: 202 | 203 | 204 | make_prediction(predictor, 205 | { 206 | 'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', 207 | 'country_code': 'us', 'osfamily': 'Android', 'osmajor': '6' 208 | } 209 | ) 210 | 211 | 212 | # A result, `{0: 0.0106238695, 1: 0.9893762}` implies that the model is 98.94% sure that the given is input configuration of the device and network will have **poor** performance quality (i.e page load time > 950ms). In this case we disable all aggresive optimizations. 213 | # 214 | # To read it the other way, the model is 1.06% sure (LOL) that the input configuration has a **good** performance, i.e. page load time <= 950ms. 215 | 216 | # Some example inputs to try, while getting started, 217 | # ```json 218 | # {'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', 'country_code': 'us', 219 | # 'osfamily': 'Android', 'osmajor': '6'} 220 | # {'asn_number': '3352', 'browser_major_version': '13', 'browser_name': 'safari', 'country_code': 'es', 221 | # 'osfamily': 'iOS', 'osmajor': '13'} 222 | # {'asn_number': '40793', 'browser_major_version': '67', 'browser_name': 'chrome', 'country_code': 'us', 223 | # 'osfamily': 'Android', 'osmajor': '6'} 224 | # ``` 225 | 226 | # ## Interactive UI 227 | # 228 | # To understand the model's behavior a bit more, use the below interactive UI. The model predicts on every keystroke. We can afford to do it, because it is so fast! 229 | 230 | # In[14]: 231 | 232 | 233 | get_ipython().system(' pip install -U -q pip && pip install -U -q dash==2.0.0 jupyter-dash==0.4.0') 234 | 235 | 236 | # In[15]: 237 | 238 | 239 | from jupyter_dash import JupyterDash 240 | from dash import dcc 241 | from dash import html 242 | from dash.dependencies import Input, Output 243 | 244 | 245 | # In[16]: 246 | 247 | 248 | def design_inline_form_control(label:str, input_type:str="text", default_val="", readonly=False): 249 | input_id = re.sub(r"\s+", "", label) 250 | div = html.Div([ 251 | html.Div([ 252 | html.Label(label, className='col-form-label', htmlFor=input_id) 253 | ], className="col-md-3"), 254 | html.Div([ 255 | dcc.Input(id=input_id, value=default_val, type=input_type, required=True, 256 | className="form-control", readOnly=readonly) 257 | ], className="col-auto") 258 | ], className="row g-3 mb-3 align-items-center") 259 | return div, input_id 260 | 261 | 262 | # In[17]: 263 | 264 | 265 | app = JupyterDash(__name__, external_stylesheets=["https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"]) 266 | 267 | asn_div, a_id = design_inline_form_control("ASN number", "number", 3352) 268 | browser_version_div, bv_id = design_inline_form_control("Browser major version", "number", 14) 269 | browser_name_div, bn_id = design_inline_form_control("Browser name", default_val='safari') 270 | country_div, cc_id = design_inline_form_control("Country code", default_val='ca') 271 | os_family_div, os_id = design_inline_form_control("OS Family", default_val='iOS') 272 | os_major_div, osm_id = design_inline_form_control("OS Major version", "number", 14) 273 | 274 | app.layout = html.Div([ 275 | html.H1("Performance Quality Predictor", className="mb-5"), 276 | html.P("The model is live and ready! Try changing any of the values below and see the prediction at the end.", className="text-muted"), 277 | html.Div([ 278 | asn_div, browser_version_div, browser_name_div, country_div, os_family_div, os_major_div 279 | ]), 280 | html.P([ 281 | "The model thinks the performance quality for the above request to be, ", 282 | html.Mark("Good", id="result_class"), 283 | " with ", 284 | html.Mark("85%", id="result_prob"), 285 | " confidence." 286 | ], className="lead mt-4") 287 | ]) 288 | 289 | @app.callback( 290 | [Output("result_class", 'children'), Output("result_prob", 'children')], 291 | [Input(a_id, "value"), Input(bv_id, "value"), 292 | Input(bn_id, "value"), Input(cc_id, "value"), 293 | Input(os_id, "value"), Input(osm_id, "value")] 294 | ) 295 | def update_figure(asn_number:int, browser_version:int, browser_name:str, country_code:str, os_family:str, os_major:int): 296 | inp = { 297 | 'asn_number': f"{asn_number}", 298 | 'browser_major_version': browser_version, 299 | 'browser_name': browser_name, 300 | 'country_code': country_code, 301 | 'osfamily': os_family, 302 | 'osmajor': os_major, 303 | } 304 | pred = make_prediction(predictor, inp) 305 | good_prob = pred[0] 306 | bad_prob = pred[1] # or 1 - good_prob 307 | if good_prob > bad_prob: 308 | return "Good", f"{good_prob:.2%}" 309 | else: 310 | return "Bad", f"{bad_prob:.2%}" 311 | 312 | # Run app and display result inline in the notebook 313 | app.run_server(mode='inline', height=630) 314 | 315 | 316 | # In[ ]: 317 | 318 | 319 | 320 | 321 | --------------------------------------------------------------------------------