├── .editorconfig ├── .gitignore ├── .python-version ├── LICENCE.CC-BY-4.0.txt ├── LICENSE ├── LICENSE.MIT.txt ├── Makefile ├── README.md ├── descriptions.yaml ├── poetry.lock ├── pyproject.toml ├── tag_attr_usage.json ├── zwift_workout_file_tag_reference.md └── zwift_zwo_docs ├── __init__.py ├── analyse_zwo.py └── render_docs.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | indent_size = 4 10 | indent_style = space 11 | 12 | # Don't corrupt git patch edit files 13 | [.git/**/*.diff] 14 | trim_trailing_whitespace = none 15 | 16 | [*.{yaml,yml}] 17 | indent_size = 2 18 | 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.editorconfig 3 | !.python-version 4 | !.gitignore 5 | 6 | __pycache__ 7 | 8 | # Shouldn't include these... 9 | workouts 10 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.7.3 2 | -------------------------------------------------------------------------------- /LICENCE.CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | http://creativecommons.org/licenses/by/4.0/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT OR CC-BY-4.0 2 | -------------------------------------------------------------------------------- /LICENSE.MIT.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Hal Blackburn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: zwift_workout_file_tag_reference.md 2 | 3 | PYTHON_FILES = $(shell find zwift_zwo_docs -name '*.py') 4 | 5 | zwift_workout_file_tag_reference.md: $(PYTHON_FILES) descriptions.yaml tag_attr_usage.json 6 | zwift-zwo-docs-render tag_attr_usage.json descriptions.yaml > zwift_workout_file_tag_reference.md 7 | 8 | tag_attr_usage.json: $(wildcard workouts) 9 | zwift-zwo-docs-analyse --json $< > tag_attr_usage.json 10 | 11 | clean-md: $(wildcard zwift_workout_file_tag_reference.md) 12 | rm -f $< 13 | 14 | clean-json: $(wildcard tag_attr_usage.json) 15 | rm -f $< 16 | 17 | clean-all: clean-md clean-json 18 | 19 | .PHONY: clean-all clean-md clean-json 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zwift Workout File Reference 2 | 3 | A project to create reference documentation for the XML elements and attributes 4 | used in Zwift custom workout files. 5 | 6 | The **[Zwift Workout File Reference is here](./zwift_workout_file_tag_reference.md)** 7 | 8 | ## Contributing 9 | 10 | Many of the elements/attributes are not yet described. Help to improve the 11 | descriptions would be great. 12 | 13 | The reference is generated from two sources: 14 | 15 | * Manually created descriptions and examples in 16 | [descriptions.yaml](descriptions.yaml) 17 | * Automatically generated data in [tag_attr_usage.json](tag_attr_usage.json) 18 | * This data is generated by 19 | [zwift_zwo_docs/analyze_zwo.py](zwift_zwo_docs/analyze_zwo.py) which 20 | analyses Zwift workout files to find the elements/attributes they use. 21 | * The `tag_attr_usage.json` committed here is the result of analysing all of 22 | the workouts that come with Zwift. The workout files themselves can't be 23 | committed here as they're copyrighted. 24 | 25 | ### How to contribute 26 | 27 | You can add descriptions and examples to `descriptions.yaml`. 28 | 29 | To re-generate `zwift_workout_file_tag_reference.md`, install Python 3.7, then: 30 | 31 | ```console 32 | $ cd 33 | $ pip install . 34 | $ make 35 | ``` 36 | 37 | Commit changes to both, then submit a PR to this repo. 38 | -------------------------------------------------------------------------------- /descriptions.yaml: -------------------------------------------------------------------------------- 1 | elements: 2 | workout_file: 3 | description: The root element of a Zwift workout file. 4 | 5 | name: 6 | description: The name of the workout in the Zwift workout list 7 | 8 | author: 9 | description: The name of the workout's creator 10 | 11 | category: 12 | description: | 13 | The name of a section in the Zwift workout list to place this workout 14 | inside. 15 | 16 | Workouts will appear under the named section instead of the custom workout 17 | section. [``](#element-subcategory) can be used for a second 18 | level of nesting. 19 | 20 | examples: | 21 | 22 | Workout 1 23 | Jim 24 | Jim's Amazing Workouts 25 | 26 | 27 | 28 | subcategory: 29 | description: | 30 | The name of the section under the workout's 31 | [``](#element-category) to list the workout under. 32 | 33 | examples: | 34 | 35 | Recovery 1 36 | Jim 37 | Jim's Amazing Workouts 38 | Easy Days 39 | 40 | 41 | 42 | ftpOverride: 43 | description: | 44 | Override the rider's FTP with a fixed value for the workout. 45 | 46 | Can be used to create workouts with fixed power intervals. 47 | examples: 48 | title: Fixed power workout 49 | description: | 50 | This intervals in this workout will have the same target power, 51 | regardless of the FTP of the rider. 52 | interval_code: | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | activitySaveName: 61 | description: The workout's activity name will default to this value (instead of the workout name). 62 | MaxEffort: 63 | description: An interval without ERG mode but enough resistance for the rider to self-select a strong effort. 64 | 65 | examples: 66 | interval_code: | 67 | 68 | 69 | TextNotification: 70 | description: Doesn't seem to have any effect. 71 | 72 | gameplayevent: 73 | description: | 74 | Trigger an event in the game, e.g. switch camera view. 75 | 76 | Possible values for [`type`](#attribute-type) seem to be: 77 | 78 | * Known to work: 79 | * `GPE_CAMERA` — Combine with [`camera`](#attribute-camera) to specify a 80 | camera angle to switch to. 81 | * `GPE_CELEBRATION` — Use without additional attributes to trigger a small 82 | audio/visual celebration effect. 83 | * Untested: 84 | * `GPE_AUDIO` 85 | * `GPE_DAILYTARGET` 86 | * `GPE_ADD_ROADPROP` 87 | * `GPE_PLAY_ANIM` 88 | * `GPE_SCREENSHOT` 89 | * `GPE_SET_ROUTE` 90 | * `GPE_SET_NAVIGATION` 91 | 92 | attributes: 93 | name: 94 | description: The name of the workout in the Zwift workout menu 95 | 96 | Quantize: 97 | description: | 98 | Doesn't seem to have any effect. 99 | 100 | It's seen on several built-in Zwift workouts, always on non-flat intervals with a value of `20`. 101 | Presumably it refers to the power steps in the interval's ramp. 102 | 103 | type: 104 | description: | 105 | The type of event to trigger. See 106 | [``](#element-gameplayevent) for more info. 107 | 108 | camera: 109 | description: | 110 | Specify the camera angle to change to in a 111 | [``](#element-gameplayevent). Not tested, but the 112 | value appears to correspond to the numeric camera shortcut keys. 113 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "Disable App Nap on OS X 10.9" 4 | marker = "sys_platform == \"darwin\"" 5 | name = "appnope" 6 | optional = false 7 | python-versions = "*" 8 | version = "0.1.0" 9 | 10 | [[package]] 11 | category = "dev" 12 | description = "Specifications for callback functions passed in to an API" 13 | name = "backcall" 14 | optional = false 15 | python-versions = "*" 16 | version = "0.1.0" 17 | 18 | [[package]] 19 | category = "dev" 20 | description = "Cross-platform colored terminal text." 21 | marker = "sys_platform == \"win32\"" 22 | name = "colorama" 23 | optional = false 24 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 25 | version = "0.4.1" 26 | 27 | [[package]] 28 | category = "dev" 29 | description = "Better living through Python with decorators" 30 | name = "decorator" 31 | optional = false 32 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 33 | version = "4.4.0" 34 | 35 | [[package]] 36 | category = "main" 37 | description = "Pythonic argument parser, that will make you smile" 38 | name = "docopt" 39 | optional = false 40 | python-versions = "*" 41 | version = "0.6.2" 42 | 43 | [[package]] 44 | category = "dev" 45 | description = "IPython: Productive Interactive Computing" 46 | name = "ipython" 47 | optional = false 48 | python-versions = ">=3.5" 49 | version = "7.5.0" 50 | 51 | [package.dependencies] 52 | appnope = "*" 53 | backcall = "*" 54 | colorama = "*" 55 | decorator = "*" 56 | jedi = ">=0.10" 57 | pexpect = "*" 58 | pickleshare = "*" 59 | prompt-toolkit = ">=2.0.0,<2.1.0" 60 | pygments = "*" 61 | setuptools = ">=18.5" 62 | traitlets = ">=4.2" 63 | 64 | [[package]] 65 | category = "dev" 66 | description = "Vestigial utilities from IPython" 67 | name = "ipython-genutils" 68 | optional = false 69 | python-versions = "*" 70 | version = "0.2.0" 71 | 72 | [[package]] 73 | category = "dev" 74 | description = "An autocompletion tool for Python that can be used for text editors." 75 | name = "jedi" 76 | optional = false 77 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 78 | version = "0.13.3" 79 | 80 | [package.dependencies] 81 | parso = ">=0.3.0" 82 | 83 | [[package]] 84 | category = "main" 85 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 86 | name = "lxml" 87 | optional = false 88 | python-versions = "*" 89 | version = "4.3.3" 90 | 91 | [[package]] 92 | category = "dev" 93 | description = "A Python Parser" 94 | name = "parso" 95 | optional = false 96 | python-versions = "*" 97 | version = "0.4.0" 98 | 99 | [[package]] 100 | category = "dev" 101 | description = "Pexpect allows easy control of interactive console applications." 102 | marker = "sys_platform != \"win32\"" 103 | name = "pexpect" 104 | optional = false 105 | python-versions = "*" 106 | version = "4.7.0" 107 | 108 | [package.dependencies] 109 | ptyprocess = ">=0.5" 110 | 111 | [[package]] 112 | category = "dev" 113 | description = "Tiny 'shelve'-like database with concurrency support" 114 | name = "pickleshare" 115 | optional = false 116 | python-versions = "*" 117 | version = "0.7.5" 118 | 119 | [[package]] 120 | category = "dev" 121 | description = "Library for building powerful interactive command lines in Python" 122 | name = "prompt-toolkit" 123 | optional = false 124 | python-versions = "*" 125 | version = "2.0.9" 126 | 127 | [package.dependencies] 128 | six = ">=1.9.0" 129 | wcwidth = "*" 130 | 131 | [[package]] 132 | category = "dev" 133 | description = "Run a subprocess in a pseudo terminal" 134 | marker = "sys_platform != \"win32\"" 135 | name = "ptyprocess" 136 | optional = false 137 | python-versions = "*" 138 | version = "0.6.0" 139 | 140 | [[package]] 141 | category = "dev" 142 | description = "Pygments is a syntax highlighting package written in Python." 143 | name = "pygments" 144 | optional = false 145 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 146 | version = "2.4.0" 147 | 148 | [[package]] 149 | category = "main" 150 | description = "YAML parser and emitter for Python" 151 | name = "pyyaml" 152 | optional = false 153 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 154 | version = "5.1" 155 | 156 | [[package]] 157 | category = "dev" 158 | description = "Python 2 and 3 compatibility utilities" 159 | name = "six" 160 | optional = false 161 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 162 | version = "1.12.0" 163 | 164 | [[package]] 165 | category = "dev" 166 | description = "Traitlets Python config system" 167 | name = "traitlets" 168 | optional = false 169 | python-versions = "*" 170 | version = "4.3.2" 171 | 172 | [package.dependencies] 173 | decorator = "*" 174 | ipython-genutils = "*" 175 | six = "*" 176 | 177 | [[package]] 178 | category = "dev" 179 | description = "Measures number of Terminal column cells of wide-character codes" 180 | name = "wcwidth" 181 | optional = false 182 | python-versions = "*" 183 | version = "0.1.7" 184 | 185 | [metadata] 186 | content-hash = "5e196acf160d2e0af532bce6c1077c69ff1084aaa3bb37f6fa69ddc82642afd3" 187 | python-versions = "^3.7" 188 | 189 | [metadata.hashes] 190 | appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] 191 | backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"] 192 | colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] 193 | decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"] 194 | docopt = ["49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"] 195 | ipython = ["54c5a8aa1eadd269ac210b96923688ccf01ebb2d0f21c18c3c717909583579a8", "e840810029224b56cd0d9e7719dc3b39cf84d577f8ac686547c8ba7a06eeab26"] 196 | ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"] 197 | jedi = ["2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b", "2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"] 198 | lxml = ["03984196d00670b2ab14ae0ea83d5cc0cfa4f5a42558afa9ab5fa745995328f5", "0815b0c9f897468de6a386dc15917a0becf48cc92425613aa8bbfc7f0f82951f", "175f3825f075cf02d15099eb52658457cf0ff103dcf11512b5d2583e1d40f58b", "30e14c62d88d1e01a26936ecd1c6e784d4afc9aa002bba4321c5897937112616", "3210da6f36cf4b835ff1be853962b22cc354d506f493b67a4303c88bbb40d57b", "40f60819fbd5bad6e191ba1329bfafa09ab7f3f174b3d034d413ef5266963294", "43b26a865a61549919f8a42e094dfdb62847113cf776d84bd6b60e4e3fc20ea3", "4a03dd682f8e35a10234904e0b9508d705ff98cf962c5851ed052e9340df3d90", "62f382cddf3d2e52cf266e161aa522d54fd624b8cc567bc18f573d9d50d40e8e", "7b98f0325be8450da70aa4a796c4f06852949fe031878b4aa1d6c417a412f314", "846a0739e595871041385d86d12af4b6999f921359b38affb99cdd6b54219a8f", "a3080470559938a09a5d0ec558c005282e99ac77bf8211fb7b9a5c66390acd8d", "ad841b78a476623955da270ab8d207c3c694aa5eba71f4792f65926dc46c6ee8", "afdd75d9735e44c639ffd6258ce04a2de3b208f148072c02478162d0944d9da3", "b4fbf9b552faff54742bcd0791ab1da5863363fb19047e68f6592be1ac2dab33", "b90c4e32d6ec089d3fa3518436bdf5ce4d902a0787dbd9bb09f37afe8b994317", "b91cfe4438c741aeff662d413fd2808ac901cc6229c838236840d11de4586d63", "bdb0593a42070b0a5f138b79b872289ee73c8e25b3f0bea6564e795b55b6bcdd", "c4e4bca2bb68ce22320297dfa1a7bf070a5b20bcbaec4ee023f83d2f6e76496f", "cec4ab14af9eae8501be3266ff50c3c2aecc017ba1e86c160209bb4f0423df6a", "e83b4b2bf029f5104bc1227dbb7bf5ace6fd8fabaebffcd4f8106fafc69fc45f", "e995b3734a46d41ae60b6097f7c51ba9958648c6d1e0935b7e0ee446ee4abe22", "f679d93dec7f7210575c85379a31322df4c46496f184ef650d3aba1484b38a2d", "fd213bb5166e46974f113c8228daaef1732abc47cb561ce9c4c8eaed4bd3b09b", "fdcb57b906dbc1f80666e6290e794ab8fb959a2e17aa5aee1758a85d1da4533f", "ff424b01d090ffe1947ec7432b07f536912e0300458f9a7f48ea217dd8362b86"] 199 | parso = ["17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33", "2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376"] 200 | pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"] 201 | pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", "9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"] 202 | prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"] 203 | ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"] 204 | pygments = ["31cba6ffb739f099a85e243eff8cb717089fdd3c7300767d9fc34cb8e1b065f5", "5ad302949b3c98dd73f8d9fcdc7e9cb592f120e32a18e23efd7f3dc51194472b"] 205 | pyyaml = ["1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", "436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", "460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", "5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", "7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", "9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", "a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", "aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", "c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", "c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", "e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"] 206 | six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] 207 | traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"] 208 | wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] 209 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "zwift-zwo-docs" 3 | include = ["analyse_zwo.py", "render_docs.py"] 4 | version = "0.1.0" 5 | description = "Documentation for Zwift's structured workout file format" 6 | authors = ["Hal Blackburn "] 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.7" 10 | docopt = "^0.6.2" 11 | lxml = "^4.3" 12 | pyyaml = "^5.1" 13 | 14 | [build-system] 15 | requires = ["poetry>=0.12"] 16 | build-backend = "poetry.masonry.api" 17 | 18 | [tool.poetry.scripts] 19 | zwift-zwo-docs-analyse = 'zwift_zwo_docs.analyse_zwo:main' 20 | zwift-zwo-docs-render = 'zwift_zwo_docs.render_docs:main' 21 | 22 | 23 | [tool.tox] 24 | legacy_tox_ini = """ 25 | [tox] 26 | envlist = py37 27 | skipsdist=True 28 | 29 | [testenv] 30 | whitelist_externals=make 31 | commands = 32 | pip install . 33 | make clean-md 34 | make zwift_workout_file_tag_reference.md 35 | 36 | [testenv:py37-all] 37 | commands = 38 | pip install . 39 | make clean-all 40 | make zwift_workout_file_tag_reference.md 41 | """ 42 | -------------------------------------------------------------------------------- /tag_attr_usage.json: -------------------------------------------------------------------------------- 1 | { 2 | "elements": [ 3 | { 4 | "tag": "Cooldown", 5 | "paths": [ 6 | [ 7 | "workout_file", 8 | "workout" 9 | ] 10 | ], 11 | "attributes": [ 12 | "Cadence", 13 | "CadenceHigh", 14 | "CadenceLow", 15 | "CadenceResting", 16 | "Duration", 17 | "EndAtRoadTime", 18 | "Pace", 19 | "Power", 20 | "PowerHigh", 21 | "PowerLow", 22 | "Zone", 23 | "pace", 24 | "replacement_prescription", 25 | "replacement_verb", 26 | "units" 27 | ], 28 | "value": null 29 | }, 30 | { 31 | "tag": "FreeRide", 32 | "paths": [ 33 | [ 34 | "workout_file", 35 | "workout" 36 | ] 37 | ], 38 | "attributes": [ 39 | "Cadence", 40 | "CadenceHigh", 41 | "CadenceLow", 42 | "Duration", 43 | "FailThresholdDuration", 44 | "FlatRoad", 45 | "Power", 46 | "ftptest", 47 | "ramptest", 48 | "show_avg" 49 | ], 50 | "value": null 51 | }, 52 | { 53 | "tag": "Freeride", 54 | "paths": [ 55 | [ 56 | "workout_file", 57 | "workout" 58 | ] 59 | ], 60 | "attributes": [ 61 | "Duration", 62 | "FlatRoad", 63 | "ftptest" 64 | ], 65 | "value": null 66 | }, 67 | { 68 | "tag": "IntervalsT", 69 | "paths": [ 70 | [ 71 | "workout_file", 72 | "workout" 73 | ] 74 | ], 75 | "attributes": [ 76 | "Cadence", 77 | "CadenceHigh", 78 | "CadenceLow", 79 | "CadenceResting", 80 | "FlatRoad", 81 | "OffDuration", 82 | "OffPower", 83 | "OnDuration", 84 | "OnPower", 85 | "OverUnder", 86 | "PowerOffHigh", 87 | "PowerOffLow", 88 | "PowerOffZone", 89 | "PowerOnHigh", 90 | "PowerOnLow", 91 | "PowerOnZone", 92 | "Repeat", 93 | "pace", 94 | "units" 95 | ], 96 | "value": null 97 | }, 98 | { 99 | "tag": "MaxEffort", 100 | "paths": [ 101 | [ 102 | "workout_file", 103 | "workout" 104 | ] 105 | ], 106 | "attributes": [ 107 | "Duration" 108 | ], 109 | "value": null 110 | }, 111 | { 112 | "tag": "Ramp", 113 | "paths": [ 114 | [ 115 | "workout_file", 116 | "workout" 117 | ] 118 | ], 119 | "attributes": [ 120 | "Cadence", 121 | "CadenceResting", 122 | "Duration", 123 | "Power", 124 | "PowerHigh", 125 | "PowerLow", 126 | "pace", 127 | "show_avg" 128 | ], 129 | "value": null 130 | }, 131 | { 132 | "tag": "RestDay", 133 | "paths": [ 134 | [ 135 | "workout_file", 136 | "workout" 137 | ] 138 | ], 139 | "attributes": [], 140 | "value": null 141 | }, 142 | { 143 | "tag": "ShowCP20", 144 | "paths": [ 145 | [ 146 | "workout_file" 147 | ] 148 | ], 149 | "attributes": [], 150 | "value": { 151 | "datatype": "integer", 152 | "value_samples": { 153 | "exhaustive": true, 154 | "values": [ 155 | [ 156 | "1", 157 | 1.0 158 | ] 159 | ], 160 | "total_occurrences": 5 161 | } 162 | } 163 | }, 164 | { 165 | "tag": "Skippable", 166 | "paths": [ 167 | [ 168 | "workout_file" 169 | ] 170 | ], 171 | "attributes": [], 172 | "value": { 173 | "datatype": "integer", 174 | "value_samples": { 175 | "exhaustive": true, 176 | "values": [ 177 | [ 178 | "0", 179 | 1.0 180 | ] 181 | ], 182 | "total_occurrences": 4 183 | } 184 | } 185 | }, 186 | { 187 | "tag": "SolidState", 188 | "paths": [ 189 | [ 190 | "workout_file", 191 | "workout" 192 | ] 193 | ], 194 | "attributes": [ 195 | "Duration", 196 | "Power" 197 | ], 198 | "value": null 199 | }, 200 | { 201 | "tag": "SteadyState", 202 | "paths": [ 203 | [ 204 | "workout_file", 205 | "workout" 206 | ] 207 | ], 208 | "attributes": [ 209 | "Cadence", 210 | "CadenceHigh", 211 | "CadenceLow", 212 | "CadenceResting", 213 | "Duration", 214 | "FailThresholdDuration", 215 | "Forced_Performance_Test", 216 | "NeverFails", 217 | "OffPower", 218 | "Power", 219 | "PowerHigh", 220 | "PowerLow", 221 | "Target", 222 | "Text", 223 | "Zone", 224 | "forced_performance_test", 225 | "pace", 226 | "ramptest", 227 | "replacement_prescription", 228 | "replacement_verb", 229 | "show_avg", 230 | "units" 231 | ], 232 | "value": null 233 | }, 234 | { 235 | "tag": "TextEvent", 236 | "paths": [ 237 | [ 238 | "workout_file", 239 | "workout", 240 | "Cooldown" 241 | ], 242 | [ 243 | "workout_file", 244 | "workout", 245 | "FreeRide" 246 | ], 247 | [ 248 | "workout_file", 249 | "workout", 250 | "IntervalsT" 251 | ], 252 | [ 253 | "workout_file", 254 | "workout", 255 | "SteadyState" 256 | ], 257 | [ 258 | "workout_file", 259 | "workout", 260 | "Warmup" 261 | ] 262 | ], 263 | "attributes": [ 264 | "Duration", 265 | "TimeOffset", 266 | "message", 267 | "timeoffset" 268 | ], 269 | "value": null 270 | }, 271 | { 272 | "tag": "TextNotification", 273 | "paths": [ 274 | [ 275 | "workout_file", 276 | "workout", 277 | "Cooldown" 278 | ], 279 | [ 280 | "workout_file", 281 | "workout", 282 | "IntervalsT" 283 | ] 284 | ], 285 | "attributes": [ 286 | "duration", 287 | "font_size", 288 | "text", 289 | "timeOffset", 290 | "x", 291 | "y" 292 | ], 293 | "value": null 294 | }, 295 | { 296 | "tag": "Tutorial", 297 | "paths": [ 298 | [ 299 | "workout_file" 300 | ] 301 | ], 302 | "attributes": [], 303 | "value": { 304 | "datatype": "integer", 305 | "value_samples": { 306 | "exhaustive": true, 307 | "values": [ 308 | [ 309 | "1", 310 | 1.0 311 | ] 312 | ], 313 | "total_occurrences": 1 314 | } 315 | } 316 | }, 317 | { 318 | "tag": "Warmup", 319 | "paths": [ 320 | [ 321 | "workout_file", 322 | "workout" 323 | ] 324 | ], 325 | "attributes": [ 326 | "Cadence", 327 | "CadenceHigh", 328 | "CadenceLow", 329 | "CadenceResting", 330 | "Duration", 331 | "Power", 332 | "PowerHigh", 333 | "PowerLow", 334 | "Quantize", 335 | "Text", 336 | "Zone", 337 | "pace", 338 | "replacement_prescription", 339 | "replacement_verb", 340 | "units" 341 | ], 342 | "value": null 343 | }, 344 | { 345 | "tag": "WorkoutPlan", 346 | "paths": [ 347 | [ 348 | "workout_file" 349 | ] 350 | ], 351 | "attributes": [], 352 | "value": { 353 | "datatype": "integer", 354 | "value_samples": { 355 | "exhaustive": true, 356 | "values": [ 357 | [ 358 | "1", 359 | 1.0 360 | ] 361 | ], 362 | "total_occurrences": 756 363 | } 364 | } 365 | }, 366 | { 367 | "tag": "activitySaveName", 368 | "paths": [ 369 | [ 370 | "workout_file" 371 | ] 372 | ], 373 | "attributes": [], 374 | "value": { 375 | "datatype": "string", 376 | "value_samples": { 377 | "exhaustive": false, 378 | "values": [ 379 | [ 380 | "Duurtraining - KNWU Fondo Wintertraining", 381 | 0.25 382 | ], 383 | [ 384 | "Tempo-duurtraining - KNWU Fondo Wintertraining", 385 | 0.07142857142857142 386 | ], 387 | [ 388 | "Bloktraining - KNWU Fondo Wintertraining", 389 | 0.0625 390 | ], 391 | [ 392 | "Explosiviteitstraining - KNWU Fondo Wintertraining", 393 | 0.044642857142857144 394 | ], 395 | [ 396 | "Cadanstraining - KNWU Fondo Wintertraining", 397 | 0.03571428571428571 398 | ], 399 | [ 400 | "VO\u2082Max-training - KNWU Fondo Wintertraining", 401 | 0.026785714285714284 402 | ], 403 | [ 404 | "Krachttraining - KNWU Fondo Wintertraining", 405 | 0.026785714285714284 406 | ], 407 | [ 408 | "Sprinttraining - KNWU Fondo Wintertraining", 409 | 0.017857142857142856 410 | ], 411 | [ 412 | "Zwift Academy Run: Workout 2 | Intervals and Threshold Training", 413 | 0.008928571428571428 414 | ], 415 | [ 416 | "Zwift Academy Run: Workout 6 | Endurance Speed Builds", 417 | 0.008928571428571428 418 | ] 419 | ], 420 | "total_occurrences": 112 421 | } 422 | } 423 | }, 424 | { 425 | "tag": "author", 426 | "paths": [ 427 | [ 428 | "workout_file" 429 | ] 430 | ], 431 | "attributes": [], 432 | "value": { 433 | "datatype": "string", 434 | "value_samples": { 435 | "exhaustive": false, 436 | "values": [ 437 | [ 438 | "Zwift", 439 | 0.3566753926701571 440 | ], 441 | [ 442 | "Kevin Poulton", 443 | 0.21858638743455497 444 | ], 445 | [ 446 | "Zwift by Greg McMillan", 447 | 0.05235602094240838 448 | ], 449 | [ 450 | "Marco Pinotti", 451 | 0.03992146596858639 452 | ], 453 | [ 454 | "KNWU Fondo", 455 | 0.03926701570680628 456 | ], 457 | [ 458 | "QT2", 459 | 0.03926701570680628 460 | ], 461 | [ 462 | "Shayne Gaffney | GC Coaching", 463 | 0.031413612565445025 464 | ], 465 | [ 466 | "trainSharp", 467 | 0.026832460732984294 468 | ], 469 | [ 470 | "Zwift Academy Coaches", 471 | 0.02225130890052356 472 | ], 473 | [ 474 | "GCN", 475 | 0.021596858638743454 476 | ] 477 | ], 478 | "total_occurrences": 1528 479 | } 480 | } 481 | }, 482 | { 483 | "tag": "authorIcon", 484 | "paths": [ 485 | [ 486 | "workout_file" 487 | ] 488 | ], 489 | "attributes": [], 490 | "value": { 491 | "datatype": "string", 492 | "value_samples": { 493 | "exhaustive": true, 494 | "values": [ 495 | [ 496 | "UI/WhiteOrangeTheme/Workout_Select/brands/Peaks_Logo.tga", 497 | 0.3986784140969163 498 | ], 499 | [ 500 | "UI/WhiteOrangeTheme/Workout_Select/brands/MarcoPinotti_Logo.tga", 501 | 0.20044052863436124 502 | ], 503 | [ 504 | "UI/WhiteOrangeTheme/Workout_Select/brands/trainSharp_Logo.tga", 505 | 0.15198237885462554 506 | ], 507 | [ 508 | "UI/WhiteOrangeTheme/Workout_Select/brands/QT2Systems_Logo.tga", 509 | 0.13215859030837004 510 | ], 511 | [ 512 | "UI/WhiteOrangeTheme/Workout_Select/brands/GCN_Logo.tga", 513 | 0.07268722466960352 514 | ], 515 | [ 516 | "UI/WhiteOrangeTheme/Workout_Select/brands/HSBC_Logo.tga", 517 | 0.04185022026431718 518 | ], 519 | [ 520 | "UI/WhiteOrangeTheme/Workout_Select/brands/Powerhouse_Logo.tga", 521 | 0.0022026431718061676 522 | ] 523 | ], 524 | "total_occurrences": 454 525 | } 526 | } 527 | }, 528 | { 529 | "tag": "author_alias", 530 | "paths": [ 531 | [ 532 | "workout_file" 533 | ] 534 | ], 535 | "attributes": [], 536 | "value": { 537 | "datatype": "string", 538 | "value_samples": { 539 | "exhaustive": true, 540 | "values": [ 541 | [ 542 | "FTP", 543 | 1.0 544 | ] 545 | ], 546 | "total_occurrences": 6 547 | } 548 | } 549 | }, 550 | { 551 | "tag": "category", 552 | "paths": [ 553 | [ 554 | "workout_file" 555 | ] 556 | ], 557 | "attributes": [], 558 | "value": { 559 | "datatype": "string", 560 | "value_samples": { 561 | "exhaustive": false, 562 | "values": [], 563 | "total_occurrences": 1766 564 | } 565 | } 566 | }, 567 | { 568 | "tag": "categoryIndex", 569 | "paths": [ 570 | [ 571 | "workout_file" 572 | ] 573 | ], 574 | "attributes": [], 575 | "value": { 576 | "datatype": "string", 577 | "value_samples": { 578 | "exhaustive": false, 579 | "values": [ 580 | [ 581 | "2", 582 | 0.20233463035019456 583 | ], 584 | [ 585 | "1", 586 | 0.1893644617380026 587 | ], 588 | [ 589 | "4", 590 | 0.16731517509727625 591 | ], 592 | [ 593 | "3", 594 | 0.1569390402075227 595 | ], 596 | [ 597 | "5", 598 | 0.1297016861219196 599 | ], 600 | [ 601 | "6", 602 | 0.0648508430609598 603 | ], 604 | [ 605 | "7", 606 | 0.029831387808041506 607 | ], 608 | [ 609 | "8", 610 | 0.010376134889753566 611 | ], 612 | [ 613 | "9", 614 | 0.00648508430609598 615 | ], 616 | [ 617 | "12", 618 | 0.005188067444876783 619 | ] 620 | ], 621 | "total_occurrences": 771 622 | } 623 | } 624 | }, 625 | { 626 | "tag": "description", 627 | "paths": [ 628 | [ 629 | "workout_file" 630 | ] 631 | ], 632 | "attributes": [], 633 | "value": { 634 | "datatype": "string", 635 | "value_samples": { 636 | "exhaustive": false, 637 | "values": [], 638 | "total_occurrences": 1759 639 | } 640 | } 641 | }, 642 | { 643 | "tag": "durationType", 644 | "paths": [ 645 | [ 646 | "workout_file" 647 | ] 648 | ], 649 | "attributes": [], 650 | "value": { 651 | "datatype": "string", 652 | "value_samples": { 653 | "exhaustive": true, 654 | "values": [ 655 | [ 656 | "time", 657 | 1.0 658 | ] 659 | ], 660 | "total_occurrences": 48 661 | } 662 | } 663 | }, 664 | { 665 | "tag": "entid", 666 | "paths": [ 667 | [ 668 | "workout_file" 669 | ] 670 | ], 671 | "attributes": [], 672 | "value": { 673 | "datatype": "integer", 674 | "value_samples": { 675 | "exhaustive": true, 676 | "values": [ 677 | [ 678 | "487", 679 | 0.43795620437956206 680 | ], 681 | [ 682 | "491", 683 | 0.35036496350364965 684 | ], 685 | [ 686 | "436", 687 | 0.1386861313868613 688 | ], 689 | [ 690 | "150", 691 | 0.072992700729927 692 | ] 693 | ], 694 | "total_occurrences": 137 695 | } 696 | } 697 | }, 698 | { 699 | "tag": "ftpFemaleOverride", 700 | "paths": [ 701 | [ 702 | "workout_file" 703 | ] 704 | ], 705 | "attributes": [], 706 | "value": { 707 | "datatype": "integer", 708 | "value_samples": { 709 | "exhaustive": true, 710 | "values": [ 711 | [ 712 | "130", 713 | 1.0 714 | ] 715 | ], 716 | "total_occurrences": 1 717 | } 718 | } 719 | }, 720 | { 721 | "tag": "ftpMaleOverride", 722 | "paths": [ 723 | [ 724 | "workout_file" 725 | ] 726 | ], 727 | "attributes": [], 728 | "value": { 729 | "datatype": "integer", 730 | "value_samples": { 731 | "exhaustive": true, 732 | "values": [ 733 | [ 734 | "200", 735 | 1.0 736 | ] 737 | ], 738 | "total_occurrences": 1 739 | } 740 | } 741 | }, 742 | { 743 | "tag": "ftpOverride", 744 | "paths": [ 745 | [ 746 | "workout_file" 747 | ] 748 | ], 749 | "attributes": [], 750 | "value": { 751 | "datatype": "integer", 752 | "value_samples": { 753 | "exhaustive": true, 754 | "values": [ 755 | [ 756 | "100", 757 | 0.75 758 | ], 759 | [ 760 | "200", 761 | 0.25 762 | ] 763 | ], 764 | "total_occurrences": 4 765 | } 766 | } 767 | }, 768 | { 769 | "tag": "gameplayevent", 770 | "paths": [ 771 | [ 772 | "workout_file", 773 | "workout", 774 | "Cooldown" 775 | ] 776 | ], 777 | "attributes": [ 778 | "camera", 779 | "duration", 780 | "timeoffset", 781 | "type" 782 | ], 783 | "value": null 784 | }, 785 | { 786 | "tag": "name", 787 | "paths": [ 788 | [ 789 | "workout_file" 790 | ] 791 | ], 792 | "attributes": [], 793 | "value": { 794 | "datatype": "string", 795 | "value_samples": { 796 | "exhaustive": false, 797 | "values": [], 798 | "total_occurrences": 1766 799 | } 800 | } 801 | }, 802 | { 803 | "tag": "nameImperial", 804 | "paths": [ 805 | [ 806 | "workout_file" 807 | ] 808 | ], 809 | "attributes": [], 810 | "value": { 811 | "datatype": "string", 812 | "value_samples": { 813 | "exhaustive": false, 814 | "values": [ 815 | [ 816 | "8mi Easy Run #1", 817 | 0.08771929824561403 818 | ], 819 | [ 820 | "8mi Easy Run #2", 821 | 0.08771929824561403 822 | ], 823 | [ 824 | "Fartlek - 400m/200m", 825 | 0.07894736842105263 826 | ], 827 | [ 828 | "8mi Easy Run #3", 829 | 0.06140350877192982 830 | ], 831 | [ 832 | "5mi Easy Run #1", 833 | 0.06140350877192982 834 | ] 835 | ], 836 | "total_occurrences": 114 837 | } 838 | } 839 | }, 840 | { 841 | "tag": "nameMetric", 842 | "paths": [ 843 | [ 844 | "workout_file" 845 | ] 846 | ], 847 | "attributes": [], 848 | "value": { 849 | "datatype": "string", 850 | "value_samples": { 851 | "exhaustive": false, 852 | "values": [ 853 | [ 854 | "13km Easy Run #1", 855 | 0.08771929824561403 856 | ], 857 | [ 858 | "13km Easy Run #2", 859 | 0.08771929824561403 860 | ], 861 | [ 862 | "Fartlek - 400m/200m", 863 | 0.07894736842105263 864 | ], 865 | [ 866 | "13km Easy Run #3", 867 | 0.06140350877192982 868 | ], 869 | [ 870 | "8km Easy Run #1", 871 | 0.06140350877192982 872 | ] 873 | ], 874 | "total_occurrences": 114 875 | } 876 | } 877 | }, 878 | { 879 | "tag": "overrideHash", 880 | "paths": [ 881 | [ 882 | "workout_file" 883 | ] 884 | ], 885 | "attributes": [], 886 | "value": { 887 | "datatype": "integer", 888 | "value_samples": { 889 | "exhaustive": false, 890 | "values": [], 891 | "total_occurrences": 58 892 | } 893 | } 894 | }, 895 | { 896 | "tag": "painIndex", 897 | "paths": [ 898 | [ 899 | "workout_file" 900 | ] 901 | ], 902 | "attributes": [], 903 | "value": { 904 | "datatype": "integer", 905 | "value_samples": { 906 | "exhaustive": true, 907 | "values": [ 908 | [ 909 | "1", 910 | 0.6521739130434783 911 | ], 912 | [ 913 | "3", 914 | 0.17391304347826086 915 | ], 916 | [ 917 | "5", 918 | 0.08695652173913043 919 | ], 920 | [ 921 | "4", 922 | 0.08695652173913043 923 | ] 924 | ], 925 | "total_occurrences": 23 926 | } 927 | } 928 | }, 929 | { 930 | "tag": "setFtpAtPercentage", 931 | "paths": [ 932 | [ 933 | "workout_file" 934 | ] 935 | ], 936 | "attributes": [], 937 | "value": { 938 | "datatype": "real", 939 | "value_samples": { 940 | "exhaustive": true, 941 | "values": [ 942 | [ 943 | "0.75", 944 | 0.6666666666666666 945 | ], 946 | [ 947 | "0.65", 948 | 0.3333333333333333 949 | ] 950 | ], 951 | "total_occurrences": 3 952 | } 953 | } 954 | }, 955 | { 956 | "tag": "sportType", 957 | "paths": [ 958 | [ 959 | "workout_file" 960 | ] 961 | ], 962 | "attributes": [], 963 | "value": { 964 | "datatype": "string", 965 | "value_samples": { 966 | "exhaustive": true, 967 | "values": [ 968 | [ 969 | "bike", 970 | 0.6129247168554297 971 | ], 972 | [ 973 | "run", 974 | 0.37241838774150565 975 | ], 976 | [ 977 | "swim", 978 | 0.010659560293137908 979 | ], 980 | [ 981 | "ride", 982 | 0.0033311125916055963 983 | ], 984 | [ 985 | "Bike", 986 | 0.0006662225183211193 987 | ] 988 | ], 989 | "total_occurrences": 1501 990 | } 991 | } 992 | }, 993 | { 994 | "tag": "subcategory", 995 | "paths": [ 996 | [ 997 | "workout_file" 998 | ] 999 | ], 1000 | "attributes": [], 1001 | "value": { 1002 | "datatype": "string", 1003 | "value_samples": { 1004 | "exhaustive": false, 1005 | "values": [ 1006 | [ 1007 | "Week 4", 1008 | 0.09951956074124914 1009 | ], 1010 | [ 1011 | "Week 3", 1012 | 0.09883321894303362 1013 | ], 1014 | [ 1015 | "Week 2", 1016 | 0.09746053534660261 1017 | ], 1018 | [ 1019 | "Week 1", 1020 | 0.09608785175017158 1021 | ], 1022 | [ 1023 | "Week 5", 1024 | 0.08236101578586136 1025 | ], 1026 | [ 1027 | "Week 6", 1028 | 0.0789293067947838 1029 | ], 1030 | [ 1031 | "Week 7", 1032 | 0.06177076183939602 1033 | ], 1034 | [ 1035 | "Week 8", 1036 | 0.05833905284831846 1037 | ] 1038 | ], 1039 | "total_occurrences": 1457 1040 | } 1041 | } 1042 | }, 1043 | { 1044 | "tag": "tag", 1045 | "paths": [ 1046 | [ 1047 | "workout_file", 1048 | "tags" 1049 | ] 1050 | ], 1051 | "attributes": [ 1052 | "name" 1053 | ], 1054 | "value": null 1055 | }, 1056 | { 1057 | "tag": "tags", 1058 | "paths": [ 1059 | [ 1060 | "workout_file" 1061 | ] 1062 | ], 1063 | "attributes": [], 1064 | "value": null 1065 | }, 1066 | { 1067 | "tag": "test_details", 1068 | "paths": [ 1069 | [ 1070 | "workout_file" 1071 | ] 1072 | ], 1073 | "attributes": [ 1074 | "name", 1075 | "paceid", 1076 | "tracking_text_paceid", 1077 | "tracking_text_post", 1078 | "tracking_text_pre" 1079 | ], 1080 | "value": null 1081 | }, 1082 | { 1083 | "tag": "textevent", 1084 | "paths": [ 1085 | [ 1086 | "workout_file", 1087 | "workout" 1088 | ], 1089 | [ 1090 | "workout_file", 1091 | "workout", 1092 | "Cooldown" 1093 | ], 1094 | [ 1095 | "workout_file", 1096 | "workout", 1097 | "FreeRide" 1098 | ], 1099 | [ 1100 | "workout_file", 1101 | "workout", 1102 | "Freeride" 1103 | ], 1104 | [ 1105 | "workout_file", 1106 | "workout", 1107 | "IntervalsT" 1108 | ], 1109 | [ 1110 | "workout_file", 1111 | "workout", 1112 | "Ramp" 1113 | ], 1114 | [ 1115 | "workout_file", 1116 | "workout", 1117 | "SolidState" 1118 | ], 1119 | [ 1120 | "workout_file", 1121 | "workout", 1122 | "SteadyState" 1123 | ], 1124 | [ 1125 | "workout_file", 1126 | "workout", 1127 | "Warmup" 1128 | ] 1129 | ], 1130 | "attributes": [ 1131 | "distoffset", 1132 | "duration", 1133 | "message", 1134 | "mssage", 1135 | "textscale", 1136 | "timeoffset", 1137 | "y" 1138 | ], 1139 | "value": null 1140 | }, 1141 | { 1142 | "tag": "visibleAfterTime", 1143 | "paths": [ 1144 | [ 1145 | "workout_file" 1146 | ] 1147 | ], 1148 | "attributes": [], 1149 | "value": { 1150 | "datatype": "integer", 1151 | "value_samples": { 1152 | "exhaustive": false, 1153 | "values": [ 1154 | [ 1155 | "1533538860", 1156 | 0.09803921568627451 1157 | ], 1158 | [ 1159 | "1603929600", 1160 | 0.09803921568627451 1161 | ], 1162 | [ 1163 | "1573459200", 1164 | 0.09803921568627451 1165 | ], 1166 | [ 1167 | "1602720000", 1168 | 0.0784313725490196 1169 | ], 1170 | [ 1171 | "1504222780", 1172 | 0.0784313725490196 1173 | ], 1174 | [ 1175 | "1601510400", 1176 | 0.0784313725490196 1177 | ], 1178 | [ 1179 | "1564963200", 1180 | 0.0784313725490196 1181 | ], 1182 | [ 1183 | "1555632060", 1184 | 0.06862745098039216 1185 | ], 1186 | [ 1187 | "1560470400", 1188 | 0.06862745098039216 1189 | ], 1190 | [ 1191 | "1553472000", 1192 | 0.06862745098039216 1193 | ] 1194 | ], 1195 | "total_occurrences": 102 1196 | } 1197 | } 1198 | }, 1199 | { 1200 | "tag": "visibleOutsidePlan", 1201 | "paths": [ 1202 | [ 1203 | "workout_file" 1204 | ] 1205 | ], 1206 | "attributes": [], 1207 | "value": { 1208 | "datatype": "integer", 1209 | "value_samples": { 1210 | "exhaustive": true, 1211 | "values": [ 1212 | [ 1213 | "0", 1214 | 1.0 1215 | ] 1216 | ], 1217 | "total_occurrences": 728 1218 | } 1219 | } 1220 | }, 1221 | { 1222 | "tag": "workout", 1223 | "paths": [ 1224 | [ 1225 | "workout_file" 1226 | ] 1227 | ], 1228 | "attributes": [], 1229 | "value": null 1230 | }, 1231 | { 1232 | "tag": "workoutLength", 1233 | "paths": [ 1234 | [ 1235 | "workout_file" 1236 | ] 1237 | ], 1238 | "attributes": [], 1239 | "value": { 1240 | "datatype": "integer", 1241 | "value_samples": { 1242 | "exhaustive": true, 1243 | "values": [ 1244 | [ 1245 | "1800", 1246 | 0.47058823529411764 1247 | ], 1248 | [ 1249 | "1900", 1250 | 0.14705882352941177 1251 | ], 1252 | [ 1253 | "600", 1254 | 0.11764705882352941 1255 | ], 1256 | [ 1257 | "2500", 1258 | 0.058823529411764705 1259 | ], 1260 | [ 1261 | "1200", 1262 | 0.058823529411764705 1263 | ], 1264 | [ 1265 | "2100", 1266 | 0.029411764705882353 1267 | ], 1268 | [ 1269 | "2700", 1270 | 0.029411764705882353 1271 | ], 1272 | [ 1273 | "2200", 1274 | 0.029411764705882353 1275 | ], 1276 | [ 1277 | "3000", 1278 | 0.029411764705882353 1279 | ], 1280 | [ 1281 | "3600", 1282 | 0.029411764705882353 1283 | ] 1284 | ], 1285 | "total_occurrences": 34 1286 | } 1287 | } 1288 | }, 1289 | { 1290 | "tag": "workout_file", 1291 | "paths": [ 1292 | [] 1293 | ], 1294 | "attributes": [], 1295 | "value": null 1296 | } 1297 | ], 1298 | "attributes": [ 1299 | { 1300 | "attribute": "Cadence", 1301 | "tags": [ 1302 | "Cooldown", 1303 | "FreeRide", 1304 | "IntervalsT", 1305 | "Ramp", 1306 | "SteadyState", 1307 | "Warmup" 1308 | ], 1309 | "value": { 1310 | "datatype": "integer", 1311 | "value_samples": { 1312 | "exhaustive": false, 1313 | "values": [ 1314 | [ 1315 | "85", 1316 | 0.29236216489630756 1317 | ], 1318 | [ 1319 | "90", 1320 | 0.1537683358624178 1321 | ], 1322 | [ 1323 | "100", 1324 | 0.10141628730399595 1325 | ], 1326 | [ 1327 | "95", 1328 | 0.09332321699544764 1329 | ], 1330 | [ 1331 | "110", 1332 | 0.08573596358118361 1333 | ], 1334 | [ 1335 | "105", 1336 | 0.05285786545270612 1337 | ], 1338 | [ 1339 | "65", 1340 | 0.04425897824987354 1341 | ], 1342 | [ 1343 | "80", 1344 | 0.0409711684370258 1345 | ], 1346 | [ 1347 | "75", 1348 | 0.039200809307030855 1349 | ], 1350 | [ 1351 | "70", 1352 | 0.029843196762771876 1353 | ] 1354 | ], 1355 | "total_occurrences": 3954 1356 | } 1357 | } 1358 | }, 1359 | { 1360 | "attribute": "CadenceHigh", 1361 | "tags": [ 1362 | "Cooldown", 1363 | "FreeRide", 1364 | "IntervalsT", 1365 | "SteadyState", 1366 | "Warmup" 1367 | ], 1368 | "value": { 1369 | "datatype": "integer", 1370 | "value_samples": { 1371 | "exhaustive": true, 1372 | "values": [ 1373 | [ 1374 | "100", 1375 | 0.7516339869281046 1376 | ], 1377 | [ 1378 | "105", 1379 | 0.16339869281045752 1380 | ], 1381 | [ 1382 | "75", 1383 | 0.032679738562091505 1384 | ], 1385 | [ 1386 | "95", 1387 | 0.0196078431372549 1388 | ], 1389 | [ 1390 | "115", 1391 | 0.0196078431372549 1392 | ], 1393 | [ 1394 | "140", 1395 | 0.006535947712418301 1396 | ], 1397 | [ 1398 | "130", 1399 | 0.006535947712418301 1400 | ] 1401 | ], 1402 | "total_occurrences": 153 1403 | } 1404 | } 1405 | }, 1406 | { 1407 | "attribute": "CadenceLow", 1408 | "tags": [ 1409 | "Cooldown", 1410 | "FreeRide", 1411 | "IntervalsT", 1412 | "SteadyState", 1413 | "Warmup" 1414 | ], 1415 | "value": { 1416 | "datatype": "integer", 1417 | "value_samples": { 1418 | "exhaustive": true, 1419 | "values": [ 1420 | [ 1421 | "70", 1422 | 0.43790849673202614 1423 | ], 1424 | [ 1425 | "80", 1426 | 0.3333333333333333 1427 | ], 1428 | [ 1429 | "85", 1430 | 0.1437908496732026 1431 | ], 1432 | [ 1433 | "60", 1434 | 0.032679738562091505 1435 | ], 1436 | [ 1437 | "100", 1438 | 0.026143790849673203 1439 | ], 1440 | [ 1441 | "90", 1442 | 0.0196078431372549 1443 | ], 1444 | [ 1445 | "110", 1446 | 0.006535947712418301 1447 | ] 1448 | ], 1449 | "total_occurrences": 153 1450 | } 1451 | } 1452 | }, 1453 | { 1454 | "attribute": "CadenceResting", 1455 | "tags": [ 1456 | "Cooldown", 1457 | "IntervalsT", 1458 | "Ramp", 1459 | "SteadyState", 1460 | "Warmup" 1461 | ], 1462 | "value": { 1463 | "datatype": "integer", 1464 | "value_samples": { 1465 | "exhaustive": false, 1466 | "values": [ 1467 | [ 1468 | "85", 1469 | 0.3559556786703601 1470 | ], 1471 | [ 1472 | "80", 1473 | 0.16204986149584488 1474 | ], 1475 | [ 1476 | "90", 1477 | 0.16066481994459833 1478 | ], 1479 | [ 1480 | "75", 1481 | 0.11357340720221606 1482 | ], 1483 | [ 1484 | "65", 1485 | 0.0443213296398892 1486 | ], 1487 | [ 1488 | "100", 1489 | 0.04155124653739612 1490 | ], 1491 | [ 1492 | "95", 1493 | 0.04155124653739612 1494 | ], 1495 | [ 1496 | "70", 1497 | 0.030470914127423823 1498 | ], 1499 | [ 1500 | "60", 1501 | 0.018005540166204988 1502 | ], 1503 | [ 1504 | "105", 1505 | 0.0110803324099723 1506 | ] 1507 | ], 1508 | "total_occurrences": 722 1509 | } 1510 | } 1511 | }, 1512 | { 1513 | "attribute": "Duration", 1514 | "tags": [ 1515 | "Cooldown", 1516 | "FreeRide", 1517 | "Freeride", 1518 | "MaxEffort", 1519 | "Ramp", 1520 | "SolidState", 1521 | "SteadyState", 1522 | "TextEvent", 1523 | "Warmup" 1524 | ], 1525 | "value": { 1526 | "datatype": "real", 1527 | "value_samples": { 1528 | "exhaustive": false, 1529 | "values": [ 1530 | [ 1531 | "60", 1532 | 0.15833096792506388 1533 | ], 1534 | [ 1535 | "300", 1536 | 0.11751348282713596 1537 | ], 1538 | [ 1539 | "180", 1540 | 0.10627306273062731 1541 | ], 1542 | [ 1543 | "120", 1544 | 0.10621629293216009 1545 | ], 1546 | [ 1547 | "30", 1548 | 0.06290093670167471 1549 | ], 1550 | [ 1551 | "600", 1552 | 0.05160374680669884 1553 | ], 1554 | [ 1555 | "240", 1556 | 0.03599205222821459 1557 | ], 1558 | [ 1559 | "400", 1560 | 0.03474311666193585 1561 | ], 1562 | [ 1563 | "800", 1564 | 0.02662503548112404 1565 | ], 1566 | [ 1567 | "20", 1568 | 0.022367300596082884 1569 | ] 1570 | ], 1571 | "total_occurrences": 17615 1572 | } 1573 | } 1574 | }, 1575 | { 1576 | "attribute": "EndAtRoadTime", 1577 | "tags": [ 1578 | "Cooldown" 1579 | ], 1580 | "value": { 1581 | "datatype": "real", 1582 | "value_samples": { 1583 | "exhaustive": true, 1584 | "values": [ 1585 | [ 1586 | "0.5296", 1587 | 1.0 1588 | ] 1589 | ], 1590 | "total_occurrences": 1 1591 | } 1592 | } 1593 | }, 1594 | { 1595 | "attribute": "FailThresholdDuration", 1596 | "tags": [ 1597 | "FreeRide", 1598 | "SteadyState" 1599 | ], 1600 | "value": { 1601 | "datatype": "integer", 1602 | "value_samples": { 1603 | "exhaustive": true, 1604 | "values": [ 1605 | [ 1606 | "15", 1607 | 0.9253731343283582 1608 | ], 1609 | [ 1610 | "10", 1611 | 0.07462686567164178 1612 | ] 1613 | ], 1614 | "total_occurrences": 67 1615 | } 1616 | } 1617 | }, 1618 | { 1619 | "attribute": "FlatRoad", 1620 | "tags": [ 1621 | "FreeRide", 1622 | "Freeride", 1623 | "IntervalsT" 1624 | ], 1625 | "value": { 1626 | "datatype": "integer", 1627 | "value_samples": { 1628 | "exhaustive": true, 1629 | "values": [ 1630 | [ 1631 | "1", 1632 | 0.5820433436532507 1633 | ], 1634 | [ 1635 | "0", 1636 | 0.4148606811145511 1637 | ], 1638 | [ 1639 | "255", 1640 | 0.0030959752321981426 1641 | ] 1642 | ], 1643 | "total_occurrences": 323 1644 | } 1645 | } 1646 | }, 1647 | { 1648 | "attribute": "Forced_Performance_Test", 1649 | "tags": [ 1650 | "SteadyState" 1651 | ], 1652 | "value": { 1653 | "datatype": "integer", 1654 | "value_samples": { 1655 | "exhaustive": true, 1656 | "values": [ 1657 | [ 1658 | "1", 1659 | 1.0 1660 | ] 1661 | ], 1662 | "total_occurrences": 1 1663 | } 1664 | } 1665 | }, 1666 | { 1667 | "attribute": "NeverFails", 1668 | "tags": [ 1669 | "SteadyState" 1670 | ], 1671 | "value": { 1672 | "datatype": "integer", 1673 | "value_samples": { 1674 | "exhaustive": true, 1675 | "values": [ 1676 | [ 1677 | "1", 1678 | 1.0 1679 | ] 1680 | ], 1681 | "total_occurrences": 155 1682 | } 1683 | } 1684 | }, 1685 | { 1686 | "attribute": "OffDuration", 1687 | "tags": [ 1688 | "IntervalsT" 1689 | ], 1690 | "value": { 1691 | "datatype": "real", 1692 | "value_samples": { 1693 | "exhaustive": false, 1694 | "values": [ 1695 | [ 1696 | "60", 1697 | 0.24896265560165975 1698 | ], 1699 | [ 1700 | "30", 1701 | 0.18494368701837582 1702 | ], 1703 | [ 1704 | "120", 1705 | 0.08535862477771192 1706 | ], 1707 | [ 1708 | "180", 1709 | 0.058091286307053944 1710 | ], 1711 | [ 1712 | "300", 1713 | 0.051570835803200946 1714 | ], 1715 | [ 1716 | "50", 1717 | 0.04860699466508595 1718 | ], 1719 | [ 1720 | "400", 1721 | 0.03615886188500297 1722 | ], 1723 | [ 1724 | "20", 1725 | 0.032009484291641965 1726 | ], 1727 | [ 1728 | "45", 1729 | 0.02963841138114997 1730 | ], 1731 | [ 1732 | "40", 1733 | 0.026674570243034972 1734 | ] 1735 | ], 1736 | "total_occurrences": 1687 1737 | } 1738 | } 1739 | }, 1740 | { 1741 | "attribute": "OffPower", 1742 | "tags": [ 1743 | "IntervalsT", 1744 | "SteadyState" 1745 | ], 1746 | "value": { 1747 | "datatype": "string", 1748 | "value_samples": { 1749 | "exhaustive": false, 1750 | "values": [ 1751 | [ 1752 | "0.5", 1753 | 0.15593434343434343 1754 | ], 1755 | [ 1756 | "0.55", 1757 | 0.1275252525252525 1758 | ], 1759 | [ 1760 | "0.50449997", 1761 | 0.07449494949494949 1762 | ], 1763 | [ 1764 | "0.65", 1765 | 0.05303030303030303 1766 | ], 1767 | [ 1768 | "0.7", 1769 | 0.041666666666666664 1770 | ], 1771 | [ 1772 | "0.75", 1773 | 0.039141414141414144 1774 | ], 1775 | [ 1776 | "0.9", 1777 | 0.028409090909090908 1778 | ], 1779 | [ 1780 | "0.85", 1781 | 0.023989898989898988 1782 | ], 1783 | [ 1784 | ".5", 1785 | 0.01893939393939394 1786 | ], 1787 | [ 1788 | "0.50450003", 1789 | 0.018308080808080808 1790 | ] 1791 | ], 1792 | "total_occurrences": 1584 1793 | } 1794 | } 1795 | }, 1796 | { 1797 | "attribute": "OnDuration", 1798 | "tags": [ 1799 | "IntervalsT" 1800 | ], 1801 | "value": { 1802 | "datatype": "real", 1803 | "value_samples": { 1804 | "exhaustive": false, 1805 | "values": [ 1806 | [ 1807 | "60", 1808 | 0.21991701244813278 1809 | ], 1810 | [ 1811 | "30", 1812 | 0.1991701244813278 1813 | ], 1814 | [ 1815 | "120", 1816 | 0.07468879668049792 1817 | ], 1818 | [ 1819 | "10", 1820 | 0.07468879668049792 1821 | ], 1822 | [ 1823 | "15", 1824 | 0.05690574985180794 1825 | ], 1826 | [ 1827 | "300", 1828 | 0.05038529934795495 1829 | ], 1830 | [ 1831 | "180", 1832 | 0.04564315352697095 1833 | ], 1834 | [ 1835 | "20", 1836 | 0.04267931238885596 1837 | ], 1838 | [ 1839 | "800", 1840 | 0.02963841138114997 1841 | ], 1842 | [ 1843 | "40", 1844 | 0.026081802015411975 1845 | ] 1846 | ], 1847 | "total_occurrences": 1687 1848 | } 1849 | } 1850 | }, 1851 | { 1852 | "attribute": "OnPower", 1853 | "tags": [ 1854 | "IntervalsT" 1855 | ], 1856 | "value": { 1857 | "datatype": "string", 1858 | "value_samples": { 1859 | "exhaustive": false, 1860 | "values": [ 1861 | [ 1862 | "1.05", 1863 | 0.05436156763590392 1864 | ], 1865 | [ 1866 | "1.2", 1867 | 0.05120101137800253 1868 | ] 1869 | ], 1870 | "total_occurrences": 1582 1871 | } 1872 | } 1873 | }, 1874 | { 1875 | "attribute": "OverUnder", 1876 | "tags": [ 1877 | "IntervalsT" 1878 | ], 1879 | "value": { 1880 | "datatype": "integer", 1881 | "value_samples": { 1882 | "exhaustive": true, 1883 | "values": [ 1884 | [ 1885 | "1", 1886 | 1.0 1887 | ] 1888 | ], 1889 | "total_occurrences": 115 1890 | } 1891 | } 1892 | }, 1893 | { 1894 | "attribute": "Pace", 1895 | "tags": [ 1896 | "Cooldown" 1897 | ], 1898 | "value": { 1899 | "datatype": "integer", 1900 | "value_samples": { 1901 | "exhaustive": true, 1902 | "values": [ 1903 | [ 1904 | "0", 1905 | 1.0 1906 | ] 1907 | ], 1908 | "total_occurrences": 1 1909 | } 1910 | } 1911 | }, 1912 | { 1913 | "attribute": "Power", 1914 | "tags": [ 1915 | "Cooldown", 1916 | "FreeRide", 1917 | "Ramp", 1918 | "SolidState", 1919 | "SteadyState", 1920 | "Warmup" 1921 | ], 1922 | "value": { 1923 | "datatype": "string", 1924 | "value_samples": { 1925 | "exhaustive": false, 1926 | "values": [ 1927 | [ 1928 | "0.5", 1929 | 0.10407274394437228 1930 | ], 1931 | [ 1932 | "0.50449997", 1933 | 0.05157790173454573 1934 | ], 1935 | [ 1936 | "0.55", 1937 | 0.04783372812714908 1938 | ], 1939 | [ 1940 | "0.85", 1941 | 0.037594559486513336 1942 | ], 1943 | [ 1944 | "0.9", 1945 | 0.036066325361045315 1946 | ], 1947 | [ 1948 | "0.55000001", 1949 | 0.03155803469091465 1950 | ], 1951 | [ 1952 | "0.8", 1953 | 0.031405211278367845 1954 | ], 1955 | [ 1956 | "0.65", 1957 | 0.03010621227172003 1958 | ], 1959 | [ 1960 | "1.1", 1961 | 0.028577978146252007 1962 | ], 1963 | [ 1964 | "0.75", 1965 | 0.026973332314510583 1966 | ] 1967 | ], 1968 | "total_occurrences": 13087 1969 | } 1970 | } 1971 | }, 1972 | { 1973 | "attribute": "PowerHigh", 1974 | "tags": [ 1975 | "Cooldown", 1976 | "Ramp", 1977 | "SteadyState", 1978 | "Warmup" 1979 | ], 1980 | "value": { 1981 | "datatype": "string", 1982 | "value_samples": { 1983 | "exhaustive": false, 1984 | "values": [ 1985 | [ 1986 | "0.65", 1987 | 0.09971656789487246 1988 | ], 1989 | [ 1990 | "0.75", 1991 | 0.08966761144035043 1992 | ], 1993 | [ 1994 | "0.9", 1995 | 0.051017778922958 1996 | ] 1997 | ], 1998 | "total_occurrences": 3881 1999 | } 2000 | } 2001 | }, 2002 | { 2003 | "attribute": "PowerLow", 2004 | "tags": [ 2005 | "Cooldown", 2006 | "Ramp", 2007 | "SteadyState", 2008 | "Warmup" 2009 | ], 2010 | "value": { 2011 | "datatype": "string", 2012 | "value_samples": { 2013 | "exhaustive": false, 2014 | "values": [ 2015 | [ 2016 | "0.75", 2017 | 0.08790925496261923 2018 | ], 2019 | [ 2020 | "0.5", 2021 | 0.07244135086362465 2022 | ], 2023 | [ 2024 | "0.7", 2025 | 0.06393400360917763 2026 | ] 2027 | ], 2028 | "total_occurrences": 3879 2029 | } 2030 | } 2031 | }, 2032 | { 2033 | "attribute": "PowerOffHigh", 2034 | "tags": [ 2035 | "IntervalsT" 2036 | ], 2037 | "value": { 2038 | "datatype": "real", 2039 | "value_samples": { 2040 | "exhaustive": false, 2041 | "values": [ 2042 | [ 2043 | "1.0", 2044 | 0.18681318681318682 2045 | ], 2046 | [ 2047 | "0.50450003", 2048 | 0.16483516483516483 2049 | ], 2050 | [ 2051 | "0.5", 2052 | 0.14285714285714285 2053 | ], 2054 | [ 2055 | "0.75", 2056 | 0.14285714285714285 2057 | ], 2058 | [ 2059 | "0.97", 2060 | 0.06593406593406594 2061 | ], 2062 | [ 2063 | "0.87", 2064 | 0.054945054945054944 2065 | ], 2066 | [ 2067 | "0.90449995", 2068 | 0.054945054945054944 2069 | ], 2070 | [ 2071 | "0.7", 2072 | 0.04395604395604396 2073 | ], 2074 | [ 2075 | "0.65449995", 2076 | 0.03296703296703297 2077 | ], 2078 | [ 2079 | "0.61000001", 2080 | 0.02197802197802198 2081 | ] 2082 | ], 2083 | "total_occurrences": 91 2084 | } 2085 | } 2086 | }, 2087 | { 2088 | "attribute": "PowerOffLow", 2089 | "tags": [ 2090 | "IntervalsT" 2091 | ], 2092 | "value": { 2093 | "datatype": "string", 2094 | "value_samples": { 2095 | "exhaustive": false, 2096 | "values": [ 2097 | [ 2098 | "0.98", 2099 | 0.18681318681318682 2100 | ], 2101 | [ 2102 | "0.5", 2103 | 0.16483516483516483 2104 | ], 2105 | [ 2106 | "0.50450003", 2107 | 0.16483516483516483 2108 | ], 2109 | [ 2110 | "0.7", 2111 | 0.14285714285714285 2112 | ], 2113 | [ 2114 | "0.93", 2115 | 0.06593406593406594 2116 | ], 2117 | [ 2118 | "0.84", 2119 | 0.054945054945054944 2120 | ], 2121 | [ 2122 | "0.90449995", 2123 | 0.054945054945054944 2124 | ], 2125 | [ 2126 | ".55", 2127 | 0.04395604395604396 2128 | ], 2129 | [ 2130 | "0.65449995", 2131 | 0.03296703296703297 2132 | ], 2133 | [ 2134 | "0.61000001", 2135 | 0.02197802197802198 2136 | ] 2137 | ], 2138 | "total_occurrences": 91 2139 | } 2140 | } 2141 | }, 2142 | { 2143 | "attribute": "PowerOffZone", 2144 | "tags": [ 2145 | "IntervalsT" 2146 | ], 2147 | "value": { 2148 | "datatype": "integer", 2149 | "value_samples": { 2150 | "exhaustive": true, 2151 | "values": [ 2152 | [ 2153 | "3", 2154 | 0.2857142857142857 2155 | ], 2156 | [ 2157 | "2", 2158 | 0.2857142857142857 2159 | ], 2160 | [ 2161 | "1", 2162 | 0.2857142857142857 2163 | ], 2164 | [ 2165 | "4", 2166 | 0.14285714285714285 2167 | ] 2168 | ], 2169 | "total_occurrences": 14 2170 | } 2171 | } 2172 | }, 2173 | { 2174 | "attribute": "PowerOnHigh", 2175 | "tags": [ 2176 | "IntervalsT" 2177 | ], 2178 | "value": { 2179 | "datatype": "real", 2180 | "value_samples": { 2181 | "exhaustive": false, 2182 | "values": [ 2183 | [ 2184 | "0.95", 2185 | 0.15384615384615385 2186 | ], 2187 | [ 2188 | "1.1", 2189 | 0.15384615384615385 2190 | ], 2191 | [ 2192 | "2.0044999", 2193 | 0.08791208791208792 2194 | ], 2195 | [ 2196 | "1.07", 2197 | 0.08791208791208792 2198 | ], 2199 | [ 2200 | "1.2045", 2201 | 0.06593406593406594 2202 | ], 2203 | [ 2204 | "0.96", 2205 | 0.054945054945054944 2206 | ], 2207 | [ 2208 | "0.65449995", 2209 | 0.054945054945054944 2210 | ], 2211 | [ 2212 | "1.75", 2213 | 0.04395604395604396 2214 | ], 2215 | [ 2216 | "1.25", 2217 | 0.03296703296703297 2218 | ], 2219 | [ 2220 | "1.5", 2221 | 0.03296703296703297 2222 | ] 2223 | ], 2224 | "total_occurrences": 91 2225 | } 2226 | } 2227 | }, 2228 | { 2229 | "attribute": "PowerOnLow", 2230 | "tags": [ 2231 | "IntervalsT" 2232 | ], 2233 | "value": { 2234 | "datatype": "real", 2235 | "value_samples": { 2236 | "exhaustive": false, 2237 | "values": [ 2238 | [ 2239 | "1.07", 2240 | 0.14285714285714285 2241 | ], 2242 | [ 2243 | "0.75", 2244 | 0.14285714285714285 2245 | ], 2246 | [ 2247 | "2.0044999", 2248 | 0.08791208791208792 2249 | ], 2250 | [ 2251 | "1.5", 2252 | 0.07692307692307693 2253 | ], 2254 | [ 2255 | "1.2045", 2256 | 0.06593406593406594 2257 | ], 2258 | [ 2259 | "1.04", 2260 | 0.06593406593406594 2261 | ], 2262 | [ 2263 | "0.94", 2264 | 0.054945054945054944 2265 | ], 2266 | [ 2267 | "0.65449995", 2268 | 0.054945054945054944 2269 | ], 2270 | [ 2271 | "1.25", 2272 | 0.03296703296703297 2273 | ], 2274 | [ 2275 | "1.05", 2276 | 0.03296703296703297 2277 | ] 2278 | ], 2279 | "total_occurrences": 91 2280 | } 2281 | } 2282 | }, 2283 | { 2284 | "attribute": "PowerOnZone", 2285 | "tags": [ 2286 | "IntervalsT" 2287 | ], 2288 | "value": { 2289 | "datatype": "integer", 2290 | "value_samples": { 2291 | "exhaustive": true, 2292 | "values": [ 2293 | [ 2294 | "5", 2295 | 0.5 2296 | ], 2297 | [ 2298 | "4", 2299 | 0.14285714285714285 2300 | ], 2301 | [ 2302 | "3", 2303 | 0.14285714285714285 2304 | ], 2305 | [ 2306 | "6", 2307 | 0.14285714285714285 2308 | ], 2309 | [ 2310 | "2", 2311 | 0.07142857142857142 2312 | ] 2313 | ], 2314 | "total_occurrences": 14 2315 | } 2316 | } 2317 | }, 2318 | { 2319 | "attribute": "Quantize", 2320 | "tags": [ 2321 | "Warmup" 2322 | ], 2323 | "value": { 2324 | "datatype": "integer", 2325 | "value_samples": { 2326 | "exhaustive": true, 2327 | "values": [ 2328 | [ 2329 | "20", 2330 | 1.0 2331 | ] 2332 | ], 2333 | "total_occurrences": 8 2334 | } 2335 | } 2336 | }, 2337 | { 2338 | "attribute": "Repeat", 2339 | "tags": [ 2340 | "IntervalsT" 2341 | ], 2342 | "value": { 2343 | "datatype": "integer", 2344 | "value_samples": { 2345 | "exhaustive": false, 2346 | "values": [ 2347 | [ 2348 | "1", 2349 | 0.2151748666271488 2350 | ], 2351 | [ 2352 | "4", 2353 | 0.17605216360403084 2354 | ], 2355 | [ 2356 | "2", 2357 | 0.15352697095435686 2358 | ], 2359 | [ 2360 | "3", 2361 | 0.13633669235328985 2362 | ], 2363 | [ 2364 | "5", 2365 | 0.10966212211025489 2366 | ], 2367 | [ 2368 | "6", 2369 | 0.07053941908713693 2370 | ], 2371 | [ 2372 | "10", 2373 | 0.04742145820983995 2374 | ], 2375 | [ 2376 | "8", 2377 | 0.03734439834024896 2378 | ], 2379 | [ 2380 | "20", 2381 | 0.01956135151155898 2382 | ], 2383 | [ 2384 | "7", 2385 | 0.014226437462951986 2386 | ] 2387 | ], 2388 | "total_occurrences": 1687 2389 | } 2390 | } 2391 | }, 2392 | { 2393 | "attribute": "Target", 2394 | "tags": [ 2395 | "SteadyState" 2396 | ], 2397 | "value": { 2398 | "datatype": "real", 2399 | "value_samples": { 2400 | "exhaustive": true, 2401 | "values": [ 2402 | [ 2403 | "0.5", 2404 | 0.3793103448275862 2405 | ], 2406 | [ 2407 | "1.0", 2408 | 0.3103448275862069 2409 | ], 2410 | [ 2411 | "0.65", 2412 | 0.10344827586206896 2413 | ], 2414 | [ 2415 | "1.1", 2416 | 0.10344827586206896 2417 | ], 2418 | [ 2419 | "0.6", 2420 | 0.10344827586206896 2421 | ] 2422 | ], 2423 | "total_occurrences": 29 2424 | } 2425 | } 2426 | }, 2427 | { 2428 | "attribute": "Text", 2429 | "tags": [ 2430 | "SteadyState", 2431 | "Warmup" 2432 | ], 2433 | "value": { 2434 | "datatype": "string", 2435 | "value_samples": { 2436 | "exhaustive": true, 2437 | "values": [ 2438 | [ 2439 | "text", 2440 | 1.0 2441 | ] 2442 | ], 2443 | "total_occurrences": 31 2444 | } 2445 | } 2446 | }, 2447 | { 2448 | "attribute": "TimeOffset", 2449 | "tags": [ 2450 | "TextEvent" 2451 | ], 2452 | "value": { 2453 | "datatype": "integer", 2454 | "value_samples": { 2455 | "exhaustive": true, 2456 | "values": [ 2457 | [ 2458 | "0", 2459 | 0.2 2460 | ], 2461 | [ 2462 | "60", 2463 | 0.2 2464 | ], 2465 | [ 2466 | "120", 2467 | 0.2 2468 | ], 2469 | [ 2470 | "180", 2471 | 0.2 2472 | ], 2473 | [ 2474 | "240", 2475 | 0.2 2476 | ] 2477 | ], 2478 | "total_occurrences": 5 2479 | } 2480 | } 2481 | }, 2482 | { 2483 | "attribute": "Zone", 2484 | "tags": [ 2485 | "Cooldown", 2486 | "SteadyState", 2487 | "Warmup" 2488 | ], 2489 | "value": { 2490 | "datatype": "integer", 2491 | "value_samples": { 2492 | "exhaustive": true, 2493 | "values": [ 2494 | [ 2495 | "2", 2496 | 0.56353591160221 2497 | ], 2498 | [ 2499 | "1", 2500 | 0.2430939226519337 2501 | ], 2502 | [ 2503 | "3", 2504 | 0.17679558011049723 2505 | ], 2506 | [ 2507 | "6", 2508 | 0.011049723756906077 2509 | ], 2510 | [ 2511 | "4", 2512 | 0.0055248618784530384 2513 | ] 2514 | ], 2515 | "total_occurrences": 181 2516 | } 2517 | } 2518 | }, 2519 | { 2520 | "attribute": "camera", 2521 | "tags": [ 2522 | "gameplayevent" 2523 | ], 2524 | "value": { 2525 | "datatype": "integer", 2526 | "value_samples": { 2527 | "exhaustive": true, 2528 | "values": [ 2529 | [ 2530 | "1", 2531 | 0.5 2532 | ], 2533 | [ 2534 | "7", 2535 | 0.3333333333333333 2536 | ], 2537 | [ 2538 | "4", 2539 | 0.16666666666666666 2540 | ] 2541 | ], 2542 | "total_occurrences": 6 2543 | } 2544 | } 2545 | }, 2546 | { 2547 | "attribute": "distoffset", 2548 | "tags": [ 2549 | "textevent" 2550 | ], 2551 | "value": { 2552 | "datatype": "string", 2553 | "value_samples": { 2554 | "exhaustive": false, 2555 | "values": [ 2556 | [ 2557 | "0", 2558 | 0.1619143143716865 2559 | ], 2560 | [ 2561 | "400", 2562 | 0.06247313368677461 2563 | ], 2564 | [ 2565 | "200", 2566 | 0.054592348473993406 2567 | ], 2568 | [ 2569 | "800", 2570 | 0.051583321392749674 2571 | ], 2572 | [ 2573 | "100", 2574 | 0.04929072933084969 2575 | ], 2576 | [ 2577 | "50", 2578 | 0.04012036108324975 2579 | ], 2580 | [ 2581 | "1600", 2582 | 0.03983378707551225 2583 | ], 2584 | [ 2585 | "300", 2586 | 0.029087261785356068 2587 | ], 2588 | [ 2589 | "1200", 2590 | 0.026364808711849836 2591 | ], 2592 | [ 2593 | "2400", 2594 | 0.026078234704112337 2595 | ] 2596 | ], 2597 | "total_occurrences": 6979 2598 | } 2599 | } 2600 | }, 2601 | { 2602 | "attribute": "duration", 2603 | "tags": [ 2604 | "TextNotification", 2605 | "gameplayevent", 2606 | "textevent" 2607 | ], 2608 | "value": { 2609 | "datatype": "integer", 2610 | "value_samples": { 2611 | "exhaustive": true, 2612 | "values": [ 2613 | [ 2614 | "10", 2615 | 0.4863013698630137 2616 | ], 2617 | [ 2618 | "20", 2619 | 0.3082191780821918 2620 | ], 2621 | [ 2622 | "1000", 2623 | 0.0684931506849315 2624 | ], 2625 | [ 2626 | "30", 2627 | 0.0684931506849315 2628 | ], 2629 | [ 2630 | "19", 2631 | 0.0547945205479452 2632 | ], 2633 | [ 2634 | "5", 2635 | 0.00684931506849315 2636 | ], 2637 | [ 2638 | "60", 2639 | 0.00684931506849315 2640 | ] 2641 | ], 2642 | "total_occurrences": 146 2643 | } 2644 | } 2645 | }, 2646 | { 2647 | "attribute": "font_size", 2648 | "tags": [ 2649 | "TextNotification" 2650 | ], 2651 | "value": { 2652 | "datatype": "real", 2653 | "value_samples": { 2654 | "exhaustive": true, 2655 | "values": [ 2656 | [ 2657 | "0.5", 2658 | 1.0 2659 | ] 2660 | ], 2661 | "total_occurrences": 11 2662 | } 2663 | } 2664 | }, 2665 | { 2666 | "attribute": "forced_performance_test", 2667 | "tags": [ 2668 | "SteadyState" 2669 | ], 2670 | "value": { 2671 | "datatype": "integer", 2672 | "value_samples": { 2673 | "exhaustive": true, 2674 | "values": [ 2675 | [ 2676 | "1", 2677 | 1.0 2678 | ] 2679 | ], 2680 | "total_occurrences": 16 2681 | } 2682 | } 2683 | }, 2684 | { 2685 | "attribute": "ftptest", 2686 | "tags": [ 2687 | "FreeRide", 2688 | "Freeride" 2689 | ], 2690 | "value": { 2691 | "datatype": "integer", 2692 | "value_samples": { 2693 | "exhaustive": true, 2694 | "values": [ 2695 | [ 2696 | "1", 2697 | 1.0 2698 | ] 2699 | ], 2700 | "total_occurrences": 11 2701 | } 2702 | } 2703 | }, 2704 | { 2705 | "attribute": "message", 2706 | "tags": [ 2707 | "TextEvent", 2708 | "textevent" 2709 | ], 2710 | "value": { 2711 | "datatype": "string", 2712 | "value_samples": { 2713 | "exhaustive": false, 2714 | "values": [], 2715 | "total_occurrences": 44668 2716 | } 2717 | } 2718 | }, 2719 | { 2720 | "attribute": "mssage", 2721 | "tags": [ 2722 | "textevent" 2723 | ], 2724 | "value": { 2725 | "datatype": "string", 2726 | "value_samples": { 2727 | "exhaustive": true, 2728 | "values": [ 2729 | [ 2730 | "As always though, a great workout starts off with a good warm up - It's time to Level Up. Are you ready?", 2731 | 1.0 2732 | ] 2733 | ], 2734 | "total_occurrences": 1 2735 | } 2736 | } 2737 | }, 2738 | { 2739 | "attribute": "name", 2740 | "tags": [ 2741 | "tag", 2742 | "test_details" 2743 | ], 2744 | "value": { 2745 | "datatype": "string", 2746 | "value_samples": { 2747 | "exhaustive": false, 2748 | "values": [ 2749 | [ 2750 | "INTERVALS", 2751 | 0.3217391304347826 2752 | ], 2753 | [ 2754 | "4weekftp", 2755 | 0.12173913043478261 2756 | ], 2757 | [ 2758 | "Zwift Academy", 2759 | 0.09855072463768116 2760 | ], 2761 | [ 2762 | "PRL", 2763 | 0.07536231884057971 2764 | ], 2765 | [ 2766 | "fondo", 2767 | 0.03768115942028986 2768 | ], 2769 | [ 2770 | "Prenatal", 2771 | 0.034782608695652174 2772 | ], 2773 | [ 2774 | "FTP", 2775 | 0.03188405797101449 2776 | ], 2777 | [ 2778 | "ZA TRI", 2779 | 0.028985507246376812 2780 | ], 2781 | [ 2782 | "ZATRI2019", 2783 | 0.028985507246376812 2784 | ], 2785 | [ 2786 | "Endurance", 2787 | 0.02608695652173913 2788 | ] 2789 | ], 2790 | "total_occurrences": 345 2791 | } 2792 | } 2793 | }, 2794 | { 2795 | "attribute": "pace", 2796 | "tags": [ 2797 | "Cooldown", 2798 | "IntervalsT", 2799 | "Ramp", 2800 | "SteadyState", 2801 | "Warmup" 2802 | ], 2803 | "value": { 2804 | "datatype": "string", 2805 | "value_samples": { 2806 | "exhaustive": false, 2807 | "values": [ 2808 | [ 2809 | "0", 2810 | 0.43390206686086835 2811 | ], 2812 | [ 2813 | "2", 2814 | 0.22263836870578443 2815 | ], 2816 | [ 2817 | "1", 2818 | 0.10750450825357193 2819 | ], 2820 | [ 2821 | "3", 2822 | 0.08156471077819392 2823 | ], 2824 | [ 2825 | "8268", 2826 | 0.05049244000554862 2827 | ], 2828 | [ 2829 | "-256", 2830 | 0.02455264253017062 2831 | ], 2832 | [ 2833 | "", 2834 | 0.021362186156193647 2835 | ], 2836 | [ 2837 | "1481003871", 2838 | 0.02122347066167291 2839 | ], 2840 | [ 2841 | "-1307163960", 2842 | 0.018033014287695937 2843 | ], 2844 | [ 2845 | "189", 2846 | 0.012623110001387154 2847 | ] 2848 | ], 2849 | "total_occurrences": 7209 2850 | } 2851 | } 2852 | }, 2853 | { 2854 | "attribute": "paceid", 2855 | "tags": [ 2856 | "test_details" 2857 | ], 2858 | "value": { 2859 | "datatype": "integer", 2860 | "value_samples": { 2861 | "exhaustive": true, 2862 | "values": [ 2863 | [ 2864 | "1", 2865 | 0.6 2866 | ], 2867 | [ 2868 | "2", 2869 | 0.4 2870 | ] 2871 | ], 2872 | "total_occurrences": 5 2873 | } 2874 | } 2875 | }, 2876 | { 2877 | "attribute": "ramptest", 2878 | "tags": [ 2879 | "FreeRide", 2880 | "SteadyState" 2881 | ], 2882 | "value": { 2883 | "datatype": "integer", 2884 | "value_samples": { 2885 | "exhaustive": true, 2886 | "values": [ 2887 | [ 2888 | "1", 2889 | 1.0 2890 | ] 2891 | ], 2892 | "total_occurrences": 67 2893 | } 2894 | } 2895 | }, 2896 | { 2897 | "attribute": "replacement_prescription", 2898 | "tags": [ 2899 | "Cooldown", 2900 | "SteadyState", 2901 | "Warmup" 2902 | ], 2903 | "value": { 2904 | "datatype": "string", 2905 | "value_samples": { 2906 | "exhaustive": true, 2907 | "values": [ 2908 | [ 2909 | "WALK", 2910 | 0.33695652173913043 2911 | ], 2912 | [ 2913 | "GO!", 2914 | 0.20108695652173914 2915 | ], 2916 | [ 2917 | "JOG", 2918 | 0.15217391304347827 2919 | ], 2920 | [ 2921 | "WARMUP", 2922 | 0.11413043478260869 2923 | ], 2924 | [ 2925 | "RUN", 2926 | 0.08695652173913043 2927 | ], 2928 | [ 2929 | "COOLDOWN", 2930 | 0.07065217391304347 2931 | ], 2932 | [ 2933 | "EASY PACE", 2934 | 0.016304347826086956 2935 | ], 2936 | [ 2937 | "Cool Down", 2938 | 0.010869565217391304 2939 | ], 2940 | [ 2941 | "MED PACE", 2942 | 0.005434782608695652 2943 | ], 2944 | [ 2945 | "FAST PACE", 2946 | 0.005434782608695652 2947 | ] 2948 | ], 2949 | "total_occurrences": 184 2950 | } 2951 | } 2952 | }, 2953 | { 2954 | "attribute": "replacement_verb", 2955 | "tags": [ 2956 | "Cooldown", 2957 | "SteadyState", 2958 | "Warmup" 2959 | ], 2960 | "value": { 2961 | "datatype": "string", 2962 | "value_samples": { 2963 | "exhaustive": false, 2964 | "values": [ 2965 | [ 2966 | "", 2967 | 0.6098901098901099 2968 | ], 2969 | [ 2970 | "FULL GAS", 2971 | 0.2032967032967033 2972 | ], 2973 | [ 2974 | "7.5/10", 2975 | 0.054945054945054944 2976 | ], 2977 | [ 2978 | "6/10", 2979 | 0.02197802197802198 2980 | ], 2981 | [ 2982 | "7/10", 2983 | 0.02197802197802198 2984 | ], 2985 | [ 2986 | "8/10", 2987 | 0.02197802197802198 2988 | ], 2989 | [ 2990 | "9/10", 2991 | 0.02197802197802198 2992 | ], 2993 | [ 2994 | "Relaxed", 2995 | 0.01098901098901099 2996 | ], 2997 | [ 2998 | "5/10", 2999 | 0.01098901098901099 3000 | ], 3001 | [ 3002 | "WALK", 3003 | 0.01098901098901099 3004 | ] 3005 | ], 3006 | "total_occurrences": 182 3007 | } 3008 | } 3009 | }, 3010 | { 3011 | "attribute": "show_avg", 3012 | "tags": [ 3013 | "FreeRide", 3014 | "Ramp", 3015 | "SteadyState" 3016 | ], 3017 | "value": { 3018 | "datatype": "integer", 3019 | "value_samples": { 3020 | "exhaustive": true, 3021 | "values": [ 3022 | [ 3023 | "1", 3024 | 1.0 3025 | ] 3026 | ], 3027 | "total_occurrences": 113 3028 | } 3029 | } 3030 | }, 3031 | { 3032 | "attribute": "text", 3033 | "tags": [ 3034 | "TextNotification" 3035 | ], 3036 | "value": { 3037 | "datatype": "string", 3038 | "value_samples": { 3039 | "exhaustive": true, 3040 | "values": [ 3041 | [ 3042 | "60 RPM CADENCE", 3043 | 0.36363636363636365 3044 | ], 3045 | [ 3046 | "100 RPM CADENCE", 3047 | 0.36363636363636365 3048 | ], 3049 | [ 3050 | "Cool off! 110 cadence!", 3051 | 0.18181818181818182 3052 | ], 3053 | [ 3054 | "Cool off!", 3055 | 0.09090909090909091 3056 | ] 3057 | ], 3058 | "total_occurrences": 11 3059 | } 3060 | } 3061 | }, 3062 | { 3063 | "attribute": "textscale", 3064 | "tags": [ 3065 | "textevent" 3066 | ], 3067 | "value": { 3068 | "datatype": "real", 3069 | "value_samples": { 3070 | "exhaustive": true, 3071 | "values": [ 3072 | [ 3073 | "0.5", 3074 | 1.0 3075 | ] 3076 | ], 3077 | "total_occurrences": 38 3078 | } 3079 | } 3080 | }, 3081 | { 3082 | "attribute": "timeOffset", 3083 | "tags": [ 3084 | "TextNotification" 3085 | ], 3086 | "value": { 3087 | "datatype": "integer", 3088 | "value_samples": { 3089 | "exhaustive": true, 3090 | "values": [ 3091 | [ 3092 | "0", 3093 | 0.36363636363636365 3094 | ], 3095 | [ 3096 | "180", 3097 | 0.09090909090909091 3098 | ], 3099 | [ 3100 | "360", 3101 | 0.09090909090909091 3102 | ], 3103 | [ 3104 | "540", 3105 | 0.09090909090909091 3106 | ], 3107 | [ 3108 | "720", 3109 | 0.09090909090909091 3110 | ], 3111 | [ 3112 | "900", 3113 | 0.09090909090909091 3114 | ], 3115 | [ 3116 | "1080", 3117 | 0.09090909090909091 3118 | ], 3119 | [ 3120 | "1260", 3121 | 0.09090909090909091 3122 | ] 3123 | ], 3124 | "total_occurrences": 11 3125 | } 3126 | } 3127 | }, 3128 | { 3129 | "attribute": "timeoffset", 3130 | "tags": [ 3131 | "TextEvent", 3132 | "gameplayevent", 3133 | "textevent" 3134 | ], 3135 | "value": { 3136 | "datatype": "string", 3137 | "value_samples": { 3138 | "exhaustive": false, 3139 | "values": [ 3140 | [ 3141 | "0", 3142 | 0.15445707607309817 3143 | ], 3144 | [ 3145 | "10", 3146 | 0.06087972800679983 3147 | ], 3148 | [ 3149 | "30", 3150 | 0.05527518062048449 3151 | ], 3152 | [ 3153 | "60", 3154 | 0.05264555886102847 3155 | ], 3156 | [ 3157 | "20", 3158 | 0.038461538461538464 3159 | ], 3160 | [ 3161 | "120", 3162 | 0.03251168720781981 3163 | ], 3164 | [ 3165 | "90", 3166 | 0.028872715682107947 3167 | ], 3168 | [ 3169 | "50", 3170 | 0.02730556736081598 3171 | ], 3172 | [ 3173 | "40", 3174 | 0.02340097747556311 3175 | ], 3176 | [ 3177 | "100", 3178 | 0.02196663833404165 3179 | ] 3180 | ], 3181 | "total_occurrences": 37648 3182 | } 3183 | } 3184 | }, 3185 | { 3186 | "attribute": "tracking_text_paceid", 3187 | "tags": [ 3188 | "test_details" 3189 | ], 3190 | "value": { 3191 | "datatype": "integer", 3192 | "value_samples": { 3193 | "exhaustive": true, 3194 | "values": [ 3195 | [ 3196 | "3", 3197 | 1.0 3198 | ] 3199 | ], 3200 | "total_occurrences": 5 3201 | } 3202 | } 3203 | }, 3204 | { 3205 | "attribute": "tracking_text_post", 3206 | "tags": [ 3207 | "test_details" 3208 | ], 3209 | "value": { 3210 | "datatype": "string", 3211 | "value_samples": { 3212 | "exhaustive": true, 3213 | "values": [ 3214 | [ 3215 | " half-marathon!", 3216 | 1.0 3217 | ] 3218 | ], 3219 | "total_occurrences": 5 3220 | } 3221 | } 3222 | }, 3223 | { 3224 | "attribute": "tracking_text_pre", 3225 | "tags": [ 3226 | "test_details" 3227 | ], 3228 | "value": { 3229 | "datatype": "string", 3230 | "value_samples": { 3231 | "exhaustive": true, 3232 | "values": [ 3233 | [ 3234 | "You're on track to run a ", 3235 | 1.0 3236 | ] 3237 | ], 3238 | "total_occurrences": 5 3239 | } 3240 | } 3241 | }, 3242 | { 3243 | "attribute": "type", 3244 | "tags": [ 3245 | "gameplayevent" 3246 | ], 3247 | "value": { 3248 | "datatype": "string", 3249 | "value_samples": { 3250 | "exhaustive": true, 3251 | "values": [ 3252 | [ 3253 | "GPE_CAMERA", 3254 | 0.6 3255 | ], 3256 | [ 3257 | "GPE_CELEBRATION", 3258 | 0.4 3259 | ] 3260 | ], 3261 | "total_occurrences": 10 3262 | } 3263 | } 3264 | }, 3265 | { 3266 | "attribute": "units", 3267 | "tags": [ 3268 | "Cooldown", 3269 | "IntervalsT", 3270 | "SteadyState", 3271 | "Warmup" 3272 | ], 3273 | "value": { 3274 | "datatype": "integer", 3275 | "value_samples": { 3276 | "exhaustive": true, 3277 | "values": [ 3278 | [ 3279 | "1", 3280 | 0.980544747081712 3281 | ], 3282 | [ 3283 | "0", 3284 | 0.019455252918287938 3285 | ] 3286 | ], 3287 | "total_occurrences": 257 3288 | } 3289 | } 3290 | }, 3291 | { 3292 | "attribute": "x", 3293 | "tags": [ 3294 | "TextNotification" 3295 | ], 3296 | "value": { 3297 | "datatype": "integer", 3298 | "value_samples": { 3299 | "exhaustive": true, 3300 | "values": [ 3301 | [ 3302 | "400", 3303 | 1.0 3304 | ] 3305 | ], 3306 | "total_occurrences": 11 3307 | } 3308 | } 3309 | }, 3310 | { 3311 | "attribute": "y", 3312 | "tags": [ 3313 | "TextNotification", 3314 | "textevent" 3315 | ], 3316 | "value": { 3317 | "datatype": "integer", 3318 | "value_samples": { 3319 | "exhaustive": true, 3320 | "values": [ 3321 | [ 3322 | "270", 3323 | 0.46153846153846156 3324 | ], 3325 | [ 3326 | "240", 3327 | 0.36752136752136755 3328 | ], 3329 | [ 3330 | "290", 3331 | 0.10683760683760683 3332 | ], 3333 | [ 3334 | "500", 3335 | 0.04700854700854701 3336 | ], 3337 | [ 3338 | "300", 3339 | 0.008547008547008548 3340 | ], 3341 | [ 3342 | "220", 3343 | 0.004273504273504274 3344 | ], 3345 | [ 3346 | "260", 3347 | 0.004273504273504274 3348 | ] 3349 | ], 3350 | "total_occurrences": 234 3351 | } 3352 | } 3353 | } 3354 | ] 3355 | } 3356 | -------------------------------------------------------------------------------- /zwift_zwo_docs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h4l/zwift-workout-file-reference/5856ea772f74611aea642fa99b32e358a3c2b7c0/zwift_zwo_docs/__init__.py -------------------------------------------------------------------------------- /zwift_zwo_docs/analyse_zwo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Scan Zwift Workout (.zwo) files and aggregate their tag and attribute usage 3 | 4 | usage: analyse_zwo [options] 5 | 6 | Options: 7 | --json Output JSON 8 | """ 9 | import io 10 | from collections import Counter 11 | import json 12 | import os 13 | import re 14 | import sys 15 | from collections import defaultdict 16 | from pathlib import Path 17 | 18 | import docopt 19 | from lxml import etree 20 | 21 | 22 | def parse_zwo(path): 23 | try: 24 | etree.parse(path) 25 | except etree.XMLSyntaxError as e: 26 | if 'xmlParseEntityRef: no name' not in str(e): 27 | raise 28 | 29 | # Some ZWO files have unescaped ampersands... 30 | with open(path, 'rb') as f: 31 | content = f.read() 32 | content = content.replace(b' & ', b' & ') 33 | return etree.parse(io.BytesIO(content), base_url=path) 34 | 35 | 36 | def list_zwo_file_paths(root_dir): 37 | for dirpath, dirnames, filenames in os.walk(root_dir): 38 | dirpath = Path(dirpath) 39 | yield from (dirpath / f for f in filenames 40 | if re.match(r'.*\.(?:xml|zwo)$', f)) 41 | 42 | 43 | def list_tag_attribute_usage(tree: etree._ElementTree): 44 | def generate_tag_attr_paths(element, parent_path): 45 | children = list(element.iterchildren(tag=etree.Element)) 46 | value = (element.text 47 | if (len(children) == 0 and element.text 48 | and element.text.strip()) else None) 49 | attrs = {k: v for k, v in element.attrib.items() 50 | # Ignore namespaced attributes 51 | if not k.startswith('{')} 52 | yield parent_path, element.tag, attrs, value 53 | path = parent_path + (element.tag,) 54 | 55 | for child_el in children: 56 | yield from generate_tag_attr_paths(child_el, path) 57 | 58 | yield from generate_tag_attr_paths(tree.getroot(), ()) 59 | 60 | 61 | def analyse_datatype(value_counts): 62 | if all(re.match(r'^\d+$', val) for val in value_counts): 63 | datatype = 'integer' 64 | elif all(re.match(r'^\d+(?:\.\d*)?$', val) for val in value_counts): 65 | datatype = 'real' 66 | else: 67 | datatype = 'string' 68 | 69 | def key(vc): 70 | value, count = vc 71 | return count 72 | 73 | total_occurrences = sum(value_counts.values()) 74 | highest_freq_value = max(value_counts.values()) / total_occurrences 75 | 76 | # Sample values are intended to demonstrate the values that can be used 77 | # when the value is one of a small set of options. E.g. always 1 or 0. 78 | # It should ignore things like which will almost always be different. 79 | sample_values = [ 80 | (value, count / total_occurrences) 81 | for value, count in sorted(value_counts.items(), reverse=True, key=key) 82 | if (count / total_occurrences) >= 0.05 or highest_freq_value > 0.1][:10] 83 | 84 | return {'datatype': datatype, 'value_samples': { 85 | 'exhaustive': len(sample_values) == len(value_counts), 86 | 'values': sample_values, 87 | 'total_occurrences': total_occurrences 88 | }} 89 | 90 | 91 | def render_value_analysis(val): 92 | 93 | samples = val['value_samples']['values'] 94 | if len(samples) > 0: 95 | samples_label = ('all values' if val['value_samples']['exhaustive'] 96 | else 'most frequent values') 97 | rendered_samples = f', {samples_label}:\n' + '\n'.join( 98 | f' - {freq * 100: 6.2f}% => {value!r}' for value, freq in samples 99 | ) 100 | else: 101 | rendered_samples = '' 102 | datatype = val['datatype'] 103 | occurrences = val['value_samples']['total_occurrences'] 104 | return (f'datatype: {datatype}, occurrences: {occurrences}' 105 | f'{rendered_samples}') 106 | 107 | 108 | def aggregate_tag_attribute_usage(tag_attr_paths): 109 | tags = defaultdict(lambda: {'paths': set(), 110 | 'attributes': set(), 111 | 'value': Counter()}) 112 | # Assume attributes used across different elements have the same semantics 113 | attributes = defaultdict(lambda: { 114 | 'tags': set(), 115 | 'values': Counter() 116 | }) 117 | 118 | for path, tag, attrs, value in tag_attr_paths: 119 | tags[tag]['paths'].add(path) 120 | tags[tag]['attributes'].update(attrs.keys()) 121 | for attr, attr_val in attrs.items(): 122 | attributes[attr]['tags'].add(tag) 123 | attributes[attr]['values'][attr_val] += 1 124 | if value is not None: 125 | tags[tag]['value'][value] += 1 126 | 127 | return { 128 | 'elements': [ 129 | { 130 | 'tag': tag, 131 | 'paths': sorted(tags[tag]['paths']), 132 | 'attributes': sorted(tags[tag]['attributes']), 133 | 'value': (analyse_datatype(tags[tag]['value']) 134 | if tags[tag]['value'] else None) 135 | } 136 | for tag in sorted(tags.keys()) 137 | ], 138 | 'attributes': [ 139 | { 140 | 'attribute': attribute, 141 | 'tags': sorted(attributes[attribute]['tags']), 142 | 'value': analyse_datatype(attributes[attribute]['values']) 143 | } 144 | for attribute in sorted(attributes.keys()) 145 | ] 146 | } 147 | 148 | 149 | def main(): 150 | args = docopt.docopt(__doc__) 151 | 152 | tag_attr_usages = ( 153 | usage 154 | for path in list_zwo_file_paths(args['']) 155 | for usage in list_tag_attribute_usage(parse_zwo(str(path)))) 156 | 157 | tag_attr_usage = aggregate_tag_attribute_usage(tag_attr_usages) 158 | 159 | if args['--json']: 160 | json.dump(tag_attr_usage, sys.stdout, indent=2) 161 | print() 162 | else: 163 | elements = tag_attr_usage['elements'] 164 | attributes = tag_attr_usage['attributes'] 165 | 166 | print('## Tags ##\n') 167 | 168 | for el in elements: 169 | paths = ' '.join(['/' + '/'.join(path) for path in el['paths']]) 170 | attrs = ' '.join(el['attributes']) 171 | print(f' <{el["tag"]}>:') 172 | print(f' paths: {paths}') 173 | print(f' attrs: {attrs}') 174 | if el['value'] is not None: 175 | print(f' values: {render_value_analysis(el["value"])}') 176 | 177 | print('\n## Attributes ##\n') 178 | 179 | for attr in attributes: 180 | value_info = render_value_analysis(attr['value']) 181 | print(f' {attr["attribute"]}="..."') 182 | print(f' tags: {" ".join(attr["tags"])}') 183 | print(f' values: {value_info}') 184 | 185 | 186 | if __name__ == '__main__': 187 | main() 188 | -------------------------------------------------------------------------------- /zwift_zwo_docs/render_docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 4 | usage: render_docs [] 5 | """ 6 | import json 7 | import html 8 | 9 | import docopt 10 | import yaml 11 | 12 | template = """\ 13 | # Zwift Workout File Reference 14 | 15 | This page describes the XML elements (tags) and attributes that can be used in 16 | [Zwift workout files][zwift-support-workouts]. It's automatically generated by 17 | analysing all of the workouts included with Zwift. 18 | 19 | It's intended to help people creating workouts by directly by writing XML, or 20 | people writing software to generate Zwift workouts. 21 | 22 | Some of the elements and attributes don't have any effect, even though they 23 | appear in the built-in workouts. The [Quantize](#attribute-quantize) attribute 24 | is such an example. 25 | 26 | [zwift-support-workouts]: https://support.zwift.com/en/-sharing-importing-custom-workouts-\(.zwo-files\)-\(cycling\)-r1IlCybrQ 27 | 28 | This is a work in progress. If you know about Zwift workouts, you can help by 29 | [contributing to this guide](./README.md#contributing). 30 | 31 | ## Structure Index 32 | 33 | {index_tree} 34 | 35 | ## Elements 36 | 37 | {elements} 38 | 39 | ## Attributes 40 | 41 | {attributes}\ 42 | """ 43 | 44 | 45 | def indexed_by_path(elements): 46 | index = {} 47 | 48 | for el in elements: 49 | for path in el['paths']: 50 | key = tuple(path) 51 | path_els = index.setdefault(key, {}) 52 | path_els.setdefault(el['tag'], el) 53 | return index 54 | 55 | 56 | def own_path(el): 57 | return el[''] 58 | 59 | 60 | def index_tree(usages): 61 | by_path = indexed_by_path(usages) 62 | 63 | def _els_under(path): 64 | return [ 65 | {**el, 'children': _els_under(path + (el['tag'],))} 66 | for el in by_path.get(path, {}).values() 67 | ] 68 | 69 | root, = _els_under(()) 70 | return root 71 | 72 | 73 | def sort_key_el_tag(el): 74 | return sort_key_no_case(el['tag']) 75 | 76 | 77 | def sort_key_attribute_name(att): 78 | return sort_key_no_case(att['attribute']) 79 | 80 | 81 | def sort_key_no_case(val): 82 | if isinstance(val, tuple): 83 | return 0, None, None 84 | return 1, val.lower(), val 85 | 86 | 87 | def render_linked_tag(tag, attributes_html=None): 88 | if tag == (): 89 | # Document root elements 90 | return '[none (top-level element)]' 91 | 92 | rendered_attributes = ' ' + attributes_html if attributes_html else '' 93 | return f'<{html.escape(tag)}{rendered_attributes}>' 94 | 95 | 96 | def render_linked_tags(tags): 97 | return ' '.join(render_linked_tag(tag) 98 | for tag in sorted(tags, key=sort_key_no_case)) 99 | 100 | 101 | def render_linked_attribute(attr): 102 | return f'{html.escape(attr)}' 103 | 104 | 105 | def render_linked_attributes(attrs): 106 | return ' '.join(render_linked_attribute(a) 107 | for a in sorted(attrs, key=sort_key_no_case)) 108 | 109 | 110 | def render_index_el_tree(tree): 111 | def _render(el, depth): 112 | indent = " " * depth * 2 113 | attributes = render_linked_attributes(el['attributes']) 114 | tag = render_linked_tag(el['tag'], attributes) 115 | 116 | yield f'{indent}- {tag}' 117 | 118 | for child in sorted(el['children'], key=sort_key_el_tag): 119 | yield from _render(child, depth + 1) 120 | 121 | return '\n'.join(_render(tree, 0)) 122 | 123 | 124 | _DEFAULT_ELEMENT_DESC = '*Not yet described.*' 125 | _EMPTY_CELL = '\u2015' 126 | 127 | 128 | def get_child_elements(el, els_by_path): 129 | own_paths = [tuple(path) + (el['tag'],) for path in el['paths']] 130 | return set(child_el['tag'] 131 | for path in own_paths 132 | for child_el in els_by_path.get(path, {}).values()) 133 | 134 | 135 | def get_parent_elements(el): 136 | return set(path[-1] if path else () for path in el['paths']) 137 | 138 | 139 | def render_value_freq(value, freq): 140 | return f'{freq * 100:.2f}%{html.escape(value)}' 141 | 142 | 143 | def render_value_description(value_desc): 144 | if value_desc is None: 145 | return (f'Inner value' 146 | f'{html.escape(_EMPTY_CELL)}') 147 | 148 | is_exhaustive = value_desc['value_samples']['exhaustive'] 149 | total_occurrences = value_desc['value_samples']['total_occurrences'] 150 | values = value_desc['value_samples']['values'] 151 | 152 | rendered_values = '\n'.join( 153 | (f'' 154 | f' {"Values seen" if is_exhaustive else "Top values seen"}' 155 | f' {render_value_freq(val, freq)}' 156 | f'' 157 | if i == 0 else 158 | f'{render_value_freq(val, freq)}') 159 | for i, (val, freq) in enumerate(values)) 160 | 161 | return f'''\ 162 | 163 | Inner value 164 | Data type 165 | {html.escape(value_desc['datatype'])} 166 | 167 | 168 | Times seen 169 | {html.escape(str(total_occurrences))} 170 | 171 | {rendered_values}\ 172 | ''' 173 | 174 | 175 | def combine_text_sections(*sections): 176 | def combine(secs): 177 | for s in secs: 178 | if s is None: 179 | continue 180 | if isinstance(s, str): 181 | yield s 182 | else: 183 | yield from combine(s) 184 | return '\n\n'.join(combine(sections)) 185 | 186 | 187 | def indent_lines(text, *, indent): 188 | return '\n'.join(f'{indent}{l}' for l in text.split('\n')) 189 | 190 | 191 | def render_code_snippet(example): 192 | if 'interval_code' in example: 193 | code = f'''\ 194 | 195 | 196 | 197 | {indent_lines(example['interval_code'].strip(), indent=' ')} 198 | 199 | 200 | ''' 201 | else: 202 | code = example['code'] 203 | 204 | return f'''\ 205 | ```xml 206 | {code}\ 207 | ```` 208 | ''' 209 | 210 | 211 | def render_example(example, default_title='Example'): 212 | if isinstance(example, str): 213 | example = {'code': example} 214 | 215 | title = example.get('title') or default_title 216 | description = example.get('description', '').strip() or None 217 | code = render_code_snippet(example) 218 | 219 | return combine_text_sections( 220 | f'##### {title}', 221 | description, 222 | code 223 | ) 224 | 225 | 226 | def render_examples(examples): 227 | if not examples: 228 | return 229 | 230 | if not isinstance(examples, list): 231 | examples = [examples] 232 | 233 | return combine_text_sections( 234 | '#### Usage examples', 235 | [render_example(e, default_title=f'Example {i}') 236 | for i, e in enumerate(examples, 1)] 237 | ) 238 | 239 | 240 | def trim_leading_line_ws(text): 241 | return '\n'.join(line.lstrip() for line in text.split('\n')) 242 | 243 | 244 | def render_element(el, els_by_path, desc_data): 245 | desc_data = desc_data or {} 246 | description = desc_data.get('description', _DEFAULT_ELEMENT_DESC) 247 | examples = render_examples(desc_data.get('examples')) or '' 248 | inner_value = render_value_description(el['value']) 249 | inner_elements = render_linked_tags(get_child_elements(el, els_by_path)) 250 | contained_by = render_linked_tags(get_parent_elements(el)) 251 | attributes = render_linked_attributes(el['attributes']) 252 | 253 | table = f'''\ 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | {inner_value} 268 |
Attributes{attributes or _EMPTY_CELL}
Inner elements{inner_elements or _EMPTY_CELL}
Contained by{contained_by or _EMPTY_CELL}
\ 269 | ''' 270 | 271 | return f'''\ 272 | ### Element **`<{el['tag']}>`** 273 | 274 | {description} 275 | 276 | {trim_leading_line_ws(table)} 277 | 278 | {examples} 279 | '''.rstrip() 280 | 281 | 282 | def render_attribute(att, desc_data): 283 | desc_data = desc_data or {} 284 | description = desc_data.get('description', _DEFAULT_ELEMENT_DESC) 285 | inner_value = render_value_description(att['value']) 286 | host_tags = render_linked_tags(att['tags']) 287 | 288 | table = f'''\ 289 | 290 | 291 | 292 | 293 | 294 | {inner_value} 295 |
Occurs on{host_tags or _EMPTY_CELL}
\ 296 | ''' 297 | # ### Attribute **`{att['attribute']}`**`="..."` 298 | # ### Attribute {att['attribute']}="…" 299 | return f'''\ 300 | 301 | ### Attribute `{att['attribute']}="…"` 302 | 303 | {description} 304 | 305 | {trim_leading_line_ws(table)}\ 306 | ''' 307 | 308 | 309 | def render_elements(elements, desc_data=None): 310 | desc_data = desc_data or {} 311 | els_by_path = indexed_by_path(elements) 312 | 313 | rendered_elements = (render_element(el, els_by_path, 314 | desc_data.get(el['tag'])) 315 | for el in sorted(elements, key=sort_key_el_tag)) 316 | 317 | return '\n\n'.join(rendered_elements) 318 | 319 | 320 | def render_attributes(attributes, desc_data=None): 321 | desc_data = desc_data or {} 322 | 323 | rendered_attributes = ( 324 | render_attribute(att, desc_data.get(att['attribute'])) 325 | for att in sorted(attributes, key=sort_key_attribute_name)) 326 | 327 | return '\n\n'.join(rendered_attributes) 328 | 329 | 330 | def main(): 331 | args = docopt.docopt(__doc__) 332 | 333 | with open(args[''], encoding='utf-8') as f: 334 | usage_stats = json.load(f) 335 | 336 | if args['']: 337 | with open(args[''], encoding='utf-8') as f: 338 | desc_data = yaml.safe_load(f) 339 | if not isinstance(desc_data, dict): 340 | raise TypeError(' did not contain a hash at ' 341 | 'the top level') 342 | else: 343 | desc_data = {} 344 | 345 | elements = usage_stats['elements'] 346 | attributes = usage_stats['attributes'] 347 | print(template.format( 348 | index_tree=render_index_el_tree(index_tree(elements)), 349 | elements=render_elements(elements, desc_data.get('elements')), 350 | attributes=render_attributes(attributes, desc_data.get('attributes')) 351 | )) 352 | 353 | 354 | if __name__ == '__main__': 355 | main() 356 | --------------------------------------------------------------------------------