commit 491efef8ebaab1c9af7b6d62036c2798920996bd Author: Siphonight Date: Tue Sep 8 14:34:58 2026 +0200 v1.4.10 — portage initial de l'app Android v1.4.10 Portage navigateur complet de l'app Android HormoneTrack v1.4.10 : - moteur PK fidèle (Estrannaise tables ODS, Transfem Science V3C, WHSAH fit Mona, Bateman) — paramètres identiques, invariants Android préservés (fixes #19-#23, #35, #52-#62), 121 tests Node épinglés - calibration par période d'ester et par modèle affiché (#60/#61) - UI 6 écrans + éditeur, CurveChart Canvas (zoom/pan fractionnaire, prévision, pics/creux, fuseau configurable), rappels web (Notification API), seuils d'alerte, i18n FR/EN - sauvegarde JSON v2 compatible Android bidirectionnelle (rétrocompat v1) - 100 % local : localStorage, aucun serveur applicatif, aucune télémétrie - processus : scripts/check.sh (syntaxe, i18n, tests, E2E navigateur, smoke HTTP), docs séparées (README + DEVELOPPEMENT + CHANGELOG) - dépôt GIT SÉPARÉ de l'Android : historique 100 % propre, versions alignées sur l'Android porté, changelogs indépendants Non porté (impossible dans un navigateur, documenté §12) : agenda récurrent, notifications onglet fermé, montre. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a90757 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# ── HormoneTrack Web — dépôt séparé (cf docs/DEVELOPPEMENT.md §3) ── +# Dépendances npm : uniquement les devDependencies des tests E2E — +# l'app elle-même n'a AUCUNE dépendance (site statique déployable tel quel). +node_modules/ +package-lock.json + +# Données de test réelles : JAMAIS dans le dépôt (miroir de la règle +# local-test-data du dépôt Android — données de santé personnelles, +# les tests la cherchent dans le dépôt Android voisin et skip si absente). +local-test-data/ + +# OS +.DS_Store +Thumbs.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e5c695 --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +# HormoneTrack Web + +> **La version navigateur d'HormoneTrack** — suivi de thérapie hormonale (THS) +> avec courbes estimées heure par heure, 100 % locale : **l'app tourne +> entièrement dans ton navigateur et tes données ne quittent jamais ta +> machine** (localStorage). Aucun compte, aucun serveur applicatif, aucune +> télémétrie. +> +> C'est le portage fidèle de l'**app Android** — les sauvegardes JSON sont +> **interchangeables dans les deux sens**. L'app Android vit dans son propre +> dépôt : [gitea.cloudyfy.fr/Siphonight/HormoneTrack](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack) +> (README, docs et releases APK). +> La doc de développement : [docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md). + +> **⚠️ Avertissement médical** : les courbes sont des **estimations +> pharmacocinétiques** à titre informatif — ce ne sont pas des mesures. +> Fie-toi toujours à tes analyses de sang et aux consignes de ton +> endocrinologue. + +- **Statut** : web v1.4.10 — portage de l'Android v1.4.10 · **121 tests verts** · + E2E navigateur ✅ · lint/i18n ✅ +- **Journal des versions web** : [docs/CHANGELOG.md](docs/CHANGELOG.md) +- **Versionnage** : les versions web sont **alignées sur l'Android porté** + (web v1.4.10 = toutes les fonctionnalités de l'Android v1.4.10, sauf les + impossibilités structurelles du navigateur) — cf + [docs/DEVELOPPEMENT.md §3](docs/DEVELOPPEMENT.md). + +--- + +## Démarrage rapide + +### Avec le serveur de développement intégré + +```bash +cd web +python3 scripts/serve.py # → http://127.0.0.1:8970/ +``` + +Ouvre **http://127.0.0.1:8970/** dans ton navigateur. C'est tout. + +- **Mode démo** (données de test, hook documenté) : + — charge 12 injections + 6 prises de sang + fictives si le stockage est vide, avec une bannière explicite. +- ⚠️ L'app doit être servie en HTTP : l'ouverture directe du `index.html` + en `file://` est bloquée par les navigateurs (modules ES). + +### Avec n'importe quel serveur statique + +Le dépôt est un site statique autonome — déploie-le tel quel +(nginx, caddy, Pages Gitea/GitLab, un NAS…) : + +```bash +# nginx (extrait) +server { + root /var/www/hormonetrack; # = le contenu de ce dépôt + index index.html; +} +``` + +Aucun build, aucune variable d'environnement, aucun composant serveur : les +données de chaque personne vivent **dans SON navigateur**, jamais sur la +machine qui sert les fichiers. + +## Fonctionnalités (à l'image de l'app Android) + +- **Courbes estimées heure par heure** : E2 (pg/mL) et T (ng/mL), vues + 24 h / 7 j / 30 j, **zoom** (pinch, molette, boutons − / +, 6 h → 300 j, + échantillonnage adaptatif), **panoramique** (glisser droite = passé, + gauche = futur avec la prévision), **pics & creux** avec valeurs estimées. +- **Trois modèles PK superposables** : **Estrannaise** (tables du `.ods`), + **Transfem Science** (méta-analyse V3C, 7 esters), **WHSAH** (fit Mona, + 6 esters) — toggles indépendants, calibrés séparément. +- **Modèle Bateman** paramétrable pour gel, patch et voie orale. +- **Simulation prévisionnelle** (Posologie) : projection des doses à venir, + extension de fenêtre sans saut, horizon jusqu'à 1 an, marqueurs de doses. +- **Log des doses** avec override d'ester par injection, édition, Δ jours + entre doses, temps sous THS. +- **Analyses de sang** E2 + T en une entrée, unités T multiples + (ng/mL, ng/dL, ng/L, nmol/L). +- **Calibration** par période d'ester et **par modèle affiché** + (auto-calibration optionnelle, désactivée par défaut + bouton manuel). +- **Seuils d'alerte** configurables (E2/T haut/bas) avec notification + navigateur et anti-spam. +- **Rappels** via les notifications du navigateur (actions « Loguer + maintenant » / « Reporter 1 h ») — tant que la page est ouverte, cf + [limites](#limites-vs-lapp-android). +- **Sauvegarde/Restauration JSON** compatible Android (import en mode + écrasement, réglages transportés). +- **Logs de diagnostic** exportables (Paramètres) pour le debug à distance. +- **FR + EN** (langue par app, indépendante du système), dialog + « Nouveautés » après mise à jour. + +## Vie privée + +- **100 % local** : les données (traitements, doses, analyses, réglages) + vivent dans le **localStorage de ton navigateur**, sur TA machine. Le + serveur qui sert l'app ne voit rien, ne stocke rien. +- Les seules requêtes réseau sont des **lectures** de fichiers statiques + (l'asset des profils PK, le changelog) — aucune donnée personnelle + n'est jamais envoyée. +- Sauvegarde = un fichier JSON que tu stockes où tu veux. +- `?demo=1` charge des données FICTIVES clairement banniérées, jamais + automatiquement. + +## Compatibilité avec l'app Android + +| Flux | Support | +|---|---| +| Export Android → import web | ✅ (backups v1 et v2 — testés sur de vrais exports) | +| Export web → import Android | ✅ (même schéma BackupData v2, champs Gson identiques) | +| Modèles PK / courbes | ✅ mêmes maths, mêmes paramètres, mêmes pins de tests | +| Calibrations (facteurs, k) | ✅ recalculées identiquement depuis les labs | + +Le schéma de backup est verrouillé par des tests des deux côtés +(`tests/backup.test.js` ↔ `BackupGsonTest.kt` Android) — toute divergence +métadonnée↔code ferait échouer la CI. + +## Limites vs l'app Android + +| Fonctionnalité | Android | Web | +|---|---|---| +| Rappels app fermée | ✅ (AlarmManager) | ❌ page ouverte uniquement (pas de scheduler système) | +| Événements d'agenda récurrents | ✅ | ❌ impossible dans un navigateur (pas de CalendarProvider) | +| Montre (Gadgetbridge) | ✅ notifications miroir | ❌ | +| Verrou biométrique / widget | roadmap | ❌ | + +Ces limites sont structurelles (plateforme navigateur), pas des choix de +design — elles sont listées dans le dialog « Nouveautés » et la doc de dev. + +## FAQ + +**Mes données sont-elles visibles par le serveur qui héberge l'app ?** +Non. Le serveur ne fait que distribuer des fichiers statiques (comme des +images). Tes données vivent dans ton navigateur et n'en sortent jamais. + +**Je change de navigateur / d'ordinateur, je fais quoi ?** +Paramètres → « Exporter JSON » sur la première machine, « Importer JSON » +sur la seconde. Le backup transporte tout (données + réglages). + +**Puis-je utiliser Android et web en parallèle ?** +Oui — exporte/importe entre les deux. Attention : l'import est un +écrasement (comme sur Android), pas une fusion. + +**Pourquoi l'app me demande-t-elle la permission « Notifications » ?** +Pour les rappels de dose. Refuser n'empêche rien d'autre (la bannière +in-app s'affiche quand même à l'heure du rappel, page ouverte). + +## Doc de développement + +Architecture, portage fichier par fichier, format du backup, processus de +test (121 tests Node + E2E navigateur), processus de push et pièges connus : +**[docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md)**. + +## Licence + +**GPL-3.0** — voir [LICENSE](LICENSE), cohérente avec l'app Android +et l'écosystème Gadgetbridge. Les modèles PK appartiennent à leurs autrices +respectives ([Estrannaise](https://estrannaise.github.io/), +[Transfem Science](https://transfemscience.org), +[WHSAH Collective via Mona](https://github.com/mona-hrt/mona)). diff --git a/assets/pk_profiles.json b/assets/pk_profiles.json new file mode 100644 index 0000000..92f2622 --- /dev/null +++ b/assets/pk_profiles.json @@ -0,0 +1,48048 @@ +{ + "params": { + "EV_ese_D": 478.0, + "EU_ese_D": 471.5, + "EEn_ese_D": 191.4, + "EV_tfs_D": 519.211912, + "EU_tfs_D": 13.18986748, + "EEn_tfs_D": 66.7748362, + "EV_ese_k1": 0.236, + "EU_ese_k1": 0.01729, + "EEn_ese_k1": 0.119, + "EV_tfs_k1": 2.38229125, + "EU_tfs_k1": 0.29634323, + "EEn_tfs_k1": 0.42412968, + "EV_ese_k2": 4.85, + "EU_ese_k2": 6.528, + "EEn_ese_k2": 0.601, + "EV_tfs_k2": 0.23345814, + "EU_tfs_k2": 4799337.57, + "EEn_tfs_k2": 0.4345298, + "EV_ese_k3": 1.24, + "EU_ese_k3": 2.285, + "EEn_ese_k3": 0.402, + "EV_tfs_k3": 1.37642769, + "EU_tfs_k3": 0.03141554, + "EEn_tfs_k3": 0.15291485 + }, + "profiles": { + "EV_ese": [ + 0.0, + 0.44, + 1.6, + 3.31, + 5.42, + 7.81, + 10.39, + 13.09, + 15.85, + 18.61, + 21.35, + 24.04, + 26.66, + 29.19, + 31.62, + 33.95, + 36.16, + 38.27, + 40.25, + 42.12, + 43.88, + 45.53, + 47.08, + 48.51, + 49.85, + 51.09, + 52.24, + 53.3, + 54.27, + 55.16, + 55.97, + 56.71, + 57.38, + 57.98, + 58.52, + 59.0, + 59.42, + 59.79, + 60.1, + 60.37, + 60.6, + 60.78, + 60.92, + 61.02, + 61.09, + 61.12, + 61.12, + 61.09, + 61.03, + 60.94, + 60.84, + 60.7, + 60.55, + 60.37, + 60.18, + 59.96, + 59.73, + 59.49, + 59.23, + 58.96, + 58.67, + 58.37, + 58.06, + 57.74, + 57.41, + 57.08, + 56.73, + 56.38, + 56.02, + 55.65, + 55.28, + 54.9, + 54.52, + 54.14, + 53.75, + 53.36, + 52.96, + 52.57, + 52.17, + 51.76, + 51.36, + 50.96, + 50.55, + 50.14, + 49.74, + 49.33, + 48.92, + 48.52, + 48.11, + 47.71, + 47.3, + 46.9, + 46.49, + 46.09, + 45.69, + 45.29, + 44.89, + 44.5, + 44.1, + 43.71, + 43.32, + 42.93, + 42.54, + 42.16, + 41.77, + 41.39, + 41.02, + 40.64, + 40.27, + 39.9, + 39.53, + 39.16, + 38.8, + 38.44, + 38.08, + 37.72, + 37.37, + 37.02, + 36.67, + 36.33, + 35.98, + 35.65, + 35.31, + 34.97, + 34.64, + 34.31, + 33.99, + 33.66, + 33.34, + 33.03, + 32.71, + 32.4, + 32.09, + 31.78, + 31.48, + 31.17, + 30.87, + 30.58, + 30.28, + 29.99, + 29.7, + 29.42, + 29.13, + 28.85, + 28.57, + 28.3, + 28.02, + 27.75, + 27.48, + 27.22, + 26.96, + 26.69, + 26.44, + 26.18, + 25.92, + 25.67, + 25.42, + 25.18, + 24.93, + 24.69, + 24.45, + 24.21, + 23.98, + 23.74, + 23.51, + 23.28, + 23.06, + 22.83, + 22.61, + 22.39, + 22.17, + 21.96, + 21.74, + 21.53, + 21.32, + 21.11, + 20.91, + 20.7, + 20.5, + 20.3, + 20.1, + 19.91, + 19.71, + 19.52, + 19.33, + 19.14, + 18.95, + 18.77, + 18.59, + 18.4, + 18.22, + 18.05, + 17.87, + 17.7, + 17.52, + 17.35, + 17.18, + 17.01, + 16.85, + 16.68, + 16.52, + 16.36, + 16.2, + 16.04, + 15.88, + 15.73, + 15.58, + 15.42, + 15.27, + 15.12, + 14.98, + 14.83, + 14.68, + 14.54, + 14.4, + 14.26, + 14.12, + 13.98, + 13.84, + 13.71, + 13.57, + 13.44, + 13.31, + 13.18, + 13.05, + 12.92, + 12.8, + 12.67, + 12.55, + 12.42, + 12.3, + 12.18, + 12.06, + 11.95, + 11.83, + 11.71, + 11.6, + 11.48, + 11.37, + 11.26, + 11.15, + 11.04, + 10.93, + 10.83, + 10.72, + 10.62, + 10.51, + 10.41, + 10.31, + 10.21, + 10.11, + 10.01, + 9.91, + 9.81, + 9.72, + 9.62, + 9.53, + 9.43, + 9.34, + 9.25, + 9.16, + 9.07, + 8.98, + 8.89, + 8.81, + 8.72, + 8.64, + 8.55, + 8.47, + 8.38, + 8.3, + 8.22, + 8.14, + 8.06, + 7.98, + 7.9, + 7.83, + 7.75, + 7.67, + 7.6, + 7.53, + 7.45, + 7.38, + 7.31, + 7.23, + 7.16, + 7.09, + 7.02, + 6.96, + 6.89, + 6.82, + 6.75, + 6.69, + 6.62, + 6.56, + 6.49, + 6.43, + 6.37, + 6.3, + 6.24, + 6.18, + 6.12, + 6.06, + 6.0, + 5.94, + 5.89, + 5.83, + 5.77, + 5.71, + 5.66, + 5.6, + 5.55, + 5.49, + 5.44, + 5.39, + 5.33, + 5.28, + 5.23, + 5.18, + 5.13, + 5.08, + 5.03, + 4.98, + 4.93, + 4.88, + 4.83, + 4.79, + 4.74, + 4.69, + 4.65, + 4.6, + 4.56, + 4.51, + 4.47, + 4.42, + 4.38, + 4.34, + 4.3, + 4.25, + 4.21, + 4.17, + 4.13, + 4.09, + 4.05, + 4.01, + 3.97, + 3.93, + 3.89, + 3.86, + 3.82, + 3.78, + 3.74, + 3.71, + 3.67, + 3.63, + 3.6, + 3.56, + 3.53, + 3.49, + 3.46, + 3.43, + 3.39, + 3.36, + 3.33, + 3.29, + 3.26, + 3.23, + 3.2, + 3.17, + 3.14, + 3.11, + 3.08, + 3.05, + 3.02, + 2.99, + 2.96, + 2.93, + 2.9, + 2.87, + 2.84, + 2.81, + 2.79, + 2.76, + 2.73, + 2.71, + 2.68, + 2.65, + 2.63, + 2.6, + 2.58, + 2.55, + 2.53, + 2.5, + 2.48, + 2.45, + 2.43, + 2.41, + 2.38, + 2.36, + 2.34, + 2.31, + 2.29, + 2.27, + 2.25, + 2.22, + 2.2, + 2.18, + 2.16, + 2.14, + 2.12, + 2.1, + 2.08, + 2.05, + 2.03, + 2.01, + 2.0, + 1.98, + 1.96, + 1.94, + 1.92, + 1.9, + 1.88, + 1.86, + 1.84, + 1.83, + 1.81, + 1.79, + 1.77, + 1.76, + 1.74, + 1.72, + 1.7, + 1.69, + 1.67, + 1.66, + 1.64, + 1.62, + 1.61, + 1.59, + 1.58, + 1.56, + 1.55, + 1.53, + 1.51, + 1.5, + 1.49, + 1.47, + 1.46, + 1.44, + 1.43, + 1.41, + 1.4, + 1.39, + 1.37, + 1.36, + 1.35, + 1.33, + 1.32, + 1.31, + 1.29, + 1.28, + 1.27, + 1.26, + 1.24, + 1.23, + 1.22, + 1.21, + 1.2, + 1.18, + 1.17, + 1.16, + 1.15, + 1.14, + 1.13, + 1.12, + 1.11, + 1.1, + 1.08, + 1.07, + 1.06, + 1.05, + 1.04, + 1.03, + 1.02, + 1.01, + 1.0, + 0.99, + 0.98, + 0.97, + 0.96, + 0.95, + 0.94, + 0.94, + 0.93, + 0.92, + 0.91, + 0.9, + 0.89, + 0.88, + 0.87, + 0.86, + 0.86, + 0.85, + 0.84, + 0.83, + 0.82, + 0.82, + 0.81, + 0.8, + 0.79, + 0.78, + 0.78, + 0.77, + 0.76, + 0.75, + 0.75, + 0.74, + 0.73, + 0.72, + 0.72, + 0.71, + 0.7, + 0.7, + 0.69, + 0.68, + 0.68, + 0.67, + 0.66, + 0.66, + 0.65, + 0.64, + 0.64, + 0.63, + 0.63, + 0.62, + 0.61, + 0.61, + 0.6, + 0.6, + 0.59, + 0.58, + 0.58, + 0.57, + 0.57, + 0.56, + 0.56, + 0.55, + 0.54, + 0.54, + 0.53, + 0.53, + 0.52, + 0.52, + 0.51, + 0.51, + 0.5, + 0.5, + 0.49, + 0.49, + 0.48, + 0.48, + 0.47, + 0.47, + 0.47, + 0.46, + 0.46, + 0.45, + 0.45, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.41, + 0.41, + 0.41, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "EU_ese": [ + 0.0, + 0.04, + 0.15, + 0.29, + 0.46, + 0.65, + 0.84, + 1.03, + 1.22, + 1.4, + 1.57, + 1.73, + 1.88, + 2.02, + 2.15, + 2.27, + 2.38, + 2.48, + 2.58, + 2.66, + 2.74, + 2.81, + 2.87, + 2.93, + 2.98, + 3.03, + 3.07, + 3.11, + 3.15, + 3.18, + 3.21, + 3.24, + 3.26, + 3.28, + 3.3, + 3.32, + 3.33, + 3.35, + 3.36, + 3.37, + 3.38, + 3.39, + 3.4, + 3.4, + 3.41, + 3.41, + 3.42, + 3.42, + 3.42, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.44, + 3.44, + 3.44, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.43, + 3.42, + 3.42, + 3.42, + 3.42, + 3.42, + 3.41, + 3.41, + 3.41, + 3.41, + 3.41, + 3.4, + 3.4, + 3.4, + 3.4, + 3.4, + 3.39, + 3.39, + 3.39, + 3.39, + 3.38, + 3.38, + 3.38, + 3.38, + 3.37, + 3.37, + 3.37, + 3.37, + 3.37, + 3.36, + 3.36, + 3.36, + 3.36, + 3.35, + 3.35, + 3.35, + 3.35, + 3.34, + 3.34, + 3.34, + 3.34, + 3.33, + 3.33, + 3.33, + 3.33, + 3.32, + 3.32, + 3.32, + 3.32, + 3.32, + 3.31, + 3.31, + 3.31, + 3.31, + 3.3, + 3.3, + 3.3, + 3.3, + 3.29, + 3.29, + 3.29, + 3.29, + 3.28, + 3.28, + 3.28, + 3.28, + 3.28, + 3.27, + 3.27, + 3.27, + 3.27, + 3.26, + 3.26, + 3.26, + 3.26, + 3.25, + 3.25, + 3.25, + 3.25, + 3.24, + 3.24, + 3.24, + 3.24, + 3.24, + 3.23, + 3.23, + 3.23, + 3.23, + 3.22, + 3.22, + 3.22, + 3.22, + 3.21, + 3.21, + 3.21, + 3.21, + 3.21, + 3.2, + 3.2, + 3.2, + 3.2, + 3.19, + 3.19, + 3.19, + 3.19, + 3.18, + 3.18, + 3.18, + 3.18, + 3.18, + 3.17, + 3.17, + 3.17, + 3.17, + 3.16, + 3.16, + 3.16, + 3.16, + 3.15, + 3.15, + 3.15, + 3.15, + 3.15, + 3.14, + 3.14, + 3.14, + 3.14, + 3.13, + 3.13, + 3.13, + 3.13, + 3.13, + 3.12, + 3.12, + 3.12, + 3.12, + 3.11, + 3.11, + 3.11, + 3.11, + 3.11, + 3.1, + 3.1, + 3.1, + 3.1, + 3.09, + 3.09, + 3.09, + 3.09, + 3.09, + 3.08, + 3.08, + 3.08, + 3.08, + 3.07, + 3.07, + 3.07, + 3.07, + 3.07, + 3.06, + 3.06, + 3.06, + 3.06, + 3.05, + 3.05, + 3.05, + 3.05, + 3.05, + 3.04, + 3.04, + 3.04, + 3.04, + 3.03, + 3.03, + 3.03, + 3.03, + 3.03, + 3.02, + 3.02, + 3.02, + 3.02, + 3.01, + 3.01, + 3.01, + 3.01, + 3.01, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 2.99, + 2.99, + 2.99, + 2.99, + 2.98, + 2.98, + 2.98, + 2.98, + 2.98, + 2.97, + 2.97, + 2.97, + 2.97, + 2.97, + 2.96, + 2.96, + 2.96, + 2.96, + 2.95, + 2.95, + 2.95, + 2.95, + 2.95, + 2.94, + 2.94, + 2.94, + 2.94, + 2.94, + 2.93, + 2.93, + 2.93, + 2.93, + 2.92, + 2.92, + 2.92, + 2.92, + 2.92, + 2.91, + 2.91, + 2.91, + 2.91, + 2.91, + 2.9, + 2.9, + 2.9, + 2.9, + 2.9, + 2.89, + 2.89, + 2.89, + 2.89, + 2.89, + 2.88, + 2.88, + 2.88, + 2.88, + 2.87, + 2.87, + 2.87, + 2.87, + 2.87, + 2.86, + 2.86, + 2.86, + 2.86, + 2.86, + 2.85, + 2.85, + 2.85, + 2.85, + 2.85, + 2.84, + 2.84, + 2.84, + 2.84, + 2.84, + 2.83, + 2.83, + 2.83, + 2.83, + 2.83, + 2.82, + 2.82, + 2.82, + 2.82, + 2.82, + 2.81, + 2.81, + 2.81, + 2.81, + 2.81, + 2.8, + 2.8, + 2.8, + 2.8, + 2.8, + 2.79, + 2.79, + 2.79, + 2.79, + 2.79, + 2.78, + 2.78, + 2.78, + 2.78, + 2.78, + 2.77, + 2.77, + 2.77, + 2.77, + 2.77, + 2.76, + 2.76, + 2.76, + 2.76, + 2.76, + 2.75, + 2.75, + 2.75, + 2.75, + 2.75, + 2.74, + 2.74, + 2.74, + 2.74, + 2.74, + 2.73, + 2.73, + 2.73, + 2.73, + 2.73, + 2.72, + 2.72, + 2.72, + 2.72, + 2.72, + 2.71, + 2.71, + 2.71, + 2.71, + 2.71, + 2.7, + 2.7, + 2.7, + 2.7, + 2.7, + 2.69, + 2.69, + 2.69, + 2.69, + 2.69, + 2.68, + 2.68, + 2.68, + 2.68, + 2.68, + 2.67, + 2.67, + 2.67, + 2.67, + 2.67, + 2.67, + 2.66, + 2.66, + 2.66, + 2.66, + 2.66, + 2.65, + 2.65, + 2.65, + 2.65, + 2.65, + 2.64, + 2.64, + 2.64, + 2.64, + 2.64, + 2.63, + 2.63, + 2.63, + 2.63, + 2.63, + 2.63, + 2.62, + 2.62, + 2.62, + 2.62, + 2.62, + 2.61, + 2.61, + 2.61, + 2.61, + 2.61, + 2.6, + 2.6, + 2.6, + 2.6, + 2.6, + 2.6, + 2.59, + 2.59, + 2.59, + 2.59, + 2.59, + 2.58, + 2.58, + 2.58, + 2.58, + 2.58, + 2.57, + 2.57, + 2.57, + 2.57, + 2.57, + 2.57, + 2.56, + 2.56, + 2.56, + 2.56, + 2.56, + 2.55, + 2.55, + 2.55, + 2.55, + 2.55, + 2.55, + 2.54, + 2.54, + 2.54, + 2.54, + 2.54, + 2.53, + 2.53, + 2.53, + 2.53, + 2.53, + 2.53, + 2.52, + 2.52, + 2.52, + 2.52, + 2.52, + 2.51, + 2.51, + 2.51, + 2.51, + 2.51, + 2.51, + 2.5, + 2.5, + 2.5, + 2.5, + 2.5, + 2.49, + 2.49, + 2.49, + 2.49, + 2.49, + 2.49, + 2.48, + 2.48, + 2.48, + 2.48, + 2.48, + 2.47, + 2.47, + 2.47, + 2.47, + 2.47, + 2.47, + 2.46, + 2.46, + 2.46, + 2.46, + 2.46, + 2.46, + 2.45, + 2.45, + 2.45, + 2.45, + 2.45, + 2.44, + 2.44, + 2.44, + 2.44, + 2.44, + 2.44, + 2.43, + 2.43, + 2.43, + 2.43, + 2.43, + 2.43, + 2.42, + 2.42, + 2.42, + 2.42, + 2.42, + 2.41, + 2.41, + 2.41, + 2.41, + 2.41, + 2.41, + 2.4, + 2.4, + 2.4, + 2.4, + 2.4, + 2.4, + 2.39, + 2.39, + 2.39, + 2.39, + 2.39, + 2.39, + 2.38, + 2.38, + 2.38, + 2.38, + 2.38, + 2.38, + 2.37, + 2.37, + 2.37, + 2.37, + 2.37, + 2.36, + 2.36, + 2.36, + 2.36, + 2.36, + 2.36, + 2.35, + 2.35, + 2.35, + 2.35, + 2.35, + 2.35, + 2.34, + 2.34, + 2.34, + 2.34, + 2.34, + 2.34, + 2.33, + 2.33, + 2.33, + 2.33, + 2.33, + 2.33, + 2.32, + 2.32, + 2.32, + 2.32, + 2.32, + 2.32, + 2.31, + 2.31, + 2.31, + 2.31, + 2.31, + 2.31, + 2.3, + 2.3, + 2.3, + 2.3, + 2.3, + 2.3, + 2.29, + 2.29, + 2.29, + 2.29, + 2.29, + 2.29, + 2.28, + 2.28, + 2.28, + 2.28, + 2.28, + 2.28, + 2.27, + 2.27, + 2.27, + 2.27, + 2.27, + 2.27, + 2.26, + 2.26, + 2.26, + 2.26, + 2.26, + 2.26, + 2.26, + 2.25, + 2.25, + 2.25, + 2.25, + 2.25, + 2.25, + 2.24, + 2.24, + 2.24, + 2.24, + 2.24, + 2.24, + 2.23, + 2.23, + 2.23, + 2.23, + 2.23, + 2.23, + 2.22, + 2.22, + 2.22, + 2.22, + 2.22, + 2.22, + 2.21, + 2.21, + 2.21, + 2.21, + 2.21, + 2.21, + 2.21, + 2.2, + 2.2, + 2.2, + 2.2, + 2.2, + 2.2, + 2.19, + 2.19, + 2.19, + 2.19, + 2.19, + 2.19, + 2.18, + 2.18, + 2.18, + 2.18, + 2.18, + 2.18, + 2.18, + 2.17, + 2.17, + 2.17, + 2.17, + 2.17, + 2.17, + 2.16, + 2.16, + 2.16, + 2.16, + 2.16, + 2.16, + 2.16, + 2.15, + 2.15, + 2.15, + 2.15, + 2.15, + 2.15, + 2.14, + 2.14, + 2.14, + 2.14, + 2.14, + 2.14, + 2.13, + 2.13, + 2.13, + 2.13, + 2.13, + 2.13, + 2.13, + 2.12, + 2.12, + 2.12, + 2.12, + 2.12, + 2.12, + 2.12, + 2.11, + 2.11, + 2.11, + 2.11, + 2.11, + 2.11, + 2.1, + 2.1, + 2.1, + 2.1, + 2.1, + 2.1, + 2.1, + 2.09, + 2.09, + 2.09, + 2.09, + 2.09, + 2.09, + 2.08, + 2.08, + 2.08, + 2.08, + 2.08, + 2.08, + 2.08, + 2.07, + 2.07, + 2.07, + 2.07, + 2.07, + 2.07, + 2.07, + 2.06, + 2.06, + 2.06, + 2.06, + 2.06, + 2.06, + 2.05, + 2.05, + 2.05, + 2.05, + 2.05, + 2.05, + 2.05, + 2.04, + 2.04, + 2.04, + 2.04, + 2.04, + 2.04, + 2.04, + 2.03, + 2.03, + 2.03, + 2.03, + 2.03, + 2.03, + 2.03, + 2.02, + 2.02, + 2.02, + 2.02, + 2.02, + 2.02, + 2.02, + 2.01, + 2.01, + 2.01, + 2.01, + 2.01, + 2.01, + 2.01, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 1.99, + 1.99, + 1.99, + 1.99, + 1.99, + 1.99, + 1.99, + 1.98, + 1.98, + 1.98, + 1.98, + 1.98, + 1.98, + 1.98, + 1.97, + 1.97, + 1.97, + 1.97, + 1.97, + 1.97, + 1.97, + 1.96, + 1.96, + 1.96, + 1.96, + 1.96, + 1.96, + 1.96, + 1.95, + 1.95, + 1.95, + 1.95, + 1.95, + 1.95, + 1.95, + 1.94, + 1.94, + 1.94, + 1.94, + 1.94, + 1.94, + 1.94, + 1.93, + 1.93, + 1.93, + 1.93, + 1.93, + 1.93, + 1.93, + 1.92, + 1.92, + 1.92, + 1.92, + 1.92, + 1.92, + 1.92, + 1.91, + 1.91, + 1.91, + 1.91, + 1.91, + 1.91, + 1.91, + 1.91, + 1.9, + 1.9, + 1.9, + 1.9, + 1.9, + 1.9, + 1.9, + 1.89, + 1.89, + 1.89, + 1.89, + 1.89, + 1.89, + 1.89, + 1.88, + 1.88, + 1.88, + 1.88, + 1.88, + 1.88, + 1.88, + 1.88, + 1.87, + 1.87, + 1.87, + 1.87, + 1.87, + 1.87, + 1.87, + 1.86, + 1.86, + 1.86, + 1.86, + 1.86, + 1.86, + 1.86, + 1.86, + 1.85, + 1.85, + 1.85, + 1.85, + 1.85, + 1.85, + 1.85, + 1.84, + 1.84, + 1.84, + 1.84, + 1.84, + 1.84, + 1.84, + 1.84, + 1.83, + 1.83, + 1.83, + 1.83, + 1.83, + 1.83, + 1.83, + 1.82, + 1.82, + 1.82, + 1.82, + 1.82, + 1.82, + 1.82, + 1.82, + 1.81, + 1.81, + 1.81, + 1.81, + 1.81, + 1.81, + 1.81, + 1.81, + 1.8, + 1.8, + 1.8, + 1.8, + 1.8, + 1.8, + 1.8, + 1.79, + 1.79, + 1.79, + 1.79, + 1.79, + 1.79, + 1.79, + 1.79, + 1.78, + 1.78, + 1.78, + 1.78, + 1.78, + 1.78, + 1.78, + 1.78, + 1.77, + 1.77, + 1.77, + 1.77, + 1.77, + 1.77, + 1.77, + 1.77, + 1.76, + 1.76, + 1.76, + 1.76, + 1.76, + 1.76, + 1.76, + 1.76, + 1.75, + 1.75, + 1.75, + 1.75, + 1.75, + 1.75, + 1.75, + 1.74, + 1.74, + 1.74, + 1.74, + 1.74, + 1.74, + 1.74, + 1.74, + 1.73, + 1.73, + 1.73, + 1.73, + 1.73, + 1.73, + 1.73, + 1.73, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.71, + 1.71, + 1.71, + 1.71, + 1.71, + 1.71, + 1.71, + 1.71, + 1.7, + 1.7, + 1.7, + 1.7, + 1.7, + 1.7, + 1.7, + 1.7, + 1.69, + 1.69, + 1.69, + 1.69, + 1.69, + 1.69, + 1.69, + 1.69, + 1.68, + 1.68, + 1.68, + 1.68, + 1.68, + 1.68, + 1.68, + 1.68, + 1.67, + 1.67, + 1.67, + 1.67, + 1.67, + 1.67, + 1.67, + 1.67, + 1.67, + 1.66, + 1.66, + 1.66, + 1.66, + 1.66, + 1.66, + 1.66, + 1.66, + 1.65, + 1.65, + 1.65, + 1.65, + 1.65, + 1.65, + 1.65, + 1.65, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.63, + 1.63, + 1.63, + 1.63, + 1.63, + 1.63, + 1.63, + 1.63, + 1.62, + 1.62, + 1.62, + 1.62, + 1.62, + 1.62, + 1.62, + 1.62, + 1.62, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.6, + 1.6, + 1.6, + 1.6, + 1.6, + 1.6, + 1.6, + 1.6, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.56, + 1.56, + 1.56, + 1.56, + 1.56, + 1.56, + 1.56, + 1.56, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01 + ], + "EEn_ese": [ + 0.0, + 0.01, + 0.05, + 0.1, + 0.18, + 0.27, + 0.39, + 0.52, + 0.67, + 0.84, + 1.02, + 1.21, + 1.42, + 1.64, + 1.88, + 2.12, + 2.38, + 2.64, + 2.92, + 3.2, + 3.49, + 3.79, + 4.1, + 4.41, + 4.73, + 5.06, + 5.39, + 5.72, + 6.06, + 6.41, + 6.75, + 7.1, + 7.45, + 7.81, + 8.17, + 8.53, + 8.89, + 9.25, + 9.61, + 9.97, + 10.33, + 10.7, + 11.06, + 11.42, + 11.78, + 12.14, + 12.5, + 12.86, + 13.22, + 13.57, + 13.92, + 14.27, + 14.62, + 14.97, + 15.31, + 15.66, + 15.99, + 16.33, + 16.66, + 16.99, + 17.32, + 17.64, + 17.96, + 18.28, + 18.6, + 18.91, + 19.21, + 19.51, + 19.81, + 20.11, + 20.4, + 20.69, + 20.97, + 21.25, + 21.53, + 21.8, + 22.07, + 22.33, + 22.59, + 22.85, + 23.1, + 23.34, + 23.59, + 23.83, + 24.06, + 24.29, + 24.52, + 24.74, + 24.96, + 25.17, + 25.38, + 25.59, + 25.79, + 25.99, + 26.18, + 26.37, + 26.56, + 26.74, + 26.92, + 27.09, + 27.26, + 27.42, + 27.59, + 27.74, + 27.9, + 28.05, + 28.19, + 28.34, + 28.47, + 28.61, + 28.74, + 28.87, + 28.99, + 29.11, + 29.23, + 29.34, + 29.45, + 29.56, + 29.66, + 29.76, + 29.85, + 29.95, + 30.03, + 30.12, + 30.2, + 30.28, + 30.36, + 30.43, + 30.5, + 30.57, + 30.63, + 30.69, + 30.75, + 30.81, + 30.86, + 30.91, + 30.95, + 31.0, + 31.04, + 31.08, + 31.11, + 31.15, + 31.18, + 31.2, + 31.23, + 31.25, + 31.27, + 31.29, + 31.31, + 31.32, + 31.33, + 31.34, + 31.35, + 31.35, + 31.35, + 31.35, + 31.35, + 31.35, + 31.34, + 31.33, + 31.32, + 31.31, + 31.3, + 31.28, + 31.27, + 31.25, + 31.22, + 31.2, + 31.18, + 31.15, + 31.12, + 31.09, + 31.06, + 31.03, + 31.0, + 30.96, + 30.92, + 30.88, + 30.84, + 30.8, + 30.76, + 30.72, + 30.67, + 30.62, + 30.58, + 30.53, + 30.48, + 30.42, + 30.37, + 30.32, + 30.26, + 30.2, + 30.15, + 30.09, + 30.03, + 29.97, + 29.91, + 29.85, + 29.78, + 29.72, + 29.65, + 29.59, + 29.52, + 29.45, + 29.38, + 29.31, + 29.24, + 29.17, + 29.1, + 29.03, + 28.96, + 28.88, + 28.81, + 28.73, + 28.66, + 28.58, + 28.5, + 28.43, + 28.35, + 28.27, + 28.19, + 28.11, + 28.03, + 27.95, + 27.87, + 27.79, + 27.7, + 27.62, + 27.54, + 27.46, + 27.37, + 27.29, + 27.2, + 27.12, + 27.03, + 26.95, + 26.86, + 26.78, + 26.69, + 26.6, + 26.52, + 26.43, + 26.34, + 26.25, + 26.17, + 26.08, + 25.99, + 25.9, + 25.81, + 25.72, + 25.63, + 25.55, + 25.46, + 25.37, + 25.28, + 25.19, + 25.1, + 25.01, + 24.92, + 24.83, + 24.74, + 24.65, + 24.56, + 24.47, + 24.38, + 24.29, + 24.2, + 24.11, + 24.02, + 23.93, + 23.83, + 23.74, + 23.65, + 23.56, + 23.47, + 23.38, + 23.29, + 23.2, + 23.11, + 23.02, + 22.93, + 22.84, + 22.75, + 22.66, + 22.57, + 22.48, + 22.39, + 22.31, + 22.22, + 22.13, + 22.04, + 21.95, + 21.86, + 21.77, + 21.68, + 21.59, + 21.51, + 21.42, + 21.33, + 21.24, + 21.15, + 21.07, + 20.98, + 20.89, + 20.8, + 20.72, + 20.63, + 20.54, + 20.46, + 20.37, + 20.29, + 20.2, + 20.11, + 20.03, + 19.94, + 19.86, + 19.77, + 19.69, + 19.61, + 19.52, + 19.44, + 19.35, + 19.27, + 19.19, + 19.1, + 19.02, + 18.94, + 18.86, + 18.77, + 18.69, + 18.61, + 18.53, + 18.45, + 18.37, + 18.29, + 18.2, + 18.12, + 18.04, + 17.96, + 17.88, + 17.81, + 17.73, + 17.65, + 17.57, + 17.49, + 17.41, + 17.33, + 17.26, + 17.18, + 17.1, + 17.03, + 16.95, + 16.87, + 16.8, + 16.72, + 16.65, + 16.57, + 16.5, + 16.42, + 16.35, + 16.27, + 16.2, + 16.12, + 16.05, + 15.98, + 15.9, + 15.83, + 15.76, + 15.69, + 15.61, + 15.54, + 15.47, + 15.4, + 15.33, + 15.26, + 15.19, + 15.12, + 15.05, + 14.98, + 14.91, + 14.84, + 14.77, + 14.7, + 14.64, + 14.57, + 14.5, + 14.43, + 14.37, + 14.3, + 14.23, + 14.17, + 14.1, + 14.03, + 13.97, + 13.9, + 13.84, + 13.77, + 13.71, + 13.64, + 13.58, + 13.52, + 13.45, + 13.39, + 13.33, + 13.26, + 13.2, + 13.14, + 13.08, + 13.02, + 12.95, + 12.89, + 12.83, + 12.77, + 12.71, + 12.65, + 12.59, + 12.53, + 12.47, + 12.41, + 12.35, + 12.3, + 12.24, + 12.18, + 12.12, + 12.06, + 12.01, + 11.95, + 11.89, + 11.84, + 11.78, + 11.72, + 11.67, + 11.61, + 11.56, + 11.5, + 11.45, + 11.39, + 11.34, + 11.28, + 11.23, + 11.17, + 11.12, + 11.07, + 11.01, + 10.96, + 10.91, + 10.86, + 10.8, + 10.75, + 10.7, + 10.65, + 10.6, + 10.55, + 10.5, + 10.45, + 10.4, + 10.35, + 10.3, + 10.25, + 10.2, + 10.15, + 10.1, + 10.05, + 10.0, + 9.95, + 9.91, + 9.86, + 9.81, + 9.76, + 9.72, + 9.67, + 9.62, + 9.58, + 9.53, + 9.48, + 9.44, + 9.39, + 9.35, + 9.3, + 9.26, + 9.21, + 9.17, + 9.12, + 9.08, + 9.03, + 8.99, + 8.95, + 8.9, + 8.86, + 8.82, + 8.77, + 8.73, + 8.69, + 8.65, + 8.6, + 8.56, + 8.52, + 8.48, + 8.44, + 8.4, + 8.36, + 8.31, + 8.27, + 8.23, + 8.19, + 8.15, + 8.11, + 8.07, + 8.04, + 8.0, + 7.96, + 7.92, + 7.88, + 7.84, + 7.8, + 7.76, + 7.73, + 7.69, + 7.65, + 7.61, + 7.58, + 7.54, + 7.5, + 7.47, + 7.43, + 7.39, + 7.36, + 7.32, + 7.29, + 7.25, + 7.21, + 7.18, + 7.14, + 7.11, + 7.07, + 7.04, + 7.01, + 6.97, + 6.94, + 6.9, + 6.87, + 6.84, + 6.8, + 6.77, + 6.74, + 6.7, + 6.67, + 6.64, + 6.6, + 6.57, + 6.54, + 6.51, + 6.48, + 6.44, + 6.41, + 6.38, + 6.35, + 6.32, + 6.29, + 6.26, + 6.23, + 6.2, + 6.17, + 6.13, + 6.1, + 6.07, + 6.04, + 6.02, + 5.99, + 5.96, + 5.93, + 5.9, + 5.87, + 5.84, + 5.81, + 5.78, + 5.75, + 5.73, + 5.7, + 5.67, + 5.64, + 5.61, + 5.59, + 5.56, + 5.53, + 5.5, + 5.48, + 5.45, + 5.42, + 5.4, + 5.37, + 5.34, + 5.32, + 5.29, + 5.27, + 5.24, + 5.21, + 5.19, + 5.16, + 5.14, + 5.11, + 5.09, + 5.06, + 5.04, + 5.01, + 4.99, + 4.96, + 4.94, + 4.91, + 4.89, + 4.87, + 4.84, + 4.82, + 4.79, + 4.77, + 4.75, + 4.72, + 4.7, + 4.68, + 4.65, + 4.63, + 4.61, + 4.59, + 4.56, + 4.54, + 4.52, + 4.5, + 4.47, + 4.45, + 4.43, + 4.41, + 4.39, + 4.37, + 4.34, + 4.32, + 4.3, + 4.28, + 4.26, + 4.24, + 4.22, + 4.2, + 4.18, + 4.15, + 4.13, + 4.11, + 4.09, + 4.07, + 4.05, + 4.03, + 4.01, + 3.99, + 3.97, + 3.95, + 3.93, + 3.92, + 3.9, + 3.88, + 3.86, + 3.84, + 3.82, + 3.8, + 3.78, + 3.76, + 3.74, + 3.73, + 3.71, + 3.69, + 3.67, + 3.65, + 3.64, + 3.62, + 3.6, + 3.58, + 3.56, + 3.55, + 3.53, + 3.51, + 3.49, + 3.48, + 3.46, + 3.44, + 3.43, + 3.41, + 3.39, + 3.38, + 3.36, + 3.34, + 3.33, + 3.31, + 3.29, + 3.28, + 3.26, + 3.24, + 3.23, + 3.21, + 3.2, + 3.18, + 3.16, + 3.15, + 3.13, + 3.12, + 3.1, + 3.09, + 3.07, + 3.06, + 3.04, + 3.03, + 3.01, + 3.0, + 2.98, + 2.97, + 2.95, + 2.94, + 2.92, + 2.91, + 2.89, + 2.88, + 2.87, + 2.85, + 2.84, + 2.82, + 2.81, + 2.8, + 2.78, + 2.77, + 2.76, + 2.74, + 2.73, + 2.71, + 2.7, + 2.69, + 2.67, + 2.66, + 2.65, + 2.63, + 2.62, + 2.61, + 2.6, + 2.58, + 2.57, + 2.56, + 2.55, + 2.53, + 2.52, + 2.51, + 2.5, + 2.48, + 2.47, + 2.46, + 2.45, + 2.43, + 2.42, + 2.41, + 2.4, + 2.39, + 2.37, + 2.36, + 2.35, + 2.34, + 2.33, + 2.32, + 2.31, + 2.29, + 2.28, + 2.27, + 2.26, + 2.25, + 2.24, + 2.23, + 2.22, + 2.2, + 2.19, + 2.18, + 2.17, + 2.16, + 2.15, + 2.14, + 2.13, + 2.12, + 2.11, + 2.1, + 2.09, + 2.08, + 2.07, + 2.06, + 2.05, + 2.04, + 2.03, + 2.02, + 2.01, + 2.0, + 1.99, + 1.98, + 1.97, + 1.96, + 1.95, + 1.94, + 1.93, + 1.92, + 1.91, + 1.9, + 1.89, + 1.88, + 1.87, + 1.86, + 1.85, + 1.84, + 1.84, + 1.83, + 1.82, + 1.81, + 1.8, + 1.79, + 1.78, + 1.77, + 1.76, + 1.76, + 1.75, + 1.74, + 1.73, + 1.72, + 1.71, + 1.7, + 1.7, + 1.69, + 1.68, + 1.67, + 1.66, + 1.65, + 1.65, + 1.64, + 1.63, + 1.62, + 1.61, + 1.61, + 1.6, + 1.59, + 1.58, + 1.57, + 1.57, + 1.56, + 1.55, + 1.54, + 1.54, + 1.53, + 1.52, + 1.51, + 1.51, + 1.5, + 1.49, + 1.48, + 1.48, + 1.47, + 1.46, + 1.45, + 1.45, + 1.44, + 1.43, + 1.43, + 1.42, + 1.41, + 1.4, + 1.4, + 1.39, + 1.38, + 1.38, + 1.37, + 1.36, + 1.36, + 1.35, + 1.34, + 1.34, + 1.33, + 1.32, + 1.32, + 1.31, + 1.3, + 1.3, + 1.29, + 1.28, + 1.28, + 1.27, + 1.27, + 1.26, + 1.25, + 1.25, + 1.24, + 1.23, + 1.23, + 1.22, + 1.22, + 1.21, + 1.2, + 1.2, + 1.19, + 1.19, + 1.18, + 1.17, + 1.17, + 1.16, + 1.16, + 1.15, + 1.15, + 1.14, + 1.13, + 1.13, + 1.12, + 1.12, + 1.11, + 1.11, + 1.1, + 1.1, + 1.09, + 1.09, + 1.08, + 1.07, + 1.07, + 1.06, + 1.06, + 1.05, + 1.05, + 1.04, + 1.04, + 1.03, + 1.03, + 1.02, + 1.02, + 1.01, + 1.01, + 1.0, + 1.0, + 0.99, + 0.99, + 0.98, + 0.98, + 0.97, + 0.97, + 0.96, + 0.96, + 0.95, + 0.95, + 0.94, + 0.94, + 0.94, + 0.93, + 0.93, + 0.92, + 0.92, + 0.91, + 0.91, + 0.9, + 0.9, + 0.89, + 0.89, + 0.89, + 0.88, + 0.88, + 0.87, + 0.87, + 0.86, + 0.86, + 0.86, + 0.85, + 0.85, + 0.84, + 0.84, + 0.83, + 0.83, + 0.83, + 0.82, + 0.82, + 0.81, + 0.81, + 0.81, + 0.8, + 0.8, + 0.79, + 0.79, + 0.79, + 0.78, + 0.78, + 0.77, + 0.77, + 0.77, + 0.76, + 0.76, + 0.76, + 0.75, + 0.75, + 0.74, + 0.74, + 0.74, + 0.73, + 0.73, + 0.73, + 0.72, + 0.72, + 0.72, + 0.71, + 0.71, + 0.7, + 0.7, + 0.7, + 0.69, + 0.69, + 0.69, + 0.68, + 0.68, + 0.68, + 0.67, + 0.67, + 0.67, + 0.66, + 0.66, + 0.66, + 0.65, + 0.65, + 0.65, + 0.64, + 0.64, + 0.64, + 0.64, + 0.63, + 0.63, + 0.63, + 0.62, + 0.62, + 0.62, + 0.61, + 0.61, + 0.61, + 0.6, + 0.6, + 0.6, + 0.6, + 0.59, + 0.59, + 0.59, + 0.58, + 0.58, + 0.58, + 0.58, + 0.57, + 0.57, + 0.57, + 0.56, + 0.56, + 0.56, + 0.56, + 0.55, + 0.55, + 0.55, + 0.54, + 0.54, + 0.54, + 0.54, + 0.53, + 0.53, + 0.53, + 0.53, + 0.52, + 0.52, + 0.52, + 0.52, + 0.51, + 0.51, + 0.51, + 0.51, + 0.5, + 0.5, + 0.5, + 0.5, + 0.49, + 0.49, + 0.49, + 0.49, + 0.48, + 0.48, + 0.48, + 0.48, + 0.47, + 0.47, + 0.47, + 0.47, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.45, + 0.45, + 0.45, + 0.45, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.42, + 0.42, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "EV_tfs": [ + 0.0, + 0.24, + 0.9, + 1.91, + 3.22, + 4.77, + 6.51, + 8.4, + 10.41, + 12.5, + 14.64, + 16.82, + 19.01, + 21.2, + 23.36, + 25.49, + 27.59, + 29.62, + 31.61, + 33.52, + 35.37, + 37.14, + 38.84, + 40.47, + 42.01, + 43.48, + 44.87, + 46.18, + 47.42, + 48.58, + 49.66, + 50.68, + 51.62, + 52.49, + 53.3, + 54.04, + 54.72, + 55.34, + 55.9, + 56.41, + 56.86, + 57.27, + 57.62, + 57.93, + 58.19, + 58.41, + 58.59, + 58.73, + 58.84, + 58.91, + 58.95, + 58.96, + 58.93, + 58.89, + 58.81, + 58.71, + 58.59, + 58.44, + 58.28, + 58.09, + 57.89, + 57.67, + 57.44, + 57.19, + 56.92, + 56.65, + 56.36, + 56.06, + 55.75, + 55.43, + 55.1, + 54.77, + 54.43, + 54.08, + 53.72, + 53.36, + 52.99, + 52.62, + 52.25, + 51.87, + 51.49, + 51.1, + 50.71, + 50.33, + 49.93, + 49.54, + 49.15, + 48.75, + 48.36, + 47.96, + 47.57, + 47.17, + 46.78, + 46.38, + 45.99, + 45.59, + 45.2, + 44.81, + 44.42, + 44.03, + 43.64, + 43.26, + 42.87, + 42.49, + 42.11, + 41.73, + 41.36, + 40.98, + 40.61, + 40.24, + 39.87, + 39.51, + 39.15, + 38.79, + 38.43, + 38.07, + 37.72, + 37.37, + 37.02, + 36.68, + 36.33, + 35.99, + 35.66, + 35.32, + 34.99, + 34.66, + 34.33, + 34.01, + 33.69, + 33.37, + 33.05, + 32.74, + 32.43, + 32.12, + 31.82, + 31.51, + 31.21, + 30.92, + 30.62, + 30.33, + 30.04, + 29.75, + 29.47, + 29.19, + 28.91, + 28.63, + 28.36, + 28.08, + 27.81, + 27.55, + 27.28, + 27.02, + 26.76, + 26.5, + 26.25, + 26.0, + 25.75, + 25.5, + 25.25, + 25.01, + 24.77, + 24.53, + 24.3, + 24.06, + 23.83, + 23.6, + 23.37, + 23.15, + 22.92, + 22.7, + 22.48, + 22.27, + 22.05, + 21.84, + 21.63, + 21.42, + 21.21, + 21.01, + 20.8, + 20.6, + 20.4, + 20.21, + 20.01, + 19.82, + 19.63, + 19.44, + 19.25, + 19.06, + 18.88, + 18.7, + 18.52, + 18.34, + 18.16, + 17.98, + 17.81, + 17.64, + 17.47, + 17.3, + 17.13, + 16.96, + 16.8, + 16.64, + 16.48, + 16.32, + 16.16, + 16.0, + 15.85, + 15.7, + 15.54, + 15.39, + 15.24, + 15.1, + 14.95, + 14.81, + 14.66, + 14.52, + 14.38, + 14.24, + 14.1, + 13.97, + 13.83, + 13.7, + 13.57, + 13.43, + 13.3, + 13.18, + 13.05, + 12.92, + 12.8, + 12.67, + 12.55, + 12.43, + 12.31, + 12.19, + 12.07, + 11.95, + 11.84, + 11.72, + 11.61, + 11.5, + 11.39, + 11.28, + 11.17, + 11.06, + 10.95, + 10.85, + 10.74, + 10.64, + 10.53, + 10.43, + 10.33, + 10.23, + 10.13, + 10.03, + 9.94, + 9.84, + 9.75, + 9.65, + 9.56, + 9.47, + 9.37, + 9.28, + 9.19, + 9.1, + 9.02, + 8.93, + 8.84, + 8.76, + 8.67, + 8.59, + 8.51, + 8.42, + 8.34, + 8.26, + 8.18, + 8.1, + 8.02, + 7.95, + 7.87, + 7.79, + 7.72, + 7.64, + 7.57, + 7.49, + 7.42, + 7.35, + 7.28, + 7.21, + 7.14, + 7.07, + 7.0, + 6.93, + 6.87, + 6.8, + 6.73, + 6.67, + 6.6, + 6.54, + 6.48, + 6.41, + 6.35, + 6.29, + 6.23, + 6.17, + 6.11, + 6.05, + 5.99, + 5.93, + 5.88, + 5.82, + 5.76, + 5.71, + 5.65, + 5.6, + 5.54, + 5.49, + 5.44, + 5.38, + 5.33, + 5.28, + 5.23, + 5.18, + 5.13, + 5.08, + 5.03, + 4.98, + 4.93, + 4.89, + 4.84, + 4.79, + 4.74, + 4.7, + 4.65, + 4.61, + 4.56, + 4.52, + 4.48, + 4.43, + 4.39, + 4.35, + 4.3, + 4.26, + 4.22, + 4.18, + 4.14, + 4.1, + 4.06, + 4.02, + 3.98, + 3.94, + 3.91, + 3.87, + 3.83, + 3.79, + 3.76, + 3.72, + 3.68, + 3.65, + 3.61, + 3.58, + 3.54, + 3.51, + 3.48, + 3.44, + 3.41, + 3.38, + 3.34, + 3.31, + 3.28, + 3.25, + 3.22, + 3.18, + 3.15, + 3.12, + 3.09, + 3.06, + 3.03, + 3.0, + 2.97, + 2.95, + 2.92, + 2.89, + 2.86, + 2.83, + 2.81, + 2.78, + 2.75, + 2.73, + 2.7, + 2.67, + 2.65, + 2.62, + 2.6, + 2.57, + 2.55, + 2.52, + 2.5, + 2.47, + 2.45, + 2.42, + 2.4, + 2.38, + 2.36, + 2.33, + 2.31, + 2.29, + 2.27, + 2.24, + 2.22, + 2.2, + 2.18, + 2.16, + 2.14, + 2.12, + 2.1, + 2.08, + 2.06, + 2.04, + 2.02, + 2.0, + 1.98, + 1.96, + 1.94, + 1.92, + 1.9, + 1.88, + 1.86, + 1.85, + 1.83, + 1.81, + 1.79, + 1.78, + 1.76, + 1.74, + 1.73, + 1.71, + 1.69, + 1.68, + 1.66, + 1.64, + 1.63, + 1.61, + 1.6, + 1.58, + 1.57, + 1.55, + 1.54, + 1.52, + 1.51, + 1.49, + 1.48, + 1.46, + 1.45, + 1.43, + 1.42, + 1.41, + 1.39, + 1.38, + 1.37, + 1.35, + 1.34, + 1.33, + 1.31, + 1.3, + 1.29, + 1.28, + 1.26, + 1.25, + 1.24, + 1.23, + 1.22, + 1.2, + 1.19, + 1.18, + 1.17, + 1.16, + 1.15, + 1.14, + 1.12, + 1.11, + 1.1, + 1.09, + 1.08, + 1.07, + 1.06, + 1.05, + 1.04, + 1.03, + 1.02, + 1.01, + 1.0, + 0.99, + 0.98, + 0.97, + 0.96, + 0.95, + 0.94, + 0.93, + 0.93, + 0.92, + 0.91, + 0.9, + 0.89, + 0.88, + 0.87, + 0.86, + 0.86, + 0.85, + 0.84, + 0.83, + 0.82, + 0.82, + 0.81, + 0.8, + 0.79, + 0.78, + 0.78, + 0.77, + 0.76, + 0.75, + 0.75, + 0.74, + 0.73, + 0.73, + 0.72, + 0.71, + 0.71, + 0.7, + 0.69, + 0.68, + 0.68, + 0.67, + 0.67, + 0.66, + 0.65, + 0.65, + 0.64, + 0.63, + 0.63, + 0.62, + 0.62, + 0.61, + 0.6, + 0.6, + 0.59, + 0.59, + 0.58, + 0.57, + 0.57, + 0.56, + 0.56, + 0.55, + 0.55, + 0.54, + 0.54, + 0.53, + 0.53, + 0.52, + 0.52, + 0.51, + 0.51, + 0.5, + 0.5, + 0.49, + 0.49, + 0.48, + 0.48, + 0.47, + 0.47, + 0.46, + 0.46, + 0.46, + 0.45, + 0.45, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.41, + 0.41, + 0.4, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "EU_tfs": [ + 0.0, + 0.16, + 0.32, + 0.48, + 0.63, + 0.79, + 0.94, + 1.09, + 1.23, + 1.38, + 1.52, + 1.66, + 1.8, + 1.94, + 2.07, + 2.21, + 2.34, + 2.47, + 2.6, + 2.72, + 2.85, + 2.97, + 3.09, + 3.21, + 3.33, + 3.44, + 3.56, + 3.67, + 3.78, + 3.89, + 4.0, + 4.11, + 4.21, + 4.31, + 4.42, + 4.52, + 4.62, + 4.71, + 4.81, + 4.9, + 5.0, + 5.09, + 5.18, + 5.27, + 5.36, + 5.45, + 5.53, + 5.62, + 5.7, + 5.78, + 5.86, + 5.94, + 6.02, + 6.1, + 6.17, + 6.25, + 6.32, + 6.39, + 6.47, + 6.54, + 6.61, + 6.67, + 6.74, + 6.81, + 6.87, + 6.94, + 7.0, + 7.06, + 7.13, + 7.19, + 7.25, + 7.3, + 7.36, + 7.42, + 7.48, + 7.53, + 7.58, + 7.64, + 7.69, + 7.74, + 7.79, + 7.84, + 7.89, + 7.94, + 7.99, + 8.04, + 8.08, + 8.13, + 8.17, + 8.22, + 8.26, + 8.3, + 8.34, + 8.38, + 8.42, + 8.46, + 8.5, + 8.54, + 8.58, + 8.62, + 8.65, + 8.69, + 8.72, + 8.76, + 8.79, + 8.82, + 8.86, + 8.89, + 8.92, + 8.95, + 8.98, + 9.01, + 9.04, + 9.07, + 9.1, + 9.13, + 9.15, + 9.18, + 9.21, + 9.23, + 9.26, + 9.28, + 9.31, + 9.33, + 9.35, + 9.37, + 9.4, + 9.42, + 9.44, + 9.46, + 9.48, + 9.5, + 9.52, + 9.54, + 9.56, + 9.58, + 9.6, + 9.61, + 9.63, + 9.65, + 9.66, + 9.68, + 9.7, + 9.71, + 9.73, + 9.74, + 9.76, + 9.77, + 9.78, + 9.8, + 9.81, + 9.82, + 9.83, + 9.85, + 9.86, + 9.87, + 9.88, + 9.89, + 9.9, + 9.91, + 9.92, + 9.93, + 9.94, + 9.95, + 9.96, + 9.96, + 9.97, + 9.98, + 9.99, + 10.0, + 10.0, + 10.01, + 10.02, + 10.02, + 10.03, + 10.03, + 10.04, + 10.04, + 10.05, + 10.05, + 10.06, + 10.06, + 10.07, + 10.07, + 10.07, + 10.08, + 10.08, + 10.08, + 10.09, + 10.09, + 10.09, + 10.09, + 10.1, + 10.1, + 10.1, + 10.1, + 10.1, + 10.1, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.11, + 10.1, + 10.1, + 10.1, + 10.1, + 10.1, + 10.1, + 10.1, + 10.09, + 10.09, + 10.09, + 10.09, + 10.08, + 10.08, + 10.08, + 10.08, + 10.07, + 10.07, + 10.07, + 10.06, + 10.06, + 10.06, + 10.05, + 10.05, + 10.04, + 10.04, + 10.04, + 10.03, + 10.03, + 10.02, + 10.02, + 10.01, + 10.01, + 10.0, + 10.0, + 9.99, + 9.99, + 9.98, + 9.98, + 9.97, + 9.97, + 9.96, + 9.96, + 9.95, + 9.95, + 9.94, + 9.93, + 9.93, + 9.92, + 9.92, + 9.91, + 9.9, + 9.9, + 9.89, + 9.88, + 9.88, + 9.87, + 9.86, + 9.86, + 9.85, + 9.84, + 9.84, + 9.83, + 9.82, + 9.81, + 9.81, + 9.8, + 9.79, + 9.78, + 9.78, + 9.77, + 9.76, + 9.75, + 9.75, + 9.74, + 9.73, + 9.72, + 9.71, + 9.71, + 9.7, + 9.69, + 9.68, + 9.67, + 9.67, + 9.66, + 9.65, + 9.64, + 9.63, + 9.62, + 9.62, + 9.61, + 9.6, + 9.59, + 9.58, + 9.57, + 9.56, + 9.56, + 9.55, + 9.54, + 9.53, + 9.52, + 9.51, + 9.5, + 9.49, + 9.48, + 9.48, + 9.47, + 9.46, + 9.45, + 9.44, + 9.43, + 9.42, + 9.41, + 9.4, + 9.39, + 9.38, + 9.37, + 9.37, + 9.36, + 9.35, + 9.34, + 9.33, + 9.32, + 9.31, + 9.3, + 9.29, + 9.28, + 9.27, + 9.26, + 9.25, + 9.24, + 9.23, + 9.22, + 9.21, + 9.2, + 9.19, + 9.18, + 9.17, + 9.16, + 9.15, + 9.15, + 9.14, + 9.13, + 9.12, + 9.11, + 9.1, + 9.09, + 9.08, + 9.07, + 9.06, + 9.05, + 9.04, + 9.03, + 9.02, + 9.01, + 9.0, + 8.99, + 8.98, + 8.97, + 8.96, + 8.95, + 8.94, + 8.93, + 8.92, + 8.91, + 8.9, + 8.89, + 8.88, + 8.87, + 8.86, + 8.85, + 8.84, + 8.83, + 8.82, + 8.81, + 8.8, + 8.79, + 8.78, + 8.77, + 8.76, + 8.75, + 8.74, + 8.73, + 8.72, + 8.71, + 8.7, + 8.69, + 8.67, + 8.66, + 8.65, + 8.64, + 8.63, + 8.62, + 8.61, + 8.6, + 8.59, + 8.58, + 8.57, + 8.56, + 8.55, + 8.54, + 8.53, + 8.52, + 8.51, + 8.5, + 8.49, + 8.48, + 8.47, + 8.46, + 8.45, + 8.44, + 8.43, + 8.42, + 8.41, + 8.4, + 8.39, + 8.38, + 8.37, + 8.36, + 8.35, + 8.34, + 8.33, + 8.32, + 8.31, + 8.3, + 8.29, + 8.28, + 8.27, + 8.26, + 8.25, + 8.24, + 8.23, + 8.22, + 8.21, + 8.2, + 8.19, + 8.18, + 8.17, + 8.16, + 8.15, + 8.14, + 8.13, + 8.12, + 8.11, + 8.1, + 8.09, + 8.08, + 8.07, + 8.06, + 8.05, + 8.04, + 8.03, + 8.02, + 8.01, + 8.0, + 7.99, + 7.98, + 7.97, + 7.96, + 7.95, + 7.94, + 7.93, + 7.92, + 7.91, + 7.9, + 7.89, + 7.88, + 7.87, + 7.86, + 7.85, + 7.84, + 7.83, + 7.82, + 7.81, + 7.8, + 7.79, + 7.78, + 7.77, + 7.76, + 7.75, + 7.74, + 7.73, + 7.72, + 7.71, + 7.7, + 7.7, + 7.69, + 7.68, + 7.67, + 7.66, + 7.65, + 7.64, + 7.63, + 7.62, + 7.61, + 7.6, + 7.59, + 7.58, + 7.57, + 7.56, + 7.55, + 7.54, + 7.53, + 7.52, + 7.51, + 7.5, + 7.49, + 7.48, + 7.47, + 7.46, + 7.46, + 7.45, + 7.44, + 7.43, + 7.42, + 7.41, + 7.4, + 7.39, + 7.38, + 7.37, + 7.36, + 7.35, + 7.34, + 7.33, + 7.32, + 7.31, + 7.3, + 7.3, + 7.29, + 7.28, + 7.27, + 7.26, + 7.25, + 7.24, + 7.23, + 7.22, + 7.21, + 7.2, + 7.19, + 7.18, + 7.17, + 7.17, + 7.16, + 7.15, + 7.14, + 7.13, + 7.12, + 7.11, + 7.1, + 7.09, + 7.08, + 7.07, + 7.06, + 7.06, + 7.05, + 7.04, + 7.03, + 7.02, + 7.01, + 7.0, + 6.99, + 6.98, + 6.97, + 6.97, + 6.96, + 6.95, + 6.94, + 6.93, + 6.92, + 6.91, + 6.9, + 6.89, + 6.89, + 6.88, + 6.87, + 6.86, + 6.85, + 6.84, + 6.83, + 6.82, + 6.81, + 6.81, + 6.8, + 6.79, + 6.78, + 6.77, + 6.76, + 6.75, + 6.74, + 6.74, + 6.73, + 6.72, + 6.71, + 6.7, + 6.69, + 6.68, + 6.67, + 6.67, + 6.66, + 6.65, + 6.64, + 6.63, + 6.62, + 6.61, + 6.61, + 6.6, + 6.59, + 6.58, + 6.57, + 6.56, + 6.55, + 6.55, + 6.54, + 6.53, + 6.52, + 6.51, + 6.5, + 6.5, + 6.49, + 6.48, + 6.47, + 6.46, + 6.45, + 6.44, + 6.44, + 6.43, + 6.42, + 6.41, + 6.4, + 6.39, + 6.39, + 6.38, + 6.37, + 6.36, + 6.35, + 6.35, + 6.34, + 6.33, + 6.32, + 6.31, + 6.3, + 6.3, + 6.29, + 6.28, + 6.27, + 6.26, + 6.26, + 6.25, + 6.24, + 6.23, + 6.22, + 6.21, + 6.21, + 6.2, + 6.19, + 6.18, + 6.17, + 6.17, + 6.16, + 6.15, + 6.14, + 6.13, + 6.13, + 6.12, + 6.11, + 6.1, + 6.09, + 6.09, + 6.08, + 6.07, + 6.06, + 6.05, + 6.05, + 6.04, + 6.03, + 6.02, + 6.02, + 6.01, + 6.0, + 5.99, + 5.98, + 5.98, + 5.97, + 5.96, + 5.95, + 5.95, + 5.94, + 5.93, + 5.92, + 5.91, + 5.91, + 5.9, + 5.89, + 5.88, + 5.88, + 5.87, + 5.86, + 5.85, + 5.85, + 5.84, + 5.83, + 5.82, + 5.81, + 5.81, + 5.8, + 5.79, + 5.78, + 5.78, + 5.77, + 5.76, + 5.75, + 5.75, + 5.74, + 5.73, + 5.72, + 5.72, + 5.71, + 5.7, + 5.69, + 5.69, + 5.68, + 5.67, + 5.67, + 5.66, + 5.65, + 5.64, + 5.64, + 5.63, + 5.62, + 5.61, + 5.61, + 5.6, + 5.59, + 5.58, + 5.58, + 5.57, + 5.56, + 5.56, + 5.55, + 5.54, + 5.53, + 5.53, + 5.52, + 5.51, + 5.5, + 5.5, + 5.49, + 5.48, + 5.48, + 5.47, + 5.46, + 5.45, + 5.45, + 5.44, + 5.43, + 5.43, + 5.42, + 5.41, + 5.4, + 5.4, + 5.39, + 5.38, + 5.38, + 5.37, + 5.36, + 5.36, + 5.35, + 5.34, + 5.33, + 5.33, + 5.32, + 5.31, + 5.31, + 5.3, + 5.29, + 5.29, + 5.28, + 5.27, + 5.27, + 5.26, + 5.25, + 5.24, + 5.24, + 5.23, + 5.22, + 5.22, + 5.21, + 5.2, + 5.2, + 5.19, + 5.18, + 5.18, + 5.17, + 5.16, + 5.16, + 5.15, + 5.14, + 5.14, + 5.13, + 5.12, + 5.12, + 5.11, + 5.1, + 5.1, + 5.09, + 5.08, + 5.08, + 5.07, + 5.06, + 5.06, + 5.05, + 5.04, + 5.04, + 5.03, + 5.02, + 5.02, + 5.01, + 5.0, + 5.0, + 4.99, + 4.98, + 4.98, + 4.97, + 4.96, + 4.96, + 4.95, + 4.95, + 4.94, + 4.93, + 4.93, + 4.92, + 4.91, + 4.91, + 4.9, + 4.89, + 4.89, + 4.88, + 4.87, + 4.87, + 4.86, + 4.86, + 4.85, + 4.84, + 4.84, + 4.83, + 4.82, + 4.82, + 4.81, + 4.8, + 4.8, + 4.79, + 4.79, + 4.78, + 4.77, + 4.77, + 4.76, + 4.75, + 4.75, + 4.74, + 4.74, + 4.73, + 4.72, + 4.72, + 4.71, + 4.71, + 4.7, + 4.69, + 4.69, + 4.68, + 4.67, + 4.67, + 4.66, + 4.66, + 4.65, + 4.64, + 4.64, + 4.63, + 4.63, + 4.62, + 4.61, + 4.61, + 4.6, + 4.6, + 4.59, + 4.58, + 4.58, + 4.57, + 4.57, + 4.56, + 4.55, + 4.55, + 4.54, + 4.54, + 4.53, + 4.52, + 4.52, + 4.51, + 4.51, + 4.5, + 4.49, + 4.49, + 4.48, + 4.48, + 4.47, + 4.47, + 4.46, + 4.45, + 4.45, + 4.44, + 4.44, + 4.43, + 4.42, + 4.42, + 4.41, + 4.41, + 4.4, + 4.4, + 4.39, + 4.38, + 4.38, + 4.37, + 4.37, + 4.36, + 4.36, + 4.35, + 4.34, + 4.34, + 4.33, + 4.33, + 4.32, + 4.32, + 4.31, + 4.3, + 4.3, + 4.29, + 4.29, + 4.28, + 4.28, + 4.27, + 4.27, + 4.26, + 4.25, + 4.25, + 4.24, + 4.24, + 4.23, + 4.23, + 4.22, + 4.22, + 4.21, + 4.2, + 4.2, + 4.19, + 4.19, + 4.18, + 4.18, + 4.17, + 4.17, + 4.16, + 4.16, + 4.15, + 4.14, + 4.14, + 4.13, + 4.13, + 4.12, + 4.12, + 4.11, + 4.11, + 4.1, + 4.1, + 4.09, + 4.09, + 4.08, + 4.07, + 4.07, + 4.06, + 4.06, + 4.05, + 4.05, + 4.04, + 4.04, + 4.03, + 4.03, + 4.02, + 4.02, + 4.01, + 4.01, + 4.0, + 4.0, + 3.99, + 3.98, + 3.98, + 3.97, + 3.97, + 3.96, + 3.96, + 3.95, + 3.95, + 3.94, + 3.94, + 3.93, + 3.93, + 3.92, + 3.92, + 3.91, + 3.91, + 3.9, + 3.9, + 3.89, + 3.89, + 3.88, + 3.88, + 3.87, + 3.87, + 3.86, + 3.86, + 3.85, + 3.85, + 3.84, + 3.84, + 3.83, + 3.83, + 3.82, + 3.82, + 3.81, + 3.81, + 3.8, + 3.8, + 3.79, + 3.79, + 3.78, + 3.78, + 3.77, + 3.77, + 3.76, + 3.76, + 3.75, + 3.75, + 3.74, + 3.74, + 3.73, + 3.73, + 3.72, + 3.72, + 3.71, + 3.71, + 3.7, + 3.7, + 3.69, + 3.69, + 3.68, + 3.68, + 3.67, + 3.67, + 3.66, + 3.66, + 3.66, + 3.65, + 3.65, + 3.64, + 3.64, + 3.63, + 3.63, + 3.62, + 3.62, + 3.61, + 3.61, + 3.6, + 3.6, + 3.59, + 3.59, + 3.58, + 3.58, + 3.57, + 3.57, + 3.57, + 3.56, + 3.56, + 3.55, + 3.55, + 3.54, + 3.54, + 3.53, + 3.53, + 3.52, + 3.52, + 3.51, + 3.51, + 3.51, + 3.5, + 3.5, + 3.49, + 3.49, + 3.48, + 3.48, + 3.47, + 3.47, + 3.46, + 3.46, + 3.46, + 3.45, + 3.45, + 3.44, + 3.44, + 3.43, + 3.43, + 3.42, + 3.42, + 3.41, + 3.41, + 3.41, + 3.4, + 3.4, + 3.39, + 3.39, + 3.38, + 3.38, + 3.37, + 3.37, + 3.37, + 3.36, + 3.36, + 3.35, + 3.35, + 3.34, + 3.34, + 3.34, + 3.33, + 3.33, + 3.32, + 3.32, + 3.31, + 3.31, + 3.3, + 3.3, + 3.3, + 3.29, + 3.29, + 3.28, + 3.28, + 3.27, + 3.27, + 3.27, + 3.26, + 3.26, + 3.25, + 3.25, + 3.24, + 3.24, + 3.24, + 3.23, + 3.23, + 3.22, + 3.22, + 3.22, + 3.21, + 3.21, + 3.2, + 3.2, + 3.19, + 3.19, + 3.19, + 3.18, + 3.18, + 3.17, + 3.17, + 3.16, + 3.16, + 3.16, + 3.15, + 3.15, + 3.14, + 3.14, + 3.14, + 3.13, + 3.13, + 3.12, + 3.12, + 3.12, + 3.11, + 3.11, + 3.1, + 3.1, + 3.1, + 3.09, + 3.09, + 3.08, + 3.08, + 3.08, + 3.07, + 3.07, + 3.06, + 3.06, + 3.06, + 3.05, + 3.05, + 3.04, + 3.04, + 3.04, + 3.03, + 3.03, + 3.02, + 3.02, + 3.02, + 3.01, + 3.01, + 3.0, + 3.0, + 3.0, + 2.99, + 2.99, + 2.98, + 2.98, + 2.98, + 2.97, + 2.97, + 2.96, + 2.96, + 2.96, + 2.95, + 2.95, + 2.95, + 2.94, + 2.94, + 2.93, + 2.93, + 2.93, + 2.92, + 2.92, + 2.91, + 2.91, + 2.91, + 2.9, + 2.9, + 2.9, + 2.89, + 2.89, + 2.88, + 2.88, + 2.88, + 2.87, + 2.87, + 2.87, + 2.86, + 2.86, + 2.85, + 2.85, + 2.85, + 2.84, + 2.84, + 2.84, + 2.83, + 2.83, + 2.82, + 2.82, + 2.82, + 2.81, + 2.81, + 2.81, + 2.8, + 2.8, + 2.79, + 2.79, + 2.79, + 2.78, + 2.78, + 2.78, + 2.77, + 2.77, + 2.77, + 2.76, + 2.76, + 2.75, + 2.75, + 2.75, + 2.74, + 2.74, + 2.74, + 2.73, + 2.73, + 2.73, + 2.72, + 2.72, + 2.72, + 2.71, + 2.71, + 2.7, + 2.7, + 2.7, + 2.69, + 2.69, + 2.69, + 2.68, + 2.68, + 2.68, + 2.67, + 2.67, + 2.67, + 2.66, + 2.66, + 2.66, + 2.65, + 2.65, + 2.65, + 2.64, + 2.64, + 2.64, + 2.63, + 2.63, + 2.62, + 2.62, + 2.62, + 2.61, + 2.61, + 2.61, + 2.6, + 2.6, + 2.6, + 2.59, + 2.59, + 2.59, + 2.58, + 2.58, + 2.58, + 2.57, + 2.57, + 2.57, + 2.56, + 2.56, + 2.56, + 2.55, + 2.55, + 2.55, + 2.54, + 2.54, + 2.54, + 2.53, + 2.53, + 2.53, + 2.52, + 2.52, + 2.52, + 2.51, + 2.51, + 2.51, + 2.5, + 2.5, + 2.5, + 2.49, + 2.49, + 2.49, + 2.48, + 2.48, + 2.48, + 2.47, + 2.47, + 2.47, + 2.46, + 2.46, + 2.46, + 2.46, + 2.45, + 2.45, + 2.45, + 2.44, + 2.44, + 2.44, + 2.43, + 2.43, + 2.43, + 2.42, + 2.42, + 2.42, + 2.41, + 2.41, + 2.41, + 2.4, + 2.4, + 2.4, + 2.39, + 2.39, + 2.39, + 2.39, + 2.38, + 2.38, + 2.38, + 2.37, + 2.37, + 2.37, + 2.36, + 2.36, + 2.36, + 2.35, + 2.35, + 2.35, + 2.35, + 2.34, + 2.34, + 2.34, + 2.33, + 2.33, + 2.33, + 2.32, + 2.32, + 2.32, + 2.31, + 2.31, + 2.31, + 2.31, + 2.3, + 2.3, + 2.3, + 2.29, + 2.29, + 2.29, + 2.28, + 2.28, + 2.28, + 2.28, + 2.27, + 2.27, + 2.27, + 2.26, + 2.26, + 2.26, + 2.25, + 2.25, + 2.25, + 2.25, + 2.24, + 2.24, + 2.24, + 2.23, + 2.23, + 2.23, + 2.23, + 2.22, + 2.22, + 2.22, + 2.21, + 2.21, + 2.21, + 2.21, + 2.2, + 2.2, + 2.2, + 2.19, + 2.19, + 2.19, + 2.19, + 2.18, + 2.18, + 2.18, + 2.17, + 2.17, + 2.17, + 2.17, + 2.16, + 2.16, + 2.16, + 2.15, + 2.15, + 2.15, + 2.15, + 2.14, + 2.14, + 2.14, + 2.13, + 2.13, + 2.13, + 2.13, + 2.12, + 2.12, + 2.12, + 2.11, + 2.11, + 2.11, + 2.11, + 2.1, + 2.1, + 2.1, + 2.1, + 2.09, + 2.09, + 2.09, + 2.08, + 2.08, + 2.08, + 2.08, + 2.07, + 2.07, + 2.07, + 2.07, + 2.06, + 2.06, + 2.06, + 2.05, + 2.05, + 2.05, + 2.05, + 2.04, + 2.04, + 2.04, + 2.04, + 2.03, + 2.03, + 2.03, + 2.03, + 2.02, + 2.02, + 2.02, + 2.01, + 2.01, + 2.01, + 2.01, + 2.0, + 2.0, + 2.0, + 2.0, + 1.99, + 1.99, + 1.99, + 1.99, + 1.98, + 1.98, + 1.98, + 1.98, + 1.97, + 1.97, + 1.97, + 1.97, + 1.96, + 1.96, + 1.96, + 1.96, + 1.95, + 1.95, + 1.95, + 1.94, + 1.94, + 1.94, + 1.94, + 1.93, + 1.93, + 1.93, + 1.93, + 1.92, + 1.92, + 1.92, + 1.92, + 1.91, + 1.91, + 1.91, + 1.91, + 1.9, + 1.9, + 1.9, + 1.9, + 1.89, + 1.89, + 1.89, + 1.89, + 1.88, + 1.88, + 1.88, + 1.88, + 1.87, + 1.87, + 1.87, + 1.87, + 1.87, + 1.86, + 1.86, + 1.86, + 1.86, + 1.85, + 1.85, + 1.85, + 1.85, + 1.84, + 1.84, + 1.84, + 1.84, + 1.83, + 1.83, + 1.83, + 1.83, + 1.82, + 1.82, + 1.82, + 1.82, + 1.81, + 1.81, + 1.81, + 1.81, + 1.81, + 1.8, + 1.8, + 1.8, + 1.8, + 1.79, + 1.79, + 1.79, + 1.79, + 1.78, + 1.78, + 1.78, + 1.78, + 1.77, + 1.77, + 1.77, + 1.77, + 1.77, + 1.76, + 1.76, + 1.76, + 1.76, + 1.75, + 1.75, + 1.75, + 1.75, + 1.74, + 1.74, + 1.74, + 1.74, + 1.74, + 1.73, + 1.73, + 1.73, + 1.73, + 1.72, + 1.72, + 1.72, + 1.72, + 1.72, + 1.71, + 1.71, + 1.71, + 1.71, + 1.7, + 1.7, + 1.7, + 1.7, + 1.7, + 1.69, + 1.69, + 1.69, + 1.69, + 1.68, + 1.68, + 1.68, + 1.68, + 1.68, + 1.67, + 1.67, + 1.67, + 1.67, + 1.66, + 1.66, + 1.66, + 1.66, + 1.66, + 1.65, + 1.65, + 1.65, + 1.65, + 1.64, + 1.64, + 1.64, + 1.64, + 1.64, + 1.63, + 1.63, + 1.63, + 1.63, + 1.63, + 1.62, + 1.62, + 1.62, + 1.62, + 1.61, + 1.61, + 1.61, + 1.61, + 1.61, + 1.6, + 1.6, + 1.6, + 1.6, + 1.6, + 1.59, + 1.59, + 1.59, + 1.59, + 1.59, + 1.58, + 1.58, + 1.58, + 1.58, + 1.58, + 1.57, + 1.57, + 1.57, + 1.57, + 1.57, + 1.56, + 1.56, + 1.56, + 1.56, + 1.55, + 1.55, + 1.55, + 1.55, + 1.55, + 1.54, + 1.54, + 1.54, + 1.54, + 1.54, + 1.53, + 1.53, + 1.53, + 1.53, + 1.53, + 1.52, + 1.52, + 1.52, + 1.52, + 1.52, + 1.51, + 1.51, + 1.51, + 1.51, + 1.51, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.49, + 1.48, + 1.48, + 1.48, + 1.48, + 1.48, + 1.47, + 1.47, + 1.47, + 1.47, + 1.47, + 1.46, + 1.46, + 1.46, + 1.46, + 1.46, + 1.45, + 1.45, + 1.45, + 1.45, + 1.45, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.44, + 1.43, + 1.43, + 1.43, + 1.43, + 1.43, + 1.42, + 1.42, + 1.42, + 1.42, + 1.42, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.41, + 1.4, + 1.4, + 1.4, + 1.4, + 1.4, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.39, + 1.38, + 1.38, + 1.38, + 1.38, + 1.38, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.37, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.36, + 1.35, + 1.35, + 1.35, + 1.35, + 1.35, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.34, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.33, + 1.32, + 1.32, + 1.32, + 1.32, + 1.32, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.31, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.3, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.29, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.28, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.27, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.26, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.25, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.24, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.23, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.22, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.21, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.2, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.19, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.18, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.17, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.16, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.15, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.14, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.13, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.12, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.11, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.1, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.09, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.08, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.07, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.06, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.05, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.04, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.03, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.02, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.01, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.99, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.98, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.97, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.96, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.95, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.94, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.93, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.92, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.91, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.89, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.88, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.87, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.86, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.85, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.84, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.83, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.82, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.81, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.8, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.79, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.78, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.77, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.76, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.75, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.74, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.73, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.72, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.71, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.69, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.68, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.67, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.66, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.65, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.64, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.63, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.62, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.61, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.6, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.59, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.58, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.57, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.56, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.55, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.54, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.53, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.52, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.51, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.49, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.48, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.47, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.46, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.45, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.42, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.41, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "EEn_tfs": [ + 0.0, + 0.01, + 0.04, + 0.09, + 0.16, + 0.25, + 0.35, + 0.47, + 0.61, + 0.76, + 0.93, + 1.11, + 1.3, + 1.5, + 1.72, + 1.95, + 2.19, + 2.43, + 2.69, + 2.96, + 3.23, + 3.51, + 3.8, + 4.1, + 4.4, + 4.71, + 5.02, + 5.34, + 5.67, + 6.0, + 6.33, + 6.67, + 7.0, + 7.35, + 7.69, + 8.04, + 8.39, + 8.74, + 9.09, + 9.45, + 9.8, + 10.16, + 10.51, + 10.87, + 11.23, + 11.58, + 11.94, + 12.29, + 12.65, + 13.0, + 13.35, + 13.71, + 14.06, + 14.4, + 14.75, + 15.09, + 15.44, + 15.78, + 16.11, + 16.45, + 16.78, + 17.11, + 17.44, + 17.76, + 18.08, + 18.4, + 18.72, + 19.03, + 19.34, + 19.64, + 19.95, + 20.25, + 20.54, + 20.83, + 21.12, + 21.4, + 21.69, + 21.96, + 22.24, + 22.5, + 22.77, + 23.03, + 23.29, + 23.54, + 23.79, + 24.04, + 24.28, + 24.52, + 24.75, + 24.98, + 25.21, + 25.43, + 25.65, + 25.86, + 26.07, + 26.28, + 26.48, + 26.68, + 26.87, + 27.06, + 27.24, + 27.43, + 27.6, + 27.78, + 27.95, + 28.11, + 28.27, + 28.43, + 28.59, + 28.74, + 28.88, + 29.03, + 29.16, + 29.3, + 29.43, + 29.56, + 29.68, + 29.8, + 29.92, + 30.03, + 30.14, + 30.25, + 30.35, + 30.45, + 30.54, + 30.64, + 30.72, + 30.81, + 30.89, + 30.97, + 31.05, + 31.12, + 31.19, + 31.25, + 31.31, + 31.37, + 31.43, + 31.48, + 31.53, + 31.58, + 31.63, + 31.67, + 31.71, + 31.74, + 31.77, + 31.81, + 31.83, + 31.86, + 31.88, + 31.9, + 31.92, + 31.93, + 31.94, + 31.95, + 31.96, + 31.96, + 31.97, + 31.97, + 31.96, + 31.96, + 31.95, + 31.94, + 31.93, + 31.92, + 31.9, + 31.88, + 31.86, + 31.84, + 31.82, + 31.79, + 31.76, + 31.73, + 31.7, + 31.67, + 31.63, + 31.6, + 31.56, + 31.52, + 31.47, + 31.43, + 31.39, + 31.34, + 31.29, + 31.24, + 31.19, + 31.13, + 31.08, + 31.02, + 30.97, + 30.91, + 30.85, + 30.79, + 30.72, + 30.66, + 30.59, + 30.53, + 30.46, + 30.39, + 30.32, + 30.25, + 30.18, + 30.1, + 30.03, + 29.95, + 29.88, + 29.8, + 29.72, + 29.64, + 29.56, + 29.48, + 29.4, + 29.32, + 29.23, + 29.15, + 29.06, + 28.98, + 28.89, + 28.8, + 28.71, + 28.62, + 28.53, + 28.44, + 28.35, + 28.26, + 28.17, + 28.08, + 27.98, + 27.89, + 27.8, + 27.7, + 27.61, + 27.51, + 27.41, + 27.32, + 27.22, + 27.12, + 27.02, + 26.93, + 26.83, + 26.73, + 26.63, + 26.53, + 26.43, + 26.33, + 26.23, + 26.13, + 26.03, + 25.92, + 25.82, + 25.72, + 25.62, + 25.52, + 25.41, + 25.31, + 25.21, + 25.11, + 25.0, + 24.9, + 24.8, + 24.69, + 24.59, + 24.49, + 24.38, + 24.28, + 24.17, + 24.07, + 23.97, + 23.86, + 23.76, + 23.65, + 23.55, + 23.45, + 23.34, + 23.24, + 23.13, + 23.03, + 22.93, + 22.82, + 22.72, + 22.62, + 22.51, + 22.41, + 22.31, + 22.2, + 22.1, + 22.0, + 21.89, + 21.79, + 21.69, + 21.59, + 21.48, + 21.38, + 21.28, + 21.18, + 21.08, + 20.97, + 20.87, + 20.77, + 20.67, + 20.57, + 20.47, + 20.37, + 20.27, + 20.17, + 20.07, + 19.97, + 19.87, + 19.77, + 19.67, + 19.57, + 19.48, + 19.38, + 19.28, + 19.18, + 19.08, + 18.99, + 18.89, + 18.79, + 18.7, + 18.6, + 18.51, + 18.41, + 18.32, + 18.22, + 18.13, + 18.03, + 17.94, + 17.84, + 17.75, + 17.66, + 17.57, + 17.47, + 17.38, + 17.29, + 17.2, + 17.11, + 17.01, + 16.92, + 16.83, + 16.74, + 16.65, + 16.57, + 16.48, + 16.39, + 16.3, + 16.21, + 16.12, + 16.04, + 15.95, + 15.86, + 15.78, + 15.69, + 15.6, + 15.52, + 15.43, + 15.35, + 15.26, + 15.18, + 15.1, + 15.01, + 14.93, + 14.85, + 14.77, + 14.68, + 14.6, + 14.52, + 14.44, + 14.36, + 14.28, + 14.2, + 14.12, + 14.04, + 13.96, + 13.88, + 13.8, + 13.73, + 13.65, + 13.57, + 13.49, + 13.42, + 13.34, + 13.27, + 13.19, + 13.12, + 13.04, + 12.97, + 12.89, + 12.82, + 12.75, + 12.67, + 12.6, + 12.53, + 12.46, + 12.38, + 12.31, + 12.24, + 12.17, + 12.1, + 12.03, + 11.96, + 11.89, + 11.82, + 11.75, + 11.69, + 11.62, + 11.55, + 11.48, + 11.42, + 11.35, + 11.28, + 11.22, + 11.15, + 11.09, + 11.02, + 10.96, + 10.89, + 10.83, + 10.77, + 10.7, + 10.64, + 10.58, + 10.51, + 10.45, + 10.39, + 10.33, + 10.27, + 10.21, + 10.15, + 10.09, + 10.03, + 9.97, + 9.91, + 9.85, + 9.79, + 9.73, + 9.68, + 9.62, + 9.56, + 9.5, + 9.45, + 9.39, + 9.33, + 9.28, + 9.22, + 9.17, + 9.11, + 9.06, + 9.0, + 8.95, + 8.9, + 8.84, + 8.79, + 8.74, + 8.68, + 8.63, + 8.58, + 8.53, + 8.48, + 8.43, + 8.38, + 8.32, + 8.27, + 8.22, + 8.17, + 8.13, + 8.08, + 8.03, + 7.98, + 7.93, + 7.88, + 7.83, + 7.79, + 7.74, + 7.69, + 7.65, + 7.6, + 7.55, + 7.51, + 7.46, + 7.42, + 7.37, + 7.33, + 7.28, + 7.24, + 7.19, + 7.15, + 7.11, + 7.06, + 7.02, + 6.98, + 6.93, + 6.89, + 6.85, + 6.81, + 6.76, + 6.72, + 6.68, + 6.64, + 6.6, + 6.56, + 6.52, + 6.48, + 6.44, + 6.4, + 6.36, + 6.32, + 6.28, + 6.24, + 6.21, + 6.17, + 6.13, + 6.09, + 6.05, + 6.02, + 5.98, + 5.94, + 5.91, + 5.87, + 5.83, + 5.8, + 5.76, + 5.73, + 5.69, + 5.66, + 5.62, + 5.59, + 5.55, + 5.52, + 5.48, + 5.45, + 5.42, + 5.38, + 5.35, + 5.32, + 5.28, + 5.25, + 5.22, + 5.18, + 5.15, + 5.12, + 5.09, + 5.06, + 5.03, + 4.99, + 4.96, + 4.93, + 4.9, + 4.87, + 4.84, + 4.81, + 4.78, + 4.75, + 4.72, + 4.69, + 4.66, + 4.63, + 4.61, + 4.58, + 4.55, + 4.52, + 4.49, + 4.46, + 4.44, + 4.41, + 4.38, + 4.35, + 4.33, + 4.3, + 4.27, + 4.25, + 4.22, + 4.19, + 4.17, + 4.14, + 4.11, + 4.09, + 4.06, + 4.04, + 4.01, + 3.99, + 3.96, + 3.94, + 3.91, + 3.89, + 3.86, + 3.84, + 3.82, + 3.79, + 3.77, + 3.75, + 3.72, + 3.7, + 3.68, + 3.65, + 3.63, + 3.61, + 3.58, + 3.56, + 3.54, + 3.52, + 3.5, + 3.47, + 3.45, + 3.43, + 3.41, + 3.39, + 3.37, + 3.34, + 3.32, + 3.3, + 3.28, + 3.26, + 3.24, + 3.22, + 3.2, + 3.18, + 3.16, + 3.14, + 3.12, + 3.1, + 3.08, + 3.06, + 3.04, + 3.02, + 3.0, + 2.99, + 2.97, + 2.95, + 2.93, + 2.91, + 2.89, + 2.87, + 2.86, + 2.84, + 2.82, + 2.8, + 2.79, + 2.77, + 2.75, + 2.73, + 2.72, + 2.7, + 2.68, + 2.66, + 2.65, + 2.63, + 2.61, + 2.6, + 2.58, + 2.57, + 2.55, + 2.53, + 2.52, + 2.5, + 2.49, + 2.47, + 2.45, + 2.44, + 2.42, + 2.41, + 2.39, + 2.38, + 2.36, + 2.35, + 2.33, + 2.32, + 2.3, + 2.29, + 2.28, + 2.26, + 2.25, + 2.23, + 2.22, + 2.2, + 2.19, + 2.18, + 2.16, + 2.15, + 2.14, + 2.12, + 2.11, + 2.1, + 2.08, + 2.07, + 2.06, + 2.04, + 2.03, + 2.02, + 2.0, + 1.99, + 1.98, + 1.97, + 1.95, + 1.94, + 1.93, + 1.92, + 1.91, + 1.89, + 1.88, + 1.87, + 1.86, + 1.85, + 1.83, + 1.82, + 1.81, + 1.8, + 1.79, + 1.78, + 1.77, + 1.75, + 1.74, + 1.73, + 1.72, + 1.71, + 1.7, + 1.69, + 1.68, + 1.67, + 1.66, + 1.65, + 1.64, + 1.63, + 1.62, + 1.61, + 1.6, + 1.59, + 1.58, + 1.57, + 1.56, + 1.55, + 1.54, + 1.53, + 1.52, + 1.51, + 1.5, + 1.49, + 1.48, + 1.47, + 1.46, + 1.45, + 1.44, + 1.43, + 1.42, + 1.41, + 1.4, + 1.4, + 1.39, + 1.38, + 1.37, + 1.36, + 1.35, + 1.34, + 1.34, + 1.33, + 1.32, + 1.31, + 1.3, + 1.29, + 1.29, + 1.28, + 1.27, + 1.26, + 1.25, + 1.25, + 1.24, + 1.23, + 1.22, + 1.21, + 1.21, + 1.2, + 1.19, + 1.18, + 1.18, + 1.17, + 1.16, + 1.15, + 1.15, + 1.14, + 1.13, + 1.12, + 1.12, + 1.11, + 1.1, + 1.1, + 1.09, + 1.08, + 1.08, + 1.07, + 1.06, + 1.06, + 1.05, + 1.04, + 1.04, + 1.03, + 1.02, + 1.02, + 1.01, + 1.0, + 1.0, + 0.99, + 0.98, + 0.98, + 0.97, + 0.97, + 0.96, + 0.95, + 0.95, + 0.94, + 0.94, + 0.93, + 0.92, + 0.92, + 0.91, + 0.91, + 0.9, + 0.89, + 0.89, + 0.88, + 0.88, + 0.87, + 0.87, + 0.86, + 0.86, + 0.85, + 0.84, + 0.84, + 0.83, + 0.83, + 0.82, + 0.82, + 0.81, + 0.81, + 0.8, + 0.8, + 0.79, + 0.79, + 0.78, + 0.78, + 0.77, + 0.77, + 0.76, + 0.76, + 0.75, + 0.75, + 0.74, + 0.74, + 0.73, + 0.73, + 0.73, + 0.72, + 0.72, + 0.71, + 0.71, + 0.7, + 0.7, + 0.69, + 0.69, + 0.68, + 0.68, + 0.68, + 0.67, + 0.67, + 0.66, + 0.66, + 0.65, + 0.65, + 0.65, + 0.64, + 0.64, + 0.63, + 0.63, + 0.63, + 0.62, + 0.62, + 0.61, + 0.61, + 0.61, + 0.6, + 0.6, + 0.6, + 0.59, + 0.59, + 0.58, + 0.58, + 0.58, + 0.57, + 0.57, + 0.57, + 0.56, + 0.56, + 0.55, + 0.55, + 0.55, + 0.54, + 0.54, + 0.54, + 0.53, + 0.53, + 0.53, + 0.52, + 0.52, + 0.52, + 0.51, + 0.51, + 0.51, + 0.5, + 0.5, + 0.5, + 0.49, + 0.49, + 0.49, + 0.49, + 0.48, + 0.48, + 0.48, + 0.47, + 0.47, + 0.47, + 0.46, + 0.46, + 0.46, + 0.46, + 0.45, + 0.45, + 0.45, + 0.44, + 0.44, + 0.44, + 0.44, + 0.43, + 0.43, + 0.43, + 0.42, + 0.42, + 0.42, + 0.42, + 0.41, + 0.41, + 0.41, + 0.41, + 0.4, + 0.4, + 0.4, + 0.4, + 0.39, + 0.39, + 0.39, + 0.39, + 0.38, + 0.38, + 0.38, + 0.38, + 0.37, + 0.37, + 0.37, + 0.37, + 0.36, + 0.36, + 0.36, + 0.36, + 0.36, + 0.35, + 0.35, + 0.35, + 0.35, + 0.34, + 0.34, + 0.34, + 0.34, + 0.34, + 0.33, + 0.33, + 0.33, + 0.33, + 0.33, + 0.32, + 0.32, + 0.32, + 0.32, + 0.31, + 0.31, + 0.31, + 0.31, + 0.31, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.29, + 0.29, + 0.29, + 0.29, + 0.29, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.28, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.27, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.26, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.25, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.24, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.23, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.22, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.21, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.19, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.18, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.17, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.14, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.13, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.12, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.11, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.09, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.04, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + } +} \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..672d8ed --- /dev/null +++ b/css/style.css @@ -0,0 +1,308 @@ +/* + * ───────────────────────────────────────────────────────────────────────────── + * HormoneTrack Web — feuille de style unique. + * + * Couleurs reprises du thème Android (ui/theme/Color.kt) : + * - primaire bleu #4F5BD5, secondaire rose #D6589E, tertiaire teal #2FA48D ; + * - bandeau TransSky #5BCEFA / TransPink #F5A9B8 ; + * - labs orange #E67E22 ; WHSAH vert #2E7D32 / violet #9C27B0. + * Design : Material-ish, mobile-first, sans framework. + * ───────────────────────────────────────────────────────────────────────────── + */ + +:root { + --blue: #4f5bd5; + --blue-dark: #3a44a8; + --blue-container: #dee0ff; + --pink: #d6589e; + --pink-container: #ffd9ec; + --teal: #2fa48d; + --teal-container: #bff0e4; + --trans-sky: #5bcefa; + --trans-pink: #f5a9b8; + --chart-e2: #4f5bd5; + --chart-t: #d6589e; + --chart-whs: #2e7d32; + --chart-twhs: #9c27b0; + --lab-dot: #e67e22; + --good: #2e7d32; + --warn: #b26a00; + --error-bg: #ffdad6; + --error-fg: #410002; + --surface: #fef7ff; + --surface-variant: #e7e0ec; + --on-surface: #1d1b20; + --on-surface-variant: #49454f; + --outline: #79747e; + --outline-variant: #cac4d0; + --radius: 14px; +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + background: var(--surface); + color: var(--on-surface); + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + font-size: 15px; +} + +/* ── En-tête + bandeau trans ──────────────────────────────────────────────── */ +.topbar { + position: sticky; + top: 0; + z-index: 10; + background: var(--surface); + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; +} +.topbar h1 { font-size: 1.25rem; margin: 0; flex: 1; font-weight: 600; } +.trans-banner { + height: 3px; + background: linear-gradient(90deg, var(--trans-sky), var(--trans-pink), var(--trans-sky)); +} + +/* ── Onglets (bottom nav, comme l'app Android) ────────────────────────────── */ +.tabbar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + background: #f3edf7; + border-top: 1px solid var(--outline-variant); + z-index: 10; +} +.tabbar button { + flex: 1; + border: none; + background: none; + padding: 8px 2px 10px; + font-size: 0.72rem; + color: var(--on-surface-variant); + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + cursor: pointer; +} +.tabbar button .ico { font-size: 1.25rem; line-height: 1; } +.tabbar button.active { color: var(--blue); font-weight: 600; } + +/* Zone de contenu : au-dessus de la tabbar */ +main.content { + padding: 8px 16px 90px; + max-width: 720px; + margin: 0 auto; +} + +/* ── Cartes ───────────────────────────────────────────────────────────────── */ +.card { + background: #fff; + border: 1px solid var(--outline-variant); + border-radius: var(--radius); + padding: 14px; + margin: 10px 0; +} +.card.primary { background: var(--blue-container); border-color: var(--blue-container); } +.card.variant { background: var(--surface-variant); border-color: var(--surface-variant); } +.card.error { background: var(--error-bg); border-color: var(--error-bg); color: var(--error-fg); } +.card h3 { margin: 0 0 8px; font-size: 1rem; font-weight: 600; } +.card .label { font-size: 0.78rem; color: var(--on-surface-variant); margin-bottom: 4px; } +.card .small { font-size: 0.78rem; color: var(--on-surface-variant); } + +/* ── Boutons / chips ──────────────────────────────────────────────────────── */ +button.btn { + border: none; + border-radius: 22px; + padding: 9px 18px; + font-size: 0.9rem; + cursor: pointer; + background: var(--blue); + color: #fff; +} +button.btn.tonal { background: var(--blue-container); color: var(--blue-dark); } +button.btn.outlined { background: none; border: 1px solid var(--blue); color: var(--blue); } +button.btn.text { background: none; color: var(--blue); padding: 6px 10px; } +button.btn.danger { background: none; color: #b3261e; } +button.btn:disabled { opacity: 0.4; cursor: default; } + +.chip { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1px solid var(--outline-variant); + border-radius: 10px; + padding: 5px 10px; + font-size: 0.8rem; + cursor: pointer; + background: #fff; + user-select: none; + white-space: nowrap; +} +.chip.selected { background: var(--blue-container); border-color: var(--blue-container); color: var(--blue-dark); font-weight: 600; } +.chip.static { cursor: default; } +.chip-row { display: flex; gap: 8px; flex-wrap: nowrap; overflow-x: auto; padding: 2px 0; } +.chip-row.wrap { flex-wrap: wrap; overflow: visible; } + +.fab { + position: fixed; + right: 18px; + bottom: 78px; + width: 56px; + height: 56px; + border-radius: 18px; + border: none; + background: var(--pink); + color: #fff; + font-size: 1.6rem; + cursor: pointer; + box-shadow: 0 3px 10px rgba(0,0,0,0.25); + z-index: 9; +} + +/* ── Listes ───────────────────────────────────────────────────────────────── */ +.list-item { + display: flex; + align-items: center; + gap: 10px; + padding: 11px 4px; + border-bottom: 1px solid var(--outline-variant); + cursor: pointer; +} +.list-item:last-child { border-bottom: none; } +.list-item .main { flex: 1; min-width: 0; } +.list-item .title { font-size: 0.95rem; } +.list-item .sub { font-size: 0.8rem; color: var(--on-surface-variant); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; } +.group-header { + font-size: 0.95rem; + font-weight: 600; + color: var(--on-surface); + padding: 12px 4px 4px; +} +.icon-btn { + border: none; + background: none; + font-size: 1.1rem; + cursor: pointer; + color: var(--on-surface-variant); + padding: 6px; +} + +/* ── Formulaires ──────────────────────────────────────────────────────────── */ +.field { display: flex; flex-direction: column; gap: 3px; margin: 8px 0; flex: 1; min-width: 0; } +.field label { font-size: 0.75rem; color: var(--on-surface-variant); } +.field input, .field select, .field textarea { + border: 1px solid var(--outline); + border-radius: 8px; + padding: 9px 10px; + font-size: 0.95rem; + background: #fff; + color: var(--on-surface); + font-family: inherit; + width: 100%; +} +.field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid var(--blue); border-color: var(--blue); } +.row { display: flex; gap: 10px; align-items: center; } +.row.wrap { flex-wrap: wrap; } +.error-text { color: #b3261e; font-size: 0.82rem; margin: 4px 0; } +.hint { font-size: 0.78rem; color: var(--on-surface-variant); margin: 3px 0; } + +/* Switch (miroir Material3 Switch) */ +.switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; } +.switch input { opacity: 0; width: 0; height: 0; } +.switch .slider { + position: absolute; inset: 0; + background: var(--outline-variant); + border-radius: 26px; + transition: 0.2s; + cursor: pointer; +} +.switch .slider::before { + content: ''; + position: absolute; + width: 20px; height: 20px; + left: 3px; top: 3px; + background: #fff; + border-radius: 50%; + transition: 0.2s; +} +.switch input:checked + .slider { background: var(--blue); } +.switch input:checked + .slider::before { transform: translateX(20px); } + +/* ── Dialogs ──────────────────────────────────────────────────────────────── */ +.dialog-backdrop { + position: fixed; inset: 0; + background: rgba(0,0,0,0.45); + display: flex; align-items: center; justify-content: center; + z-index: 50; + padding: 16px; +} +.dialog { + background: var(--surface); + border-radius: 24px; + padding: 20px; + width: 100%; + max-width: 480px; + max-height: 86vh; + overflow-y: auto; +} +.dialog h2 { margin: 0 0 12px; font-size: 1.1rem; } +.dialog .actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 14px; flex-wrap: wrap; } + +/* ── Divers ───────────────────────────────────────────────────────────────── */ +.big-number { font-size: 2rem; font-weight: 700; color: var(--blue-dark); } +.unit { font-size: 1rem; color: var(--on-surface-variant); } +.t-inline { font-size: 1rem; } +.delta-up { color: var(--good); } +.delta-down { color: var(--warn); } +.legend-text { font-size: 0.78rem; margin: 2px 0; } +.legend-muted { font-size: 0.78rem; color: var(--on-surface-variant); } +.message-banner { color: var(--blue-dark); font-weight: 600; margin: 6px 0; } +.demo-banner { + background: var(--pink-container); + border-radius: 10px; + padding: 8px 12px; + font-size: 0.8rem; + margin: 8px 0; +} +.reminder-banner { + background: var(--pink-container); + border-radius: 12px; + padding: 10px 14px; + margin: 8px 0; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +canvas.chart { width: 100%; display: block; touch-action: none; } +canvas.chart-cursor { cursor: grab; } + +/* Toast (feedback Paramètres) */ +.toast { + position: fixed; + bottom: 96px; + left: 50%; + transform: translateX(-50%); + background: #322f35; + color: #fff; + border-radius: 10px; + padding: 10px 18px; + font-size: 0.88rem; + z-index: 60; + max-width: 90vw; +} + +/* Écrans larges : la tabbar passe à gauche */ +@media (min-width: 800px) { + .tabbar { right: auto; width: 170px; top: 0; bottom: 0; flex-direction: column; justify-content: flex-start; padding-top: 64px; border-top: none; border-right: 1px solid var(--outline-variant); } + .tabbar button { flex-direction: row; justify-content: flex-start; gap: 12px; padding: 14px 20px; font-size: 0.9rem; } + main.content { margin-left: 170px; max-width: 760px; } + .fab { bottom: 24px; } + .toast { bottom: 32px; } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..d8c4e33 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,83 @@ +# CHANGELOG — HormoneTrack Web + +> Journal des versions de l'**app web** (séparé du CHANGELOG Android — +> `docs/CHANGELOG.md` à la racine du dépôt). Convention Keep a Changelog : +> section la plus récente EN PREMIER ; ce fichier est lu par le dialog +> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`). + +## [1.4.10] — 2026-09-08 + +Version initiale de la version web — portage navigateur de l'app Android +v1.4.10, 100 % locale (données dans le localStorage, aucun serveur +applicatif, aucune télémétrie). + +### Moteur PK (fidèle à l'Android, testé) + +- **Trois modèles superposables** : Estrannaise (tables ODS de + `pk_profiles.json`, même asset que l'app Android), Transfem Science (forme + close V3C de la méta-analyse, 7 esters), WHSAH (fit license-free de Mona, + 6 esters — PEP non couvert) + Bateman paramétrable (gel/patch/oral). +- Pics de l'article TFS épinglés (±2 %) ; équilibre EV 5 mg/7 j = Figure 11 + (Cmax 384 / Cmin 142 / Cavg 269) ; fidélité Mona WHSAH (EV 367,5 @ 1,69 j, + EEn 187,9 @ 4,99 j, t½ 7,34 j). +- Invariants Android préservés : bisection `computeKa` dans le bon sens + (#19), modèle strict + extrapolation terminale ≥ 1 % du pic (#20/#21), + lookup insensible à la casse (#22), conversion des labs T (#23/#26), + garde de significativité de calibration (#61), créneaux de prévision + passés sautés (#35), rappels sur la grille Posologie (#52). +- Calibration **par période d'ester** et **par modèle affiché** (#60) : + auto-calibration optionnelle (désactivée par défaut) + calibration + manuelle par traitement ; k T par période d'ester. + +### Fonctionnalités + +- Accueil : niveau actuel estimé (E2 pg/mL + T ng/mL) avec delta « vs il y a + 6 h » réel (fix #53), cartes d'alerte de seuil, carte prochaine dose + (jours au-delà de 24 h, fix v1.4.1), log rapide, mini-graphique 24 h. +- Graphiques : 24 h/7 j/30 j, zoom 6 h → 300 j (pinch, molette, boutons, + focal du pinch conservée), panoramique avec **cumul fractionnaire** des + deltas (fix #62), prévision avec extension de fenêtre sans saut (fix #54) + et horizon borné 30 j–1 an, toggles 3 modèles alignés sur les traitements + (fix #58), pics/creux avec valeurs, marqueurs de doses réelles et + projetées (fix #55), labels X à minuit local dans le fuseau choisi + (v1.4.5), labs T convertis en ng/mL au rendu. +- Doses : historique groupé par jour, Δ jours depuis la dose précédente du + même traitement, édition (override d'ester par injection selon le modèle), + en-tête « temps sous THS » (3 placeholders, bug #47 épinglé). +- Analyses : prise de sang E2 + T en une entrée, groupement par timestamp, + suppression par prise entière, unités T multiples (ng/mL, ng/dL, ng/L, + nmol/L). +- Traitements : 22 presets, éditeur complet (ester/modèle/Bateman, + calibration, Posologie, rappel), traitement inactif = drapeau + administratif (la simulation reste, bug v1.2.4). +- Rappels web : notifications navigateur tant que la page est ouverte + (bannière in-app + actions « Loguer maintenant » / « Reporter 1 h »). + Limite documentée : pas de notification onglet fermé. +- Seuils d'alerte : évaluation du niveau estimé, notification web, anti-spam + par état persisté (même codec que l'app Android). +- Sauvegarde JSON **compatible Android dans les deux sens** (BackupData v2, + rétrocompat v1, import en mode écrasement avec IDs conservés) ; paramètres + utilisateur transportés (langue, auto-cal, seuils). +- Logs de diagnostic exportables (buffer 500 lignes, même format que + l'app Android). +- FR + EN (langue par app, indépendante du système), dialog « Nouveautés » + après mise à jour. + +### Processus + +- 121 tests Node (`node --test`) : profils ODS (pins ODS identiques à + l'Android), fidélité TFS/WHSAH, moteur, calibration, backup (dont import + des exports Android réels de `local-test-data/` si présents), helpers du + graphique, alertes, rappels, changelog, store. +- `scripts/check.sh` : syntaxe de tous les modules + cohérence i18n FR/EN + + tests + smoke HTTP optionnel. +- **Dépôt GIT SÉPARÉ** de l'app Android (HormoneTrack-web) — historique + 100 % propre (aucune dette de confidentialité), versions alignées sur + l'Android, changelogs totalement indépendants. + +### Non porté (impossible ou hors périmètre navigateur) + +- Événements d'agenda récurrents (pas de CalendarProvider dans un + navigateur) — l'éditeur affiche une note explicative. +- Notifications quand l'onglet est fermé (aucun scheduler système côté web). +- Verrou biométrique, widget — non implémentés non plus côté Android v1. diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md new file mode 100644 index 0000000..ca72381 --- /dev/null +++ b/docs/DEVELOPPEMENT.md @@ -0,0 +1,608 @@ +# Documentation de développement — HormoneTrack **Web** + +> Doc de référence pour toute session (humaine ou IA) travaillant sur la +> version web : architecture, correspondances avec l'app Android, format du +> backup, tests, déploiement, pièges. Le code est commenté au même niveau que +> la doc — cette page explique le POURQUOI et le LIEN à l'Android. +> +> Projet : `~/projects/HormoneTrack-web` — **dépôt GIT SÉPARÉ** de l'app +> Android (décision v1.4.10, cf §3) ; l'app Android vit dans +> `~/projects/HormoneTrack` +> ([gitea.cloudyfy.fr/Siphonight/HormoneTrack](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack), +> docs mère : son `docs/DEVELOPPEMENT.md`). +> Guide utilisateur : [../README.md](../README.md). + +--- + +## Table des matières + +1. [Contexte & objectifs du portage](#1-contexte--objectifs-du-portage) +2. [Stack & décisions structurantes](#2-stack--décisions-structurantes) +3. [Deux dépôts séparés : HormoneTrack (APK) ↔ HormoneTrack-web](#3-deux-dépôts-séparés--hormonetrack-apk--hormonetrack-web) +4. [Architecture & correspondance fichier par fichier](#4-architecture--correspondance-fichier-par-fichier) +5. [Persistance (localStorage) & schéma de données](#5-persistance-localstorage--schéma-de-données) +6. [Compatibilité des backups avec l'Android](#6-compatibilité-des-backups-avec-landroid) +7. [Fuseaux horaires (Intl)](#7-fuseaux-horaires-intl) +8. [Tests — processus complet](#8-tests--processus-complet) +9. [Workflow de développement](#9-workflow-de-développement) +10. [Processus de push & releases (sync Android)](#10-processus-de-push--releases-sync-android) +11. [Bugs potentiels évités pendant le portage](#11-bugs-potentiels-évités-pendant-le-portage) +12. [Limites connues & non-portés](#12-limites-connues--non-portés) +13. [Idées d'évolution](#13-idées-dévolution) + +--- + +## 1. Contexte & objectifs du portage + +L'utilisatrice suit son THS sur l'app Android (cf doc mère). La version web +existe pour : + +- **disponibilité** : consulter ses courbes depuis n'importe quel appareil + (ordinateur, iPad) sans installation ; +- **portabilité des données** : les backups JSON de l'app Android + s'importent tels quels (et réciproquement) — pas de verrou. + +Règles du portage, fixées au départ : + +1. **Fidélité du moteur PK** : mêmes maths, mêmes paramètres, mêmes pins de + tests que l'Android (les courbes doivent être INDISTINGUABLES). +2. **Compatibilité backup bidirectionnelle** (schéma `BackupData` v2, champs + Gson/Kotlin identiques). +3. **100 % local** : tout dans le navigateur (localStorage), le serveur ne + sert que des fichiers statiques. +4. **Aucune dépendance applicative** : pas de framework, pas de build, pas + de node_modules à l'exécution — le dépôt est un site statique déployable + tel quel. (Les devDependencies npm ne servent QU'aux tests E2E.) +5. **Dépôt GIT SÉPARÉ** de l'app Android (décision v1.4.10, cf §3) — + changelogs, docs, historique, tags et releases totalement indépendants. + +## 2. Stack & décisions structurantes + +| Choix | Décision | Pourquoi | +|---|---|---| +| Langage | JavaScript ES modules natifs (pas de TS, pas de build) | zéro chaîne de compilation → le déploiement = copier le dossier ; les types critiques sont documentés en JSDoc et épinglés par 121 tests | +| Framework UI | Aucun — DOM via un helper `el()` (`ui/components.js`) | même philosophie que la v1 Android (pas de ViewModel/DI) ; une seule abstraction à connaître | +| Rendu graphique | Canvas 2D natif (portage de `CurveChart.kt`) | pas de lib de charts : fidélité du rendu, zéro dépendance | +| Persistance | `localStorage` (clés préfixées `hormonetrack.`) | données petites (JSON) ; backend INJECTABLE → testable en Node (`store.setBackend(new Map())`) | +| i18n | dictionnaires JS (`util/i18n.js`), langue persistée | miroir values/strings.xml ; `system` suit `navigator.language` | +| Fuseaux | `Intl.DateTimeFormat` (natif) | remplace `java.util.Calendar/TimeZone` — cf §7 | +| Tests | `node --test` (runner natif Node ≥ 20) + E2E playwright | zéro dépendance pour les tests unitaires ; l'E2E utilise le build Firefox de Playwright | +| Asset PK | **copie** de l'asset Android (`pk_profiles.json`) | mêmes tables que l'app Android — `tests/pk-profiles.test.js` vérifie les 6 profils et les pics ODS ; TOUTE retouche se fait dans les DEUX dépôts (règle §3) | + +⚠️ **Ne pas introduire de bundler/build sans décision inverse documentée** : +le déploiement « copier un dossier » est une propriété voulue du projet. + +## 3. Deux dépôts séparés : HormoneTrack (APK) ↔ HormoneTrack-web + +**Décision (8 sept. 2026)** : le portage vit dans son PROPRE dépôt git, +avec ses propres remotes, historique, tags et releases — les deux apps ne +partagent plus qu'une CONVENTION de version. + +### Pourquoi (motifs qui ont tranché) + +1. **Confidentialité** : l'historique du dépôt Android contient une dette + connue et jugée (fragments de labs dans les révisions v1.1.0→v1.2.3, cf + §8.bis de la doc Android) — non réexportable sans re-décision. Le dépôt + web **naît avec un historique 100 % propre** : il peut devenir public un + jour sans AUCUNE re-décision, pendant que la dette reste confinée à + l'Android. +2. **Releases indépendantes** : « pas de release web pour l'instant » = + ne rien faire côté web. Les releases APK ne traînent jamais un artifact + web et inversement. +3. **Simplicité** : plus de conventions de préfixes (`web:`) ni de tags + appariés à maintenir dans un historique unique. + +### Ce que ça implique (règles de croisement) + +| Aspect | Règle | +|---|---| +| **Version** | `WEB_VERSION` = version ANDROID portée, **alignée à chaque release** (web v1.4.10 = portage complet de l'Android v1.4.10, sauf impossibilités structurelles documentées §12) | +| **Tags** | les DEUX dépôts tagguent `vX.Y.Z` au même rythme (dans HormoneTrack-web, `v1.4.10` = l'état web correspondant à l'Android v1.4.10 — aucun risque d'ambiguïté, dépôts séparés) | +| **Changelogs** | TOTALEMENT SÉPARÉS : `docs/CHANGELOG.md` (web, lu par le dialog « Nouveautés ») vs `docs/CHANGELOG.md` Android (source des releases Gitea APK) — jamais fusionnés, jamais synchronisés automatiquement | +| **Asset PK** (`pk_profiles.json`) | COPIE dans chaque dépôt. ⚠️ Toute retouche (re-fit, nouvel ester) se fait dans les DEUX dépôts avec les DEUX suites de tests (pins identiques des pics ODS) — une divergence ferait échouer la CI de l'un ou l'autre | +| **Fix de moteur cross-cutting** | le même fix se fait dans les DEUX dépôts (même test épinglé, même n° de bug) — cf §11 pour l'exemple du portage | +| **Données de test réelles** (`local-test-data/`) | vivent dans le dépôt ANDROID (gitigné, jamais versionnées). Le web ne les duplique PAS : `tests/helpers.localTestDataDir()` les cherche dans le dépôt Android voisin (layout standard `~/projects/HormoneTrack*`), skip des tests si absent | +| **Commits** | convention Android conservée : commits par couche (noyau / UI / docs), release = commit dédié | + +### Layout disque attendu + +``` +~/projects/ +├── HormoneTrack/ dépôt Android (existant) — héberge local-test-data/ +│ └── local-test-data/ exports réels, gitigné, JAMAIS versionnés +└── HormoneTrack-web/ dépôt web (ce projet) +``` + +Les tests web cherchent les exports dans le dépôt voisin : si tu clones +HormoneTrack-web ailleurs, les 22 tests de régression data-driven sont +simplement SKIPPÉS (comportement Assume identique à l'Android) — ou +dépose les exports dans un `local-test-data/` local au dépôt web (gitigné). + +### Remotes du dépôt web (même double-instance que l'Android) + +- `origin` → `https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web` (privé, HTTPS + trousseau) +- `farewell` → `git@farewell:Siphonight/HormoneTrack-web.git` (SSH, alias existant) + +⚠️ La CRÉATION des repos sur les deux instances Gitea nécessite le scope +`write:user` (le token `write:repository` ne suffit PAS, et le +push-to-create est désactivé sur farewell) — cf §10. + +## 4. Architecture & correspondance fichier par fichier + +Pas de DI, pas de reactive framework : le store pub/sub notifie les écrans, +qui se re-rendent entièrement (le DOM est jetable, l'état vit dans le store +et le hash). Le routing est par **hash** (`#home`, `#chart`, `#doses`, +`#labs`, `#treatments`, `#settings`, `#treatment-edit/{id}`) — navigable +headless, lien profond possible. + +``` +HormoneTrack-web/ (dépôt séparé) +├── index.html page unique (SPA) — shell construit par app.js +├── css/style.css thème (couleurs = Color.kt Android), mobile-first +├── LICENSE GPL-3.0 (copie de l'Android — dépôt autonome) +├── .gitignore node_modules, package-lock, local-test-data +├── assets/pk_profiles.json COPIE de l'asset Android (tables Estrannaise) +├── js/ +│ ├── pk/ ── NOYAU PUR (aucun DOM/storage) — testable Node ── +│ │ ├── pk-engine.js PharmacokineticEngine.kt (1/4) : Bateman, +│ │ │ contribution d'une dose, e2At/computeCurve, +│ │ │ modèle T, convertTToNgMl, prévision +│ │ ├── pk-calibration.js engine (2/4) : échelles par ester/période/ +│ │ │ modèle (#60), garde labIsSignificant (#61), +│ │ │ autoCalibrated, k T, calibration manuelle +│ │ ├── pk-reminders.js engine (3/4) : nextReminderFireFor (grille +│ │ │ Posologie, fix #52) +│ │ ├── pk-extrema.js engine (4/4) : detectExtrema (pics/creux) +│ │ ├── pk-profile-store.js PKProfileStore.kt : asset + échantillonnage, +│ │ │ lookup insensible à la casse (#22), modèle +│ │ │ strict, extrapolation terminale (#20) +│ │ ├── transfem-science-models.js V3C méta-analyse TFS (7 esters, params +│ │ │ copiés à l'identique du Kotlin) +│ │ ├── whsah-models.js fit WHSAH de Mona (6 esters, params identiques) +│ │ ├── alerts.js Alerts.kt : seuils opt-in, codec d'état, +│ │ │ shouldNotify (anti-spam) +│ │ ├── presets.js PKPresets.kt (22 presets, clés i18n) +│ │ ├── chart-helpers.kt fonctions pures de CurveChart.kt : +│ │ │ panDeltaHours (#62), xLabelTicks (#55), +│ │ │ pointHoursBefore (#53), clampPanHours (#54), +│ │ │ niceCeil, stepForRange, forecast*, +│ │ │ defaultModelToggles (#58) + helpers Intl +│ │ └── index.js barrel (point d'import des écrans/tests) +│ ├── data/ +│ │ ├── models.js Treatment/DoseLog/LabResult (CHAMPS GSON +│ │ │ IDENTIQUES — ne jamais renommer, cf §6), +│ │ │ enums, helpers (choicesForModel, labels) +│ │ ├── store.js persistance localStorage + pub/sub + import +│ │ │ écrasement (miroir Room + BackupManager) +│ │ └── backup.js BackupData v2 : build/parse compat Android, +│ │ noms de fichiers d'export (ExportFileNames) +│ ├── util/ +│ │ ├── i18n.js dictionnaires FR/EN + t() (+ presets) +│ │ ├── format.js formatDose/formatLabValue/formatValue, +│ │ │ HrtDuration, formatage Intl des dates +│ │ ├── changelog.js ChangelogHelper.kt (isVersionNewer, +│ │ │ sectionsSince — comparaison numérique #38) +│ │ └── app-log.js AppLog.kt : journal 500 lignes, fail-safe +│ └── ui/ ── ÉCRANS (DOM uniquement ici) ── +│ ├── app.js HormoneTrackApp+MainActivity+Root : shell, +│ │ routing hash, « Nouveautés » (markdown +│ │ rendu), hook ?demo=1, boucle ticks +│ ├── components.js el(), showDialog/toast, fields, switch, +│ │ chips, FAB, dateTimeField (M3 → natifs) +│ ├── dialogs.js DoseDialog.kt (override ester par modèle) +│ │ + LabDialog.kt (E2+T une entrée) +│ ├── home.js HomeScreen.kt (niveau actuel #53, alertes, +│ │ prochaine dose, log rapide, mini-chart) +│ ├── chart.js ChartScreen.kt : fenêtre/pan/zoom (fixes +│ │ #54/#56/#62), toggles, calibration par +│ │ modèle (#60), prévision +│ ├── chart-canvas.js CurveChart.kt : rendu Canvas multi-séries, +│ │ labs orange, marqueurs, extrema +│ ├── doses.js DosesScreen.kt (groupes jour, Δ jours, +│ │ temps sous THS — 3 placeholders, bug #47) +│ ├── labs.js LabsScreen.kt (groupement, suppression +│ │ par prise entière, sélecteur E2/T) +│ ├── treatments.js TreatmentsScreen.kt (cartes, modelLabelKey #59) +│ ├── treatment-editor.js TreatmentEditorScreen.kt (presets, +│ │ calibration, Posologie, rappel, actif) +│ ├── settings.js SettingsScreen.kt (langue, auto-cal, T, +│ │ seuils, fuseau, notifs, backup, logs, à propos) +│ └── reminders.js ReminderManager+AlertNotifier (web) : +│ boucle 30 s rappels + 5 min seuils, +│ Notification API + bannière in-app +├── docs/ +│ ├── DEVELOPPEMENT.md cette doc +│ └── CHANGELOG.md journal web (lu par le dialog « Nouveautés ») +├── scripts/ +│ ├── serve.py serveur statique de dev (no-cache, MIME ok) +│ ├── check.sh vérifications 1→6 (syntaxe, i18n, tests, E2E, HTTP) +│ ├── i18n-check.mjs cohérence FR/EN + clés utilisées +│ └── e2e.mjs E2E playwright (Firefox headless, ?demo=1) +├── tests/ ── node --test (miroirs des suites Kotlin) ── +│ ├── helpers.js fixtures (makeTreatment/makeDose/makeLab, asset) +│ ├── pk-profiles.test.js ← PKProfileStoreTest.kt (pics ODS, casse #22…) +│ ├── tfs-models.test.js ← TransfemScienceModelsTest.kt (article ±2 %) +│ ├── whsah-models.test.js ← WhsahModelsTest.kt (Mona ±2 %) +│ ├── pk-engine.test.js ← PharmacokineticEngineTest.kt (+ #35, §6.bis) +│ ├── calibration.test.js ← CalibrationPerModel/ScaleFactorWhsahRepro/ +│ │ V120Features (fixes #60/#61, périodes d'ester) +│ ├── backup.test.js ← BackupGsonTest.kt + exports Android RÉELS +│ │ (local-test-data/ si présent, sinon skip) +│ ├── chart-helpers.test.js ← ChartZoomTest.kt (#53/#54/#55/#58/#62) +│ ├── alerts.test.js ← AlertsTest.kt (codec, anti-spam) +│ ├── misc.test.js ← ReminderSchedule/Extrema/Changelog/HrtDuration +│ └── store.test.js CRUD, IDs auto, CASCADE, pub/sub, corruption +└── package.json scripts npm + devDependencies (E2E seulement) +``` + +### Points clés de l'architecture + +- **Le noyau `js/pk/` est PUR** : aucune importation de `data/`, `ui/` ou + DOM → testé en Node exactement comme le Kotlin l'est en JVM. C'est la + garantie que les courbes web == courbes Android. +- **Le store est le seul point de persistance** : les écrans ne touchent + jamais localStorage directement ; les mutations passent par + `store.*` qui notifie les abonnés. +- **`isActive` = drapeau administratif** (Android §6.bis, appliqué tel quel) : + la saisie (chips, dropdown création) filtre les actifs ; la simulation, + la calibration et les rappels du moteur reçoivent TOUS les traitements. +- **Calibration par modèle affiché** (fix #60) : l'écran Graphiques calcule + une `autoCalibrated` par modèle (ESE/TFS/WHS) ; l'accueil utilise le + modèle stocké — comme l'Android. +- **« now » mémoïsé sur un tick minute** (fix #56 transposé) : `ctx.nowMs` + est fourni par le shell et ne change qu'au re-rendu (tick 60 s, mutation + du store, navigation) — jamais de dérive de fenêtre pendant les gestes. +- **Le graphique est purement déclaratif** : pan/zoom vivent dans + `chart.js` (parent), `chart-canvas.js` ne fait que dessiner une fenêtre + donnée — même séparation que Compose (piège #62/§11 Android). + +## 5. Persistance (localStorage) & schéma de données + +Clés (préfixe `hormonetrack.`) — miroir des tables Room / clés DataStore : + +| Clé | Contenu | Équivalent Android | +|---|---|---| +| `treatments` | tableau JSON d'objets Treatment (champs = entités Room) | table `treatments` | +| `doseLogs` | idem DoseLog (FK `treatmentId`, CASCADE au delete) | table `dose_logs` | +| `labResults` | idem LabResult | table `lab_results` | +| `tConfig` | `{base, floor, k}` (défauts 6.0 / 0.2 / 0.19) | DataStore `t_base/t_floor/t_k` | +| `settings` | `{language, autoCalibrate, alertE2High/Low, alertTHigh/Low, chartTimezone, changelogSeenVersion, alertNotifiedState}` (fusion avec défauts à la lecture) | DataStore (plusieurs clés) | +| `debugLog` | texte, `\n` (buffer 500 lignes) | `filesDir/debug-log.txt` | + +Règles : + +- **IDs auto-incrémentés par table** (max + 1) — miroir Room + `autoGenerate` ; à l'import, les IDs du backup sont conservés (FK + dose→traitement) et les compteurs repartent de max+1. +- **Import = écrasement** (miroir v1.2.6) : effacement enfants→parents puis + restauration, un seul `emit()` final. +- **Donnée corrompue ≠ crash** : `read()` retombe sur le défaut (même + philosophie qu'AppLog.init Android). +- `calendarEventId` existe dans les objets (fidélité du schéma) mais reste + TOUJOURS `null` côté web. + +## 6. Compatibilité des backups avec l'Android + +Format `BackupData` v2 — **verrouillé par des tests des deux côtés** : + +```json +{ + "version": 2, + "exportedAt": 1749000000000, + "treatments": [ { "id": 1, "name": "…", "type": "ESTRADIOL", "route": "INJECTION_SUBCUT", + "doseAmount": 5, "doseUnit": "mg", "isActive": true, "notes": null, + "esterType": "EEN", "pkModel": "TFS", + "absorptionHours": 156, "eliminationHalfLifeHours": 110, + "bioavailabilityFraction": 1, "scaleFactor": 1, + "forecastIntervalDays": 7, "reminderHour": 18, "reminderMinute": 0, + "reminderEnabled": true, "calendarEventId": null, "createdAt": … } ], + "doseLogs": [ { "id": 1, "treatmentId": 1, "timestamp": …, "doseAmount": 5, + "notes": null, "esterType": null } ], + "labResults": [ { "id": 1, "marker": "E2", "value": 300, "unit": "pg/mL", + "timestamp": …, "notes": null } ], + "tConfig": { "base": 6.0, "floor": 0.2, "k": 0.19 }, + "settings": { "language": "fr", "autoCalibrate": false, + "alertE2High": null, "alertE2Low": null, + "alertTHigh": null, "alertTLow": null } +} +``` + +- **Champs PLATS** dans `settings` (miroir `UserSettings.kt` — Gson lit par + réflexion côté Android). ⚠️ Les clés web extra (`chartTimezone`, + `alertNotifiedState`, `changelogSeenVersion`) sont **exclues** de + l'export (testé) — un backup importé dans l'Android ne doit rien contenir + d'inconnu. +- **Rétrocompat v1** : backup sans `settings` → `settings = null` → + l'import ne touche pas aux réglages (comportement Gson identique). +- **L'import web est un écrasement** (dialog d'avertissement, bouton + « Effacer & restaurer ») ; le `tConfig` et les réglages du backup sont + restaurés comme sur l'Android. +- `tests/backup.test.js` importe les **vrais exports Android** via + `helpers.localTestDataDir()` (dépôt Android voisin, skip si absent — + comportement `Assume` identique au Kotlin) — garde de confidentialité + identique : ces fichiers restent HORS dépôts, et les assertions sont + data-driven (aucune valeur réelle en dur). + +**Règle de maintenance** : tout changement de schéma (côté Android OU web) +doit (a) passer par les deux suites de tests backup, (b) rester +rétrocompatible, (c) être documenté dans les DEUX docs de dev. + +## 7. Fuseaux horaires (Intl) + +L'Android utilise `Calendar`/`TimeZone` ; le web utilise `Intl` : + +- `zonedParts(ms, tz)` = `Calendar.getInstance(tz).get(...)` (via + `Intl.DateTimeFormat.formatToParts`) ; +- `zonedTimeToMs(parts, tz)` = construction inverse avec **deux passes de + correction DST** (mesure de l'offset à l'instant naïf, puis à l'instant + corrigé — point fixe) ; +- `xLabelTicks` (fix #55) : les pas ≥ 24 h sont alignés sur **minuit LOCAL + du fuseau de lecture** (Paramètres → « Fuseau du graphique », vide = + navigateur), avancés de N jours CALENDAIRES (DST-safe, testé sur la + traversée de fin mars) ; les pas horaires sur des heures rondes locales. + +Pièges rencontrés : + +- `Intl` renvoie `hour: '24'` avec `h24` et pas `h23` — toujours + `hourCycle: 'h23'` ; +- un formatter avec SEULEMENT `minute` ne pads pas à 2 chiffres partout — + tester sur `(hour, minute)` ensemble ; +- `-0` : `Math.trunc(-0.8) === -0` — `assert.equal(-0, 0)` échoue en strict ; + les helpers normalisent (`clampPanHours`, `panDeltaHours`). + +## 8. Tests — processus complet + +### Panorama + +| Niveau | Outil | Couverture | Commande | +|---|---|---|---| +| Syntaxe | `node --check` | tous les modules ES | `bash scripts/check.sh` | +| i18n | `scripts/i18n-check.mjs` | clés FR/EN synchronisées + clés utilisées | (dans check.sh) | +| Unitaires | `node --test` | **121 tests** : noyau PK, calibration, backup, store, helpers, alertes, rappels, changelog | `npm test` | +| E2E navigateur | playwright-core + build Firefox | app réelle : rendu, canvas peint (pixels), navigation, dialog changelog, langue | `npm run e2e` | +| Smoke HTTP | curl | ressources clés en 200 | `check.sh --with-serve` | + +La commande unique avant tout push : **`bash scripts/check.sh`** (tout +exécuter, E2E inclus s'il est installé). + +### Installation (une fois) + +```bash +cd web +npm i # devDependencies (playwright) — l'APP n'en a aucune +npx playwright install firefox # le build Firefox piloté (~90 Mo, cache ~/.cache) +``` + +⚠️ Le **Firefox système** ne convient PAS : depuis ~Firefox 150, le +protocole Juggler de Playwright n'y est plus fonctionnel (exit immédiat au +launch) — d'où le build dédié dans le cache Playwright, hors dépôt. + +### Ce que chaque suite épinglera TOUJOURS + +- **pk-profiles** : les 6 profils × 8001 points, pics == valeurs ODS + (61,12 / 3,44 / 31,35 / 58,96 / 10,11 / 31,97), casse `EEn` (#22), + modèle strict (#21), extrapolation ≥ 1 % du pic (#20). +- **tfs-models** : pics/t½ des Tableaux 9–10 ±2 %, équilibre EV 5 mg/7 j = + Figure 11, PEP, EU ultra-rapide. *Toute retouche de + transfem-science-models.js passe par là.* +- **whsah-models** : fidélité Mona ±2 %, PEP non couvert, EEn J+1 ×3. +- **pk-engine** : pic Bateman ≈ Tmax (bisection #19), dispatch 3 modèles, + override par dose, §6.bis (inactif simulé), prévision (#35), T monotone. +- **calibration** : par période d'ester, **par modèle** (#60, chaque courbe + calibrée passe par le lab), garde #61 (labs hors fenêtre exclus des DEUX + pipelines — jamais de médiane contaminée), k T contre l'E2 CALIBRÉE. +- **backup** : round-trip v2, rétrocompat v1, écrasement + IDs conservés, + **imports des exports Android réels** (data-driven, hors dépôt). +- **chart-helpers** : cumul fractionnaire du pan (#62), minuit local (#55), + pointHoursBefore (#53), clamps (#54), toggles (#58). +- **alerts / misc / store** : codec strict, anti-spam, grille Posologie + (#52), extrema, comparaison numérique de versions (#38), résilience du + store (donnée corrompue → défauts). + +### Écrire un nouveau test + +1. Le code visé doit être dans `js/pk/` (pur) ou testable via le store + (backend Map) — l'UI est couverte par l'E2E. +2. Réutiliser `tests/helpers.js` (`makeTreatment/makeDose/makeLab`, + `initProfiles`, `assertClose`). +3. ⚠️ Les fixtures ont des **defaults identiques aux entités Kotlin** : + `id: 0` = « pas en base » (un `id: 1` par défaut a faussé trois tests + pendant le portage — cf §11). +4. Les scénarios de labs/calibration se posent **data-driven** : + `lab = e2At(...) × facteur` — ne jamais recalculer la prédiction à la + main (l'accumulation est celle du moteur). + +### E2E (scripts/e2e.mjs) + +- Démarre `serve.py` sur un port libre en argument, lance le Firefox + Playwright headless, navigue avec `?demo=1` (données fictives banniérées, + jamais sur un store non vide) ; +- vérifie le **rendu réel** : cartes, valeurs, dialog « Nouveautés » + (markdown rendu), navigation des 5 onglets + Paramètres, **pixels du + canvas** (`getImageData` — le graphique PEINT vraiment) ; +- vérifie l'**absence d'erreur console/page** sur toute la session ; +- captures dans `/tmp/hrt-web-shots/` (inspection visuelle). +- ⚠️ Course au screenshot évitée par le marqueur `body[data-ready="1"]` + (posé par `renderRoute` une fois l'asset chargé) — attendre ce sélecteur, + jamais un `sleep` nu. +- ⚠️ Le seed de langue (`addInitScript`) est **conditionnel** : l'écraser à + chaque navigation effacerait `changelogSeenVersion` et ferait réapparaître + le dialog à chaque `page.goto` (une goto = reboot complet de la SPA). + +## 9. Workflow de développement + +```bash +cd web +python3 scripts/serve.py # → http://127.0.0.1:8970 (no-cache : F5 = code à jour) +# … développement … +bash scripts/check.sh # AVANT chaque commit (syntaxe + i18n + tests + E2E) +``` + +Règles de code (appliquées partout, à conserver) : + +- **Tout fichier commence par un bloc KDoc** qui dit QUOI il porte (fichier + Kotlin d'origine), POURQUOI il existe et les invariants à préserver. +- **Toute fonction non triviale a un JSDoc** avec `@param`/`@returns` et, + quand c'est un fix Android transposé, le numéro du bug (#NN). +- **Le noyau `js/pk/` ne connaît ni le DOM ni le storage** — si une feature + exige l'inverse, c'est le design qu'il faut revoir. +- **Strings** : passer par `t('clé')`, dans les DEUX dictionnaires + (`i18n-check.mjs` bloque sinon) — leçon Android §12. +- **Champs de modèles** : ne JAMAIS renommer un champ de + `data/models.js` sans vérifier `BackupManager.kt` (compat Gson, cf §6). +- **Tout fix de bug** : test qui l'épingle d'abord (rouge), puis fix, puis + §11 de cette doc + `docs/CHANGELOG.md`. + +## 10. Processus de push & releases (sync Android) + +Les releases web sont **SYNCHRONISÉES avec les releases APK** (décision du +8 sept. 2026) : chaque feature release de l'Android est portée et tagguée +au MÊME numéro dans ce dépôt — l'utilisatrice a toujours la parité des +fonctionnalités (sauf impossibilités structurelles §12). La publication +Gitea (release avec assets) du côté web est GELÉE tant que la qualité n'est +pas validée au standard APK : en attendant, une release web = commit + tag +UNIQUEMENT. + +### Remotes + +``` +origin https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web (privé, HTTPS + trousseau) +farewell git@farewell:Siphonight/HormoneTrack-web.git (SSH, alias existant) +``` + +⚠️ **Création des repos** (one-shot, faite par l'utilisatrice — le token +`write:repository` ne permet PAS de créer un repo via API, et le +push-to-create est désactivé sur farewell) : créer `HormoneTrack-web` sur +les DEUX instances (cloudyfy + farewell), privé, puis configurer les +remotes ci-dessus. (Historique : Android a fait la même opération au +push initial v1.2.3, cf doc Android §16.) + +### Checklist de release SYNC (Android + web, une seule session) + +1. **Android** (doc Android §16 checklist complète) : bump `versionName`/ + `versionCode`, tests + lint verts, section `docs/CHANGELOG.md`, commit, + tag annoté `vX.Y.Z`. +2. **Web** (cette doc) : + a. porter la/les features si pas déjà fait (commits par couche) ; + b. `WEB_VERSION = "X.Y.Z"` dans `js/ui/settings.js` **= même numéro que + l'Android** + section `## [X.Y.Z]` en tête de `docs/CHANGELOG.md` + (le dialog « Nouveautés » la lira) ; + c. `bash scripts/check.sh` vert (E2E inclus) ; + d. commit `vX.Y.Z — résumé du portage` + tag annoté `vX.Y.Z`. +3. **Push des deux dépôts** : + ```bash + # Android + git push origin main --tags && git push farewell main --tags + # Web + git push origin main && git push origin vX.Y.Z + git push farewell main && git push farewell vX.Y.Z + ``` + ⚠️ Ne JAMAIS comparer les versions en chaînes (piège #38 Android) — + tout script futur passe par une comparaison numérique. +4. **Releases Gitea** : + - Android : `publish-release.py` sur les deux instances (2 APK), cf doc + Android §16.bis ; + - Web : **GELÉ** tant que la parité qualité n'est pas validée — sinon + créer la release avec le corps = section `docs/CHANGELOG.md` et, si + un jour on distribue un zip, le vérifier par téléchargement (leçon + #43 Android : une invocation fait tout, les uploads rapprochés se + remplacent mutuellement). +5. **Smoke-test** : Android sur le Pixel 9 (checklist §21 Android) ; web + sur le navigateur : import d'un backup réel, pan/zoom, export/import, + langue. + +### Avant CHAQUE push (release ou non) + +1. `bash scripts/check.sh` **vert** (E2E inclus si installé) ; +2. les données de test réelles restent **hors des DEUX dépôts** + (`git check-ignore -v local-test-data/…` côté Android ; le web ne + doit contenir AUCUN export réel — `git log --all -- local-test-data` + vide si le dossier local existe) ; +3. si l'asset PK change : les DEUX dépôts dans la même session (tests + pins identiques) — jamais un seul côté. + +### Déploiement du site (quand décidé) + +- **copier le dépôt** (ou `git archive vX.Y.Z`) sur n'importe quel + hébergeur statique ; rien à compiler ; +- optionnel : exclure `tests/`, `scripts/`, `docs/` du document root + (inoffensifs mais inutiles en prod). + +## 11. Bugs potentiels évités pendant le portage + +Trouvés pendant l'écriture (à ne pas réintroduire) : + +1. **Propriétés dérivées Kotlin = fonctions JS** : `usesProfileModel` est un + *getter* en Kotlin mais n'existe pas sur les objets JSON plats → le + moteur retombait silencieusement en Bateman (courbes ÷100). Fix : + `usesProfileModel(treatment)` dans `pk-engine.js`. Toute propriété + dérivée d'entité (`isInjection`, …) doit être recalculée, jamais lue. +2. **Fixtures de test** : `makeTreatment` avec `id: 1` par défaut (au lieu + de 0 = « pas en base ») faisait échouer auto-increment/CASCADE. Defaults + des helpers == valeurs d'insertion Kotlin. +3. **Extrapolation terminale** : première version linéaire (pente) au lieu + du **taux logarithmique** du Kotlin — queues croissantes sur EU (plancher + 0,01/0,00). Portage fidèle exigé, tests EU/EV des deux régimes. +4. **`zonedTimeToMs`** : la 2ᵉ passe de correction DST doit mesurer + l'offset à l'instant CORRIGÉ (`naive − offset(instant)`) — une première + version référençait le mauvais instant (ticks à minuit UTC+2 = 02:00). +5. **`-0`** : `Math.trunc`/négation produisent `-0` ; `assert.equal(-0, 0)` + échoue en strict — normaliser dans les helpers (`panDeltaHours`, + `clampPanHours`). +6. **Imports relatifs** : `app.js` vit dans `js/ui/` — un import écrit comme + depuis `js/` donne des 404 MIME silencieuses (le module root ne charge + PAS, aucune erreur fatale visible). D'où le check `node --check` + + l'E2E « aucune erreur console ». +7. **Exports du barrel** : `pk/index.js` doit ré-exporter tout ce que les + écrans importent nommément (`pointHoursBefore`, …) — une omission = + SyntaxError au chargement. L'E2E l'attrape (page vide). +8. **Assignation à une variable non déclarée** en ESM (strict) = crash du + module entier (`lastRoute` déclaré dans `main()` mais assigné dans + `renderRoute()`) → remonter l'état partagé au niveau module. +9. **Closures de dialog** : `const dlg = showDialog({ actions: [… dlg.close()] })` + — oublier le `const` donne une ReferenceError AU CLIC (pas au chargement), + invisible sans test d'interaction. L'E2E clique maintenant les actions. +10. **Seed de test conditionnel** : un `addInitScript` non conditionnel + écrase `settings` à chaque navigation (re-lecture du changelog à chaque + goto). Cf §8. +11. **`getSettings()` fusionne les défauts** : un stockage partiel ne doit + jamais exposer des champs `undefined` (les gardes `!== null` deviennent + fausses autrement). + +## 12. Limites connues & non-portés + +Structurels (plateforme), pas des bugs : + +- **Rappels** : uniquement page ouverte (boucle 30 s + Notification API). + Aucun équivalent d'AlarmManager — un service worker + push exigerait un + serveur de push (contre la règle « 100 % local, aucun serveur »). +- **Agenda récurrent** : impossible (pas de CalendarProvider). L'éditeur + affiche une note explicative ; `calendarEventId` reste null. +- **Montre** : hors sujet navigateur. +- **Fuseau système** : si le navigateur change de fuseau (voyage), les + labels X suivent le fuseau CHOISI (Paramètres) ou le nouveau fuseau + système — l'historique des doses reste en epoch ms (aucun effet sur les + données, contrairement à un LocalDateTime stocké). +- **localStorage ≈ 5 Mo** : largement suffisant (années de doses/labs) ; + en cas de quota dépassé, AppLog avalera l'erreur (fail-safe) et il faudra + un export/import de nettoyage. + +## 13. Idées d'évolution + +1. **Service worker de cache offline** (l'app serait installable en PWA) — + sans push notifications, uniquement cache statique ; ne casse pas « 100 % + local » (aucun serveur de données). +2. Import en mode **fusion** (détection de doublons) — miroir de l'idée + Android §20. +3. Tooltip au survol du graphique (valeur + date au point). +4. Export CSV (miroir Android roadmap). +5. Tests E2E des gestes (drag du pan via `mouse.drag`) — l'équivalent du + pinch tactile reste hors de portée headless (comme l'émulateur Android, + §16.ter de la doc mère). + +--- +*Doc mise à jour le 8 sept. 2026 (web v1.4.10, dépôt séparé +HormoneTrack-web) — portage de l'Android v1.4.10, 121 tests verts + E2E, +backup compatible bidirectionnel, historique propre (aucune donnée de +santée, aucune dette de confidentialité), versions alignées sur +l'Android.* diff --git a/index.html b/index.html new file mode 100644 index 0000000..dae1fde --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + + + + HormoneTrack + + + + + + + + + + diff --git a/js/data/backup.js b/js/data/backup.js new file mode 100644 index 0000000..7d3d8bf --- /dev/null +++ b/js/data/backup.js @@ -0,0 +1,156 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Sauvegarde JSON (portage de `data/backup/BackupManager.kt`). + * + * ⚠️ COMPATIBILITÉ BIDIRECTIONNELLE avec les exports de l'app Android : + * format BackupData v2 — + * + * { + * "version": 2, + * "exportedAt": 1749..., + * "treatments": [ Treatment... (champs Gson/Kotlin, cf data/models.js) ], + * "doseLogs": [ DoseLog... ], + * "labResults": [ LabResult... ], + * "tConfig": { "base": 6.0, "floor": 0.2, "k": 0.19 }, + * "settings": { "language": ..., "autoCalibrate": ..., + * "alertE2High": ..., "alertE2Low": ..., + * "alertTHigh": ..., "alertTLow": ... } ← optionnel (v2) + * } + * + * RÉTROCOMPATIBILITÉ : les backups v1 (SANS `settings`) restent parsables + * (settings = null) et importables — l'import ne vérifie pas strictement la + * version (miroir du comportement Gson côté Android). `changelog_seen_version` + * est volontairement EXCLU des settings d'export (pas une donnée utile à + * restaurer — cf BackupManager.kt). + * + * PUR (DOM-free) → testable en Node (web/tests/backup.test.js, y compris + * round-trip avec un vrai export Android si local-test-data/ est présent). + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** + * Construit le JSON de sauvegarde (BackupData v2, mêmes noms de champs que + * Gson). + * + * @param {object} collections { treatments, doseLogs, labResults } (store.exportAll) + * @param {object} tConfig { base, floor, k } + * @param {object|null} [userSettings=null] Réglages à embarquer (v2) — + * SEULS les champs du schéma UserSettings Android sont sérialisés (champs + * plats), jamais les clés web extra (chartTimezone, alertNotifiedState…) : + * un backup importé dans l'app Android ne doit pas contenir de clés + * inconnues (Gson les ignorerait, mais autant rester stricts). + * @returns {string} JSON + */ +export function buildBackupJson(collections, tConfig, userSettings = null) { + const data = { + version: 2, + exportedAt: Date.now(), + treatments: collections.treatments, + doseLogs: collections.doseLogs, + labResults: collections.labResults, + tConfig: { + base: tConfig.base, + floor: tConfig.floor, + k: tConfig.k, + }, + }; + if (userSettings) { + // Champs PLATS volontairement (miroir UserSettings.kt — Gson lit par + // réflexion côté Android : pas de nested inconnu) + data.settings = { + language: userSettings.language ?? null, + autoCalibrate: userSettings.autoCalibrate ?? null, + alertE2High: userSettings.alertE2High ?? null, + alertE2Low: userSettings.alertE2Low ?? null, + alertTHigh: userSettings.alertTHigh ?? null, + alertTLow: userSettings.alertTLow ?? null, + }; + } + return JSON.stringify(data); +} + +/** + * Parse un backup (Android v1 ou v2, ou export web) en structure validée. + * + * Les listes manquantes deviennent vides, les types numériques sont + * normalisés (Gson accepte des entiers pour des Double) — l'import ne + * vérifie pas strictement la version, comme côté Android. + * + * @param {string} json + * @returns {{version:number, exportedAt:number|null, + * treatments:object[], doseLogs:object[], labResults:object[], + * tConfig:{base:number,floor:number,k:number}, + * settings:object|null}} + * @throws {Error} si le JSON est illisible ou n'a pas la forme d'un backup + */ +export function parseBackupJson(json) { + let raw; + try { + raw = JSON.parse(json); + } catch (e) { + throw new Error(`JSON illisible : ${e.message}`); + } + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + throw new Error('Le fichier ne contient pas un backup HormoneTrack'); + } + if (!('treatments' in raw) && !('doseLogs' in raw) && !('labResults' in raw)) { + throw new Error('Aucune donnée de backup reconnue (treatments/doseLogs/labResults absents)'); + } + return { + version: typeof raw.version === 'number' ? raw.version : 1, + exportedAt: typeof raw.exportedAt === 'number' ? raw.exportedAt : null, + treatments: Array.isArray(raw.treatments) ? raw.treatments : [], + doseLogs: Array.isArray(raw.doseLogs) ? raw.doseLogs : [], + labResults: Array.isArray(raw.labResults) ? raw.labResults : [], + // Défauts TConfig.kt si absent (backup tronqué) — comme Gson + // reconstruirait TConfig() par défaut + tConfig: { + base: numOrNull(raw.tConfig && raw.tConfig.base) ?? 6.0, + floor: numOrNull(raw.tConfig && raw.tConfig.floor) ?? 0.2, + k: numOrNull(raw.tConfig && raw.tConfig.k) ?? 0.19, + }, + // settings absent = backup v1 → null (l'appelant ne touche pas aux réglages) + settings: raw.settings && typeof raw.settings === 'object' ? raw.settings : null, + }; +} + +/** @private Convertit en nombre si possible, sinon null. */ +function numOrNull(v) { + const n = typeof v === 'string' ? Number(v) : v; + return typeof n === 'number' && Number.isFinite(n) ? n : null; +} + +/** + * Nom de fichier d'export (miroir d'ExportFileNames.backupFileName Android) : + * `hormonetrack-backup-YYYYMMDD.json` — pattern SANS heure. + * + * Le helper Android existait parce qu'un pattern horaire passé à LocalDate + * crashait l'app (#48) ; côté web, `new Date()` porte tout — le helper reste + * centralisé par discipline (une seule source pour le nom). + * + * @param {Date} [now=new Date()] + * @returns {string} + */ +export function backupFileName(now = new Date()) { + const y = now.getFullYear(); + const m = String(now.getMonth() + 1).padStart(2, '0'); + const d = String(now.getDate()).padStart(2, '0'); + return `hormonetrack-backup-${y}${m}${d}.json`; +} + +/** + * Nom de fichier d'export des logs de diagnostic (miroir + * ExportFileNames.diagnosticLogFileName) : `hormonetrack-logs-YYYYMMDD-HHmm.txt` + * — pattern AVEC heure (le fichier Android documente pourquoi : bug #48). + * + * @param {Date} [now=new Date()] + * @returns {string} + */ +export function diagnosticLogFileName(now = new Date()) { + const y = now.getFullYear(); + const mo = String(now.getMonth() + 1).padStart(2, '0'); + const d = String(now.getDate()).padStart(2, '0'); + const h = String(now.getHours()).padStart(2, '0'); + const mi = String(now.getMinutes()).padStart(2, '0'); + return `hormonetrack-logs-${y}${mo}${d}-${h}${mi}.txt`; +} diff --git a/js/data/models.js b/js/data/models.js new file mode 100644 index 0000000..62dfe03 --- /dev/null +++ b/js/data/models.js @@ -0,0 +1,212 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Modèles de données (portage de `data/model/*.kt`). + * + * ⚠️ COMPATIBILITÉ BACKUP : les noms de champs sont CEUX DE GSON (noms de + * propriétés Kotlin) — identiques aux entités Room. NE JAMAIS renommer un + * champ ici sans vérifier BackupManager.kt (sinon les exports Android ne + * s'importent plus) : + * - Treatment : id, name, type, route, doseAmount, doseUnit, isActive, + * notes, esterType, pkModel, absorptionHours, eliminationHalfLifeHours, + * bioavailabilityFraction, scaleFactor, forecastIntervalDays, + * reminderHour, reminderMinute, reminderEnabled, calendarEventId, createdAt + * - DoseLog : id, treatmentId, timestamp, doseAmount, notes, esterType + * - LabResult : id, marker, value, unit, timestamp, notes + * - TConfig : base, floor, k + * - UserSettings : language, autoCalibrate, alertE2High, alertE2Low, + * alertTHigh, alertTLow (champs PLATS — cf BackupManager.kt) + * + * `calendarEventId` est conservé (fidelité du schéma) mais TOUJOURS null + * côté web : les événements d'agenda récurrents sont impossibles dans un + * navigateur (pas de CalendarProvider) — fonctionnalité Android non portée. + * ───────────────────────────────────────────────────────────────────────────── + */ + +export const TreatmentType = { + ESTRADIOL: 'ESTRADIOL', + ANTI_ANDROGEN: 'ANTI_ANDROGEN', + PROGESTOGEN: 'PROGESTOGEN', + OTHER: 'OTHER', +}; + +export const AdministrationRoute = { + ORAL: 'ORAL', + TRANSDERMAL_GEL: 'TRANSDERMAL_GEL', + TRANSDERMAL_PATCH: 'TRANSDERMAL_PATCH', + INJECTION_IM: 'INJECTION_IM', + INJECTION_SUBCUT: 'INJECTION_SUBCUT', + OTHER: 'OTHER', +}; + +/** + * Esters proposables dans l'override par dose pour un MODÈLE PK donné + * (miroir de `Esters.choicesForModel`, v1.4.6 — la liste suit la couverture + * du modèle : TFS = 7 esters ; WHSAH = 6 (PEP non couvert) ; Estrannaise + * (tables ODS) = EV/EU/EEn seulement — un traitement ESE + EB produirait + * 0 pg/mL). + * + * @param {string} pkModel + * @returns {string[]} + */ +export function choicesForModel(pkModel) { + if (pkModel === 'TFS') return ['EV', 'EU', 'EEN', 'EB', 'EC', 'ECS', 'PEP']; + if (pkModel === 'WHS') return ['EV', 'EU', 'EEN', 'EB', 'EC', 'ECS']; + return ['EV', 'EU', 'EEN']; +} + +/** + * Libellé humain d'un ester (miroir de `esterLabel` du DoseDialog Android). + * @param {string} ester + * @returns {string} + */ +export function esterLabel(ester) { + switch (ester) { + case 'EV': return 'EV — valerate'; + case 'EU': return 'EU — undecylate'; + case 'EEN': return 'EEn — enanthate'; + case 'EB': return 'EB — benzoate'; + case 'EC': return 'EC — cypionate (oil)'; + case 'ECS': return 'EC — cypionate (suspension)'; + case 'PEP': return 'PEP — polyestradiol phosphate'; + default: return ester; + } +} + +/** + * Clé i18n du label d'un modèle PK (miroir de `modelLabelRes`, fix #59 — + * UNIQUE source du label : TOUTE nouvelle utilisation doit passer par ce + * helper, pas par un ternaire à 2 branches qui oubliait WHSAH). + * @param {string} pkModel + * @returns {string} clé i18n + */ +export function modelLabelKey(pkModel) { + if (pkModel === 'TFS') return 'model_tfs'; + if (pkModel === 'WHS') return 'model_whs'; + return 'model_ese'; +} + +/** Clé i18n d'une voie d'administration. */ +export function routeLabelKey(route) { + switch (route) { + case 'ORAL': return 'route_oral'; + case 'TRANSDERMAL_GEL': return 'route_gel'; + case 'TRANSDERMAL_PATCH': return 'route_patch'; + case 'INJECTION_IM': return 'route_injection_im'; + case 'INJECTION_SUBCUT': return 'route_injection_sc'; + default: return 'route_other'; + } +} + +/** Clé i18n d'un type de traitement. */ +export function typeLabelKey(type) { + switch (type) { + case 'ESTRADIOL': return 'type_estradiol'; + case 'ANTI_ANDROGEN': return 'type_antiandrogen'; + case 'PROGESTOGEN': return 'type_progestogen'; + default: return 'type_other'; + } +} + +/** + * Est-ce une voie injectable (IM/SC) ? + * @param {string} route + * @returns {boolean} + */ +export function isInjectionRoute(route) { + return route === 'INJECTION_IM' || route === 'INJECTION_SUBCUT'; +} + +/** + * Suggère une clé i18n de route depuis un preset (utilisé par l'éditeur). + * (Aucune logique : lisibilité des écrans.) + */ + +/** + * Crée un Treatment avec les valeurs par défaut de Room (`Treatment.kt`). + * Les traitements sont des objets PLATS (JSON-serialisables directement). + * + * @param {Partial} [init={}] Champs à surcharger + * @returns {object} + */ +export function newTreatment(init = {}) { + return { + id: 0, // 0 = pas encore en base (auto-increment côté store) + name: '', + type: TreatmentType.ESTRADIOL, + route: AdministrationRoute.INJECTION_IM, + doseAmount: 0, + doseUnit: 'mg', + isActive: true, + notes: null, + esterType: 'NONE', + pkModel: 'ESE', + // PK Bateman (gel/patch/oral) + absorptionHours: 4, + eliminationHalfLifeHours: 24, + bioavailabilityFraction: 1.0, + // Calibration : ratio lab / prédiction (le « Scale factor » du .ods) + scaleFactor: 1.0, + // Simulation prévisionnelle : intervalle en JOURS (null = pas de prévision) + forecastIntervalDays: null, + // Rappel + reminderHour: null, + reminderMinute: null, + reminderEnabled: false, + // Agenda : TOUJOURS null côté web (feature Android non portable) + calendarEventId: null, + createdAt: Date.now(), + ...init, + }; +} + +/** + * Propriétés DÉRIVÉES d'un treatment — recalculées à la demande (pas + * stockées) : miroir des getters Kotlin `isInjection` / `usesProfileModel`. + * + * @param {object} tr + * @returns {{isInjection:boolean, usesProfileModel:boolean}} + */ +export function treatmentFlags(tr) { + const isInjection = isInjectionRoute(tr.route); + return { + isInjection, + // Un traitement a un PROFIL PK s'il est injectable ET avec un ester + // (les traitements Bateman — gel/patch/oral — n'en ont pas) + usesProfileModel: isInjection && tr.esterType !== 'NONE', + }; +} + +/** + * Crée un DoseLog (valeurs par défaut = `DoseLog.kt`). + * @param {Partial} [init={}] + * @returns {object} + */ +export function newDoseLog(init = {}) { + return { + id: 0, + treatmentId: 0, + timestamp: Date.now(), + doseAmount: 0, + notes: null, + // Override par injection (null = ester du traitement) + esterType: null, + ...init, + }; +} + +/** + * Crée un LabResult (valeurs par défaut = `LabResult.kt`). + * @param {Partial} [init={}] + * @returns {object} + */ +export function newLabResult(init = {}) { + return { + id: 0, + marker: 'E2', + value: 0, + unit: 'pg/mL', + timestamp: Date.now(), + notes: null, + ...init, + }; +} diff --git a/js/data/store.js b/js/data/store.js new file mode 100644 index 0000000..68e34e6 --- /dev/null +++ b/js/data/store.js @@ -0,0 +1,306 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Store — persistance LOCALE (portage de la couche Room + DataStore Android). + * + * ⚠️ 100 % LOCAL : toutes les données vivent dans le localStorage du + * navigateur, sous des clés préfixées `hormonetrack.` — AUCUN envoi réseau + * (l'app ne fait qu'un fetch de son asset PK et de son changelog, en lecture). + * + * Architecture : + * - un backend de stockage INJECTABLE (localStorage en production, Map en + * Node pour les tests) — cf `WebStore.setBackend()` ; + * - un pub/sub simple (`subscribe`) : les écrans se re-rendent sur mutation ; + * - IDs auto-incrémentés par table (miroir de Room autoGenerate) ; à + * l'import, les IDs du backup sont conservés (FK dose→traitement) et les + * compteurs recalculés (max + 1). + * + * Clés localStorage (miroir des tables Room / clés DataStore Android) : + * - hormonetrack.treatments / .doseLogs / .labResults : tableaux JSON + * - hormonetrack.tConfig : {base, floor, k} + * - hormonetrack.settings : {language, autoCalibrate, alertE2High/Low, + * alertTHigh/Low, chartTimezone, changelogSeenVersion, alertNotifiedState} + * - hormonetrack.debugLog : journal de diagnostic (texte, cf AppLog) + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** Préfixe de toutes les clés (évite les collisions dans un localStorage partagé). */ +const PREFIX = 'hormonetrack.'; + +/** Backend par défaut : window.localStorage (production navigateur). */ +function defaultBackend() { + if (typeof window !== 'undefined' && window.localStorage) return window.localStorage; + throw new Error('Aucun backend de stockage (localStorage indisponible) — utiliser setBackend() en Node'); +} + +/** + * Store applicatif. Une seule instance est exportée (`store`) — les tests + * utilisent `setBackend(new Map())` pour isoler. + */ +class WebStore { + constructor() { + this.backend = null; + this.listeners = new Set(); + } + + /** Injecte un backend (localStorage-like ou Map — Node : new Map()). */ + setBackend(backend) { + if (backend && typeof backend.getItem !== 'function' && typeof backend.get === 'function') { + // Adaptateur Map → API localStorage (utilisé par les tests Node) + const map = backend; + this.backend = { + getItem: (k) => (map.has(k) ? map.get(k) : null), + setItem: (k, v) => map.set(k, v), + removeItem: (k) => map.delete(k), + }; + } else { + this.backend = backend; + } + } + + /** @private */ + b() { + if (!this.backend) this.backend = defaultBackend(); + return this.backend; + } + + // ── primitives JSON ────────────────────────────────────────────────────── + + /** @private */ + read(key, fallback) { + const raw = this.b().getItem(PREFIX + key); + if (raw === null || raw === undefined || raw === '') return fallback; + try { + return JSON.parse(raw); + } catch (e) { + // Donnée corrompue : ne JAMAIS bloquer l'app au démarrage (même + // philosophie qu'AppLog.init côté Android — on repart sur le défaut). + return fallback; + } + } + + /** @private */ + write(key, value) { + this.b().setItem(PREFIX + key, JSON.stringify(value)); + } + + /** @private */ + remove(key) { + this.b().removeItem(PREFIX + key); + } + + // ── pub/sub ────────────────────────────────────────────────────────────── + + /** Abonne un écouteur aux mutations ; retourne la fonction de désabonnement. */ + subscribe(listener) { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + /** Notifie les écrans après une mutation (batch : un appel par action). */ + emit() { + for (const l of [...this.listeners]) { + try { + l(); + } catch (e) { + // Un écran qui plante ne doit pas casser les autres ni la donnée + console.error('store listener error', e); + } + } + } + + // ── traitements ────────────────────────────────────────────────────────── + + getTreatments() { + return this.read('treatments', []); + } + + /** + * Insère (id=0) ou met à jour (id>0) un traitement. Retourne l'id réellement + * stocké (nouveau si insertion). + */ + upsertTreatment(treatment) { + const all = this.read('treatments', []); + if (!treatment.id) { + const nextId = all.reduce((m, t) => Math.max(m, t.id), 0) + 1; + const tr = { ...treatment, id: nextId }; + all.push(tr); + this.write('treatments', all); + this.emit(); + return nextId; + } + const idx = all.findIndex((t) => t.id === treatment.id); + if (idx >= 0) all[idx] = { ...treatment }; + else all.push({ ...treatment }); + this.write('treatments', all); + this.emit(); + return treatment.id; + } + + deleteTreatment(id) { + // FK CASCADE : les doses du traitement partent avec lui (miroir Room) + this.write('treatments', this.read('treatments', []).filter((t) => t.id !== id)); + this.write('doseLogs', this.read('doseLogs', []).filter((d) => d.treatmentId !== id)); + this.emit(); + } + + // ── doses ──────────────────────────────────────────────────────────────── + + getDoseLogs() { + return this.read('doseLogs', []); + } + + upsertDoseLog(dose) { + const all = this.read('doseLogs', []); + if (!dose.id) { + const nextId = all.reduce((m, d) => Math.max(m, d.id), 0) + 1; + const d = { ...dose, id: nextId }; + all.push(d); + this.write('doseLogs', all); + this.emit(); + return nextId; + } + const idx = all.findIndex((x) => x.id === dose.id); + if (idx >= 0) all[idx] = { ...dose }; + else all.push({ ...dose }); + this.write('doseLogs', all); + this.emit(); + return dose.id; + } + + deleteDoseLog(id) { + this.write('doseLogs', this.read('doseLogs', []).filter((d) => d.id !== id)); + this.emit(); + } + + // ── labs ───────────────────────────────────────────────────────────────── + + getLabResults() { + return this.read('labResults', []); + } + + upsertLabResult(lab) { + const all = this.read('labResults', []); + if (!lab.id) { + const nextId = all.reduce((m, l) => Math.max(m, l.id), 0) + 1; + const l = { ...lab, id: nextId }; + all.push(l); + this.write('labResults', all); + this.emit(); + return nextId; + } + const idx = all.findIndex((x) => x.id === lab.id); + if (idx >= 0) all[idx] = { ...lab }; + else all.push({ ...lab }); + this.write('labResults', all); + this.emit(); + return lab.id; + } + + deleteLabResult(id) { + this.write('labResults', this.read('labResults', []).filter((l) => l.id !== id)); + this.emit(); + } + + // ── TConfig (DataStore Android) ────────────────────────────────────────── + + getTConfig() { + const c = this.read('tConfig', null); + // Défauts identiques à TConfig.kt (base 6.0, floor 0.2, k 0.19) + return c || { base: 6.0, floor: 0.2, k: 0.19 }; + } + + setTConfig(config) { + this.write('tConfig', config); + this.emit(); + } + + // ── réglages utilisateur (DataStore Android → un objet JSON) ────────────── + + getSettings() { + // Fusion avec les défauts : un stockage partiel (ex. seed de test avec + // {language:'fr'} seul) ne doit jamais exposer des champs undefined. + return { + language: 'system', // "system" | "fr" | "en" + autoCalibrate: false, // option désactivée par défaut (contrôle utilisateur) + alertE2High: null, // seuils d'alerte : null = pas de limite (opt-in) + alertE2Low: null, + alertTHigh: null, + alertTLow: null, + chartTimezone: null, // fuseau de LECTURE du graphique (null = auto) + changelogSeenVersion: null, // dialog « Nouveautés » + alertNotifiedState: null, // anti-spam des notifications d'alerte + ...this.read('settings', {}), + }; + } + + /** Fusion partielle (setSettings({ language: 'fr' })) + notification. */ + setSettings(patch) { + this.write('settings', { ...this.getSettings(), ...patch }); + this.emit(); + } + + // ── journal de diagnostic (AppLog) ─────────────────────────────────────── + + getDebugLog() { + return this.read('debugLog', ''); + } + + setDebugLog(text) { + this.write('debugLog', text); + } + + // ── import/export ──────────────────────────────────────────────────────── + + /** + * Import en mode ÉCRASEMENT (miroir BackupManager.importJson v1.2.6) : + * les données actuelles sont effacées AVANT l'insertion — sinon les IDs du + * backup (conservés pour les FK dose→traitement) entraient en conflit. + * Ordre d'effacement : enfants (doses, labs) puis parents (traitements). + * + * @param {object} data BackupData parsé (data/backup.js) + * @returns {{treatments:number, doseLogs:number, labResults:number}} + */ + importOverwrite(data) { + this.write('doseLogs', []); + this.write('labResults', []); + this.write('treatments', []); + for (const tr of data.treatments) this.upsertTreatment(tr); + for (const d of data.doseLogs) this.upsertDoseLog(d); + for (const l of data.labResults) this.upsertLabResult(l); + // Un seul emit final (importOverwrite est atomique pour l'UI) + this.emit(); + return { + treatments: data.treatments.length, + doseLogs: data.doseLogs.length, + labResults: data.labResults.length, + }; + } + + /** + * Exporte tout (miroir BackupManager.exportJson — sérialisation dans + * data/backup.js, cette fonction fournit juste les collections). + */ + exportAll() { + return { + treatments: this.getTreatments(), + doseLogs: this.getDoseLogs(), + labResults: this.getLabResults(), + tConfig: this.getTConfig(), + settings: this.getSettings(), + }; + } + + /** Efface TOUTES les données locales (danger : confirmation côté UI). */ + clearAll() { + this.remove('treatments'); + this.remove('doseLogs'); + this.remove('labResults'); + this.remove('tConfig'); + this.remove('settings'); + this.remove('debugLog'); + this.emit(); + } +} + +/** Instance unique de l'app. */ +export const store = new WebStore(); diff --git a/js/pk/alerts.js b/js/pk/alerts.js new file mode 100644 index 0000000..6f19450 --- /dev/null +++ b/js/pk/alerts.js @@ -0,0 +1,144 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Seuils d'alerte configurables (portage de `pk/Alerts.kt`, v1.4.2). + * + * Limites HAUTE et BASSE définies par l'utilisatrice pour l'estradiol (pg/mL) + * et la testostérone (ng/mL), évaluées contre le TAUX ESTIMÉ ACTUEL (dernier + * point de courbe = le niveau « ≈ X pg/mL » affiché sur l'accueil — pas une + * mesure de labo). + * + * Philosophie : + * - **opt-in** : aucun seuil par défaut (l'app n'invente pas de normes + * médicales — tout champ laissé vide désactive l'alerte correspondante) ; + * - PUR (aucune dépendance DOM/storage) → testable en Node ; + * - les courbes restent des ESTIMATIONS pharmacocinétiques : l'avertissement + * est indicatif, libellé en ce sens dans l'UI. + * + * Validation de cohérence : si haut ET bas sont définis, il faut haut > bas + * (sinon l'évaluation serait ambiguë) — vérifié à la saisie dans Paramètres + * avec feedback, et défensivement ici (isCoherent). + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** Niveaux de dépassement. */ +export const Level = { HIGH: 'HIGH', LOW: 'LOW' }; + +/** + * Seuils de l'utilisatrice (persistés dans le store). null = pas de limite + * pour cette valeur → jamais d'alerte dessus. + */ +export class Thresholds { + /** + * @param {number|null} [e2High=null] Limite HAUTE E2 (pg/mL) + * @param {number|null} [e2Low=null] Limite BASSE E2 (pg/mL) + * @param {number|null} [tHigh=null] Limite HAUTE T (ng/mL) + * @param {number|null} [tLow=null] Limite BASSE T (ng/mL) + */ + constructor(e2High = null, e2Low = null, tHigh = null, tLow = null) { + this.e2High = e2High; + this.e2Low = e2Low; + this.tHigh = tHigh; + this.tLow = tLow; + } + + /** Cohérence : une limite haute doit être strictement au-dessus de la basse. */ + isCoherent() { + return (this.e2High === null || this.e2Low === null || this.e2High > this.e2Low) + && (this.tHigh === null || this.tLow === null || this.tHigh > this.tLow); + } +} + +/** + * Évalue UNE valeur contre ses limites. + * + * - `value > high` → HIGH ; `value < low` → LOW (STRICT : la valeur + * exactement à la limite ne déclenche rien — éviter les alertes + * « clignotantes » sur la précision d'affichage) ; + * - limite null → jamais d'alerte sur ce côté ; + * - un seul verdict par appel : HIGH prime LOW si la configuration était + * incohérente (haut < bas) — défensif, la saisie interdit ce cas. + * + * @param {number} value + * @param {number|null} low + * @param {number|null} high + * @returns {[string, number]|null} [niveau, limite franchie] ou null + */ +export function evaluate(value, low, high) { + if (high !== null && high !== undefined && value > high) return [Level.HIGH, high]; + if (low !== null && low !== undefined && value < low) return [Level.LOW, low]; + return null; +} + +/** + * Évalue le niveau actuel (E2 pg/mL + T ng/mL) contre tous les seuils. + * + * @param {number} currentE2 + * @param {number} currentT + * @param {Thresholds} thresholds + * @returns {{marker:string, level:string, value:number, limit:number, unit:string}[]} + * Alertes déclenchées (vide = tout va bien / rien de configuré). + * Ordre stable : E2 d'abord, puis T. + */ +export function evaluateAll(currentE2, currentT, thresholds) { + const out = []; + const e2 = evaluate(currentE2, thresholds.e2Low, thresholds.e2High); + if (e2) out.push({ marker: 'E2', level: e2[0], value: currentE2, limit: e2[1], unit: 'pg/mL' }); + const t = evaluate(currentT, thresholds.tLow, thresholds.tHigh); + if (t) out.push({ marker: 'T', level: t[0], value: currentT, limit: t[1], unit: 'ng/mL' }); + return out; +} + +// ── État de notification (v1.4.2) — anti-spam des vérifications périodiques ── + +/** + * Sérialise l'état des alertes déjà NOTIFIÉES pour la persistance + * (localStorage) : `"E2:HIGH;T:LOW"`. Chaîne vide = rien de notifié. + * (Côté Android, l'encodage est stocké en DataStore entre les vérifications + * du worker ; côté web, entre les cycles de la boucle de vérification.) + * + * @param {{marker:string, level:string}[]} alerts + * @returns {string} + */ +export function encodeState(alerts) { + return alerts.map((a) => `${a.marker}:${a.level}`).join(';'); +} + +/** + * Décodage de l'état persisté (format encodeState) → map marqueur → niveau. + * Résistant aux entrées malformées (segments sans « : », niveaux inconnus). + * + * @param {string|null} encoded + * @returns {Object} ex. { E2: 'HIGH', T: 'LOW' } + */ +export function parseState(encoded) { + const out = {}; + if (!encoded) return out; + for (const part of String(encoded).split(';')) { + if (!part.includes(':')) continue; + const idx = part.indexOf(':'); + const marker = part.slice(0, idx); + const level = part.slice(idx + 1); + if ((level === Level.HIGH || level === Level.LOW) && marker) out[marker] = level; + } + return out; +} + +/** + * Décide si une NOTIFICATION doit être envoyée pour cette vérification + * (anti-spam de la boucle périodique — 15 min côté Android, 5 min côté web) : + * + * - current vide → **false** (pas de notif de « retour à la normale » ; + * l'appelant efface l'état persisté pour permettre la re-notification + * au PROCHAIN franchissement) ; + * - current identique au dernier état notifié → **false** (l'écart + * continue, pas de re-notif à chaque cycle) ; + * - nouveau franchissement OU changement de niveau (H↔L) → **true**. + * + * @param {Object} current État évalué à CETTE vérification + * @param {Object|null} lastNotified État persisté de la dernière notification + * @returns {boolean} + */ +export function shouldNotify(current, lastNotified) { + if (Object.keys(current).length === 0) return false; + return JSON.stringify(current) !== JSON.stringify(lastNotified || {}); +} diff --git a/js/pk/chart-helpers.js b/js/pk/chart-helpers.js new file mode 100644 index 0000000..3581266 --- /dev/null +++ b/js/pk/chart-helpers.js @@ -0,0 +1,301 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Helpers du graphique (portage des fonctions pures de + * `ui/components/CurveChart.kt`) — TOUTES testables en Node + * (web/tests/chart-helpers.test.js). + * + * ⚠️ Chaque fonction porte l'historique du bug qu'elle corrige : + * - panDeltaHours : fix #62 (pan mort sur 24 h — cumul fractionnaire) ; + * - pointHoursBefore : fix #53 (delta « 6 h » qui comparait 24 h) ; + * - clampPanHours : fix #54 (pan futur figé — clamp unique) ; + * - forecastExtensionHrs : fix #54 (chip prévision sans effet visible) ; + * - xLabelTicks : fix #55 (labels à minuit UTC = 02:00 FR). + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** + * Arrondit une valeur max à une borne « propre » (1/2/2.5/5/10 × 10ⁿ) pour + * l'axe Y. + * @param {number} value + * @returns {number} + */ +export function niceCeil(value) { + if (value <= 0) return 100.0; + const exp = Math.pow(10.0, Math.floor(Math.log10(value))); + const f = value / exp; + let nice; + if (f <= 1) nice = 1.0; + else if (f <= 2) nice = 2.0; + else if (f <= 2.5) nice = 2.5; + else if (f <= 5) nice = 5.0; + else nice = 10.0; + return nice * exp; +} + +/** + * Pas d'échantillonnage de la courbe selon la plage affichée (v1.2.9) : + * plus on zoome, plus le pas finit pour des courbes lisses + * (≤ 12 h → 15 min ; ≤ 24 h → 30 min ; sinon 1 h). + * + * @param {number} rangeHours + * @returns {number} ms + */ +export function stepForRange(rangeHours) { + if (rangeHours <= 12) return 900000; // 15 min + if (rangeHours <= 24) return 1800000; // 30 min + return 3600000; // 1 h +} + +/** + * Toggles de MODÈLES PK par défaut (v1.4.7, fix #58) : au chargement, seuls + * les modèles RÉELLEMENT utilisés par des traitements à PROFIL PK (injection + * + ester — `usesProfileModel`) sont affichés ; les autres restent + * activables au tap. ⚠️ FILTRER par usesProfileModel : un traitement + * oral/anti-androgène (pkModel stocké = ESE par défaut, sans courbe profil) + * activerait Estrannaise à tort. + * + * GARDE-FOU : si AUCUN modèle profil n'est utilisé (traitements gel/oral + * uniquement), TFS est forcé à ON — les traitements Bateman sont tracés + * IDENTIQUEMENT dans chaque série : sans garde-fou, le graphique serait vide. + * + * @param {Set} pkModelsUsed Ensemble des pkModel des traitements à profil + * @returns {Object} { ESE, TFS, WHS } + */ +export function defaultModelToggles(pkModelsUsed) { + const toggles = { + ESE: pkModelsUsed.has('ESE'), + TFS: pkModelsUsed.has('TFS'), + WHS: pkModelsUsed.has('WHS'), + }; + if (!toggles.ESE && !toggles.TFS && !toggles.WHS) toggles.TFS = true; + return toggles; +} + +/** + * Convertit un delta de drag en heures de pan, en CUMULANT le résiduel + * fractionnaire (v1.4.10, fix #62). + * + * ⚠️ BUG #62 : l'ancien code tronquait CHAQUE delta incrémental en heures + * entières — à la plage 24 h, le delta d'un doigt réel (~30 px ≈ 0,67 h) + * était tronqué à 0 → le pan ne bougeait JAMAIS sur la vue 24 h (alors + * qu'il marchait sur 7 j+ : le même delta ≈ 4,7 h). Le fix : le résiduel + * fractionnaire (la part < 1 h) est conservé d'un événement de mouvement au + * suivant — le pan se déclenche dès que le CUMUL franchit une heure entière. + * + * @param {number} panPx Delta horizontal du geste (px, signé) + * @param {number} chartWidthPx Largeur du canvas en px + * @param {number} rangeHours Plage temporelle courante (24 h / 7 j / 30 j…) + * @param {number} residualHours Résiduel fractionnaire précédent (à ré-injecter) + * @returns {{hours:number, residual:number}} heures entières à appliquer + nouveau résiduel + */ +export function panDeltaHours(panPx, chartWidthPx, rangeHours, residualHours) { + if (chartWidthPx <= 0 || rangeHours <= 0) return { hours: 0, residual: residualHours }; + const total = (panPx / chartWidthPx) * rangeHours + residualHours; + let whole = Math.trunc(total); // troncature vers ZÉRO (gère les deux signes) + if (whole === 0) whole = 0; // normalise -0 (troncature de petits négatifs) + return { hours: whole, residual: total - whole }; +} + +/** + * Bornes du panoramique (v1.4.1) : la fenêtre peut reculer dans le PASSÉ + * jusqu'à [0 ; maxPastHours] et — quand la prévision est active — AVANCER + * dans le FUTUR jusqu'à [-futureHorizonHours ; …] (panHours négatif = + * fenêtre décalée vers le futur). Unique point de clamp partagé par le drag + * et le zoom pour éviter les divergences. + * + * @param {number} panHours + * @param {number} futureHorizonHours + * @param {number} maxPastHours + * @returns {number} + */ +export function clampPanHours(panHours, futureHorizonHours, maxPastHours) { + const lo = -Math.max(futureHorizonHours, 0); + const hi = Math.max(maxPastHours, 0); + if (panHours <= lo) return lo === 0 ? 0 : lo; // normalise -0 + if (panHours >= hi) return hi; + return panHours; +} + +/** + * Horizon de PRÉVISION en heures (v1.4.1) : quantité de doses projetées + * générées au-delà de « maintenant » quand le chip Prévision est actif. + * 12 × la plus grande Posologie, borné [30 j ; 365 j] — « scroller à + * l'infini » s'arrête à 1 an (compromis calcul maîtrisé). + * + * @param {number|null} maxIntervalDays Plus grande Posologie configurée (jours) + * @returns {number|null} null si aucun traitement n'a de Posologie (chip désactivé) + */ +export function forecastHorizonHours(maxIntervalDays) { + if (maxIntervalDays === null || maxIntervalDays === undefined || maxIntervalDays <= 0.0) return null; + return Math.min(Math.max(Math.trunc(maxIntervalDays * 24.0 * 12.0), 24 * 30), 24 * 365); +} + +/** + * EXTENSION de fenêtre à droite quand le chip Prévision est activé + * (v1.4.4, fix #54) : le toggle doit avoir un effet IMMÉDIAT et visible. + * (Historique : `endMs = now` faisait que generateForecastDoses ne générait + * AUCUNE dose — le chip semblait inerte.) Solution : étendre la fenêtre à + * droite SANS déplacer son début (l'historique visible reste à sa place) + * jusqu'à couvrir au moins la prochaine dose projetée, sinon d'un tiers de + * la plage actuelle. + * + * @param {number} rangeHours Plage courante (24 h / 7 j / 30 j…) + * @param {number} horizonHours Horizon de prévision (≤ 0 = pas de prévision) + * @param {number|null} firstSlotGapHours Écart « maintenant → 1ᵉʳ créneau projeté » + * @returns {number} heures à ajouter à endMs (0 = rien) + */ +export function forecastExtensionHours(rangeHours, horizonHours, firstSlotGapHours) { + if (horizonHours === null || horizonHours === undefined || horizonHours <= 0) return 0; + // Montrer AU MOINS la prochaine dose (+12 h de contexte), sinon 1/3 de + // la plage actuelle — jamais au-delà de l'horizon. + const toFirstSlot = (firstSlotGapHours === null || firstSlotGapHours === undefined ? 0 : firstSlotGapHours) + 12; + const ext = Math.max(Math.trunc(rangeHours / 3), toFirstSlot); + return Math.min(Math.max(ext, 0), horizonHours); +} + +/** + * Point le plus PROCHE de `hours` heures AVANT le dernier point de la série + * (v1.4.3, fix #53) : utilisé par le delta « vs il y a 6 h » de la carte + * d'accueil. + * + * ⚠️ BUG corrigé ici : l'ancien code faisait `curve.find(p => écart >= 6 h)` + * sur une fenêtre de 24 h → le PREMIER point de la liste (il y a ~24 h !) + * satisfaisait la condition immédiatement, et le delta affiché « vs 6 h » + * comparait en réalité à il y a 24 h (trompeur le lendemain d'une injection + * EEn — cf régression #4). Cette fonction prend le point dont l'écart au + * dernier est le plus PETIT au-dessus de `hours` (par excès — avec une + * grille régulière, c'est le point à ~6 h exactement). + * + * @param {import('./pk-engine.js').LevelPoint[]} points + * @param {number} hours + * @returns {object|null} null si aucun point n'est au-delà de `hours` + */ +export function pointHoursBefore(points, hours) { + const last = points[points.length - 1]; + if (!last) return null; + let best = null; + let bestGap = Number.MAX_VALUE; + for (const p of points) { + const gap = (last.timestamp - p.timestamp) / 3600000.0; + if (gap < hours) continue; // seulement AVANT le point de référence + if (gap < bestGap) { + bestGap = gap; + best = p; + } + } + return best; +} + +// ── Fuseaux horaires (Intl) ─────────────────────────────────────────────────── + +/** + * Parties calendaires d'un instant DANS un fuseau donné (via Intl.DateTimeFormat). + * C'est l'équivalent JS de Calendar.getInstance(TimeZone) + get(...). + * + * @param {number} ms + * @param {string} timeZone ID IANA ("Europe/Paris"…) + * @returns {{year:number,month:number,day:number,hour:number,minute:number,second:number}} + */ +export function zonedParts(ms, timeZone) { + const fmt = new Intl.DateTimeFormat('en-US', { + timeZone, + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', + hourCycle: 'h23', + }); + const parts = {}; + for (const p of fmt.formatToParts(new Date(ms))) parts[p.type] = p.value; + return { + year: Number(parts.year), + month: Number(parts.month), + day: Number(parts.day), + hour: Number(parts.hour), + minute: Number(parts.minute), + second: Number(parts.second), + }; +} + +/** + * Reconstruit un epoch ms depuis des parties calendaires interprétées DANS + * [timeZone] (algorithme de conversion inverse : UTC initial, correction par + * l'offset réel mesuré sur ce point). + * + * @param {{year:number,month:number,day:number,hour:number,minute:number,second:number}} p + * @param {string} timeZone + * @returns {number} epoch ms + */ +export function zonedTimeToMs(p, timeZone) { + // Conventions : offset(ms) = Date.UTC(parties de ms dans la zone) − ms. + // UTC = W − offset(UTC) (W = instant naïf interprété comme UTC) — deux + // passes couvrent les cas de bord DST. + const naive = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second); + const offsetAt = (ms) => { + const q = zonedParts(ms, timeZone); + return Date.UTC(q.year, q.month - 1, q.day, q.hour, q.minute, q.second) - ms; + }; + let guess = naive - offsetAt(naive); // 1ʳᵉ approximation + guess = naive - offsetAt(guess); // correction DST (point fixe) + return guess; +} + +/** + * Instants des LABELS X (v1.4.5, fix #55) : alignés sur les frontières + * LOCALES de [timeZone] (minuit local pour les pas en jours, heures rondes + * locales pour les pas horaires) et NON sur des multiples UTC — l'ancien + * alignement plaçait le label « 13/09 » à 02:00 du matin FR, ce qui décalait + * la lecture visuelle des événements (remontée : « la prévision simule mon + * injection le 13 au lieu du 12 »). + * + * @param {number} t0 Début de fenêtre (epoch ms) + * @param {number} t1 Fin de fenêtre (epoch ms) + * @param {number} stepHours Pas des labels (1/3/6/24/120…) + * @param {string} [timeZone] Fuseau de LECTURE (défaut = fuseau du système) + * @returns {number[]} instants de tick croissants dans [t0 ; t1] + */ +export function xLabelTicks(t0, t1, stepHours, timeZone) { + const zone = timeZone || Intl.DateTimeFormat().resolvedOptions().timeZone; + if (t1 <= t0 || stepHours <= 0) return []; + + // Premier tick : première frontière locale STRICTEMENT ≥ t0 + let first; + if (stepHours >= 24) { + // pas en JOURS : frontières = MINUIT LOCAL dans [zone] + const parts = zonedParts(t0, zone); + const midnightSameDay = zonedTimeToMs( + { year: parts.year, month: parts.month, day: parts.day, hour: 0, minute: 0, second: 0 }, + zone, + ); + const days = Math.max(Math.trunc(stepHours / 24), 1); + first = midnightSameDay; + while (first <= t0) { + // avance de N jours CALENDAIRES dans le fuseau (DST-safe) + const p = zonedParts(first, zone); + first = zonedTimeToMs({ year: p.year, month: p.month, day: p.day + days, hour: 0, minute: 0, second: 0 }, zone); + } + } else { + // pas horaire : heures rondes LOCALES dans [zone] + const parts = zonedParts(t0, zone); + const topOfHour = zonedTimeToMs( + { year: parts.year, month: parts.month, day: parts.day, hour: parts.hour, minute: 0, second: 0 }, + zone, + ); + first = topOfHour; + while (first <= t0) first += 3600000; + } + + const stepMs = stepHours * 3600000; + const out = []; + // Pour les pas ≥ 24 h, on régénère chaque tick par pas calendaire (DST-safe) + if (stepHours >= 24) { + const days = Math.max(Math.trunc(stepHours / 24), 1); + let t = first; + while (t <= t1) { + out.push(t); + const p = zonedParts(t, zone); + t = zonedTimeToMs({ year: p.year, month: p.month, day: p.day + days, hour: 0, minute: 0, second: 0 }, zone); + } + } else { + for (let t = first; t <= t1; t += stepMs) out.push(t); + } + return out; +} diff --git a/js/pk/index.js b/js/pk/index.js new file mode 100644 index 0000000..8b0134c --- /dev/null +++ b/js/pk/index.js @@ -0,0 +1,17 @@ +/** + * Barrel du noyau PK : un point d'import pour les écrans et les tests. + * (Les modules restent importables individuellement — ce fichier ne fait + * que regrouper les exports.) + */ + +export * from './pk-engine.js'; +export * from './pk-calibration.js'; +export * from './pk-reminders.js'; +export * from './pk-extrema.js'; +export * from './chart-helpers.js'; +export * as Alerts from './alerts.js'; +export * as PKProfileStore from './pk-profile-store.js'; +export * as TransfemScienceModels from './transfem-science-models.js'; +export * as WhsahModels from './whsah-models.js'; +export * as PKPresets from './presets.js'; +export * as ChartHelpers from './chart-helpers.js'; diff --git a/js/pk/pk-calibration.js b/js/pk/pk-calibration.js new file mode 100644 index 0000000..4a4938d --- /dev/null +++ b/js/pk/pk-calibration.js @@ -0,0 +1,306 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Calibration (partie 2/4 du moteur) — portage de `PharmacokineticEngine.kt` + * sections « Calibration » (Android v1.2.1 → v1.4.9). + * + * - PAR PÉRIODE D'ESTER (v1.2.1) : labs valerate → doses valerate, labs + * enanthate → doses enanthate (un facteur unique mélangeait les périodes + * → courbes gonflées à 250–375 pg/mL) ; + * - PAR MODÈLE (v1.4.8, fix #60) : chaque courbe affichée (ESE/TFS/WHS) est + * calibrée avec la prédiction de SON modèle (`modelOverride` propagé) ; + * - GARDE DE SIGNIFICATIVITÉ (v1.4.9, fix #61) : un lab ne calibre que si + * la prédiction reste ≥ 15 % du max observé — sinon ratios aberrants et + * médiane tombant à ×2,21 (dose de test ancienne + labs tardifs). + * + * PUR, testable en Node (web/tests/calibration.test.js). + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { e2At, convertTToNgMl, batemanParams, concentrationOfDose, cutoffHours, doseEster, Esters, TConfig } from './pk-engine.js'; + +/** + * Garde de significativité (fix #61) : fraction minimale de la prédiction max + * observée pour qu'un lab participe à la calibration. + */ +export const LAB_MIN_PREDICTION_FRACTION = 0.15; + +/** + * Un lab ne CALIBRE que si la prédiction du modèle à son instant reste + * ≥ [LAB_MIN_PREDICTION_FRACTION] du max de prédiction déjà observé — + * c'est-à-dire tant que l'injection « gouverne » encore le taux. Au-delà de + * la fenêtre d'action (prédiction résiduelle), le lab reflète AUTRE CHOSE + * (temps mort, début de traitement, autre ester) et son ratio lab ÷ + * prédiction devient aberrant. + * + * Pourquoi une garde RELATIVE (et pas temporelle en jours) : la fenêtre + * d'action dépend du profil (l'injection gouverne ~2 j pour le benzoate, + * des mois pour l'undécylate) — une fenêtre fixe casserait autant les labs + * réels que les tests. La PRÉDICTION, elle, contient déjà toute la + * physiologie du modèle. + * + * @param {number} predicted Prédiction du modèle à l'instant du lab (pg/mL) + * @param {number} maxSoFar Max des prédictions déjà observées (labs par timestamp CROISSANT) + * @returns {boolean} + */ +export function labIsSignificant(predicted, maxSoFar) { + return predicted > 0.5 && (maxSoFar <= 0.0 || predicted >= LAB_MIN_PREDICTION_FRACTION * maxSoFar); +} + +/** Médiane d'une liste numérique (triée ou non). */ +function median(values) { + const sorted = values.slice().sort((a, b) => a - b); + const n = sorted.length; + if (n % 2 === 1) return sorted[(n - 1) / 2]; + return (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0; +} + +/** Arrondi « facteur » à 2 décimales, troncature (miroir du Kotlin (x*100|0)/100). */ +function round2(x) { + return Math.trunc(x * 100) / 100; +} + +/** Arrondi « k T » à 3 décimales, troncature. */ +function round3(x) { + return Math.trunc(x * 1000) / 1000; +} + +/** Dernier élément (par timestamp) d'une liste non vide ; null si vide. */ +function latest(list) { + let acc = null; + for (const d of list) { + if (acc === null || d.timestamp > acc.timestamp) acc = d; + } + return acc; +} + +/** + * Calibration PAR PÉRIODE D'ESTER + PAR MODÈLE (fix #60). + * + * Principe : chaque lab est attribué à la période d'injection dans laquelle + * il tombe = la dernière dose E2 ≤ lab (une prise de sang reflète d'abord + * l'injection qui précède). Le ratio lab ÷ prédiction (toutes doses + * superposées, SANS calibration, avec le MODÈLE demandé) est rattaché à + * l'ester de cette dose. Facteur final par ester = MÉDIANE des ratios de sa + * période, arrondie à 2 décimales. + * + * @param {object[]} treatments TOUS les traitements (actifs ET inactifs — §6.bis) + * @param {object[]} doseLogs + * @param {object[]} e2Labs Labs E2 (marker "E2", unité pg/mL) + * @param {string|null} [modelOverride=null] "ESE" | "TFS" | "WHS" + * @returns {Object} facteur par ester (≥ 1 lab exploitable) + */ +export function computeEsterScaleFactors(treatments, doseLogs, e2Labs, modelOverride = null) { + const estrogenTreatments = treatments.filter((tr) => tr.type === 'ESTRADIOL'); + const estrogenDoses = doseLogs.filter( + (d) => estrogenTreatments.some((tr) => tr.id === d.treatmentId), + ); + if (estrogenDoses.length === 0) return {}; + + // Prédictions non calibrées (scaleFactor forcé à 1) pour chaque lab, + // avec le MODÈLE demandé (fix #60 : chaque courbe est calibrée avec SA + // propre prédiction). + const unscaled = estrogenTreatments.map((tr) => ({ ...tr, scaleFactor: 1.0 })); + const ratiosByEster = new Map(); + let maxPredictionSoFar = 0.0; + // ⚠️ labs par timestamp CROISSANT + garde labIsSignificant (fix #61) + const sortedLabs = e2Labs.slice().sort((a, b) => a.timestamp - b.timestamp); + for (const lab of sortedLabs) { + const predicted = e2At(unscaled, doseLogs, lab.timestamp, modelOverride, null); + if (!labIsSignificant(predicted, maxPredictionSoFar)) continue; + + // Attribution : dernière dose E2 ≤ lab → son ester (override compris) + const attributedDose = latest(estrogenDoses.filter((d) => d.timestamp <= lab.timestamp)); + if (!attributedDose) continue; + const attributedTreatment = estrogenTreatments.find((tr) => tr.id === attributedDose.treatmentId); + const ester = doseEster(attributedTreatment, attributedDose); + if (ester === Esters.NONE) continue; + + if (!ratiosByEster.has(ester)) ratiosByEster.set(ester, []); + ratiosByEster.get(ester).push(lab.value / predicted); + if (predicted > maxPredictionSoFar) maxPredictionSoFar = predicted; + } + const out = {}; + for (const [ester, ratios] of ratiosByEster) out[ester] = round2(median(ratios)); + return out; +} + +/** + * Calibration du k du modèle T PAR PÉRIODE D'ESTER (v1.2.3) + PAR MODÈLE + * (v1.4.8) : la suppression de la testostérone diffère selon l'ester + * (valerate = pics hauts et courts, enanthate = plateau plus doux) → chaque + * lab T est attribué à la période de la dernière dose E2 ≤ lab et + * `k = ((base−floor)/(T_lab − floor) − 1) / E2_est(t_lab)`, garde + * k ∈ (1e-4, 10), médiane par période, arrondie à 3 décimales. + * + * ⚠️ L'E2 utilisée est la version CALIBRÉE (scalePerEster) et suit le MODÈLE + * demandé — calibrer k contre une E2 brute/fausse faussait les k. + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {object[]} tLabs Labs T (marker "T", unités variées) + * @param {TConfig} current + * @param {Object|null} [scalePerEster=null] + * @param {string|null} [modelOverride=null] + * @returns {Object} k par ester + */ +export function computeTKPerEster(treatments, doseLogs, tLabs, current, scalePerEster = null, modelOverride = null) { + const estrogenIds = new Set(treatments.filter((tr) => tr.type === 'ESTRADIOL').map((tr) => tr.id)); + const estrogenDoses = doseLogs.filter((d) => estrogenIds.has(d.treatmentId)); + if (estrogenDoses.length === 0) return {}; + + const ksByEster = new Map(); + let maxE2SoFar = 0.0; + // ⚠️ labs par timestamp CROISSANT + garde labIsSignificant (fix #61) + const sortedLabs = tLabs.slice().sort((a, b) => a.timestamp - b.timestamp); + for (const lab of sortedLabs) { + const tNgMl = convertTToNgMl(lab.value, lab.unit); + if (tNgMl <= current.floor + 0.02) continue; + const attributed = latest(estrogenDoses.filter((d) => d.timestamp <= lab.timestamp)); + if (!attributed) continue; + const tr = treatments.find((x) => x.id === attributed.treatmentId); + const ester = doseEster(tr, attributed); + if (ester === Esters.NONE) continue; + + // v1.4.8 : l'E2 de référence suit le MODÈLE demandé (fix #60) + const e2 = e2At(treatments, doseLogs, lab.timestamp, modelOverride, scalePerEster); + if (!labIsSignificant(e2, maxE2SoFar)) continue; + + const k = ((current.base - current.floor) / (tNgMl - current.floor) - 1.0) / e2; + if (k > 1e-4 && k < 10.0) { + if (!ksByEster.has(ester)) ksByEster.set(ester, []); + ksByEster.get(ester).push(k); + } + if (e2 > maxE2SoFar) maxE2SoFar = e2; + } + const out = {}; + for (const [ester, ks] of ksByEster) out[ester] = round3(median(ks)); + return out; +} + +/** + * Résultat de l'auto-calibration (miroir de AutoCalibrated.kt) : + * traitements/réglages stockés INCHANGÉS — tout est renvoyé en copies pour + * l'affichage uniquement (rien n'est persisté par l'auto-calibration). + */ +export class AutoCalibrated { + /** + * @param {object[]} treatments (identiques à l'entrée) + * @param {TConfig} tConfig (identique à l'entrée) + * @param {Object} esterScales facteur par ester recalculé + * @param {number} calibratedEsters nb d'esters avec ≥ 1 lab exploitable + * @param {boolean} tRecalibrated true si le modèle T a pu être recalibré + * @param {Object} tKPerEster k T par ester recalculé + */ + constructor(treatments, tConfig, esterScales, calibratedEsters, tRecalibrated, tKPerEster) { + this.treatments = treatments; + this.tConfig = tConfig; + this.esterScales = esterScales; + this.calibratedEsters = calibratedEsters; + this.tRecalibrated = tRecalibrated; + this.tKPerEster = tKPerEster; + } +} + +/** + * Calibration automatique (option « Calibration automatique » des Paramètres, + * désactivée par défaut) : + * - facteurs d'échelle E2 par période d'ester ([computeEsterScaleFactors]) ; + * - k du modèle T par période d'ester ([computeTKPerEster]), calibré contre + * l'E2 déjà calibrée. + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {object[]} labs Tous les labs (filtrés E2/T ici) + * @param {TConfig} tConfig + * @param {string|null} [modelOverride=null] Modèle pour les PRÉDICTIONS + * (null = modèle stocké de chaque traitement = comportement Home ; le + * graphique appelle une fois PAR MODÈLE affiché — fix #60) + * @returns {AutoCalibrated} + */ +export function autoCalibrated(treatments, doseLogs, labs, tConfig, modelOverride = null) { + const e2Labs = labs.filter((l) => String(l.marker).toUpperCase() === 'E2'); + const tLabs = labs.filter((l) => String(l.marker).toUpperCase() === 'T'); + + const esterScales = computeEsterScaleFactors(treatments, doseLogs, e2Labs, modelOverride); + const tKPerEster = computeTKPerEster(treatments, doseLogs, tLabs, tConfig, esterScales, modelOverride); + return new AutoCalibrated( + treatments, + tConfig, + esterScales, + Object.keys(esterScales).length, + Object.keys(tKPerEster).length > 0, + tKPerEster, + ); +} + +/** + * Calibration MANUELLE (bouton « Calibrer avec les analyses » de l'éditeur) : + * facteur UNIQUE par traitement (médiane lab ÷ prédiction, modèle stocké), + * garde labIsSignificant (fix #61), arrondi 2 décimales. + * + * ⚠️ Limite documentée (Android §7.6) : en cas de changement d'ester dans un + * même traitement, la manuelle mélange les périodes — préférer l'auto-calibration. + * + * @param {object} treatment + * @param {object[]} allDoseLogs + * @param {object[]} e2Labs + * @returns {number|null} facteur, ou null (pas de lab exploitable) + */ +export function computeScaleFactor(treatment, allDoseLogs, e2Labs) { + if (treatment.type !== 'ESTRADIOL') return null; + const myDoses = allDoseLogs.filter((d) => d.treatmentId === treatment.id); + if (myDoses.length === 0) return null; + + const p = batemanParams(treatment); + const ratios = []; + let maxPredictionSoFar = 0.0; + // ⚠️ labs par timestamp CROISSANT (fix #61) + const sortedLabs = e2Labs.slice().sort((a, b) => a.timestamp - b.timestamp); + for (const lab of sortedLabs) { + let predicted = 0.0; + for (const dose of myDoses) { + if (dose.timestamp > lab.timestamp) continue; + const dtH = (lab.timestamp - dose.timestamp) / 3600000.0; + if (dtH > cutoffHours(treatment)) continue; + predicted += concentrationOfDose(treatment, dose, lab.timestamp, p, null); + } + if (labIsSignificant(predicted, maxPredictionSoFar)) { + ratios.push(lab.value / predicted); + } + if (predicted > maxPredictionSoFar) maxPredictionSoFar = predicted; + } + if (ratios.length === 0) return null; + return round2(median(ratios)); +} + +/** + * Calibration T GLOBALE (k unique — bouton « Calibrer avec les analyses » des + * Paramètres) : labs T normalisés via convertTToNgMl, k = médiane, garde + * k ∈ (1e-4, 10). L'auto-calibration (k par ester) la remplace à l'affichage + * quand elle est active. + * + * @param {object[]} tLabs + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {TConfig} current + * @returns {TConfig|null} nouvelle config, ou null (aucun lab exploitable) + */ +export function computeTConfigCalibration(tLabs, treatments, doseLogs, current) { + // Filtrage pré-conversion (valeur brute) puis post-conversion (ng/mL) — + // un lab déjà sous le plancher est ignoré dans les deux cas. + const usable = tLabs + .filter((l) => l.value > current.floor + 0.02) + .map((l) => ({ ...l, value: convertTToNgMl(l.value, l.unit) })) + .filter((l) => l.value > current.floor + 0.02); + if (usable.length === 0) return null; + + const ks = []; + for (const lab of usable) { + const e2 = e2At(treatments, doseLogs, lab.timestamp, null, null); + if (e2 <= 1.0) continue; + const k = ((current.base - current.floor) / (lab.value - current.floor) - 1.0) / e2; + if (k > 1e-4 && k < 10.0) ks.push(k); + } + if (ks.length === 0) return null; + return new TConfig(current.base, current.floor, round3(median(ks))); +} diff --git a/js/pk/pk-engine.js b/js/pk/pk-engine.js new file mode 100644 index 0000000..d6cbb50 --- /dev/null +++ b/js/pk/pk-engine.js @@ -0,0 +1,474 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * PharmacokineticEngine (partie 1/4 : noyau) — portage web de + * `pk/PharmacokineticEngine.kt`. Le reste est découpé en modules : + * - pk-calibration.js : auto/manuelle par ester/période/modèle (#60/#61) ; + * - pk-reminders.js : prochain déclenchement (grille Posologie, #52) ; + * - pk-extrema.js : détection pics/creux (triangles du graphique). + * + * Chaque injection contribue `dose_mg × profil(dt)` où `profil` est la + * réponse normalisée (pg/mL par mg) ; les contributions se superposent. + * Trois modèles superposables : + * - **Estrannaise (ESE)** = tables horaires de l'asset (ODS) ; + * - **Transfem Science (TFS)** = forme close V3C de la méta-analyse (7 esters) ; + * - **WHSAH (WHS)** = fit « license-free » de Mona (6 esters, PEP non couvert). + * Gel/patch/oral → modèle de Bateman paramétrable. + * + * ⚠️ INVARIANTS à préserver (doc Android §14 — bugs à ne pas réintroduire) : + * - `computeKa` : bisection DANS LE BON SENS (#19) ; + * - tables ESE : modèle strict + extrapolation terminale ≥ 1 % du pic (#20/#21) ; + * - `convertTToNgMl` appliquée à la calibration ET au rendu (#23) ; + * - `generateForecastDoses` : créneaux passés SAUTÉS (#35). + * + * PUR : aucune dépendance au DOM ni au localStorage → testable en Node. + * ───────────────────────────────────────────────────────────────────────────── + */ + +import * as TransfemScienceModels from './transfem-science-models.js'; +import * as WhsahModels from './whsah-models.js'; +import * as PKProfileStore from './pk-profile-store.js'; + +/** Une heure en ms. */ +export const HOUR_MS = 3600000; + +/** Un point de courbe : E2 en pg/mL, T en ng/mL. */ +export class LevelPoint { + /** @param {number} timestamp Epoch ms · @param {number} e2 pg/mL · @param {number} t ng/mL */ + constructor(timestamp, e2, t) { + this.timestamp = timestamp; + this.e2 = e2; + this.t = t; + } +} + +/** Réglages du modèle T empirique (mêmes défauts que TConfig.kt). */ +export class TConfig { + /** @param {number} [base=6.0] T de base (ng/mL) à E2 ≈ 0 · @param {number} [floor=0.2] plancher · @param {number} [k=0.19] constante d'inhibition */ + constructor(base = 6.0, floor = 0.2, k = 0.19) { + this.base = base; + this.floor = floor; + this.k = k; + } +} + +/** Constantes d'ester (miroir de `Esters.kt` — mêmes clés que les backups JSON). */ +export const Esters = { + NONE: 'NONE', EV: 'EV', EU: 'EU', EEN: 'EEN', + EB: 'EB', EC: 'EC', ECS: 'ECS', PEP: 'PEP', +}; + +/** Constantes de modèles PK (miroir de `PKModels.kt`). */ +export const PKModels = { ESTRANNAISE: 'ESE', TRANSFEM_SCIENCE: 'TFS', WHSAH: 'WHS' }; + +/** + * Propriété DÉRIVÉE (miroir du getter Kotlin `usesProfileModel`) : le + * traitement a un PROFIL PK s'il est injectable (IM/SC) ET avec un ester. + * En JS ce n'est pas une propriété stockée (objets JSON plats, mêmes champs + * que Gson) — le moteur la recalcule à la demande. + * + * @param {object} treatment + * @returns {boolean} + */ +export function usesProfileModel(treatment) { + return (treatment.route === 'INJECTION_IM' || treatment.route === 'INJECTION_SUBCUT') + && treatment.esterType !== Esters.NONE; +} + +// --------------------------------------------------------------------------- +// Bateman (gel / patch / oral) +// --------------------------------------------------------------------------- + +/** + * Résout `ln(ka/ke) = (ka−ke)·Tmax` par bisection (50 itérations). + * + * ⚠️ BUG #19 (épinglé depuis la session 1) : eq(mid) DÉCROÎT en mid et + * s'annule en ka > ke → `eq > 0` signifie que la racine est AU-DESSUS de + * mid ⇒ `lo = mid` (l'ancienne bisection inversée convergeait vers un ka + * énorme, pic à ~0 h au lieu de Tmax). + * + * @param {number} tHalfHours Demi-vie d'élimination (h) + * @param {number} tMaxHours Temps au pic souhaité (h) + * @returns {number} ka (h⁻¹) + */ +export function computeKa(tHalfHours, tMaxHours) { + if (tMaxHours <= 0 || tHalfHours <= 0) return 1.0; + const ke = Math.log(2.0) / tHalfHours; + if (tMaxHours < 0.01) return ke * 100.0; + let lo = ke * 1.001; + let hi = ke * 1000.0; + for (let i = 0; i < 50; i++) { + const mid = (lo + hi) / 2.0; + const eq = Math.log(mid / ke) - (mid - ke) * tMaxHours; + if (eq > 0) lo = mid; else hi = mid; // direction corrigée (bug #19) + } + return (lo + hi) / 2.0; +} + +/** + * Paramètres (ke, ka) d'un traitement Bateman, bornes défensives incluses. + * @param {object} treatment Treatment (data/models.js) + * @returns {{ke:number, ka:number}} + */ +export function batemanParams(treatment) { + const ke = Math.log(2.0) / Math.max(treatment.eliminationHalfLifeHours, 0.01); + const ka = computeKa( + Math.max(treatment.eliminationHalfLifeHours, 0.01), + Math.max(treatment.absorptionHours, 0.01), + ); + return { ke, ka }; +} + +// --------------------------------------------------------------------------- +// Contribution d'une dose +// --------------------------------------------------------------------------- + +/** + * Ester EFFECTIF d'une dose : override par injection (`dose.esterType`) ou + * ester par défaut du traitement — permet de switcher d'ester d'une + * injection à l'autre (comme dans le tableur d'origine). + * + * @param {object} treatment + * @param {object} dose DoseLog + * @returns {string} clé ester ("EV"…, "NONE" si aucun) + */ +export function doseEster(treatment, dose) { + return dose.esterType ?? treatment.esterType; +} + +/** + * Contribution d'UNE dose au niveau d'E2 à queryTimeMs (pg/mL), tous modèles : + * 1. traitement à PROFIL PK (injection + ester ≠ NONE) : + * - TFS + ester couvert par le V3C → TransfemScienceModels ; + * - WHSAH + ester couvert par le fit Mona → WhsahModels ; + * - sinon → tables ODS via PKProfileStore (ester sans modèle → 0) ; + * 2. sinon → Bateman (gel / patch / oral / custom). + * + * `modelOverride` force ESE/TFS/WHS pour CE calcul (le graphique dessine les + * trois modèles côte à côte depuis le même traitement) ; Bateman n'est pas + * concerné (les séries y sont identiques). + * + * @param {object} treatment + * @param {object} dose + * @param {number} queryTimeMs + * @param {{ke:number,ka:number}|null} [bateman=null] Params précalculés (cache) + * @param {string|null} [modelOverride=null] "ESE" | "TFS" | "WHS" + * @returns {number} pg/mL + */ +export function concentrationOfDose(treatment, dose, queryTimeMs, bateman = null, modelOverride = null) { + const dtH = (queryTimeMs - dose.timestamp) / 3600000.0; + if (dtH <= 0.0) return 0.0; + + const mg = dose.doseAmount; + if (mg <= 0.0) return 0.0; + + if (usesProfileModel(treatment)) { + const ester = doseEster(treatment, dose); + if (ester !== Esters.NONE) { + const mod = modelOverride || treatment.pkModel; + // Dispatch parallèle des 3 modèles (aucun ne remplace un autre) : + // TFS (V3C méta-analyse) puis WHSAH (fit Mona), fallback tables ODS. + if (mod === PKModels.TRANSFEM_SCIENCE && TransfemScienceModels.hasModel(ester)) { + return TransfemScienceModels.sample(ester, dtH) * mg; + } + if (mod === PKModels.WHSAH && WhsahModels.hasModel(ester)) { + return WhsahModels.sample(ester, dtH) * mg; + } + return PKProfileStore.sample(ester, mod, dtH) * mg; + } + } + + // Fallback Bateman (gel / patch / oral / custom) + const p = bateman || batemanParams(treatment); + const diff = p.ka - p.ke; + const a = mg * treatment.bioavailabilityFraction; + // Cas dégénéré ka≈ke : limite analytique (sinon division par ~0) + const c = Math.abs(diff) < 1e-3 + ? a * p.ke * dtH * Math.exp(-p.ke * dtH) + : (a * p.ka / diff) * (Math.exp(-p.ke * dtH) - Math.exp(-p.ka * dtH)); + return Math.max(0.0, c); +} + +/** + * Fenêtre de contribution d'un traitement en heures (au-delà : la dose est + * ignorée par les boucles d'agrégation — performance). + * - TFS/WHS : 10 demi-vies TERMINALES du fit (ex. EV 30 j, PEP 284 j) ; + * - tables ODS : longueur de table (8001 h) ; + * - Bateman : 30 × t½. Toujours ≥ 24 h. + * + * @param {object} treatment + * @returns {number} heures + */ +export function cutoffHours(treatment) { + let profileH = 0.0; + if (usesProfileModel(treatment)) { + const mod = treatment.pkModel; + if (mod === PKModels.WHSAH && WhsahModels.hasModel(treatment.esterType)) { + profileH = WhsahModels.model(treatment.esterType).terminalHalfLifeDays * 24.0 * 10.0; + } else if (mod === PKModels.TRANSFEM_SCIENCE && TransfemScienceModels.hasModel(treatment.esterType)) { + profileH = TransfemScienceModels.model(treatment.esterType).terminalHalfLifeDays * 24.0 * 10.0; + } else { + profileH = PKProfileStore.profileLength(treatment.esterType, mod); + } + } + const batemanH = usesProfileModel(treatment) ? 0.0 : 30.0 * treatment.eliminationHalfLifeHours; + const cut = Math.max(profileH, batemanH); + return cut >= 24.0 ? cut : 24.0; +} + +// --------------------------------------------------------------------------- +// Niveaux agrégés +// --------------------------------------------------------------------------- + +/** + * Niveau E2 total à tMs : somme des contributions de toutes les doses E2 + * (traitements ESTRADIOL uniquement), chacune multipliée par SON facteur + * d'échelle — `scalePerEster[ester]` si présent (calibration par période + * d'ester, v1.2.1), sinon `scaleFactor` stocké du traitement. + * + * ⚠️ TOUS les traitements sont passés ici, actifs comme INACTIFS : + * « isActive » est un drapeau administratif (plus de nouvelles doses), + * JAMAIS un filtre de données (bug v1.2.4 — l'historique EV d'un traitement + * inactivé doit rester simulé, cf régression #3). + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {number} tMs Instant d'évaluation (epoch ms) + * @param {string|null} [modelOverride=null] + * @param {Object|null} [scalePerEster=null] + * @returns {number} pg/mL + */ +export function e2At(treatments, doseLogs, tMs, modelOverride = null, scalePerEster = null) { + const batemanCache = new Map(); + let total = 0.0; + for (const treatment of treatments) { + if (treatment.type !== 'ESTRADIOL') continue; // AA/progestatifs → 0 en E2 + if (!batemanCache.has(treatment.id)) { + batemanCache.set(treatment.id, batemanParams(treatment)); + } + for (const dose of doseLogs) { + if (dose.treatmentId !== treatment.id || dose.timestamp > tMs) continue; + const dtH = (tMs - dose.timestamp) / 3600000.0; + if (dtH > cutoffHours(treatment)) continue; + const c = concentrationOfDose(treatment, dose, tMs, batemanCache.get(treatment.id), modelOverride); + if (c > 0.0) { + const scale = (scalePerEster && scalePerEster[doseEster(treatment, dose)]) ?? treatment.scaleFactor; + total += c * scale; + } + } + } + return total; +} + +/** + * Modèle T EMPIRIQUE (non publié — étiqueté « estimation » partout) : + * `T = floor + (base − floor) / (1 + k·E2)` en ng/mL. + * + * @param {number} e2Level E2 calibrée (pg/mL) + * @param {TConfig} config + * @returns {number} ng/mL + */ +export function testosteroneAt(e2Level, config) { + if (e2Level <= 0.0) return config.base; + return config.floor + (config.base - config.floor) / (1.0 + config.k * e2Level); +} + +/** + * Normalisation des labs T vers ng/mL — appliquée à la calibration ET au + * rendu du graphique (sinon l'axe T est faux d'un facteur 100, bug #23 : + * labs 33/44 ng/dL). Branche défensive pg/mL ÷ 1000 : un lab saisi avec une + * unité aberrante (« 38 pg/mL », bug #26) ne doit pas écraser l'axe T. + * + * @param {number} value Valeur du lab + * @param {string} unit Unité saisie (ng/mL, ng/dL, ng/L, nmol/L…) + * @returns {number} ng/mL + */ +export function convertTToNgMl(value, unit) { + const u = String(unit).toLowerCase().replace(/\s/g, ''); + if (u.includes('dl')) return value / 100.0; + if (u.includes('nmol')) return value * 0.2884; + if (u.includes('pg')) return value / 1000.0; + if (u.includes('µg') || u.includes('μg')) return value / 1000.0; + if (u.includes('ng/l') || u.endsWith('/l')) return value / 1000.0; + return value; // ng/mL (ou unité inconnue : valeur brute) +} + +/** + * L'ester « actif » à l'instant t = celui de la DERNIÈRE dose E2 ≤ t + * (une prise de sang / un point de courbe reflète l'injection qui précède). + * Utilisé pour choisir le k de la courbe T (k par période d'ester, v1.2.3). + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {number} tMs + * @returns {string|null} clé ester, ou null (avant la 1ʳᵉ dose / ester NONE) + */ +export function activeEsterAt(treatments, doseLogs, tMs) { + const estrogenIds = new Set(treatments.filter((tr) => tr.type === 'ESTRADIOL').map((tr) => tr.id)); + let last = null; + for (const d of doseLogs) { + if (d.timestamp <= tMs && estrogenIds.has(d.treatmentId)) { + if (last === null || d.timestamp > last.timestamp) last = d; + } + } + if (!last) return null; + const tr = treatments.find((t) => t.id === last.treatmentId); + const ester = doseEster(tr, last); + return ester !== Esters.NONE ? ester : null; +} + +/** + * Niveau combiné (E2 + T) à un instant donné — la notification d'alerte et la + * carte « niveau actuel » de l'accueil partagent CE calcul (même calibration : + * `scalePerEster` + `tKPerEster`, cf §9.bis Android). + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {number} tMs + * @param {TConfig} tConfig + * @param {Object|null} [tKPerEster=null] k T par ester (période active) + * @param {Object|null} [scalePerEster=null] + * @returns {LevelPoint} + */ +export function levelAt(treatments, doseLogs, tMs, tConfig, tKPerEster = null, scalePerEster = null) { + const e2 = e2At(treatments, doseLogs, tMs, null, scalePerEster); + const active = activeEsterAt(treatments, doseLogs, tMs); + const k = (active && tKPerEster && tKPerEster[active]) ?? tConfig.k; + return new LevelPoint(tMs, e2, testosteroneAt(e2, new TConfig(tConfig.base, tConfig.floor, k))); +} + +/** Niveau actuel (now par défaut) — enveloppe de levelAt. */ +export function currentLevel(treatments, doseLogs, tConfig, tKPerEster = null, nowMs = Date.now(), scalePerEster = null) { + return levelAt(treatments, doseLogs, nowMs, tConfig, tKPerEster, scalePerEster); +} + +/** + * Courbe complète entre startMs et endMs (grille régulière stepMs, défaut 1 h). + * - départ clampé à la 1ʳᵉ dose (rien avant) ; + * - k de la T suivant l'ester ACTIF à chaque point (curseur sur les doses triées) ; + * - modèle via `modelOverride`, calibration via `scalePerEster` + `tKPerEster`. + * + * @param {object[]} treatments + * @param {object[]} doseLogs + * @param {number} startMs + * @param {number} endMs + * @param {number} [stepMs=HOUR_MS] + * @param {TConfig} tConfig + * @param {object} [opts={}] { modelOverride, scalePerEster, tKPerEster } + * @returns {LevelPoint[]} + */ +export function computeCurve(treatments, doseLogs, startMs, endMs, stepMs, tConfig, opts = {}) { + if (stepMs === undefined) stepMs = HOUR_MS; + const { modelOverride = null, scalePerEster = null, tKPerEster = null } = opts; + if (treatments.length === 0 || doseLogs.length === 0 || endMs <= startMs) return []; + + const relevantTreatments = treatments.filter( + (tr) => tr.type === 'ESTRADIOL' && doseLogs.some((d) => d.treatmentId === tr.id), + ); + if (relevantTreatments.length === 0) return []; + + const earliestDose = doseLogs.reduce((m, d) => Math.min(m, d.timestamp), Infinity); + const searchStart = Math.max(startMs, earliestDose); + + const batemanCache = new Map(); + relevantTreatments.forEach((tr) => batemanCache.set(tr.id, batemanParams(tr))); + + // Doses triées pour suivre l'« ester actif » le long de la grille (k de T) + const sortedEstrogenDoses = doseLogs + .filter((d) => relevantTreatments.some((tr) => tr.id === d.treatmentId)) + .slice() + .sort((a, b) => a.timestamp - b.timestamp); + let doseCursor = 0; + let activeEster = null; + + const points = []; + let t = searchStart; + while (t <= endMs) { + let e2 = 0.0; + // Avance le curseur : la dose à t définit l'ester actif + while (doseCursor < sortedEstrogenDoses.length && sortedEstrogenDoses[doseCursor].timestamp <= t) { + const d = sortedEstrogenDoses[doseCursor]; + const tr = relevantTreatments.find((x) => x.id === d.treatmentId); + activeEster = doseEster(tr, d); + doseCursor++; + } + const k = (activeEster && tKPerEster && tKPerEster[activeEster]) ?? tConfig.k; + for (const treatment of relevantTreatments) { + const cutoff = cutoffHours(treatment); + const p = batemanCache.get(treatment.id); + for (const dose of doseLogs) { + if (dose.treatmentId !== treatment.id || dose.timestamp > t) continue; + const dtH = (t - dose.timestamp) / 3600000.0; + if (dtH > cutoff) continue; + const c = concentrationOfDose(treatment, dose, t, p, modelOverride); + if (c > 0.0) { + const scale = (scalePerEster && scalePerEster[doseEster(treatment, dose)]) ?? treatment.scaleFactor; + e2 += c * scale; + } + } + } + points.push(new LevelPoint(t, e2, testosteroneAt(e2, new TConfig(tConfig.base, tConfig.floor, k)))); + t += stepMs; + } + return points; +} + +// --------------------------------------------------------------------------- +// Prévision +// --------------------------------------------------------------------------- + +/** + * Génère les doses PRÉVISIONNELLES d'un traitement, à partir de sa Posologie + * (`forecastIntervalDays`, en jours) et de la dernière dose réellement + * enregistrée. + * + * Règles : + * - intervalle null ou ≤ 0 → aucune prévision ; + * - la première dose projetée suit EXACTEMENT l'intervalle après la dernière + * dose réelle (le rythme reste sous le contrôle de l'utilisatrice) ; + * - dose = standard du traitement, ester = override de la dernière injection ; + * - ⚠️ les créneaux déjà PASSÉS sont sautés au rythme configuré (#35, cas + * d'un OUBLI : simuler un créneau passé = faux pic dans l'historique) ; + * un RETARD décale naturellement toute la prévision (comportement voulu). + * + * Les doses retournées ne sont JAMAIS persistées : elles alimentent + * computeCurve et les marqueurs du graphique quand le chip est actif. + * + * @param {object} treatment + * @param {object[]} allDoseLogs + * @param {number} toMs Fin de la fenêtre de projection + * @param {number} [nowMs=Date.now()] + * @returns {object[]} DoseLog[] (objets purs, jamais en base) + */ +export function generateForecastDoses(treatment, allDoseLogs, toMs, nowMs = Date.now()) { + const intervalDays = treatment.forecastIntervalDays; + if (intervalDays === null || intervalDays === undefined || intervalDays <= 0.0) return []; + const intervalMs = Math.trunc(intervalDays * 24.0 * HOUR_MS); + if (intervalMs <= 0) return []; + + let last = null; + for (const d of allDoseLogs) { + if (d.treatmentId === treatment.id && d.timestamp <= nowMs) { + if (last === null || d.timestamp > last.timestamp) last = d; + } + } + if (!last) return []; + + const forecast = []; + let t = last.timestamp + intervalMs; + // ⚠️ Oubli d'une injection : le premier créneau théorique tombe dans le + // PASSÉ → on avance au premier créneau STRICTEMENT FUTUR, au rythme + // configuré (bug #35). + while (t <= nowMs) t += intervalMs; + while (t <= toMs) { + forecast.push({ + id: 0, // jamais persisté + treatmentId: treatment.id, + timestamp: t, + doseAmount: treatment.doseAmount, + notes: null, + esterType: doseEster(treatment, last), + }); + t += intervalMs; + } + return forecast; +} diff --git a/js/pk/pk-extrema.js b/js/pk/pk-extrema.js new file mode 100644 index 0000000..725584f --- /dev/null +++ b/js/pk/pk-extrema.js @@ -0,0 +1,74 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Détection des pics et creux (partie 4/4 du moteur) — portage de + * `PharmacokineticEngine.detectExtrema` (v1.2.3) : triangles ▲▼ aux extrema + * locaux de chaque courbe, avec valeurs estimées (v1.2.6). + * + * PUR, testable en Node (web/tests/extrema.test.js). + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** + * Un extremum détecté : pic (isPeak = true) ou creux, avec sa valeur. + */ +export class Extremum { + /** @param {number} timestamp · @param {number} value · @param {boolean} isPeak */ + constructor(timestamp, value, isPeak) { + this.timestamp = timestamp; + this.value = value; + this.isPeak = isPeak; + } +} + +/** + * Détecte les maxima et minima LOCAUX d'une courbe (les pics après chaque + * injection et les creux juste avant la suivante, en régime d'équilibre). + * + * - les plateaux (valeurs égales consécutives) comptent pour UN seul extremum ; + * - les extrema consécutifs de même type sont FUSIONNÉS (le plus extrême gagne) ; + * - les oscillations d'amplitude < minSwingAbs sont ignorées (bruit + * d'arrondi) — utiliser ~2 pg/mL pour l'E2 et ~0,02 ng/mL pour la T. + * + * @param {import('./pk-engine.js').LevelPoint[]} points Points de courbe + * @param {(p:object)=>number} selector Ce qu'on analyse (`p => p.e2` ou `p => p.t`) + * @param {number} [minSwingAbs=2.0] Amplitude minimale d'une oscillation + * @returns {Extremum[]} + */ +export function detectExtrema(points, selector, minSwingAbs = 2.0) { + if (points.length < 3) return []; + const values = points.map(selector); + const abs = Math.abs; + + // 1) extrema locaux stricts (un plateau = son premier point) + const raw = []; // [index, isPeak] + let i = 1; + while (i < values.length - 1) { + const v = values[i]; + const prev = values[i - 1]; + let j = i; + while (j < values.length - 1 && values[j + 1] === v) j++; // plateau + if (j >= values.length - 1) break; + const next = values[j + 1]; + if (v > prev && v > next) { + raw.push([i, true]); + } else if (v < prev && v < next) { + raw.push([i, false]); + } + i = j + 1; + } + + // 2) alternance pic/creux + filtre d'amplitude + const out = []; + for (const [idx, isPeak] of raw) { + const last = out[out.length - 1] || null; + if (last && last.isPeak === isPeak) { + // doublon de même type : garder le plus extrême + const better = isPeak ? values[idx] > last.value : values[idx] < last.value; + if (better) out[out.length - 1] = new Extremum(points[idx].timestamp, values[idx], isPeak); + continue; + } + if (last && abs(last.value - values[idx]) < minSwingAbs) continue; + out.push(new Extremum(points[idx].timestamp, values[idx], isPeak)); + } + return out; +} diff --git a/js/pk/pk-profile-store.js b/js/pk/pk-profile-store.js new file mode 100644 index 0000000..7e1ab2c --- /dev/null +++ b/js/pk/pk-profile-store.js @@ -0,0 +1,171 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * PKProfileStore — chargeur de l'asset `pk_profiles.json` + échantillonnage + * (portage web de `pk/PKProfileStore.kt`). + * + * L'asset contient les tables horaires Estrannaise extraites du tableur + * `Estrogen.ods` : { "params": {D/k1/k2/k3…}, "profiles": { "EV_ese": [8001 + * floats], … } } — profils NORMALISÉS (pg/mL par mg injecté) sur 8001 h. + * + * ⚠️ Les tables affichent 2 décimales → plancher d'affichage 0,01/0,00 en + * queue (bug « courbe plate à 0 » du début du projet, cf DEVELOPPEMENT.md + * Android §14 #20) : l'échantillonnage EXTRAPOLE au-delà de la table depuis + * le dernier point encore ≥ 1 % du pic (cf `sample`). + * + * Ennavigateur : `init(url)` (fetch de l'asset). En Node (tests) : + * `initWithJson(texte)` — point d'entrée PUR, identique au Kotlin. + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** Modèle strict : seule cette table de suffixes est acceptée (bug §14 #21 : + * tout modèle inconnu renvoyait silencieusement Estrannaise → validation stricte). */ +const MODEL_SUFFIX = { TFS: 'tfs', ESE: 'ese' }; + +let profiles = null; // Map — null tant que non initialisé + +/** + * Initialise le store depuis un JSON (PUR — testable en Node). + * ⚠️ La racine du JSON contient `profiles` : lire la racine directement = le + * crash du tout premier build (bug §14 #4). Structure attendue : + * `{ "params": {...}, "profiles": { "_": [floats] } }`. + * + * @param {string} json Contenu texte de pk_profiles.json + */ +export function initWithJson(json) { + const root = JSON.parse(json); + const obj = root.profiles; // ⚠️ pas la racine (bug §14 #4) + const parsed = new Map(); + for (const [key, arr] of Object.entries(obj)) { + parsed.set(key, Float64Array.from(arr)); + } + profiles = parsed; +} + +/** + * Initialise le store en navigateur (fetch de l'asset statique). + * À appeler UNE fois au démarrage de l'app, avant tout rendu. + * + * @param {string} [url='assets/pk_profiles.json'] URL de l'asset + */ +export async function init(url = 'assets/pk_profiles.json') { + const resp = await fetch(url); + if (!resp.ok) throw new Error(`pk_profiles.json : HTTP ${resp.status}`); + initWithJson(await resp.text()); +} + +/** + * Clé de profil pour un couple (ester, modèle) — ex. ("EV","ESE") → "EV_ese". + * (Le Kotlin construit la même clé ; le lookup réel est insensible à la casse.) + * + * @param {string} ester "EV" / "EU" / "EEN" … + * @param {string} mod "ESE" | "TFS" (les autres modèles n'ont pas de table) + * @returns {string} clé de table, ex. "EV_ese" + */ +export function profileKey(ester, mod) { + const suffix = mod === 'TFS' ? 'tfs' : 'ese'; + return `${ester}_${suffix}`; +} + +/** + * Lookup INSENSIBLE À LA CASSE (bug #22 — LE bug « courbes vides » : l'asset + * contient "EEn_ese" (casse biologique de l'ODS) alors que la constante de + * l'app est Esters.EEN = "EEN" ; un lookup exact renvoyait null pour tout + * traitement EEn → courbe E2 plate à 0). + * + * @param {string} key Clé de table ("EEn_ese" …) + * @returns {Float64Array|null} + */ +function lookup(key) { + if (!profiles) return null; + return profiles.get(key) + // fallback insensible à la casse (première clé qui matche) + || [...profiles.entries()].find(([k]) => k.toLowerCase() === key.toLowerCase())?.[1] + || null; +} + +/** + * @param {string} ester Clé ester + * @param {string} mod "ESE" | "TFS" + * @returns {boolean} true si une table horaire existe pour ce couple + */ +export function hasProfile(ester, mod) { + return lookup(profileKey(ester, mod)) !== null; +} + +/** + * Longueur de table (heures) — utilisée par le moteur comme `cutoffHours` + * pour les profils à tables (la contribution d'une dose est coupée au-delà). + * + * @param {string} ester Clé ester + * @param {string} mod "ESE" | "TFS" + * @returns {number} nombre de points (0 si pas de table) + */ +export function profileLength(ester, mod) { + return lookup(profileKey(ester, mod))?.length ?? 0; +} + +/** + * Réponse normalisée (pg/mL par mg injecté) à dtHours après une injection de + * 1 mg — modèle STRICT par tables : + * - modèle inconnu (ni TFS ni ESE) → 0 (jamais de fallback silencieux — bug §14 #21) ; + * - interpolation LINÉAIRE entre heures entières ; + * - extrapolation TERMINALE au-delà de la table depuis le dernier point + * ≥ 1 % du pic, avec la pente = décroissance moyenne des 48 h précédentes + * (jamais avant le pic — cf plancher 0,01/0,00 de l'ODS, bug §14 #20). + * + * ⚠️ Tout est calculé en double précision JS (équivalent Double Kotlin — le + * mélange Float/Double était une source d'erreurs de compilation côté Android). + * + * @param {string} ester Clé ester ("EV"…) + * @param {string} mod "ESE" | "TFS" + * @param {number} dtHours Heures depuis l'injection (≤ 0 → 0) + * @returns {number} pg/mL par mg + */ +export function sample(ester, mod, dtHours) { + if (!profiles || dtHours <= 0.0) return 0.0; + const suffix = MODEL_SUFFIX[mod]; // strict : TFS→tfs, ESE→ese, autre → undefined + if (!suffix) return 0.0; + const arr = lookup(`${ester}_${suffix}`); + if (!arr || arr.length < 2) return 0.0; + + const lastIdx = arr.length - 1; + if (dtHours >= lastIdx) { + // ── Extrapolation terminale (miroir exact du Kotlin) ─────────────────── + // Les tables ODS sont arrondies à 2 décimales et s'effondrent en un + // plancher 0,01/0,00 bien avant que la vraie valeur ne s'annule. + // Extrapoler depuis la FIN de table donnait 0 à vie (ou une constante + // plate) → on part du dernier point encore ≥ 1 % du pic, avec le TAUX + // logarithmique moyen des 48 h précédentes (jamais avant le pic) — + // extrapolation EXPONENTIELLE, donc jamais croissante. + let peakIdx = 0; + let peakV = 0; + for (let idx = 0; idx < arr.length; idx++) { + if (arr[idx] > peakV) { + peakV = arr[idx]; + peakIdx = idx; + } + } + if (peakV <= 0) return 0.0; + let j = lastIdx; + while (j > 0 && arr[j] < peakV * 0.01) j--; + if (j <= 0) return 0.0; + const WINDOW = 48; + const j0 = Math.max(peakIdx, j - WINDOW); + let rate = 0.0; + if (j > j0) { + rate = Math.log(Math.max(arr[j], 1e-12) / Math.max(arr[j0], 1e-12)) / (j - j0); + } + return arr[j] * Math.exp(rate * (dtHours - j)); + } + + // ── Interpolation linéaire entre heures entières ──────────────────────── + const i0 = Math.floor(dtHours); + const frac = dtHours - i0; + if (i0 >= lastIdx) return arr[lastIdx]; // garde défensive + return arr[i0] + (arr[i0 + 1] - arr[i0]) * frac; +} + +/** @returns {boolean} true si le store a été initialisé (asset chargé). */ +export function isInitialized() { + return profiles !== null; +} diff --git a/js/pk/pk-reminders.js b/js/pk/pk-reminders.js new file mode 100644 index 0000000..9e92e4a --- /dev/null +++ b/js/pk/pk-reminders.js @@ -0,0 +1,105 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Rappels (partie 3/4 du moteur) — portage de `PharmacokineticEngine.kt` + * sections `nextReminderFireFor` / `nextReminderFireMs` (fix #52, v1.4.0). + * + * Le WEB ne peut pas programmer d'alarmes système : ces fonctions alimentent + * la boucle de vérification de l'app (`ui/reminders.js`, toutes les 30 s + * tant que la page est ouverte) et la carte « Prochaine dose » de l'accueil. + * Limite documentée : pas de notification quand l'onglet est fermé. + * + * ⚠️ FIX #52 (v1.4.0) : si le traitement a une Posologie + * (`forecastIntervalDays`) ET un historique de doses, le rappel suit la + * GRILLE des prises (dernière dose réelle + k × intervalle, créneaux passés + * sautés — même sémantique que generateForecastDoses) et sonne le jour du + * créneau à l'heure choisie : une injection tous les samedis à 18 h → + * rappel UNIQUEMENT le samedi à 18 h. Sinon (gel/oral, ou aucune dose pour + * cadrer la grille) → comportement QUOTIDIEN historique (prochaine + * occurrence HH:mm). + * + * PUR, testable en Node (web/tests/reminders.test.js). + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { HOUR_MS } from './pk-engine.js'; + +/** Une journée en ms. */ +const DAY_MS = 86400000; + +/** + * Met une date sur HH:mm:00.000 du MÊME jour calendaire (heure LOCALE du + * navigateur / du process Node — équivalent Calendar.set(HOUR_OF_DAY…)). + * + * @param {number} ms + * @param {number} hour + * @param {number} minute + * @returns {number} epoch ms + */ +function atTimeSameDay(ms, hour, minute) { + const d = new Date(ms); + d.setHours(hour, minute, 0, 0); + return d.getTime(); +} + +/** + * Prochain déclenchement de rappel pour UN traitement. + * + * @param {object} treatment (reminderEnabled, reminderHour/Minute, isActive, + * forecastIntervalDays) + * @param {object[]} doseLogs Toutes les doses (le traitement repère les siennes) + * @param {number} nowMs + * @returns {number|null} epoch ms du prochain déclenchement, null si désactivé + */ +export function nextReminderFireFor(treatment, doseLogs, nowMs) { + if (!treatment.reminderEnabled || !treatment.isActive) return null; + const h = treatment.reminderHour; + const m = treatment.reminderMinute; + if (h === null || h === undefined || m === null || m === undefined) return null; + + const intervalDays = treatment.forecastIntervalDays; + const myDoses = doseLogs.filter( + (d) => d.treatmentId === treatment.id && d.timestamp <= nowMs, + ); + if (intervalDays !== null && intervalDays !== undefined && intervalDays > 0.0 && myDoses.length > 0) { + const intervalMs = Math.trunc(intervalDays * 24.0 * HOUR_MS); + if (intervalMs > 0) { + // Dernière dose réelle = ancre de la grille + let slot = myDoses.reduce((acc, d) => (acc === null || d.timestamp > acc.timestamp ? d : acc), null).timestamp; + // Le premier créneau STRICTEMENT futur (un créneau passé = oubli : + // on avance au rythme configuré — cf generateForecastDoses, #35). + while (slot <= nowMs) slot += intervalMs; + // L'alarme sonne le jour du créneau, à l'heure de rappel choisie ; + // si cette heure est déjà passée le jour du créneau → créneau suivant. + let candidate = slot; + for (;;) { + const fireMs = atTimeSameDay(candidate, h, m); + if (fireMs > nowMs) return fireMs; + candidate += intervalMs; + } + } + } + + // Fallback QUOTIDIEN (comportement historique) : prochaine occurrence HH:mm + let fire = atTimeSameDay(nowMs, h, m); + if (fire <= nowMs) fire += DAY_MS; + return fire; +} + +/** + * Prochain rappel parmi tous les traitements (min). [doseLogs] active la + * planification par Posologie (cf nextReminderFireFor) ; vide = fallback + * quotidien pur (compatibilité avec les appels historiques). + * + * @param {object[]} treatments + * @param {object[]} [doseLogs=[]] + * @param {number} [nowMs=Date.now()] + * @returns {number|null} + */ +export function nextReminderFireMs(treatments, doseLogs = [], nowMs = Date.now()) { + let next = null; + for (const tr of treatments) { + const fire = nextReminderFireFor(tr, doseLogs, nowMs); + if (fire !== null && (next === null || fire < next)) next = fire; + } + return next; +} diff --git a/js/pk/presets.js b/js/pk/presets.js new file mode 100644 index 0000000..e09be42 --- /dev/null +++ b/js/pk/presets.js @@ -0,0 +1,79 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Presets de traitements (portage de `data/model/PKPresets.kt`) : pré-remplis + * dans l'éditeur. Les champs Bateman des presets à PROFIL PK reflètent les + * Tmax/t½ des modèles (affichés dans l'éditeur, non utilisés au calcul — le + * dispatch passe par les profils/tables). + * + * Les libellés passent par des clés i18n (presets.fr.js / presets.en.js via + * util/i18n.js) — mêmes textes que les strings Android pour la cohérence des + * deux apps. + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { Esters, PKModels } from './pk-engine.js'; + +/** + * Un preset (miroir de PKPreset.kt). + */ +export class PKPreset { + /** + * @param {string} nameKey Clé i18n du libellé ("preset_ev_ese"…) + * @param {string} type TreatmentType + * @param {string} route AdministrationRoute + * @param {string} esterType Clé ester ("EV"…, "NONE" pour Bateman) + * @param {string} pkModel "ESE" | "TFS" | "WHS" + * @param {number} absorptionHours Tmax (h, Bateman) + * @param {number} eliminationHalfLifeHours t½ (h, Bateman) + * @param {number} bioavailabilityFraction F (0..1, Bateman) + * @param {string} defaultDoseUnit + * @param {number} defaultDoseAmount + */ + constructor(nameKey, type, route, esterType, pkModel, absorptionHours, eliminationHalfLifeHours, bioavailabilityFraction, defaultDoseUnit, defaultDoseAmount) { + this.nameKey = nameKey; + this.type = type; + this.route = route; + this.esterType = esterType; + this.pkModel = pkModel; + this.absorptionHours = absorptionHours; + this.eliminationHalfLifeHours = eliminationHalfLifeHours; + this.bioavailabilityFraction = bioavailabilityFraction; + this.defaultDoseUnit = defaultDoseUnit; + this.defaultDoseAmount = defaultDoseAmount; + } +} + +/** Tous les presets, dans l'ordre de l'éditeur (identique au Kotlin). */ +export const all = [ + // --- Injections : modèles Estrannaise (tables ODS) --- + new PKPreset('preset_ev_ese', 'ESTRADIOL', 'INJECTION_IM', Esters.EV, PKModels.ESTRANNAISE, 46, 100, 1, 'mg', 4.0), + new PKPreset('preset_eu_ese', 'ESTRADIOL', 'INJECTION_IM', Esters.EU, PKModels.ESTRANNAISE, 55, 400, 1, 'mg', 10.0), + new PKPreset('preset_een_ese', 'ESTRADIOL', 'INJECTION_SUBCUT', Esters.EEN, PKModels.ESTRANNAISE, 152, 150, 1, 'mg', 10.0), + // --- Injections : modèles Transfem Science --- + new PKPreset('preset_ev_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.EV, PKModels.TRANSFEM_SCIENCE, 50, 72, 1, 'mg', 4.0), + new PKPreset('preset_eu_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.EU, PKModels.TRANSFEM_SCIENCE, 204, 528, 1, 'mg', 10.0), + new PKPreset('preset_een_tfs', 'ESTRADIOL', 'INJECTION_SUBCUT', Esters.EEN, PKModels.TRANSFEM_SCIENCE, 156, 110, 1, 'mg', 5.0), + // --- Nouveaux esters de la méta-analyse TFS (v1.4.0, modèle V3C) : + // EB / EC (huile) / EC suspension / PEP — PAS de tables Estrannaise + // pour ces clés : presets créés en modèle TFS uniquement. + new PKPreset('preset_eb_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.EB, PKModels.TRANSFEM_SCIENCE, 16, 29, 1, 'mg', 1.0), + new PKPreset('preset_ec_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.EC, PKModels.TRANSFEM_SCIENCE, 103, 161, 1, 'mg', 5.0), + new PKPreset('preset_ecs_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.ECS, PKModels.TRANSFEM_SCIENCE, 30, 122, 1, 'mg', 5.0), + new PKPreset('preset_pep_tfs', 'ESTRADIOL', 'INJECTION_IM', Esters.PEP, PKModels.TRANSFEM_SCIENCE, 432, 682, 1, 'mg', 32.5), + // --- Modèle WHSAH (v1.4.6, fit license-free de Mona) : 6 esters, PEP non + // couvert. Champs Bateman = t½ WHSAH (pas utilisés au calcul). + new PKPreset('preset_ev_whs', 'ESTRADIOL', 'INJECTION_IM', Esters.EV, PKModels.WHSAH, 41, 75, 1, 'mg', 4.0), + new PKPreset('preset_eu_whs', 'ESTRADIOL', 'INJECTION_IM', Esters.EU, PKModels.WHSAH, 80, 722, 1, 'mg', 10.0), + new PKPreset('preset_een_whs', 'ESTRADIOL', 'INJECTION_SUBCUT', Esters.EEN, PKModels.WHSAH, 120, 176, 1, 'mg', 5.0), + new PKPreset('preset_eb_whs', 'ESTRADIOL', 'INJECTION_IM', Esters.EB, PKModels.WHSAH, 13, 25, 1, 'mg', 1.0), + new PKPreset('preset_ec_whs', 'ESTRADIOL', 'INJECTION_IM', Esters.EC, PKModels.WHSAH, 91, 161, 1, 'mg', 5.0), + new PKPreset('preset_ecs_whs', 'ESTRADIOL', 'INJECTION_IM', Esters.ECS, PKModels.WHSAH, 60, 110, 1, 'mg', 5.0), + // --- Transdermique / oral (fallback Bateman) --- + new PKPreset('preset_e2_gel', 'ESTRADIOL', 'TRANSDERMAL_GEL', Esters.NONE, PKModels.ESTRANNAISE, 4, 24, 0.8, 'mg', 2.0), + new PKPreset('preset_e2_patch', 'ESTRADIOL', 'TRANSDERMAL_PATCH', Esters.NONE, PKModels.ESTRANNAISE, 8, 24, 0.9, 'mg/day', 0.1), + new PKPreset('preset_e2_oral', 'ESTRADIOL', 'ORAL', Esters.NONE, PKModels.ESTRANNAISE, 1.5, 16, 0.4, 'mg', 2.0), + // --- Anti-androgènes (Bateman) --- + new PKPreset('preset_cpa', 'ANTI_ANDROGEN', 'ORAL', Esters.NONE, PKModels.ESTRANNAISE, 2, 30, 0.8, 'mg', 10.0), + new PKPreset('preset_spiro', 'ANTI_ANDROGEN', 'ORAL', Esters.NONE, PKModels.ESTRANNAISE, 1.5, 8, 0.7, 'mg', 100.0), + new PKPreset('preset_bica', 'ANTI_ANDROGEN', 'ORAL', Esters.NONE, PKModels.ESTRANNAISE, 3, 168, 0.9, 'mg', 50.0), +]; diff --git a/js/pk/transfem-science-models.js b/js/pk/transfem-science-models.js new file mode 100644 index 0000000..73ad359 --- /dev/null +++ b/js/pk/transfem-science-models.js @@ -0,0 +1,137 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Modèles pharmacocinétiques « Transfem Science » — méta-analyse des esters + * d'estradiol injectables (transfemscience.org/articles/injectable-e2-meta-analysis/). + * + * PORTAGE WEB du fichier Android `app/src/main/java/com/hormonetrack/pk/ + * TransfemScienceModels.kt` (v1.4.0) — les paramètres sont copiés À + * L'IDENTIQUE : toute retouche doit être faite dans les DEUX codebases et + * épinglée par les tests (web/tests/tfs-models.test.js côté web, + * TransfemScienceModelsTest.kt côté Android). + * + * SOURCE DES PARAMÈTRES : les coefficients du modèle à 3 compartiments (V3C) + * publiés par le simulateur officiel de Transfem Science + * (https://transfemscience.org/misc/injectable-e2-simulator/ — valeurs = + * DONNÉES SCIENTIFIQUES issues des études agrégées, Tableaux 9–10 de + * l'article). + * + * Forme close exacte (t en JOURS, Cp en pg/mL pour la dose de fit 5 mg) : + * + * Cp(t) = D·k1·k2 · [ e^(−k1·t)/((k1−k2)(k1−k3)) + * + e^(−k3·t)/((k1−k3)(k2−k3)) + * + e^(−k2·t)·(k3−k1)/((k1−k2)(k1−k3)(k2−k3)) ] + * + * La courbe est NORMALISÉE PAR MG : `sample()` retourne Cp(t) / fitDose — + * le moteur PK multiplie déjà par la dose réelle (dose_mg), exactement comme + * avec les tables Estrannaise (ODS). + * + * Sanity checks épinglés (web/tests/tfs-models.test.js, fidélité à l'article + * reproduite à ~1–2 %) : + * EV : Cmax 295 pg/mL @ 2,1 j, t½ 3,0 j | EEn : 160 @ 6,5 j, t½ 4,6 j + * EB : 971 @ 0,65 j, t½ 1,2 j | EC (huile) : 155 @ 4,3 j, t½ 6,7 j + * EC susp. : 241 @ 1,2 j, t½ 5,1 j | PEP : ~34 pg/mL @ 32,5 mg @ ~18 j, t½ 28,4 j + * + * Remarques de l'article conservées : l'undécylate (EU) a des données d'étude + * limitées (ajustement moins fiable) ; PEP a une biodisponibilité réduite + * (~15–20 % — d'où des doses ~6,5× plus élevées). + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** Modèle à 3 compartiments (V3C) d'un ester, tel que publié par TFS. */ +export class V3C { + /** + * @param {string} ester Clé ester de l'app ("EV", "EB", …) + * @param {number} D Constante d'amplitude (pg/mL pour la dose de fit, unités TFS) + * @param {number} k1 Constante de vitesse j⁻¹ (absorption dépôt) + * @param {number} k2 Constante de vitesse j⁻¹ (phase rapide) + * @param {number} k3 Constante de vitesse j⁻¹ (phase lente / terminale) + * @param {number} [fitDoseMg=5] Dose à laquelle les paramètres ont été ajustés (5 mg dans l'article) + */ + constructor(ester, D, k1, k2, k3, fitDoseMg = 5.0) { + this.ester = ester; + this.D = D; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + this.fitDoseMg = fitDoseMg; + } + + /** Demi-vie TERMINALE (jours) = la phase la plus lente des trois. */ + get terminalHalfLifeDays() { + return Math.log(2.0) / Math.min(this.k1, this.k2, this.k3); + } +} + +/** + * Table des modèles V3C par clé ester (identique au Kotlin — NE PAS éditer + * les valeurs sans re-épingler les tests de fidélité à l'article). + */ +const MODELS = new Map([ + // estradiol valerate (solution huileuse) + ['EV', new V3C('EV', 2596.05956, 2.38229125, 0.23345814, 1.37642769)], + // estradiol enanthate (solution huileuse) + ['EEN', new V3C('EEN', 333.874181, 0.42412968, 0.43452980, 0.15291485)], + // estradiol undecylate — ⚠️ données d'étude limitées (cf article, §Limites) + ['EU', new V3C('EU', 65.9493374, 0.29634323, 4799337.57, 0.03141554)], + // estradiol benzoate (solution huileuse) + ['EB', new V3C('EB', 1.7050e+08, 3.22397192, 0.58870148, 70721.4018)], + // estradiol cypionate — SOLUTION HUILEUSE + ['EC', new V3C('EC', 1920.89671, 0.10321089, 0.89854779, 0.89359759)], + // estradiol cypionate — SUSPENSION AQUEUSE (microcristaux) ; clé dédiée + ['ECS', new V3C('ECS', 1.5669e+08, 0.13586726, 2.51772731, 74768.1493)], + // polyestradiol phosphate — biodispo réduite, doses ~6,5× (cf article) + ['PEP', new V3C('PEP', 34.46836875, 0.02456035, 135643.711, 0.10582368)], +]); + +/** + * @param {string} ester Clé ester ("EV", "EEN", …) — lookup insensible à la casse + * @returns {boolean} true si un modèle V3C existe pour cet ester + */ +export function hasModel(ester) { + return MODELS.has(String(ester).toUpperCase()); +} + +/** + * @param {string} ester Clé ester (lookup insensible à la casse) + * @returns {V3C|null} le modèle V3C de l'ester, ou null + */ +export function model(ester) { + return MODELS.get(String(ester).toUpperCase()) || null; +} + +/** + * Réponse normalisée (pg/mL PAR mg) à dtHours après une injection de 1 mg, + * modèle V3C de la méta-analyse TFS. + * + * @param {string} ester Clé ester ("EV", "EB", …) + * @param {number} dtHours Heures écoulées depuis l'injection (≤ 0 → 0 : rien avant la dose) + * @returns {number} pg/mL par mg + */ +export function sample(ester, dtHours) { + if (dtHours <= 0.0) return 0.0; + const m = model(ester); + if (!m) return 0.0; + const t = dtHours / 24.0; // la formule TFS travaille en jours + return rawConcentrationDays(m, t) / m.fitDoseMg; +} + +/** + * Cp(t) en pg/mL pour la dose de fit (formule V3C exacte du simulateur). + * + * ⚠️ Le compartiment k2 peut être « ultra-rapide » (EU, PEP : k2 ~10⁵–10⁶ j⁻¹) : + * son terme décroît instantanément et n'affecte que t≈0 — la double précision + * JS (Float64, comme Kotlin Double) est suffisante (vérifié par les tests de + * pics de l'article). + * + * @param {V3C} m Modèle de l'ester + * @param {number} tDays Temps en JOURS depuis l'injection + * @returns {number} pg/mL pour la dose de fit + */ +function rawConcentrationDays(m, tDays) { + const D = m.D, k1 = m.k1, k2 = m.k2, k3 = m.k3; + return D * k1 * k2 * ( + Math.exp(-k1 * tDays) / ((k1 - k2) * (k1 - k3)) + + Math.exp(-k3 * tDays) / ((k1 - k3) * (k2 - k3)) + + Math.exp(-k2 * tDays) * (k3 - k1) / ((k1 - k2) * (k1 - k3) * (k2 - k3)) + ); +} diff --git a/js/pk/whsah-models.js b/js/pk/whsah-models.js new file mode 100644 index 0000000..8e101e5 --- /dev/null +++ b/js/pk/whsah-models.js @@ -0,0 +1,127 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Modèle PK « WHSAH » (portage web de `pk/WhsahModels.kt`, v1.4.6) — 3ᵉ modèle + * au niveau d'Estrannaise et Transfem Science, superposable sur le graphique. + * + * SOURCE : le fit « license-free » publié par le WHSAH Collective dans l'app + * open-source Mona (projet Flutter — lib/data/model/graph_calculator.dart, + * commit « new license-free pk params » ; leurs auteurs déclarent explicitement + * les coefficients libres d'usage, « Inferred with love exclusively for Mona »). + * Crédit : alix / WHSAH Collective via Mona. + * + * POURQUOI CE MODÈLE EXISTE : Mona a d'abord utilisé les paramètres Transfem + * Science (identiques aux nôtres) PUIS les a remplacés par ce fit indépendant, + * pour des raisons de licence. Les deux sont des tri-exponentielles en forme + * close ajustées sur les mêmes études, avec des conventions différentes : + * + * - WHSAH paramètre une BIODISPONIBILITÉ explicite F < 1 (0,62–0,76) et un + * facteur d'échelle `auc` intégré — les courbes « brutes » sont ~24 % plus + * basses que TFS non calibrée (la calibration de l'app absorbe ça) ; + * - la MONTÉE est plus rapide (compartiment rapide plus vif) et la t½ + * terminale plus longue sur certains esters : EV Cmax 367 pg/mL @ 1,7 j + * (TFS : 295 @ 2,1 j), EEn Cmax 188 @ 5,0 j (TFS : 160 @ 6,5 j) et t½ EEn + * 7,3 j (TFS : 4,5 j) — à J+1 l'EEn WHSAH est ~3× plus haut (70 vs 22 pg/mL) ; + * - PEP NON COUVERT (retiré du jeu WHSAH) → `sample('PEP', …) = 0`. + * + * FORMULE (telle qu'implémentée dans Mona, normalisée PAR MG — le + * multiplicateur de dose est appliqué par le moteur PK) : + * + * sample = F · auc · k1·k2·k3 · [ e^(−k1·t)/((k1−k2)(k1−k3)) + * − e^(−k2·t)/((k1−k2)(k2−k3)) + * + e^(−k3·t)/((k1−k3)(k2−k3)) ] + * + * avec t en JOURS, résultat en pg/mL pour 1 mg. Sanity checks épinglés par + * web/tests/whsah-models.test.js (fidélité au comportement de Mona, ±2 %) : + * EV Cmax 367,5 @ 1,69 j · EEn 187,9 @ 4,99 j · t½ EEn 7,34 j. + * ───────────────────────────────────────────────────────────────────────────── + */ + +/** Modèle WHSAH d'un ester (fit F/auc/k1/k2/k3). */ +export class WHS { + /** + * @param {string} ester Clé ester de l'app ("EV", "EB", …) + * @param {number} F Biodisponibilité explicite (< 1 dans le fit WHSAH) + * @param {number} auc Constante d'échelle du fit (pg/mL intégrés) + * @param {number} k1 Constante de vitesse j⁻¹ (absorption) + * @param {number} k2 Constante de vitesse j⁻¹ (phase rapide) + * @param {number} k3 Constante de vitesse j⁻¹ (phase lente / terminale) + */ + constructor(ester, F, auc, k1, k2, k3) { + this.ester = ester; + this.F = F; + this.auc = auc; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + } + + /** Demi-vie TERMINALE (jours) = la phase la plus lente des trois. */ + get terminalHalfLifeDays() { + return Math.log(2.0) / Math.min(this.k1, this.k2, this.k3); + } +} + +/** Table des modèles WHSAH par clé ester (identique au Kotlin — ne pas éditer sans ré-épingler les tests). */ +const MODELS = new Map([ + // estradiol enanthate (solution huileuse) + ['EEN', new WHS('EEN', 0.708, 875.4, 0.09441, 3.354, 0.4078)], + // estradiol valerate (solution huileuse) + ['EV', new WHS('EV', 0.764, 621.3, 0.2230, 17.62, 1.305)], + // estradiol benzoate (solution huileuse) + ['EB', new WHS('EB', 0.723, 889.9, 0.5220, 521.9, 5.223)], + // estradiol cypionate — SOLUTION HUILEUSE + ['EC', new WHS('EC', 0.687, 554.5, 0.0880, 17.95, 0.7177)], + // estradiol cypionate — SUSPENSION AQUEUSE (microcristaux) + ['ECS', new WHS('ECS', 0.687, 852.6, 0.0973, 218.67, 6.624)], + // estradiol undecylate — données d'étude limitées (comme chez TFS) + ['EU', new WHS('EU', 0.618, 385.8, 0.02189, 183.4, 1.564)], +]); + +/** + * @param {string} ester Clé ester — lookup insensible à la casse + * @returns {boolean} true si un modèle WHSAH existe pour cet ester + */ +export function hasModel(ester) { + return MODELS.has(String(ester).toUpperCase()); +} + +/** + * @param {string} ester Clé ester (lookup insensible à la casse) + * @returns {WHS|null} le modèle WHSAH de l'ester, ou null + */ +export function model(ester) { + return MODELS.get(String(ester).toUpperCase()) || null; +} + +/** + * Réponse normalisée (pg/mL PAR mg) à dtHours après une injection de 1 mg, + * modèle WHSAH. + * + * @param {string} ester Clé ester ("EV", …) + * @param {number} dtHours Heures écoulées depuis l'injection (≤ 0 → 0) + * @returns {number} pg/mL par mg + */ +export function sample(ester, dtHours) { + if (dtHours <= 0.0) return 0.0; + const m = model(ester); + if (!m) return 0.0; + return rawConcentrationDays(m, dtHours / 24.0); // t en jours +} + +/** + * Cp(t) en pg/mL pour 1 mg (forme Mona exacte — voir KDoc de l'objet). + * + * Les compartiments « ultra-rapides » (k2 jusqu'à ~520 j⁻¹ : EB) décroissent + * instantanément et n'affectent que t≈0 — double précision suffisante + * (vérifié par les tests de pics). + * + * @param {WHS} m Modèle de l'ester + * @param {number} tDays Temps en JOURS depuis l'injection + * @returns {number} pg/mL pour 1 mg + */ +function rawConcentrationDays(m, tDays) { + const part1 = Math.exp(-m.k1 * tDays) / ((m.k1 - m.k2) * (m.k1 - m.k3)); + const part2 = Math.exp(-m.k2 * tDays) / ((m.k1 - m.k2) * (m.k2 - m.k3)); + const part3 = Math.exp(-m.k3 * tDays) / ((m.k1 - m.k3) * (m.k2 - m.k3)); + return m.F * m.auc * m.k1 * m.k2 * m.k3 * (part1 - part2 + part3); +} diff --git a/js/ui/app.js b/js/ui/app.js new file mode 100644 index 0000000..1491ae5 --- /dev/null +++ b/js/ui/app.js @@ -0,0 +1,357 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * HormoneTrack Web — shell applicatif (portage de HormoneTrackApp.kt + + * MainActivity.kt + HormoneTrackRoot.kt). + * + * - attend l'initialisation de PKProfileStore (fetch de l'asset) ; + * - routing par hash (#home/#chart/#doses/#labs/#treatments/#settings/ + * #treatment-edit/{id}) — permet la navigation headless et le lien profond ; + * - NavigationBar 5 onglets (barre masquée sur settings/éditeur, miroir + * Android) ; + * - dialog « Nouveautés » (changelog.md fetché, version vue persistée) ; + * - boucle de rappels (notifications navigateur) ; + * - re-rendu global sur mutation du store (pub/sub) + tick minute (les + * estimations « maintenant » vivent) ; + * - hook de TEST ?demo=1 : charge un jeu de données de démonstration si le + * store est vide (voir scripts de smoke-test + doc de dev §Tests). + * + * ⚠️ 100 % LOCAL : aucun envoi de données. Le seul réseau est le fetch + * (lecture) de l'asset PK et du changelog, servis en statique. + * ───────────────────────────────────────────────────────────────────────────── + */ + +import * as PKProfileStore from '../pk/pk-profile-store.js'; +import { store } from '../data/store.js'; +import * as AppLog from '../util/app-log.js'; +import { isVersionNewer, sectionsSince } from '../util/changelog.js'; +import { t, resolveLocale } from '../util/i18n.js'; +import { el, showDialog } from './components.js'; +import { renderHome } from './home.js'; +import { renderChart } from './chart.js'; +import { renderDoses } from './doses.js'; +import { renderLabs } from './labs.js'; +import { renderTreatments } from './treatments.js'; +import { renderTreatmentEditor } from './treatment-editor.js'; +import { renderSettings, WEB_VERSION } from './settings.js'; +import { startReminderLoop } from './reminders.js'; +import { openDoseDialog } from './dialogs.js'; + +/** Point d'entrée. */ +async function main() { + // 1) Backend de stockage (localStorage) + chargement du journal + store.setBackend(window.localStorage); + AppLog.init(); + + // 2) Hook de test ?demo=1 : données de démo si le store est vide + if (new URLSearchParams(window.location.search).get('demo') === '1') { + demoMode = true; + maybeLoadDemoData(); + } + + // 3) Shell rendu SYNCHRONIQUEMENT (avant le fetch de l'asset) : l'utilisateur + // voit immédiatement la structure + « Chargement… » au lieu d'une page + // blanche — et les captures d'écran smoke-test ne sont plus vides. + buildShell(); + renderRoute(); // affiche l'écran courant avec le marqueur de chargement + + // 4) Asset PK (équivalent de PKProfileStore.init(context) dans + // HormoneTrackApp.onCreate) — les courbes ne sont calculées qu'après ; + // le re-rendu ci-dessous remplace le « Chargement… » par les données. + await PKProfileStore.init(); + + // 5) Langue au démarrage (miroir MainActivity : setApplicationLocales) + document.documentElement.lang = resolveLocale(store.getSettings().language); + + // 6) Boucle de rappels (notifications navigateur) + startReminderLoop((treatmentId) => { + location.hash = '#home'; + // petit délai pour laisser le re-render se faire avant le dialog + setTimeout(() => openDoseDialog({ + treatments: store.getTreatments().filter((x) => x.isActive), + preselected: store.getTreatments().find((x) => x.id === treatmentId) || null, + existing: null, + }), 60); + }); + + // 7) Dialog « Nouveautés » après mise à jour (v1.3.0 Android) + await maybeShowWhatsNew(); + + // 8) Re-rendu global : mutations du store + tick minute + navigation hash + store.subscribe(() => renderRoute()); + setInterval(() => { + if (lastRoute === currentRoute()) renderRoute(); // rafraîchit « now » + }, 60000); + window.addEventListener('hashchange', () => renderRoute()); + window.addEventListener('resize', () => renderRoute()); // le canvas suit la largeur + + AppLog.log('app', `démarrage web v${WEB_VERSION}`); + renderRoute(); +} + +// ── Shell ──────────────────────────────────────────────────────────────────── + +let contentEl = null; +let bannerSlot = null; +/** Dernière route rendue (le tick minute ne re-rend que si inchangée). */ +let lastRoute = null; +/** Mode démo (?demo=1) : bannière affichée au-dessus du contenu. */ +let demoMode = false; + +/** Construit topbar + bandeau trans + main + tabbar (une seule fois). */ +function buildShell() { + const topbar = el('div', { class: 'topbar' }, [ + el('h1', {}, t('app_name')), + el('button', { class: 'btn text', onclick: () => { location.hash = '#settings'; } }, '⚙'), + ]); + document.body.appendChild(topbar); + document.body.appendChild(el('div', { class: 'trans-banner' })); + bannerSlot = el('div', { id: 'banner-slot', style: 'max-width:720px;margin:0 auto;padding:0 16px;' }); + document.body.appendChild(bannerSlot); + contentEl = el('main', { class: 'content' }); + document.body.appendChild(contentEl); + + const tabs = [ + ['home', '🏠', t('nav_home')], + ['chart', '📈', t('nav_chart')], + ['doses', '💉', t('nav_doses')], + ['labs', '🧪', t('nav_labs')], + ['treatments', '💊', t('nav_treatments')], + ]; + const tabbar = el('nav', { class: 'tabbar' }); + for (const [route, icon, label] of tabs) { + const btn = el('button', { 'data-route': route, onclick: () => { location.hash = `#${route}`; } }, [ + el('span', { class: 'ico' }, icon), + el('span', {}, label), + ]); + tabbar.appendChild(btn); + } + document.body.appendChild(tabbar); +} + +/** Route courante depuis le hash (défaut #home). */ +function currentRoute() { + const h = (window.location.hash || '#home').replace(/^#/, ''); + return h === '' ? 'home' : h; +} + +/** + * Rend la route courante dans contentEl — re-création complète de l'écran + * (le DOM est jetable, l'état vit dans le store / le hash). + */ +function renderRoute() { + if (!contentEl) return; + const route = currentRoute(); + lastRoute = route; + + // Écran de chargement tant que l'asset PK n'est pas chargé (les écrans + // afficheraient « aucune donnée » à tort — les profils valent 0 avant init). + if (!PKProfileStore.isInitialized()) { + contentEl.textContent = ''; + contentEl.appendChild(el('p', { class: 'hint', style: 'padding:24px 0;' }, t('loading'))); + return; + } + + // Marqueur E2E : l'app est prête (asset chargé, écran rendu) — utilisé par + // scripts/e2e.mjs pour attendre la fin du rendu (plus de course au screenshot). + document.body.setAttribute('data-ready', '1'); + // Onglet actif + document.querySelectorAll('.tabbar button').forEach((b) => { + b.classList.toggle('active', b.getAttribute('data-route') === route); + }); + // Barre masquée sur settings + éditeur (miroir HormoneTrackRoot) + document.querySelector('.tabbar').style.display + = (route === 'settings' || route.startsWith('treatment-edit')) ? 'none' : 'flex'; + + // Re-traduit la topbar (changement de langue) + const h1 = document.querySelector('.topbar h1'); + if (h1) h1.textContent = t('app_name'); + document.documentElement.lang = resolveLocale(store.getSettings().language); + + // Bannières de rappel : nettoyées au changement d'écran (elles se recréent + // via la boucle si toujours pertinentes) + if (bannerSlot) bannerSlot.textContent = ''; + + contentEl.textContent = ''; + // Bannière mode démo (hook de test ?demo=1) : explicite pour ne jamais + // confondre des données de démo avec de vraies données de santé + if (demoMode) { + contentEl.appendChild(el('div', { class: 'demo-banner' }, t('demo_banner'))); + } + const nowMs = Date.now(); + + switch (true) { + case route === 'home': + renderHome(contentEl, { nowMs, openChart: () => { location.hash = '#chart'; } }); + break; + case route === 'chart': + renderChart(contentEl, { nowMs }); + break; + case route === 'doses': + renderDoses(contentEl); + break; + case route === 'labs': + renderLabs(contentEl); + break; + case route === 'treatments': + renderTreatments(contentEl, { + openEditor: (id) => { location.hash = `#treatment-edit/${id}`; }, + openNew: () => { location.hash = '#treatment-edit/0'; }, + }); + break; + case route === 'settings': + renderSettings(contentEl); + break; + case route.startsWith('treatment-edit/'): + renderTreatmentEditor(contentEl, { + treatmentId: Number(route.split('/')[1] || 0), + onDone: () => { location.hash = '#treatments'; }, + }); + break; + default: + location.hash = '#home'; + } +} + +// ── Dialog « Nouveautés » ──────────────────────────────────────────────────── + +/** + * Rendu MINIMAL de markdown pour le dialog « Nouveautés » : titres, listes, + * gras, code inline — suffisant pour le format de docs/CHANGELOG.md (pas un + * parseur général, volontairement : aucune dépendance, surface réduite). + * + * @param {string} markdown + * @returns {HTMLElement} + */ +function renderMarkdown(md) { + const container = el('div', { class: 'md-body', style: 'font-size:0.88rem;' }); + let list = null; //
    courant (regroupe les lignes « - » consécutives) + const inline = (text) => { + // **gras** et `code` — split simple par regex, échappé par le DOM (textContent) + const frag = document.createDocumentFragment(); + for (const part of text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g)) { + if (part.startsWith('**') && part.endsWith('**')) { + frag.appendChild(el('strong', {}, part.slice(2, -2))); + } else if (part.startsWith('`') && part.endsWith('`') && part.length > 2) { + frag.appendChild(el('code', { style: 'background:var(--surface-variant);border-radius:4px;padding:1px 4px;font-size:0.8em;' }, part.slice(1, -1))); + } else if (part) { + frag.appendChild(document.createTextNode(part)); + } + } + return frag; + }; + for (const rawLine of md.split('\n')) { + const line = rawLine.trimEnd(); + if (line.startsWith('### ')) { + list = null; + container.appendChild(el('h4', {}, line.slice(4))); + } else if (line.startsWith('## ')) { + list = null; + container.appendChild(el('h3', { style: 'margin:10px 0 4px;' }, line.slice(3))); + } else if (line.startsWith('- ')) { + if (!list) list = el('ul', { style: 'margin:4px 0;padding-left:20px;' }); + list.appendChild(el('li', {}, inline(line.slice(2)))); + container.appendChild(list); // (ré-append le même nœud : pas d'effet) + } else if (line === '') { + list = null; + } else { + list = null; + container.appendChild(el('p', { style: 'margin:4px 0;' }, inline(line))); + } + } + return container; +} + +/** + * Au démarrage : si WEB_VERSION est plus récente que la dernière vue + * (DataStore `changelogSeenVersion` côté Android), affiche les sections + * CHANGELOG concernées — la version vue est mémorisée AVANT l'affichage + * (bug Android #42 : si l'app est fermée pendant le dialog, ne pas + * re-montrer les mêmes nouveautés). + */ +async function maybeShowWhatsNew() { + let markdown = ''; + try { + const resp = await fetch('docs/CHANGELOG.md'); + if (resp.ok) markdown = await resp.text(); + } catch (e) { + // Pas grave : pas de dialog (app servie file:// ou changelog absent) + AppLog.log('changelog', `indisponible : ${e.message}`); + return; + } + if (!markdown) return; + const seen = store.getSettings().changelogSeenVersion; + if (!isVersionNewer(WEB_VERSION, seen)) return; + + store.setSettings({ changelogSeenVersion: WEB_VERSION }); // mémorisé AVANT + const sections = sectionsSince(markdown, seen, WEB_VERSION); + if (sections === '') return; + + const dlg = showDialog({ + title: t('whats_new_title'), + body: renderMarkdown(sections), + actions: [{ label: t('whats_new_close'), className: 'btn', onClick: () => dlg.close() }], + }); +} + +// ── Hook de démo/test (?demo=1) ────────────────────────────────────────────── + +/** + * Charge un jeu de données de DÉMONSTRATION si le store est vide. Utilisé + * par les smoke-tests headless (et pour découvrir l'app). Jamais automatique + * sans le paramètre d'URL, jamais sur un store déjà rempli. + */ +function maybeLoadDemoData() { + if (store.getTreatments().length > 0 || store.getDoseLogs().length > 0) return; + const now = Date.now(); + const HOUR = 3600000; + const DAY = 24 * HOUR; + const trId = store.upsertTreatment({ + name: 'EEn — démo', + type: 'ESTRADIOL', + route: 'INJECTION_SUBCUT', + doseAmount: 5, + doseUnit: 'mg', + isActive: true, + notes: null, + esterType: 'EEN', + pkModel: 'TFS', + absorptionHours: 156, + eliminationHalfLifeHours: 110, + bioavailabilityFraction: 1, + scaleFactor: 1.0, + forecastIntervalDays: 7, + reminderHour: 18, + reminderMinute: 0, + reminderEnabled: true, + calendarEventId: null, + createdAt: now - 90 * DAY, + }); + // 12 injections hebdomadaires passées (à 18 h) + for (let k = 12; k >= 1; k--) { + store.upsertDoseLog({ + treatmentId: trId, + timestamp: now - k * 7 * DAY, + doseAmount: 5, + notes: null, + esterType: null, + }); + } + // Labs E2/T plausibles (1 prise de sang toutes les 2 semaines) + const labs = [[280, 0.35], [300, 0.31], [260, 0.4], [290, 0.33], [275, 0.36], [305, 0.3]]; + labs.forEach(([e2, tVal], i) => { + const ts = now - (11 - i * 2) * 7 * DAY; + store.upsertLabResult({ marker: 'E2', value: e2, unit: 'pg/mL', timestamp: ts, notes: null }); + store.upsertLabResult({ marker: 'T', value: Math.round(tVal * 100), unit: 'ng/dL', timestamp: ts, notes: null }); + }); + AppLog.log('demo', 'données de démonstration chargées (?demo=1)'); +} + +main().catch((e) => { + // Erreur de démarrage : l'afficher lisible (asset absent, serveur mal configuré…) + document.body.innerHTML = `
    +

    HormoneTrack

    +

    Erreur de démarrage : ${e.message}

    +

    L'app doit être servie en HTTP statique (voir web/README.md — + python3 web/scripts/serve.py), pas ouverte en file://.

    +
    `; +}); diff --git a/js/ui/chart-canvas.js b/js/ui/chart-canvas.js new file mode 100644 index 0000000..6a4bc6a --- /dev/null +++ b/js/ui/chart-canvas.js @@ -0,0 +1,314 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * CurveChart — rendu Canvas pur multi-séries (portage fidèle de + * `ui/components/CurveChart.kt`, Canvas Compose → Canvas 2D navigateur). + * + * - axe gauche = E2 (pg/mL), axe droit = T (ng/mL) ; + * - toutes les séries partagent les mêmes échelles (comparaison directe) ; + * - labs E2 = cercles orange, labs T = carrés orange (T convertie en ng/mL + * via convertTToNgMl — les labs peuvent être saisis en ng/dL/nmol/L, bug + * #23 : à utiliser aussi au rendu, sinon l'axe T est faux ×100) ; + * - ligne verticale « maintenant » ; + * - doses PRÉVISIONNELLES : ligne pointillée + triangle ▼ + heure (v1.4.5, + * fix #55 — sinon la projection paraît décalée d'un jour) ; + * - doses RÉELLES : petits triangles ▲ discrets au bas (peigne des prises) ; + * - pics/creux : triangles ▲▼ + valeur estimée (v1.2.6). + * + * Le composant est PUREMENT DÉCLARATIF : pan/zoom sont gérés PAR LE PARENT + * (chart.js) qui modifie la fenêtre et redessine — même séparation que le + * Compose (piège Android : un scroll parent avale le drag horizontal, cf + * fix #62/§11 — le parent web n'a PAS de scroll horizontal). + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { convertTToNgMl, detectExtrema } from '../pk/index.js'; +import { xLabelTicks } from '../pk/chart-helpers.js'; +import { formatValue } from '../util/format.js'; + +/** Couleurs (miroir ui/theme/Color.kt). */ +export const COLORS = { + chartE2: '#4f5bd5', + chartT: '#d6589e', + chartWhs: '#2e7d32', + chartTWhs: '#9c27b0', + tealTertiary: '#2fa48d', + labDot: '#e67e22', + grid: '#cac4d0', + label: '#6b7280', + nowLine: '#2fa48d', + forecastMarker: 'rgba(47,164,141,0.85)', + realDoseMarker: 'rgba(73,69,79,0.55)', +}; + +/** + * Dessine le graphique dans un canvas. + * + * @param {HTMLCanvasElement} canvas + * @param {object[]} series [{ points: LevelPoint[], e2Style:{color,dashed}, tStyle|null }] + * @param {object[]} e2Labs Labs E2 (LabResult) + * @param {object[]} tLabs Labs T (déjà filtrés par le parent si toggle off) + * @param {object} options { showT, showLabs, showExtrema, nowMs } + * @param {object[]} [forecastDoses=[]] Doses projetées (marqueurs) + * @param {object[]} [realDoses=[]] Doses réelles (marqueurs) + * @param {string} [timeZone=null] Fuseau de LECTURE (null = navigateur) + */ +export function drawChart(canvas, series, e2Labs, tLabs, options, forecastDoses = [], realDoses = [], timeZone = null) { + const ctx = canvas.getContext('2d'); + const dpr = window.devicePixelRatio || 1; + const cssW = canvas.clientWidth; + const cssH = canvas.clientHeight; + if (cssW <= 0 || cssH <= 0) return; + // HiDPI : le canvas est dimensionné en pixels réels, le dessin en unités CSS + canvas.width = Math.round(cssW * dpr); + canvas.height = Math.round(cssH * dpr); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + ctx.clearRect(0, 0, cssW, cssH); + + const allPoints = series.flatMap((s) => s.points); + if (allPoints.length < 2) return; + + // Marges (px) — miroir padLeft/padRight/padTop/padBottom (42/42/12/26 dp) + const padLeft = 42; + const padRight = 42; + const padTop = 12; + const padBottom = 26; + const w = cssW - padLeft - padRight; + const h = cssH - padTop - padBottom; + if (w <= 0 || h <= 0) return; + + const t0 = Math.min(...allPoints.map((p) => p.timestamp)); + const t1 = Math.max(...allPoints.map((p) => p.timestamp)); + if (t1 <= t0) return; + + // Labs T convertis en ng/mL AVANT l'échelle (fix #23) + const convertedTLabs = tLabs.map((lab) => ({ ...lab, value: convertTToNgMl(lab.value, lab.unit) })); + + const e2DataMax = Math.max( + Math.max(...allPoints.map((p) => p.e2)), + options.showLabs && e2Labs.length ? Math.max(...e2Labs.map((l) => l.value)) : 0, + ); + const e2Max = niceCeil(Math.max(e2DataMax, 50.0)); + const tDataMax = Math.max( + Math.max(...allPoints.map((p) => p.t)), + options.showLabs && convertedTLabs.length ? Math.max(...convertedTLabs.map((l) => l.value)) : 0, + ); + const tMax = niceCeil(tDataMax * 1.1); + + const xOf = (ms) => padLeft + (w * (ms - t0)) / (t1 - t0); + const yE2 = (v) => padTop + h * (1 - v / e2Max); + const yT = (v) => padTop + h * (1 - v / tMax); + + // ── Grille 4 lignes + labels Y gauche (E2) / droit (T) ─────────────────── + ctx.strokeStyle = COLORS.grid; + ctx.lineWidth = 1; + for (let i = 0; i <= 4; i++) { + const y = padTop + (h * i) / 4; + ctx.beginPath(); + ctx.moveTo(padLeft, y); + ctx.lineTo(padLeft + w, y); + ctx.stroke(); + } + ctx.fillStyle = COLORS.label; + ctx.font = '10px system-ui, sans-serif'; + ctx.textAlign = 'right'; + ctx.textBaseline = 'middle'; + for (let i = 0; i <= 4; i++) { + ctx.fillText(formatValue((e2Max * i) / 4), padLeft - 6, padTop + h * (1 - i / 4)); + } + if (options.showT && series.some((s) => s.tStyle)) { + ctx.textAlign = 'left'; + for (let i = 0; i <= 4; i++) { + ctx.fillText(formatValue((tMax * i) / 4), padLeft + w + 6, padTop + h * (1 - i / 4)); + } + } + + // ── Labels X : pas adaptatif + ticks alignés minuit LOCAL (fix #55) ─────── + const spanH = (t1 - t0) / 3600000.0; + const stepHours = spanH <= 8 ? 1 : spanH <= 18 ? 3 : spanH <= 25 ? 6 : spanH <= 25 * 7 ? 24 : 120; + const fmt = new Intl.DateTimeFormat(undefined, spanH <= 25 + ? { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone: timeZone || undefined } + : { day: '2-digit', month: '2-digit', timeZone: timeZone || undefined }); + ctx.textAlign = 'center'; + ctx.textBaseline = 'alphabetic'; + for (const tick of xLabelTicks(t0, t1, stepHours, timeZone)) { + const x = padLeft + (w * (tick - t0)) / (t1 - t0); + ctx.fillText(fmt.format(new Date(tick)), x, padTop + h + 16); + } + + // ── Doses PRÉVISIONNELLES : ligne pointillée + ▼ + heure (fix #55) ─────── + for (const dose of forecastDoses) { + if (dose.timestamp < t0 || dose.timestamp > t1) continue; + const x = xOf(dose.timestamp); + ctx.strokeStyle = COLORS.forecastMarker; + ctx.setLineDash([5, 5]); + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x, padTop + 14); + ctx.lineTo(x, padTop + h); + ctx.stroke(); + ctx.setLineDash([]); + ctx.fillStyle = COLORS.forecastMarker; + ctx.beginPath(); + ctx.moveTo(x, padTop + 14); + ctx.lineTo(x - 4, padTop + 6); + ctx.lineTo(x + 4, padTop + 6); + ctx.closePath(); + ctx.fill(); + const hourFmt = new Intl.DateTimeFormat(undefined, { + hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone: timeZone || undefined, + }); + ctx.fillStyle = COLORS.forecastMarker; + ctx.textAlign = 'center'; + ctx.fillText(hourFmt.format(new Date(dose.timestamp)), x, padTop + 4); + } + + // ── Courbes (E2 pleine + T de la série) ────────────────────────────────── + for (const s of series) { + if (s.points.length < 2) continue; + + // E2 de la série + ctx.strokeStyle = s.e2Style.color; + ctx.lineWidth = 2.5; + ctx.setLineDash(s.e2Style.dashed ? [10, 8] : []); + ctx.beginPath(); + s.points.forEach((p, i) => { + const x = xOf(p.timestamp); + const y = yE2(p.e2); + if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + }); + ctx.stroke(); + ctx.setLineDash([]); + + // T de la série (si toggle T actif et style présent) + if (options.showT && s.tStyle) { + ctx.strokeStyle = s.tStyle.color; + ctx.lineWidth = 2; + ctx.setLineDash(s.tStyle.dashed ? [10, 8] : []); + ctx.beginPath(); + s.points.forEach((p, i) => { + const x = xOf(p.timestamp); + const y = yT(p.t); + if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + }); + ctx.stroke(); + ctx.setLineDash([]); + } + + // Pics/creux (v1.2.3/v1.2.6) : triangles + valeur estimée + if (options.showExtrema) { + for (const ex of detectExtrema(s.points, (p) => p.e2, 2.0)) { + drawExtremum(ctx, xOf(ex.timestamp), yE2(ex.value), ex, s.e2Style.color); + } + if (options.showT && s.tStyle) { + for (const ex of detectExtrema(s.points, (p) => p.t, 0.02)) { + drawExtremum(ctx, xOf(ex.timestamp), yT(ex.value), ex, s.tStyle.color); + } + } + } + } + + // ── Labs : cercles (E2) et carrés (T) orange + valeur ──────────────────── + if (options.showLabs) { + ctx.font = 'bold 9px system-ui, sans-serif'; + for (const lab of e2Labs) { + if (lab.timestamp < t0 || lab.timestamp > t1) continue; + const x = xOf(lab.timestamp); + const y = yE2(lab.value); + ctx.fillStyle = COLORS.labDot; + ctx.beginPath(); + ctx.arc(x, y, 5, 0, Math.PI * 2); + ctx.fill(); + ctx.textAlign = 'center'; + ctx.fillText(formatValue(lab.value), x, y - 10); + } + for (const lab of convertedTLabs) { + if (lab.timestamp < t0 || lab.timestamp > t1) continue; + const x = xOf(lab.timestamp); + const y = yT(lab.value); + ctx.fillStyle = COLORS.labDot; + ctx.fillRect(x - 4, y - 4, 8, 8); + ctx.textAlign = 'center'; + ctx.fillText(formatValue(lab.value), x, y - 10); + } + } + + // ── Doses RÉELLES : petits triangles ▲ discrets au bas (v1.4.5) ────────── + ctx.fillStyle = COLORS.realDoseMarker; + for (const dose of realDoses) { + if (dose.timestamp < t0 || dose.timestamp > t1) continue; + const x = xOf(dose.timestamp); + const base = padTop + h; + ctx.beginPath(); + ctx.moveTo(x, base - 8); + ctx.lineTo(x - 3.5, base); + ctx.lineTo(x + 3.5, base); + ctx.closePath(); + ctx.fill(); + } + + // ── Repère « maintenant » ──────────────────────────────────────────────── + if (options.nowMs >= t0 && options.nowMs <= t1) { + ctx.strokeStyle = COLORS.nowLine; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(xOf(options.nowMs), padTop); + ctx.lineTo(xOf(options.nowMs), padTop + h); + ctx.stroke(); + } +} + +/** + * Triangle ▲ (pic, au-dessus du point) ou ▼ (creux, en dessous) + la VALEUR + * estimée de l'extremum (miroir drawExtremum, v1.2.6). + * + * @private + */ +function drawExtremum(ctx, cx, cy, extremum, color) { + const r = 5; + const offset = 6; + let valueY; + ctx.fillStyle = color; + if (extremum.isPeak) { + const y = cy - offset; + ctx.beginPath(); + ctx.moveTo(cx, y - r); + ctx.lineTo(cx - r, y + r * 0.5); + ctx.lineTo(cx + r, y + r * 0.5); + ctx.closePath(); + ctx.fill(); + valueY = y - r - 4; + } else { + const y = cy + offset; + ctx.beginPath(); + ctx.moveTo(cx, y + r); + ctx.lineTo(cx - r, y - r * 0.5); + ctx.lineTo(cx + r, y - r * 0.5); + ctx.closePath(); + ctx.fill(); + valueY = y + r + 12; + } + ctx.fillStyle = color; + ctx.font = 'bold 9px system-ui, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(formatValue(extremum.value), cx, valueY); +} + +/** + * Arrondi « nice » — re-export local (miroir niceCeil de CurveChart.kt) pour + * ne pas dépendre de chart-helpers ici (le module pk/index regroupe déjà). + * + * @param {number} value + * @returns {number} + */ +function niceCeil(value) { + if (value <= 0) return 100.0; + const exp = Math.pow(10.0, Math.floor(Math.log10(value))); + const f = value / exp; + let nice; + if (f <= 1) nice = 1.0; + else if (f <= 2) nice = 2.0; + else if (f <= 2.5) nice = 2.5; + else if (f <= 5) nice = 5.0; + else nice = 10.0; + return nice * exp; +} diff --git a/js/ui/chart.js b/js/ui/chart.js new file mode 100644 index 0000000..f8e0b67 --- /dev/null +++ b/js/ui/chart.js @@ -0,0 +1,410 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Écran Graphiques (portage de `ui/screens/ChartScreen.kt` — l'écran le plus + * riche de l'app). + * + * Fonctions portées à l'identique : + * - plages 24 h / 7 j / 30 j + ZOOM −/+ (pinch + molette, borné 6 h → 300 j, + * échantillonnage adaptatif stepForRange, focal du pinch maintenu) ; + * - PANORAMIQUE : glisser = passé, glisser ← = futur (prévision active), + * cumul fractionnaire des deltas (fix #62) + clamp unique clampPanHours ; + * - toggles de MODÈLES superposables (ESE / TFS / WHSAH), alignés au + * chargement sur les traitements à PROFIL PK (fix #58, garde-fou TFS) ; + * - toggles T (masque aussi les labs T) / labs / pics-creux / prévision ; + * - PRÉVISION : doses projetées (grille Posologie), horizon 12× Posologie + * borné [30 j ; 1 an], extension de fenêtre à droite sans déplacement + * (fix #54), retour à maintenant à la désactivation (v1.4.1) ; + * - CALIBRATION PAR MODÈLE (fix #60) : une AutoCalibrated par modèle + * affiché — chaque courbe colle à ses labs quand l'auto est active ; + * - « now » mémoïsé sur un tick minute (fix #56 : boucle de recomposition + * côté Android — côté web, même discipline : endMs ne dérive pas à chaque + * frame, il ne change qu'au tick ou sur interaction) ; + * - PAS de scroll horizontal parent : le drag horizontal est réservé au pan + * (piège Android #62/§11). + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { + computeCurve, generateForecastDoses, autoCalibrated, TConfig, +} from '../pk/index.js'; +import { + stepForRange, defaultModelToggles, panDeltaHours, clampPanHours, + forecastHorizonHours, forecastExtensionHours, +} from '../pk/chart-helpers.js'; +import { store } from '../data/store.js'; +import { t } from '../util/i18n.js'; +import { formatValue } from '../util/format.js'; +import { el, filterChip } from './components.js'; +import { drawChart, COLORS } from './chart-canvas.js'; + +const HOUR_MS = 3600000; +// Zoom (v1.2.9) : plage temporelle bornée 6 h → 300 j +const MIN_RANGE_H = 6; +const MAX_RANGE_H = 24 * 300; + +/** Styles visuels des séries (miroir de la map du ChartScreen Android). */ +function seriesStyles(model) { + if (model === 'ESE') return { e2: { color: COLORS.chartE2, dashed: false }, t: { color: COLORS.chartT, dashed: false } }; + if (model === 'WHS') return { e2: { color: COLORS.chartWhs, dashed: false }, t: { color: COLORS.chartTWhs, dashed: true } }; + return { e2: { color: COLORS.tealTertiary, dashed: false }, t: { color: COLORS.chartT, dashed: true } }; +} + +/** + * Rend l'écran Graphiques dans [container]. + * @param {HTMLElement} container + * @param {object} ctx { nowMs } + */ +export function renderChart(container, ctx) { + const treatments = store.getTreatments(); // TOUS (actifs ET inactifs, §6.bis) + const doseLogs = store.getDoseLogs(); + const labResults = store.getLabResults(); + const tConfigRaw = store.getTConfig(); + const tConfig = new TConfig(tConfigRaw.base, tConfigRaw.floor, tConfigRaw.k); + const settings = store.getSettings(); + const chartTimeZone = settings.chartTimezone || null; + + // ── État d'interaction (miroir des mutableStateOf Compose) ──────────────── + const state = { + rangeHours: 24, + panHours: 0, + showT: true, + showLabs: true, + showEse: true, + showTfs: true, + showWhs: false, + showForecast: false, + showExtrema: false, + panResidualHours: 0.0, // résiduel fractionnaire du pan (fix #62) + togglesInitialized: false, + }; + + // ── Colonne racine ─────────────────────────────────────────────────────── + const col = el('div', {}); + container.appendChild(col); + + // Rangée 1 : plages + zoom + const rangeRow = el('div', { class: 'chip-row' }); + col.appendChild(rangeRow); + + // Rangée 2 : toggles affichage + const displayRow = el('div', { class: 'chip-row' }); + col.appendChild(displayRow); + + // Rangée 3 : toggles modèles + const modelRow = el('div', { class: 'chip-row' }); + col.appendChild(modelRow); + + // Carte graphique + légende + const chartCard = el('div', { class: 'card' }); + const canvas = el('canvas', { class: 'chart chart-cursor', style: 'height:320px;' }); + chartCard.appendChild(canvas); + const backBtnWrap = el('div', {}); + chartCard.appendChild(backBtnWrap); + const legend = el('div', {}); + chartCard.appendChild(legend); + col.appendChild(chartCard); + + // ── Calibration PAR MODÈLE (fix #60) : une AutoCalibrated par modèle ───── + const autoByModel = new Map(); + if (settings.autoCalibrate) { + for (const m of ['ESE', 'TFS', 'WHS']) { + autoByModel.set(m, autoCalibrated(treatments, doseLogs, labResults, tConfig, m)); + } + } + + // ── Horizon de prévision (12 × plus grande Posologie, borné 30 j–1 an) ──── + const maxIntervalDays = treatments + .map((tr) => tr.forecastIntervalDays) + .filter((x) => x !== null && x !== undefined) + .reduce((m, x) => Math.max(m, x), 0) || null; + const horizonHours = forecastHorizonHours(maxIntervalDays); + + // Gap jusqu'au 1ᵉʳ créneau projeté (mémoïsé — même discipline que fix #56 : + // ce calcul n'est refait qu'au re-rendu, pas à chaque événement de drag) + let firstSlotGapH = null; + if (horizonHours !== null) { + const at = ctx.nowMs; + const horizonMs = horizonHours * HOUR_MS; + let minSlot = null; + for (const tr of treatments) { + for (const d of generateForecastDoses(tr, doseLogs, at + horizonMs, at)) { + if (minSlot === null || d.timestamp < minSlot) minSlot = d.timestamp; + } + } + if (minSlot !== null) firstSlotGapH = Math.trunc((minSlot - at) / HOUR_MS); + } + + // ── Fenêtre temporelle ─────────────────────────────────────────────────── + function windowRange() { + const nowMs = ctx.nowMs; + const futurePanHorizon = state.showForecast ? (horizonHours || 0) : 0; + const extensionH = (state.showForecast && horizonHours !== null) + ? forecastExtensionHours(state.rangeHours, horizonHours, firstSlotGapH) + : 0; + const endMs = nowMs - state.panHours * HOUR_MS + extensionH * HOUR_MS; + const startMs = endMs - state.rangeHours * HOUR_MS; + return { nowMs, futurePanHorizon, extensionH, startMs, endMs }; + } + + // ── Toggle init des modèles : aligné sur les traitements à PROFIL PK ───── + // (fix #58 — filtrer par usesProfileModel : un traitement oral polluait le + // set avec son pkModel stocké ESE ; garde-fou TFS si aucun profil). + if (!state.togglesInitialized && treatments.length > 0) { + const used = new Set( + treatments + .filter((tr) => tr.route === 'INJECTION_IM' || tr.route === 'INJECTION_SUBCUT') + .filter((tr) => tr.esterType !== 'NONE') + .map((tr) => tr.pkModel), + ); + const toggles = defaultModelToggles(used); + state.showEse = toggles.ESE; + state.showTfs = toggles.TFS; + state.showWhs = toggles.WHS; + state.togglesInitialized = true; + } + + // ── Calcul + dessin ────────────────────────────────────────────────────── + function redraw() { + const { nowMs, futurePanHorizon, extensionH, startMs, endMs } = windowRange(); + + // Rangée 1 : plages + zoom + rangeRow.textContent = ''; + rangeRow.appendChild(filterChip(t('chart_24h'), state.rangeHours === 24, () => { state.rangeHours = 24; state.panHours = clampPan(state.panHours); redraw(); })); + rangeRow.appendChild(filterChip(t('chart_7j'), state.rangeHours === 24 * 7, () => { state.rangeHours = 24 * 7; redraw(); })); + rangeRow.appendChild(filterChip(t('chart_30j'), state.rangeHours === 24 * 30, () => { state.rangeHours = 24 * 30; redraw(); })); + const zoomIn = el('button', { class: 'chip', disabled: state.rangeHours <= MIN_RANGE_H }, '+'); + zoomIn.addEventListener('click', () => { + // Convention carte : « + » = zoom AVANT (fenêtre plus courte, v1.2.10) + state.rangeHours = Math.max(Math.trunc(state.rangeHours / 2), MIN_RANGE_H); + redraw(); + }); + const zoomOut = el('button', { class: 'chip', disabled: state.rangeHours >= MAX_RANGE_H }, '−'); + zoomOut.addEventListener('click', () => { + state.rangeHours = Math.min(state.rangeHours * 2, MAX_RANGE_H); + redraw(); + }); + rangeRow.appendChild(zoomIn); + rangeRow.appendChild(zoomOut); + + // Rangée 2 : toggles affichage + displayRow.textContent = ''; + displayRow.appendChild(filterChip('T', state.showT, () => { state.showT = !state.showT; redraw(); })); + displayRow.appendChild(filterChip(t('show_labs'), state.showLabs, () => { state.showLabs = !state.showLabs; redraw(); })); + displayRow.appendChild(filterChip(t('show_forecast'), state.showForecast, () => { + state.showForecast = !state.showForecast; + // v1.4.1 : désactiver la prévision pendant un voyage futur → retour + // à maintenant (sinon fenêtre vide au-delà de « maintenant ») + if (!state.showForecast) state.panHours = Math.max(state.panHours, 0); + redraw(); + }, horizonHours === null)); + displayRow.appendChild(filterChip(t('show_extrema'), state.showExtrema, () => { state.showExtrema = !state.showExtrema; redraw(); })); + + // Rangée 3 : toggles modèles superposables + modelRow.textContent = ''; + modelRow.appendChild(filterChip(t('model_ese'), state.showEse, () => { state.showEse = !state.showEse; redraw(); })); + modelRow.appendChild(filterChip(t('model_tfs'), state.showTfs, () => { state.showTfs = !state.showTfs; redraw(); })); + modelRow.appendChild(filterChip(t('model_whs'), state.showWhs, () => { state.showWhs = !state.showWhs; redraw(); })); + + // ── Courbes par modèle sélectionné ───────────────────────────────────── + const curves = []; + if (state.showEse || state.showTfs || state.showWhs) { + // Home utilise le modèle stocké ; le graphique calibre chaque modèle + // avec SA prédiction (fix #60). Référence = auto du modèle TFS (les + // traitements/le tConfig y sont identiques — seules échelles/k diffèrent). + const refAuto = autoByModel.get('TFS'); + const effTreatments = refAuto ? refAuto.treatments : treatments; + const effTConfig = refAuto ? refAuto.tConfig : tConfig; + + // Doses réelles + doses prévisionnelles éventuelles + const forecastDoses = state.showForecast && horizonHours !== null + ? treatments.flatMap((tr) => generateForecastDoses(tr, doseLogs, endMs, nowMs)) + : []; + const allDoses = doseLogs.concat(forecastDoses); + + // Pas d'échantillonnage adaptatif au zoom (v1.2.9) + const step = stepForRange(state.rangeHours); + const addCurve = (model) => { + const autoM = autoByModel.get(model); + curves.push([model, computeCurve( + effTreatments, allDoses, startMs, endMs, step, autoM ? autoM.tConfig : effTConfig, + { + modelOverride: model, + scalePerEster: autoM ? autoM.esterScales : null, + tKPerEster: autoM ? autoM.tKPerEster : null, + }, + )]); + }; + if (state.showEse) addCurve('ESE'); + if (state.showTfs) addCurve('TFS'); + if (state.showWhs) addCurve('WHS'); + } + + const allPoints = curves.flatMap(([, pts]) => pts); + + // Labs filtrés sur la fenêtre ; le toggle T masque AUSSI les labs T + let a = 0; + let b = 0; + if (allPoints.length >= 2) { + a = Math.min(...allPoints.map((p) => p.timestamp)); + b = Math.max(...allPoints.map((p) => p.timestamp)); + } + const e2Labs = labResults.filter((l) => String(l.marker).toUpperCase() === 'E2' && l.timestamp >= a && l.timestamp <= b); + const tLabs = state.showT + ? labResults.filter((l) => String(l.marker).toUpperCase() === 'T' && l.timestamp >= a && l.timestamp <= b) + : []; + + // ── Contenu : graphique ou « aucune donnée » ──────────────────────────── + backBtnWrap.textContent = ''; + legend.textContent = ''; + + if (curves.length === 0 || allPoints.length < 2) { + canvas.style.display = 'none'; + legend.appendChild(el('p', {}, t('no_data'))); + } else { + canvas.style.display = ''; + const forecastDosesForMarkers = state.showForecast && horizonHours !== null + ? treatments.flatMap((tr) => generateForecastDoses(tr, doseLogs, endMs, nowMs)) + : []; + requestAnimationFrame(() => { + drawChart( + canvas, + curves.map(([model, pts]) => { + const st = seriesStyles(model); + return { points: pts, e2Style: st.e2, tStyle: st.t }; + }), + e2Labs, + tLabs, + { showT: state.showT, showLabs: state.showLabs, showExtrema: state.showExtrema, nowMs }, + forecastDosesForMarkers, + doseLogs, + chartTimeZone, + ); + }); + + // Bouton « Revenir à maintenant » dès qu'on a voyagé (passé OU futur) + if (state.panHours !== 0) { + backBtnWrap.appendChild(el('button', { + class: 'btn text', + onclick: () => { state.panHours = 0; state.panResidualHours = 0; redraw(); }, + }, t('back_to_present'))); + } + + // Légende dynamique (miroir du bloc Compose) + for (const [model] of curves) { + const st = seriesStyles(model); + if (model === 'ESE') { + legend.appendChild(legendLine(t('legend_ese_e2'), st.e2.color)); + if (state.showT) legend.appendChild(legendLine(t('legend_ese_t'), st.t.color)); + } else if (model === 'WHS') { + legend.appendChild(legendLine(t('legend_whs_e2'), st.e2.color)); + if (state.showT) legend.appendChild(legendLine(t('legend_whs_t'), st.t.color)); + } else { + legend.appendChild(legendLine(t('legend_tfs_e2'), st.e2.color)); + if (state.showT) legend.appendChild(legendLine(t('legend_tfs_t'), st.t.color)); + } + } + if (state.showLabs) legend.appendChild(legendLine(t('legend_labs'), COLORS.labDot)); + if (state.showForecast && horizonHours !== null) legend.appendChild(legendLine(t('legend_forecast_dose'), COLORS.nowLine)); + if (state.showExtrema) legend.appendChild(el('div', { class: 'legend-muted' }, t('legend_extrema'))); + legend.appendChild(el('div', { class: 'legend-muted' }, t('drag_hint'))); + } + + // Re-clamp du pan avec l'horizon courant + function clampPan(pan) { + const nowMs = ctx.nowMs; + const earliestDoseMs = doseLogs.length ? Math.min(...doseLogs.map((d) => d.timestamp)) : nowMs; + const maxPanHours = Math.max(Math.trunc((nowMs - earliestDoseMs) / HOUR_MS) + state.rangeHours / HOUR_MS, 0); + return clampPanHours(pan, state.showForecast ? (horizonHours || 0) : 0, maxPanHours); + } + } + + // ── Gestes : pan (1 pointeur) + pinch (2 pointeurs) + molette ──────────── + // ⚠️ FIX #62 : les deltas < 1 h sont CUMULÉS via panResidualHours (pan + // fonctionnel sur la vue 24 h). Un seul handler pour pan+pinch (miroir + // detectTransformGestures — un handler = pas de conflit de consommation). + const activePointers = new Map(); + let pinchStart = null; // { dist, centroidX, rangeHoursAtStart } + + function maxPanHoursNow() { + const nowMs = ctx.nowMs; + const earliestDoseMs = doseLogs.length ? Math.min(...doseLogs.map((d) => d.timestamp)) : nowMs; + return Math.max(Math.trunc((nowMs - earliestDoseMs) / HOUR_MS) + state.rangeHours / HOUR_MS, 0); + } + + canvas.addEventListener('pointerdown', (e) => { + canvas.setPointerCapture(e.pointerId); + activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY }); + if (activePointers.size === 2) { + const [p1, p2] = [...activePointers.values()]; + pinchStart = { + dist: Math.abs(p1.x - p2.x) || 1, + centroidX: (p1.x + p2.x) / 2, + rangeHoursAtStart: state.rangeHours, + }; + } + }); + + canvas.addEventListener('pointermove', (e) => { + if (!activePointers.has(e.pointerId)) return; + const prev = activePointers.get(e.pointerId); + const cur = { x: e.clientX, y: e.clientY }; + activePointers.set(e.pointerId, cur); + + if (activePointers.size === 2 && pinchStart) { + // ── Pinch : zoom autour du centroid (le temps sous les doigts reste fixe) + const [p1, p2] = [...activePointers.values()]; + const dist = Math.abs(p1.x - p2.x) || 1; + const zoom = dist / pinchStart.dist; // > 1 = doigts écartés = zoom avant + const rect = canvas.getBoundingClientRect(); + const f = Math.min(Math.max((pinchStart.centroidX - rect.left) / rect.width, 0), 1); + const newRange = Math.min(Math.max(Math.trunc(pinchStart.rangeHoursAtStart / zoom), MIN_RANGE_H), MAX_RANGE_H); + if (newRange !== state.rangeHours) { + const { nowMs, extensionH } = windowRange(); + const endAnchor = nowMs - state.panHours * HOUR_MS + extensionH * HOUR_MS; + const tFocal = endAnchor - Math.trunc((1 - f) * state.rangeHours * HOUR_MS); + const newEnd = tFocal + Math.trunc((1 - f) * newRange * HOUR_MS); + const newPan = Math.trunc((nowMs - newEnd) / HOUR_MS); + state.panHours = clampPanHours(newPan, state.showForecast ? (horizonHours || 0) : 0, maxPanHoursNow()); + state.rangeHours = newRange; + redraw(); + } + return; + } + + // ── Pan 1 pointeur : glisser à DROITE = passé (panHours ↑), GAUCHE = futur + const dx = cur.x - prev.x; + const rect = canvas.getBoundingClientRect(); + // fix #62 : cumul fractionnaire (le résiduel < 1 h est conservé) + const { hours, residual } = panDeltaHours(dx, rect.width, state.rangeHours, state.panResidualHours); + state.panResidualHours = residual; + if (hours !== 0) { + state.panHours = clampPanHours(state.panHours + hours, state.showForecast ? (horizonHours || 0) : 0, maxPanHoursNow()); + redraw(); + } + }); + + const releasePointer = (e) => { + activePointers.delete(e.pointerId); + if (activePointers.size < 2) pinchStart = null; + }; + canvas.addEventListener('pointerup', releasePointer); + canvas.addEventListener('pointercancel', releasePointer); + + // Molette = zoom (utilisateur desktop — le pinch tactile est déjà couvert) + canvas.addEventListener('wheel', (e) => { + e.preventDefault(); + if (e.deltaY < 0) { + state.rangeHours = Math.max(Math.trunc(state.rangeHours / 2), MIN_RANGE_H); + } else { + state.rangeHours = Math.min(state.rangeHours * 2, MAX_RANGE_H); + } + redraw(); + }, { passive: false }); + + redraw(); +} + +/** @private Ligne de légende colorée. */ +function legendLine(text, color) { + return el('div', { class: 'legend-text', style: `color:${color};` }, text); +} diff --git a/js/ui/components.js b/js/ui/components.js new file mode 100644 index 0000000..c14529f --- /dev/null +++ b/js/ui/components.js @@ -0,0 +1,250 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * Composants UI réutilisables (equivalent des composants Compose Android) : + * fabrique DOM `el()`, dialogs modaux, dropdown, champs date/heure, toast. + * + * Aucun framework : tout est construit via `el(tag, attrs, children)` — un + * helper minimal et lisible, commenté, qui remplace JSX/hyperscript. + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { t as tr } from '../util/i18n.js'; + +/** + * Fabrique DOM : crée un élément, applique les attributs/écouteurs, attache + * les enfants (string → nœud texte ; Element → append ; Array → aplatit ; + * null/undefined/false → ignoré). + * + * attrs spéciaux : + * - `class` : liste de classes (string) ; + * - `on` : addEventListener (ex. `onclick`, `oninput`) ; + * - tout autre attribut passe par setAttribute (sauf `value`/`checked`/ + * `disabled`/`selected` → propriétés DOM directes). + * + * @param {string} tag + * @param {object} [attrs={}] + * @param {Array|Node|string|null} [children] + * @returns {HTMLElement} + */ +export function el(tag, attrs = {}, children = null) { + const node = document.createElement(tag); + for (const [key, value] of Object.entries(attrs || {})) { + if (value === null || value === undefined || value === false) continue; + if (key.startsWith('on') && typeof value === 'function') { + node.addEventListener(key.slice(2), value); + } else if (key === 'class') { + node.className = value; + } else if (key === 'value' || key === 'checked' || key === 'disabled' || key === 'selected') { + node[key] = value; + } else { + node.setAttribute(key, value); + } + } + appendChildren(node, children); + return node; +} + +/** @private */ +function appendChildren(node, children) { + if (children === null || children === undefined || children === false) return; + if (Array.isArray(children)) { + for (const c of children) appendChildren(node, c); + } else if (children instanceof Node) { + node.appendChild(children); + } else { + node.appendChild(document.createTextNode(String(children))); + } +} + +/** + * Dialog modal générique (miroir AlertDialog M3) : + * - fond cliquable = dismiss (comme onDismissRequest) ; + * - body : nœud ou string ; actions : [{label, className, onClick}]. + * + * @param {object} opts { title, body, actions, dismissible=true } + * @returns {{close:Function, root:HTMLElement}} + */ +export function showDialog({ title, body, actions = [], dismissible = true }) { + const root = el('div', { class: 'dialog-backdrop' }); + const dialog = el('div', { class: 'dialog' }); + if (title) dialog.appendChild(el('h2', {}, title)); + if (body) appendChildren(dialog, body); + if (actions.length) { + const row = el('div', { class: 'actions' }); + for (const a of actions) { + row.appendChild(el('button', { + class: a.className || 'btn text', + onclick: () => a.onClick(), + }, a.label)); + } + dialog.appendChild(row); + } + root.appendChild(dialog); + + const close = () => root.remove(); + if (dismissible) { + root.addEventListener('click', (e) => { + if (e.target === root) close(); // clic sur le fond seulement + }); + } + document.body.appendChild(root); + return { close, root }; +} + +/** + * Toast de feedback (message temporaire en bas d'écran — feedback des + * actions Paramètres, miroir du `message` SettingsScreen Android). + * + * @param {string} text + */ +export function toast(text) { + const node = el('div', { class: 'toast' }, text); + document.body.appendChild(node); + setTimeout(() => node.remove(), 3500); +} + +/** + * Champ de saisie (label + input/select/textarea) — miroir OutlinedTextField. + * + * @param {object} opts { label, type, value, onInput, options (select), + * placeholder, attrs, inputEl (retourne l'élément via + * result.input) } + * @returns {HTMLElement} le conteneur (.input = l'élément de saisie) + */ +export function field({ label, type = 'text', value = '', onInput = null, options = null, placeholder = null, attrs = {} }) { + let input; + if (options) { + input = el('select', { + onchange: (e) => onInput && onInput(e.target.value), + ...attrs, + }, options.map((o) => el('option', { value: o.value, selected: o.value === value }, o.label))); + } else if (type === 'textarea') { + input = el('textarea', { oninput: (e) => onInput && onInput(e.target.value), placeholder, ...attrs }, value || ''); + } else { + input = el('input', { + type, + value, + oninput: (e) => onInput && onInput(e.target.value), + placeholder, + ...attrs, + }); + } + const wrap = el('div', { class: 'field' }, [el('label', {}, label), input]); + wrap.input = input; + return wrap; +} + +/** + * Dropdown (miroir ExposedDropdownMenuBox) : un ` + `` + * (locale) — les dialogs web utilisent ces champs natifs (équivalent + * DatePicker+TimePicker Material3). + * + * @param {number} ms + * @returns {{date:string, time:string}} ex. { date: "2026-09-08", time: "18:30" } + */ +export function msToInputValues(ms) { + const d = new Date(ms); + return { + date: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`, + time: `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`, + }; +} + +/** + * Valeurs de champs date/heure (locale) → epoch ms. + * @param {string} dateISO "2026-09-08" + * @param {string} timeHM "18:30" + * @returns {number|null} null si invalide + */ +export function inputValuesToMs(dateISO, timeHM) { + if (!dateISO || !timeHM) return null; + const [y, mo, d] = dateISO.split('-').map(Number); + const [h, mi] = timeHM.split(':').map(Number); + if ([y, mo, d, h, mi].some((n) => Number.isNaN(n))) return null; + const ms = new Date(y, mo - 1, d, h, mi, 0, 0).getTime(); + return Number.isFinite(ms) ? ms : null; +} diff --git a/js/util/i18n.js b/js/util/i18n.js new file mode 100644 index 0000000..5e5394a --- /dev/null +++ b/js/util/i18n.js @@ -0,0 +1,464 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * i18n FR/EN (miroir du système Android : values/strings.xml EN défaut + + * values-fr/strings.xml). + * + * - `t(key, ...args)` : traduction + substitution de placeholders {0},{1}… ; + * - langue persistée dans le store ("system"/"fr"/"en") — "system" suit + * navigator.language (équivalent AppCompatDelegate.setApplicationLocales) ; + * - ⚠️ TOUTE nouvelle string existe dans les DEUX dictionnaires (leçon + * Android §12 : une référence manquante = affichage brut de la clé). + * ───────────────────────────────────────────────────────────────────────────── + */ + +import { store } from '../data/store.js'; + +/** Dictionnaire ANGLAIS (défaut — même convention que values/strings.xml). */ +const EN = { + app_name: 'HormoneTrack', + // Navigation + nav_home: 'Home', + nav_chart: 'Charts', + nav_doses: 'Doses', + nav_labs: 'Labs', + nav_treatments: 'Treatments', + settings: 'Settings', + back: 'Back', + save: 'Save', + cancel: 'Cancel', + delete: 'Delete', + ok: 'OK', + add_dose: 'Log dose', + add_lab: 'Add blood test', + add_treatment: 'Add treatment', + edit_treatment: 'Edit treatment', + edit_dose: 'Edit dose', + no_data: 'No data yet', + loading: 'Loading…', + // Avertissement médical + disclaimer: '⚠️ Medical disclaimer: the curves are pharmacokinetic estimates for information purposes — not measurements. Always trust your blood tests and your endocrinologist.', + // Accueil + current_level: 'Current level (estimated)', + no_data_short: 'No data — log an injection to see estimates', + delta_6h: '{0} {1} pg/mL vs 6 h ago', + next_dose: 'Next dose', + next_dose_days: '{0} d {1} h · {2} ({3})', + logged_today: 'Quick log', + home_chart_title: 'Last 24 h', + home_legend_e2: 'E2 — estradiol (pg/mL)', + home_legend_t: 'T — testosterone (ng/mL)', + open_charts_hint: 'Tap to open full charts', + // Alertes + alert_card_title: 'Threshold crossed (estimate)', + alert_high: '▲ {0} estimated ≈ {1} {2} — above your limit ({3})', + alert_low: '▼ {0} estimated ≈ {1} {2} — below your limit ({3})', + alert_disclaimer: 'Based on the estimated level, not a measurement.', + // Doses + hrt_since: 'On HRT since {0}', + hrt_duration: '{0} months, {1} days — {2} days total', + hrt_duration_days_only: '{0} days', + interval_value: '{0} d since previous', + confirm_delete: 'Delete this entry?', + confirm_delete_treatment: 'Delete this treatment? Its dose history will be removed too (labs are kept).', + // Labs + edit_which_lab: 'Which entry to edit?', + confirm_delete_blood_draw: 'Delete the whole blood draw ({0})?', + // Traitements + inactive: 'inactive', + pk_absorption: 'Tmax', + pk_halflife: 'Half-life (h)', + pk_bioavail: 'Bioavailability (0–1)', + treatment_name: 'Name', + treatment_type: 'Type', + treatment_route: 'Route', + ester: 'Ester', + pk_model: 'PK model', + default_dose: 'Standard dose', + dose_unit: 'Unit', + select_preset: 'Select a preset', + type_estradiol: 'Estradiol', + type_antiandrogen: 'Anti-androgen', + type_progestogen: 'Progestogen', + type_other: 'Other', + route_oral: 'Oral', + route_gel: 'Transdermal gel', + route_patch: 'Transdermal patch', + route_injection_im: 'IM injection', + route_injection_sc: 'Subcutaneous injection', + route_other: 'Other route', + model_ese: 'Estrannaise', + model_tfs: 'Transfem Science', + model_whs: 'WHSAH', + // Éditeur : calibration + calibration_title: 'Calibration', + scale_factor: 'Scale factor', + calibration_hint: 'Ratio lab ÷ model prediction. Use “Calibrate from labs” or enable auto-calibration in Settings.', + calibration_auto_override: 'Auto-calibration is ON: this manual factor is ignored on charts display.', + calibrate_from_labs: 'Calibrate from labs', + // Éditeur : posologie / prévision + freq_section: 'Posology (forecast)', + freq_interval: 'Interval (days)', + freq_hint: 'Projects upcoming doses on the chart and schedules reminders on this grid.', + // Éditeur : rappel + reminder: 'Reminder', + reminder_time: 'Reminder time', + // Éditeur : agenda + agenda_section: 'Calendar event', + agenda_hint: 'Recurring event in a local calendar (Android only). Not available in the web app.', + agenda_needs_posology: 'A posology is required for recurring events.', + agenda_permission: 'Requires calendar permissions on Android.', + // Éditeur : actif + active: 'Active', + // Dose dialog + dose_amount: 'Dose amount', + dose_time: 'Date & time', + dose_notes: 'Notes', + ester_default: 'Default ({0})', + no_treatment_hint: 'Create a treatment first (Treatments tab).', + // Lab dialog + lab_e2_value: 'E2 (pg/mL)', + lab_t_value: 'T', + lab_units_hint: 'Accepted T units: ng/mL, ng/dL, ng/L, nmol/L', + lab_timestamp: 'Date & time', + lab_notes: 'Notes', + lab_optional: 'optional', + // Paramètres + language: 'Language', + language_system: 'System', + auto_calibrate: 'Auto-calibration (E2 + T)', + auto_calibrate_hint: 'Adjusts scale factors per ester period and the T model from your labs — applied to charts display, nothing is stored.', + t_model_title: 'Testosterone estimate (manual settings)', + t_model_hint: 'T = floor + (base − floor) / (1 + k·E2). Empirical model — estimate only.', + t_base: 'Base (ng/mL)', + t_floor: 'Floor (ng/mL)', + t_calibrated: 'k calibrated from labs', + alert_section: 'Alert thresholds', + alert_hint: 'Evaluated against the ESTIMATED level on Home (opt-in: empty = no alert). High must be above low.', + alert_e2_high: 'E2 high (pg/mL)', + alert_e2_low: 'E2 low (pg/mL)', + alert_t_high: 'T high (ng/mL)', + alert_t_low: 'T low (ng/mL)', + alert_invalid: 'Invalid values (high must be above low, numbers only)', + saved: 'Saved', + timezone_section: 'Chart timezone', + timezone_hint: 'Timezone used for chart X labels and dose markers (IANA ID, e.g. Europe/Paris). Empty = device timezone.', + timezone_field: 'IANA timezone ID', + timezone_auto: 'Auto (device)', + timezone_invalid: 'Unknown timezone ID', + reminders_section: 'Reminders (web)', + reminders_note: 'Reminders fire via browser notifications while this page is open. No notification when the tab is closed (unlike the Android app).', + notif_grant: 'Enable notifications', + notif_granted: 'Notifications enabled', + notif_denied: 'Notifications blocked by the browser', + backup_section: 'Backup (JSON)', + backup_hint: 'Same format as the Android app — backups are interchangeable in both directions.', + export_json: 'Export JSON', + import_json: 'Import JSON', + export_ok: 'Backup exported', + export_fail: 'Export failed', + import_ok: 'Restored: {0} treatments, {1} doses, {2} labs', + import_fail: 'Import failed (invalid file?)', + import_title: 'Import backup', + import_warning: '⚠️ Importing REPLACES ALL current data (treatments, doses, labs) with the backup content. Continue?', + import_confirm: 'Erase & restore', + logs_section: 'Diagnostic logs', + logs_count: '{0} lines', + logs_export: 'Export logs', + logs_clear: 'Clear', + logs_export_ok: 'Diagnostic logs exported', + logs_export_fail: 'Logs export failed', + about_title: 'About', + settings_version: 'Version {0} (web)', + releases_link: 'Releases', + models_credit: 'PK models belong to their respective authors (Estrannaise, Transfem Science, WHSAH Collective via Mona).', + privacy_note: '100% local: your data never leaves this browser (localStorage). No account, no server.', + // Chart + chart_24h: '24 h', + chart_7j: '7 d', + chart_30j: '30 d', + zoom_in: 'Zoom in', + zoom_out: 'Zoom out', + show_labs: 'Labs', + show_forecast: 'Forecast', + show_extrema: 'Peaks & troughs', + back_to_present: 'Back to present', + legend_ese_e2: 'E2 — Estrannaise (blue)', + legend_ese_t: 'T — Estrannaise (pink)', + legend_tfs_e2: 'E2 — Transfem Science (teal)', + legend_tfs_t: 'T — Transfem Science (pink dashed)', + legend_whs_e2: 'E2 — WHSAH (green)', + legend_whs_t: 'T — WHSAH (purple dashed)', + legend_labs: '● labs (orange) — circle = E2, square = T', + legend_forecast_dose: 'Dashed line + ▼ = projected dose', + legend_extrema: '▲▼ = local peaks & troughs (with estimated values)', + drag_hint: 'Drag horizontally to pan (right = past, left = future with forecast). Pinch or − / + to zoom (6 h → 300 d).', + // Rappels (bannière + notification) + reminder_notif_title: 'HormoneTrack — dose reminder', + reminder_notif_body: '{0} · {1}{2} — scheduled for {3}', + reminder_log_now: 'Log now', + reminder_snooze: 'Snooze 1 h', + reminder_logged: 'Dose logged from reminder', + // Dialog « Nouveautés » + whats_new_title: 'What\'s new', + whats_new_close: 'Close', + // Demo (hook de test ?demo=1) + demo_banner: 'DEMO DATA (test hook) — this data lives only in this browser.', +}; + +/** Dictionnaire FRANÇAIS (miroir values-fr/strings.xml). */ +const FR = { + app_name: 'HormoneTrack', + nav_home: 'Accueil', + nav_chart: 'Graphiques', + nav_doses: 'Doses', + nav_labs: 'Analyses', + nav_treatments: 'Traitements', + settings: 'Paramètres', + back: 'Retour', + save: 'Enregistrer', + cancel: 'Annuler', + delete: 'Supprimer', + ok: 'OK', + add_dose: 'Loguer une dose', + add_lab: 'Ajouter une analyse', + add_treatment: 'Ajouter un traitement', + edit_treatment: 'Modifier le traitement', + edit_dose: 'Modifier la dose', + no_data: 'Aucune donnée', + loading: 'Chargement…', + disclaimer: '⚠️ Avertissement médical : les courbes sont des estimations pharmacocinétiques à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de sang et aux consignes de ton endocrinologue.', + current_level: 'Niveau actuel (estimé)', + no_data_short: 'Aucune donnée — logue une injection pour voir les estimations', + delta_6h: '{0} {1} pg/mL vs il y a 6 h', + next_dose: 'Prochaine dose', + next_dose_days: '{0} j {1} h · {2} ({3})', + logged_today: 'Log rapide', + home_chart_title: 'Dernières 24 h', + home_legend_e2: 'E2 — œstradiol (pg/mL)', + home_legend_t: 'T — testostérone (ng/mL)', + open_charts_hint: 'Taper pour ouvrir le graphique complet', + alert_card_title: 'Seuil franchi (estimation)', + alert_high: '▲ {0} estimé ≈ {1} {2} — au-dessus de ta limite ({3})', + alert_low: '▼ {0} estimé ≈ {1} {2} — en dessous de ta limite ({3})', + alert_disclaimer: 'Basé sur le niveau estimé, pas une mesure.', + hrt_since: 'Sous THS depuis le {0}', + hrt_duration: '{0} mois, {1} jours — {2} jours au total', + hrt_duration_days_only: '{0} jours', + interval_value: '{0} j depuis la précédente', + confirm_delete: 'Supprimer cette entrée ?', + confirm_delete_treatment: 'Supprimer ce traitement ? Son historique de doses sera supprimé aussi (les analyses sont conservées).', + edit_which_lab: 'Quelle entrée modifier ?', + confirm_delete_blood_draw: 'Supprimer la prise de sang entière ({0}) ?', + inactive: 'inactive', + pk_absorption: 'Tmax', + pk_halflife: 'Demi-vie (h)', + pk_bioavail: 'Biodisponibilité (0–1)', + treatment_name: 'Nom', + treatment_type: 'Type', + treatment_route: 'Voie', + ester: 'Ester', + pk_model: 'Modèle PK', + default_dose: 'Dose standard', + dose_unit: 'Unité', + select_preset: 'Choisir un preset', + type_estradiol: 'Œstradiol', + type_antiandrogen: 'Anti-androgène', + type_progestogen: 'Progestatif', + type_other: 'Autre', + route_oral: 'Orale', + route_gel: 'Gel transdermique', + route_patch: 'Patch transdermique', + route_injection_im: 'Injection IM', + route_injection_sc: 'Injection SC', + route_other: 'Autre voie', + model_ese: 'Estrannaise', + model_tfs: 'Transfem Science', + model_whs: 'WHSAH', + calibration_title: 'Calibration', + scale_factor: 'Facteur d\'échelle', + calibration_hint: 'Ratio lab ÷ prédiction du modèle. Utilise « Calibrer avec les analyses » ou active la calibration automatique dans Paramètres.', + calibration_auto_override: 'Auto-calibration active : ce facteur manuel est ignoré à l\'affichage des courbes.', + calibrate_from_labs: 'Calibrer avec les analyses', + freq_section: 'Posologie (prévision)', + freq_interval: 'Intervalle (jours)', + freq_hint: 'Projette les doses à venir sur le graphique et cale les rappels sur cette grille.', + reminder: 'Rappel', + reminder_time: 'Heure du rappel', + agenda_section: 'Événement d\'agenda', + agenda_hint: 'Événement récurrent dans un calendrier local (Android uniquement). Non disponible dans l\'app web.', + agenda_needs_posology: 'Une Posologie est requise pour les événements récurrents.', + agenda_permission: 'Nécessite les permissions agenda sur Android.', + active: 'Active', + dose_amount: 'Dose (quantité)', + dose_time: 'Date & heure', + dose_notes: 'Notes', + ester_default: 'Défaut ({0})', + no_treatment_hint: 'Crée d\'abord un traitement (onglet Traitements).', + lab_e2_value: 'E2 (pg/mL)', + lab_t_value: 'T', + lab_units_hint: 'Unités T acceptées : ng/mL, ng/dL, ng/L, nmol/L', + lab_timestamp: 'Date & heure', + lab_notes: 'Notes', + lab_optional: 'optionnel', + language: 'Langue', + language_system: 'Système', + auto_calibrate: 'Calibration automatique (E2 + T)', + auto_calibrate_hint: 'Ajuste les facteurs d\'échelle par période d\'ester et le modèle T depuis tes labs — appliqué à l\'affichage des courbes, rien n\'est stocké.', + t_model_title: 'Estimation T (réglages manuels)', + t_model_hint: 'T = plancher + (base − plancher) / (1 + k·E2). Modèle empirique — estimation seulement.', + t_base: 'Base (ng/mL)', + t_floor: 'Plancher (ng/mL)', + t_calibrated: 'k calibré avec les analyses', + alert_section: 'Seuils d\'alerte', + alert_hint: 'Évalués contre le niveau ESTIMÉ de l\'accueil (opt-in : vide = pas d\'alerte). Haut > bas requis.', + alert_e2_high: 'E2 haute (pg/mL)', + alert_e2_low: 'E2 basse (pg/mL)', + alert_t_high: 'T haute (ng/mL)', + alert_t_low: 'T basse (ng/mL)', + alert_invalid: 'Valeurs invalides (haut > bas requis, nombres seulement)', + saved: 'Enregistré', + timezone_section: 'Fuseau du graphique', + timezone_hint: 'Fuseau des labels X et marqueurs de doses (ID IANA, ex. Europe/Paris). Vide = fuseau du téléphone.', + timezone_field: 'ID de fuseau IANA', + timezone_auto: 'Auto (téléphone)', + timezone_invalid: 'ID de fuseau inconnu', + reminders_section: 'Rappels (web)', + reminders_note: 'Les rappels passent par les notifications du navigateur tant que cette page est ouverte. Pas de notification onglet fermé (contrairement à l\'app Android).', + notif_grant: 'Activer les notifications', + notif_granted: 'Notifications activées', + notif_denied: 'Notifications bloquées par le navigateur', + backup_section: 'Sauvegarde (JSON)', + backup_hint: 'Même format que l\'app Android — les backups sont interchangeables dans les deux sens.', + export_json: 'Exporter JSON', + import_json: 'Importer JSON', + export_ok: 'Sauvegarde exportée', + export_fail: 'Échec de l\'export', + import_ok: 'Restauré : {0} traitements, {1} doses, {2} analyses', + import_fail: 'Échec de l\'import (fichier invalide ?)', + import_title: 'Importer une sauvegarde', + import_warning: '⚠️ L\'import REMPLACE toutes les données actuelles (traitements, doses, analyses) par le contenu de la sauvegarde. Continuer ?', + import_confirm: 'Effacer & restaurer', + logs_section: 'Logs de diagnostic', + logs_count: '{0} lignes', + logs_export: 'Exporter les logs', + logs_clear: 'Effacer', + logs_export_ok: 'Logs de diagnostic exportés', + logs_export_fail: 'Échec de l\'export des logs', + about_title: 'À propos', + settings_version: 'Version {0} (web)', + releases_link: 'Releases', + models_credit: 'Les modèles PK appartiennent à leurs autrices respectives (Estrannaise, Transfem Science, WHSAH Collective via Mona).', + privacy_note: '100 % local : tes données ne quittent jamais ce navigateur (localStorage). Aucun compte, aucun serveur.', + chart_24h: '24 h', + chart_7j: '7 j', + chart_30j: '30 j', + zoom_in: 'Zoom avant', + zoom_out: 'Zoom arrière', + show_labs: 'Labs', + show_forecast: 'Prévision', + show_extrema: 'Pics & creux', + back_to_present: 'Revenir à maintenant', + legend_ese_e2: 'E2 — Estrannaise (bleu)', + legend_ese_t: 'T — Estrannaise (rose)', + legend_tfs_e2: 'E2 — Transfem Science (turquoise)', + legend_tfs_t: 'T — Transfem Science (rose pointillé)', + legend_whs_e2: 'E2 — WHSAH (vert)', + legend_whs_t: 'T — WHSAH (violet pointillé)', + legend_labs: '● labs (orange) — cercle = E2, carré = T', + legend_forecast_dose: 'Ligne pointillée + ▼ = dose projetée', + legend_extrema: '▲▼ = pics/creux locaux (avec valeurs estimées)', + drag_hint: 'Glisser horizontalement pour panoramique (droite = passé, gauche = futur avec la prévision). Pincer ou − / + pour zoomer (6 h → 300 j).', + reminder_notif_title: 'HormoneTrack — rappel de dose', + reminder_notif_body: '{0} · {1}{2} — prévu à {3}', + reminder_log_now: 'Loguer maintenant', + reminder_snooze: 'Reporter 1 h', + reminder_logged: 'Dose loguée depuis le rappel', + whats_new_title: 'Nouveautés', + whats_new_close: 'Fermer', + demo_banner: 'DONNÉES DE DÉMO (hook de test) — ces données ne vivent que dans ce navigateur.', +}; + +/** Presets (même libellé que les strings Android). */ +const EN_PRESETS = { + preset_ev_ese: 'EV injection — Estrannaise', + preset_eu_ese: 'EU injection — Estrannaise', + preset_een_ese: 'EEn injection — Estrannaise', + preset_ev_tfs: 'EV injection — Transfem Science', + preset_eu_tfs: 'EU injection — Transfem Science', + preset_een_tfs: 'EEn injection — Transfem Science', + preset_eb_tfs: 'EB injection — Transfem Science', + preset_ec_tfs: 'EC injection (oil) — Transfem Science', + preset_ecs_tfs: 'EC injection (aqueous suspension) — Transfem Science', + preset_pep_tfs: 'PEP injection — Transfem Science', + preset_ev_whs: 'EV injection — WHSAH', + preset_eu_whs: 'EU injection — WHSAH', + preset_een_whs: 'EEn injection — WHSAH', + preset_eb_whs: 'EB injection — WHSAH', + preset_ec_whs: 'EC injection (oil) — WHSAH', + preset_ecs_whs: 'EC injection (aqueous suspension) — WHSAH', + preset_e2_gel: 'E2 transdermal gel', + preset_e2_patch: 'E2 patch', + preset_e2_oral: 'Oral E2', + preset_cpa: 'Cyproterone acetate (CPA)', + preset_spiro: 'Spironolactone', + preset_bica: 'Bicalutamide', +}; + +const FR_PRESETS = { + preset_ev_ese: 'Injection EV — Estrannaise', + preset_eu_ese: 'Injection EU — Estrannaise', + preset_een_ese: 'Injection EEn — Estrannaise', + preset_ev_tfs: 'Injection EV — Transfem Science', + preset_eu_tfs: 'Injection EU — Transfem Science', + preset_een_tfs: 'Injection EEn — Transfem Science', + preset_eb_tfs: 'Injection EB — Transfem Science', + preset_ec_tfs: 'Injection EC (huile) — Transfem Science', + preset_ecs_tfs: 'Injection EC (suspension aqueuse) — Transfem Science', + preset_pep_tfs: 'Injection PEP — Transfem Science', + preset_ev_whs: 'Injection EV — WHSAH', + preset_eu_whs: 'Injection EU — WHSAH', + preset_een_whs: 'Injection EEn — WHSAH', + preset_eb_whs: 'Injection EB — WHSAH', + preset_ec_whs: 'Injection EC (huile) — WHSAH', + preset_ecs_whs: 'Injection EC (suspension aqueuse) — WHSAH', + preset_e2_gel: 'E2 gel transdermique', + preset_e2_patch: 'E2 patch', + preset_e2_oral: 'E2 orale', + preset_cpa: 'Acétate de cyprotérone (CPA)', + preset_spiro: 'Spironolactone', + preset_bica: 'Bicalutamide', +}; + +const DICTS = { + en: { ...EN, ...EN_PRESETS }, + fr: { ...FR, ...FR_PRESETS }, +}; + +/** Langue résolue pour un réglage ("system" suit le navigateur). */ +export function resolveLocale(setting) { + if (setting === 'fr' || setting === 'en') return setting; + const nav = typeof navigator !== 'undefined' && navigator.language ? navigator.language.toLowerCase() : 'en'; + return nav.startsWith('fr') ? 'fr' : 'en'; +} + +/** + * Traduit une clé dans la langue COURANTE du store, avec substitution de + * placeholders {0}, {1}… + * + * @param {string} key + * @param {...(string|number)} args + * @returns {string} + */ +export function t(key, ...args) { + const locale = resolveLocale(store.getSettings().language); + let s = DICTS[locale][key] ?? DICTS.en[key] ?? key; + args.forEach((a, i) => { + s = s.replaceAll(`{${i}}`, String(a)); + }); + return s; +} + +/** Clé i18n → libellé dans la langue courante (utile pour les listes). */ +export function trKey(key) { + return t(key); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b11ab92 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "hormonetrack-web", + "version": "1.4.10", + "description": "HormoneTrack — version web (100% locale, localStorage). Portage navigateur de l'app Android.", + "private": true, + "type": "module", + "license": "GPL-3.0", + "repository": "https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web", + "scripts": { + "test": "node --test tests/", + "e2e": "node scripts/e2e.mjs", + "check": "bash scripts/check.sh", + "serve": "python3 scripts/serve.py" + }, + "dependencies": { + "playwright-core": "^1.63.0" + }, + "devDependencies": { + "playwright": "^1.63.0" + } +} diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100644 index 0000000..44702e0 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# check.sh — vérifications de l'app web (pré-commit / pré-push). +# +# 1. node --check sur chaque module ES (syntaxe) — attrape les typos avant +# même de charger l'app dans un navigateur ; +# 2. présence des fichiers critiques (index.html, asset PK, CSS, changelog) ; +# 3. cohérence i18n : clés FR/EN synchronisées + clés référencées existantes +# (leçon Android §12 : une string manquante s'affiche brute) ; +# 4. tests unitaires Node (node --test) ; +# 5. E2E navigateur (si playwright est installé — npm i + npx playwright +# install firefox, cf docs/DEVELOPPEMENT.md §Tests) ; +# 6. smoke HTTP (--with-serve : serveur + curl des ressources clés). +# +# Usage : bash scripts/check.sh [--with-serve] +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail +cd "$(dirname "$0")/.." # racine du dépôt + +fail=0 + +echo "── 1. Syntaxe des modules (node --check) ──" +while IFS= read -r f; do + if ! node --check "$f" 2>/tmp/hrt-check.err; then + echo "✗ $f" + cat /tmp/hrt-check.err + fail=1 + fi +done < <(find js -name '*.js' -type f | sort) +echo " tous les modules de js/ sont syntaxiquement valides" + +echo "── 2. Fichiers critiques ──" +for f in index.html css/style.css assets/pk_profiles.json docs/CHANGELOG.md \ + js/ui/app.js js/pk/pk-engine.js; do + if [[ ! -f "$f" ]]; then + echo "✗ manquant : $f" + fail=1 + fi +done +echo " fichiers critiques présents" + +echo "── 3. Cohérence i18n FR/EN ──" +# Vérifie : (a) chaque clé de EN existe dans FR et inversement ; +# (b) chaque clé utilisée par t('...') dans js/ existe dans EN et FR. +# (leçon Android §12 : une string manquante s'affiche brute — et FR/EN +# désynchronisés = UI mi-anglaise mi-française) +if ! node scripts/i18n-check.mjs; then + echo "✗ clés i18n manquantes (voir ci-dessus)" + fail=1 +fi + +echo "── 4. Tests unitaires ──" +if ! node --test tests/ 2>&1 | tail -6; then + fail=1 +fi + +echo "── 5. E2E navigateur (si playwright installé) ──" +if [[ -d node_modules/playwright-core && -d "$HOME/Library/Caches/ms-playwright" ]]; then + if ! node scripts/e2e.mjs 8989; then + fail=1 + fi +else + echo " (ignoré : playwright non installé — npm i && npx playwright install firefox)" +fi + +if [[ "${1:-}" == "--with-serve" ]]; then + echo "── 6. Smoke HTTP ──" + python3 scripts/serve.py --port 8971 >/tmp/hrt-serve.log 2>&1 & + SRV=$! + sleep 1.5 + for path in / /css/style.css /assets/pk_profiles.json /js/ui/app.js /docs/CHANGELOG.md; do + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8971${path}") + if [[ "$code" != "200" ]]; then + echo "✗ ${path} → HTTP ${code}" + fail=1 + else + echo " 200 ${path}" + fi + done + kill $SRV 2>/dev/null || true + wait $SRV 2>/dev/null || true +fi + +if [[ $fail -eq 0 ]]; then + echo "✓ CHECK OK" +else + echo "✗ CHECK FAILED" + exit 1 +fi diff --git a/scripts/e2e.mjs b/scripts/e2e.mjs new file mode 100644 index 0000000..1f7f69d --- /dev/null +++ b/scripts/e2e.mjs @@ -0,0 +1,202 @@ +#!/usr/bin/env node +/** + * e2e.mjs — test end-to-end de l'app web dans un VRAI navigateur. + * + * Ce que ce test vérifie (impossible en Node pur) : + * 1. l'app démarre dans un navigateur : modules ES chargés, asset PK fetché, + * aucune erreur console fatale ; + * 2. les écrans se rendent avec de vraies données (?demo=1) ; + * 3. le Canvas du graphique PEINT réellement des courbes (pixels non vides) ; + * 4. la navigation par hash fonctionne (5 onglets + settings) ; + * + * Prérequis (une fois, cf docs/DEVELOPPEMENT.md §Tests) : + * npm i → installe playwright/playwright-core (devDependencies) + * npx playwright install firefox → le build Firefox piloté par Playwright + * Le navigateur système n'est PAS utilisé (Firefox ≥ ~150 ne supporte plus + * le protocole Juggler de Playwright) ; le build téléchargé vit dans le + * cache Playwright (~90 Mo, hors dépôt). + * + * Usage : node scripts/e2e.mjs [port] + */ + +import { firefox } from 'playwright-core'; +import { spawn } from 'node:child_process'; +import { readFileSync, existsSync, mkdirSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const PORT = Number(process.argv[2] || 8980); +const BASE = `http://127.0.0.1:${PORT}`; +const SHOTS = '/tmp/hrt-web-shots'; + +let failures = 0; +function ok(condition, label, detail = '') { + if (condition) { + console.log(` ✓ ${label}`); + } else { + console.error(` ✗ ${label}${detail ? ` — ${detail}` : ''}`); + failures++; + } +} + +/** Démarre le serveur statique de dev et attend qu'il réponde. */ +async function startServer() { + const proc = spawn('python3', ['scripts/serve.py', '--port', String(PORT)], { + cwd: WEB_ROOT, + stdio: 'ignore', + }); + for (let i = 0; i < 50; i++) { + try { + await fetch(`${BASE}/index.html`); + return proc; + } catch (e) { + await new Promise((r) => setTimeout(r, 100)); + } + } + throw new Error('serveur de dev non démarré'); +} + +async function main() { + mkdirSync(SHOTS, { recursive: true }); + const server = await startServer(); + console.log(`serveur : ${BASE}`); + + const browser = await firefox.launch({ + headless: true, + firefoxUserPrefs: { + 'dom.notifications.enabled': false, // pas de popup de permission en test + }, + }); + const page = await browser.newPage({ viewport: { width: 420, height: 900 } }); + // Le Firefox headless démarre en-US : on seed la langue FR AVANT toute + // exécution de script (comme une utilisatrice qui a choisi Français) — + // toutes les assertions de texte de ce test sont en FR. + // ⚠️ CONDITIONNEL : écraser à chaque navigation effacerait aussi + // changelogSeenVersion → le dialog « Nouveautés » réapparaîtrait à chaque + // page.goto (une goto = reboot complet de la SPA). + await page.addInitScript(() => { + if (!window.localStorage.getItem('hormonetrack.settings')) { + window.localStorage.setItem('hormonetrack.settings', + JSON.stringify({ language: 'fr' })); + } + }); + const consoleErrors = []; + page.on('console', (msg) => { + if (msg.type() === 'error') consoleErrors.push(msg.text()); + }); + page.on('pageerror', (err) => consoleErrors.push(String(err))); + + try { + // ── 1. Accueil avec données de démo ───────────────────────────────────── + console.log('── Accueil (?demo=1) ──'); + await page.goto(`${BASE}/?demo=1`, { waitUntil: 'load' }); + await page.waitForSelector('body[data-ready="1"]', { timeout: 15000 }); + await page.waitForSelector('.card', { timeout: 15000 }); + const bodyText = await page.textContent('main.content'); + ok(bodyText.includes('Niveau actuel'), 'carte « niveau actuel » rendue'); + ok(/≈ \d+/.test(bodyText), `valeur E2 estimée affichée (${(bodyText.match(/≈ \d+(?:\.\d+)?/) || ['?'])[0]})`); + ok(bodyText.includes('ng/mL'), 'valeur T estimée affichée'); + ok(bodyText.includes('Prochaine dose'), 'carte « prochaine dose » (rappel démo 18:00)'); + + // Le niveau doit être physiologique (EEn 5 mg à l'équilibre TFS ≈ 160-270) + const levelMatch = bodyText.match(/≈ (\d+(?:\.\d+)?)/); + if (levelMatch) { + const e2 = Number(levelMatch[1]); + ok(e2 > 50 && e2 < 600, `E2 estimé plausible (${e2} pg/mL)`, `obtenu ${e2}`); + } + + // Premier lancement → le dialog « Nouveautés » est affiché (changelog v0.1.0 + // en markdown rendu) : vérifier son contenu PUIS le fermer pour capturer + // l'accueil réel (les captures doivent montrer l'app, pas l'overlay). + const dialog = await page.$('.dialog'); + ok(dialog !== null, 'dialog « Nouveautés » au premier lancement (markdown rendu)'); + if (dialog) { + const dlgText = await dialog.textContent(); + ok(dlgText.includes('Version 1.4.10'), 'dialog : section de la version courante'); + const hasRenderedMd = await dialog.$('.md-body h3, .md-body strong'); + ok(hasRenderedMd !== null, 'dialog : markdown RENDU (titres/gras), pas de brut'); + await page.click('.dialog .actions button'); + await page.waitForSelector('.dialog', { state: 'detached' }); + } + // Après fermeture : la bannière démo est visible au-dessus du contenu + const afterClose = await page.textContent('main.content'); + ok(afterClose.includes('DONNÉES DE DÉMO'), 'bannière démo visible'); + await page.screenshot({ path: join(SHOTS, 'e2e-home.png'), fullPage: false }); + + // ── 2. Graphiques : le Canvas peint de vraies courbes ─────────────────── + console.log('── Graphiques (#chart) ──'); + await page.goto(`${BASE}/#chart`, { waitUntil: 'load' }); + await page.waitForSelector('canvas.chart', { timeout: 15000 }); + await page.waitForTimeout(600); // requestAnimationFrame du dessin + const canvasInfo = await page.evaluate(() => { + const canvas = document.querySelector('canvas.chart'); + const ctx = canvas.getContext('2d'); + const { width, height } = canvas; + const data = ctx.getImageData(0, 0, width, height).data; + let colored = 0; // pixels non transparents (le fond du canvas est transparent) + for (let i = 3; i < data.length; i += 4) { + if (data[i] > 0) colored++; + } + return { width, height, colored }; + }); + ok(canvasInfo.colored > 5000, + `canvas peint (${canvasInfo.colored} px colorés sur ${canvasInfo.width}×${canvasInfo.height})`); + const legendText = await page.textContent('main.content'); + ok(legendText.includes('Transfem Science'), 'toggle/légende TFS présente'); + // Légende E2 + T + labs + hint pan + ok(legendText.includes('E2 —'), 'légende E2'); + ok(legendText.includes('Glisser horizontalement'), 'hint panoramique'); + await page.screenshot({ path: join(SHOTS, 'e2e-chart.png') }); + + // ── 3. Navigation : les 5 onglets + settings ──────────────────────────── + console.log('── Navigation ──'); + await page.goto(`${BASE}/#doses`, { waitUntil: 'load' }); + await page.waitForSelector('body[data-ready="1"]'); + await page.waitForTimeout(300); + let text = await page.textContent('main.content'); + ok(text.includes('Sous THS depuis'), 'Doses : en-tête « temps sous THS »'); + ok(text.includes('EEn — démo'), 'Doses : la dose de démo est listée'); + ok(text.includes('7.0 j depuis la précédente'), 'Doses : intervalle entre doses'); + + await page.goto(`${BASE}/#labs`, { waitUntil: 'load' }); + await page.waitForTimeout(300); + text = await page.textContent('main.content'); + ok(text.includes('E2') && text.includes('ng/dL'), 'Labs : prise groupée E2 + T (ng/dL converti au rendu seulement)'); + + await page.goto(`${BASE}/#treatments`, { waitUntil: 'load' }); + await page.waitForTimeout(300); + text = await page.textContent('main.content'); + ok(text.includes('EEn — démo'), 'Traitements : carte du traitement démo'); + ok(text.includes('Transfem Science'), 'Traitements : chip modèle (modelLabelKey)'); + + await page.goto(`${BASE}/#settings`, { waitUntil: 'load' }); + await page.waitForTimeout(300); + text = await page.textContent('main.content'); + ok(text.includes('Langue'), 'Paramètres : carte langue'); + ok(text.includes('Version 1.4.10 (web)'), 'Paramètres : version affichée'); + ok(text.includes('100 % local'), 'Paramètres : note vie privée'); + await page.screenshot({ path: join(SHOTS, 'e2e-settings.png') }); + + // ── 4. Erreurs console ────────────────────────────────────────────────── + console.log('── Santé ──'); + ok(consoleErrors.length === 0, + 'aucune erreur console/page', + consoleErrors.slice(0, 3).join(' | ')); + } finally { + await browser.close(); + server.kill(); + } + + if (failures === 0) { + console.log('✓ E2E OK'); + } else { + console.error(`✗ E2E : ${failures} échec(s)`); + process.exit(1); + } +} + +main().catch((e) => { + console.error('✗ E2E a planté :', e.message); + process.exit(1); +}); diff --git a/scripts/i18n-check.mjs b/scripts/i18n-check.mjs new file mode 100644 index 0000000..8dfd792 --- /dev/null +++ b/scripts/i18n-check.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * i18n-check.mjs — cohérence des dictionnaires FR/EN (utilisé par check.sh). + * + * Vérifie : + * 1. chaque clé de EN existe dans FR et inversement (UI mi-traduite sinon) ; + * 2. chaque clé utilisée via t('…') dans js/ existe dans les deux dicts + * (leçon Android §12 : une référence manquante = texte brut à l'écran). + * + * Sortie : silencieuse si tout va bien, liste les problèmes sinon (exit 1). + * Aucune dépendance : parse le module i18n.js par regex (les dicts sont des + * littéraux plats) et grep les usages dans les sources. + */ +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); +const i18nPath = join(root, 'js/util/i18n.js'); +const src = readFileSync(i18nPath, 'utf8'); + +function extractDict(name) { + const m = src.match(new RegExp(`const ${name} = \\{([\\s\\S]*?)\\n\\};`)); + if (!m) throw new Error(`dictionnaire ${name} introuvable`); + const keys = []; + // Les clés suivent la convention identifiant_underscore (a-z0-9_) + for (const km of m[1].matchAll(/^\s{2}([a-z0-9_]+):/gm)) keys.push(km[1]); + return new Set(keys); +} + +const EN = extractDict('EN'); +const FR = extractDict('FR'); +// Les presets vivent dans des dicts dédiés (même convention de clés) +for (const k of extractDict('EN_PRESETS')) EN.add(k); +for (const k of extractDict('FR_PRESETS')) FR.add(k); + +let failed = false; + +// 1. EN ↔ FR +for (const k of EN) { + if (!FR.has(k)) { + console.error(`✗ clé EN absente de FR : ${k}`); + failed = true; + } +} +for (const k of FR) { + if (!EN.has(k)) { + console.error(`✗ clé FR absente de EN : ${k}`); + failed = true; + } +} + +// 2. Clés utilisées dans les sources (t('key') et trKey('key')) +function walk(dir, out = []) { + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + if (statSync(p).isDirectory()) walk(p, out); + else if (p.endsWith('.js')) out.push(p); + } + return out; +} +const files = walk(join(root, 'js')).filter((f) => !f.endsWith('i18n.js')); +const used = new Set(); +const usageRe = /\b(?:t|trKey)\('([a-z0-9_]+)'/g; +for (const f of files) { + const content = readFileSync(f, 'utf8'); + for (const m of content.matchAll(usageRe)) used.add(m[1]); +} +// Clés de presets (résolues dynamiquement via nameKey — hors t('…') littéral) +const presetsSrc = readFileSync(join(root, 'js/pk/presets.js'), 'utf8'); +for (const m of presetsSrc.matchAll(/'([a-z0-9_]+)'/g)) { + if (m[1].startsWith('preset_')) used.add(m[1]); +} +// Clés de modèles/labels résolues dynamiquement (modelLabelKey, routeLabelKey…) +for (const k of ['model_ese', 'model_tfs', 'model_whs', 'route_oral', 'route_gel', 'route_patch', + 'route_injection_im', 'route_injection_sc', 'route_other', 'type_estradiol', 'type_antiandrogen', + 'type_progestogen', 'type_other']) { + used.add(k); +} + +for (const k of used) { + if (!EN.has(k)) { + console.error(`✗ clé utilisée absente de EN : ${k}`); + failed = true; + } + if (!FR.has(k)) { + console.error(`✗ clé utilisée absente de FR : ${k}`); + failed = true; + } +} + +console.error(` i18n : ${EN.size} clés EN / ${FR.size} clés FR / ${used.size} clés utilisées`); +if (!failed) console.error(' i18n cohérent'); +process.exit(failed ? 1 : 0); diff --git a/scripts/serve.py b/scripts/serve.py new file mode 100644 index 0000000..3cb68d3 --- /dev/null +++ b/scripts/serve.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +serve.py — serveur statique de développement/test pour HormoneTrack Web. + +Pourquoi un script dédié (au lieu de `python3 -m http.server`) : + - headers Cache-Control: no-cache → un rechargement teste toujours le code + à jour (indispensable en développement et en smoke-test) ; + - Content-Type corrects (json, svg, wasm-safe) sur toutes les plateformes ; + - messages de démarrage explicites (URL + raccourci ?demo=1 documenté). + +⚠️ C'est un serveur de DÉVELOPPEMENT (mono-thread, sans TLS). Pour la +production, servir CE DOSSIER avec n'importe quel serveur statique +(nginx, caddy, Gitea pages, etc.) — l'app n'a AUCUN composant serveur. + +Usage : + python3 scripts/serve.py [--port 8970] [--bind 127.0.0.1] +""" + +import argparse +import mimetypes +import os +import sys +from http.server import HTTPServer, SimpleHTTPRequestHandler + +# S'assurer que les types marginaux sont corrects (utile pour l'asset PK) +mimetypes.add_type("application/json", ".json") +mimetypes.add_type("image/svg+xml", ".svg") +mimetypes.add_type("text/markdown", ".md") + + +class NoCacheHandler(SimpleHTTPRequestHandler): + """Handler statique avec cache désactivé (dev/test).""" + + def end_headers(self): + self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") + self.send_header("Pragma", "no-cache") + self.send_header("Expires", "0") + super().end_headers() + + def log_message(self, format, *args): + # Log concis vers stdout (une ligne par requête) + sys.stderr.write(" %s\n" % (format % args)) + + +def main(): + parser = argparse.ArgumentParser(description="Serveur statique HormoneTrack Web (dev)") + parser.add_argument("--port", type=int, default=8970, help="port d'écoute (défaut 8970)") + parser.add_argument("--bind", default="127.0.0.1", help="interface d'écoute (défaut 127.0.0.1)") + args = parser.parse_args() + + # Racine = le dossier parent de scripts/ (la racine du dépôt), peu importe + # d'où le script est appelé. + web_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + os.chdir(web_root) + + server = HTTPServer((args.bind, args.port), NoCacheHandler) + print(f"HormoneTrack Web — serveur de dev sur http://{args.bind}:{args.port}/") + print(" app : http://" + f"{args.bind}:{args.port}/") + print(" mode démo : http://" + f"{args.bind}:{args.port}/?demo=1 (données de test, si store vide)") + print(" Ctrl-C pour arrêter") + try: + server.serve_forever() + except KeyboardInterrupt: + print("\narrêté") + + +if __name__ == "__main__": + main() diff --git a/tests/alerts.test.js b/tests/alerts.test.js new file mode 100644 index 0000000..58d5d09 --- /dev/null +++ b/tests/alerts.test.js @@ -0,0 +1,79 @@ +/** + * Tests des seuils d'alerte (miroir de AlertsTest.kt + AlertsEngineTest.kt) : + * comparaison stricte, cohérence, codec d'état, décision de notification. + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import * as Alerts from '../js/pk/alerts.js'; + +describe('évaluation des seuils', () => { + const t = new Alerts.Thresholds(400, 50, 1.2, 0.1); + + test('strict : la valeur EXACTEMENT à la limite ne déclenche rien', () => { + assert.equal(Alerts.evaluate(400, 50, 400), null, '== high → rien'); + assert.equal(Alerts.evaluate(50, 50, 400), null, '== low → rien'); + assert.equal(Alerts.evaluate(400.01, 50, 400)[0], 'HIGH'); + assert.equal(Alerts.evaluate(49.99, 50, 400)[0], 'LOW'); + }); + + test('null = jamais d\'alerte sur ce côté', () => { + assert.equal(Alerts.evaluate(1000, null, null), null); + assert.equal(Alerts.evaluate(1, null, null), null); + assert.equal(Alerts.evaluate(500, null, 400)[0], 'HIGH'); + }); + + test('HIGH prime LOW en configuration incohérente (défensif)', () => { + const [level, limit] = Alerts.evaluate(50, 100, 40); // haut < bas (interdit à la saisie) + assert.equal(level, 'HIGH'); + assert.equal(limit, 40); + }); + + test('evaluateAll : ordre stable E2 puis T, unités correctes', () => { + const alerts = Alerts.evaluateAll(500, 0.05, t); + assert.equal(alerts.length, 2); + assert.deepEqual(alerts.map((a) => a.marker), ['E2', 'T']); + assert.deepEqual(alerts.map((a) => a.unit), ['pg/mL', 'ng/mL']); + assert.equal(alerts[0].level, 'HIGH'); + assert.equal(alerts[1].level, 'LOW'); + }); + + test('isCoherent : haut > bas requis (les deux marqueurs indépendants)', () => { + assert.equal(new Alerts.Thresholds(400, 50, 1.2, 0.1).isCoherent(), true); + assert.equal(new Alerts.Thresholds(50, 400, null, null).isCoherent(), false); + assert.equal(new Alerts.Thresholds(null, null, 0.1, 1.2).isCoherent(), false); + // un seul côté défini → cohérent + assert.equal(new Alerts.Thresholds(400, null, null, 0.1).isCoherent(), true); + }); +}); + +describe('codec d\'état (anti-spam) — rétro-résistant aux entrées malformées', () => { + test('encode → parse round-trip', () => { + const alerts = [ + { marker: 'E2', level: 'HIGH' }, + { marker: 'T', level: 'LOW' }, + ]; + const encoded = Alerts.encodeState(alerts); + assert.equal(encoded, 'E2:HIGH;T:LOW'); + assert.deepEqual(Alerts.parseState(encoded), { E2: 'HIGH', T: 'LOW' }); + }); + + test('parse : résistant aux segments cassés', () => { + assert.deepEqual(Alerts.parseState('E2:HIGH;garbage;T:BOGUS;:LOW'), { E2: 'HIGH' }); + assert.deepEqual(Alerts.parseState(''), {}); + assert.deepEqual(Alerts.parseState(null), {}); + assert.deepEqual(Alerts.parseState(undefined), {}); + }); + + test('shouldNotify : nouveau franchissement → true, même état → false', () => { + const current = { E2: 'HIGH' }; + assert.equal(Alerts.shouldNotify(current, null), true, 'première notification'); + assert.equal(Alerts.shouldNotify(current, { E2: 'HIGH' }), false, 'pas de spam'); + assert.equal(Alerts.shouldNotify(current, { E2: 'LOW' }), true, 'changement H↔L'); + assert.equal(Alerts.shouldNotify({ E2: 'HIGH', T: 'LOW' }, { E2: 'HIGH' }), true, 'nouveau marqueur'); + }); + + test('retour à la normale → false (pas de notif) — l\'appelant efface l\'état', () => { + assert.equal(Alerts.shouldNotify({}, { E2: 'HIGH' }), false); + }); +}); diff --git a/tests/backup.test.js b/tests/backup.test.js new file mode 100644 index 0000000..49166eb --- /dev/null +++ b/tests/backup.test.js @@ -0,0 +1,177 @@ +/** + * Tests du format de backup (miroir de BackupGsonTest.kt + régressions sur + * les exports Android réels) : + * + * - round-trip build → parse (compatibilité exacte du schéma v2) ; + * - rétrocompat v1 (sans settings) ; + * - import ÉCRASEMENT (IDs conservés, ordre enfants → parents) ; + * - **les exports Android réels s'importent dans le web** (test data-driven + * sur local-test-data/ si présent — mêmes fichiers que les régressions + * Android, hors dépôt) ; + * - noms de fichiers d'export. + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync, existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { + WEB_ROOT, localTestDataDir, makeTreatment, makeDose, makeLab, HOUR_MS, +} from './helpers.js'; +import { buildBackupJson, parseBackupJson, backupFileName, diagnosticLogFileName } from '../js/data/backup.js'; +import { store } from '../js/data/store.js'; + +/** Backend Map isolé par test (localStorage indisponible en Node). */ +function freshStore() { + store.setBackend(new Map()); + return store; +} + +describe('format BackupData v2 (compatibilité Android)', () => { + test('build → parse round-trip : tous les champs survivent', () => { + const treatments = [makeTreatment({ id: 3, scaleFactor: 0.72, forecastIntervalDays: 7, reminderHour: 18, reminderMinute: 30, reminderEnabled: true })]; + const doseLogs = [makeDose(1000, 5, { id: 7, notes: 'cuisse L', esterType: 'EV' })]; + const labResults = [makeLab('T', 44, 'ng/dL', 2000, { id: 9, notes: 'jeûne' })]; + const tConfig = { base: 6.0, floor: 0.2, k: 0.21 }; + const settings = { + language: 'fr', autoCalibrate: true, + alertE2High: 400, alertE2Low: 50, alertTHigh: 1.2, alertTLow: null, + }; + const json = buildBackupJson({ treatments, doseLogs, labResults }, tConfig, settings); + const parsed = parseBackupJson(json); + + assert.equal(parsed.version, 2); + assert.equal(parsed.treatments.length, 1); + assert.equal(parsed.doseLogs.length, 1); + assert.equal(parsed.labResults.length, 1); + assert.deepEqual(parsed.tConfig, tConfig); + // champs plats UserSettings exactement comme Gson + assert.deepEqual(parsed.settings, { + language: 'fr', autoCalibrate: true, + alertE2High: 400, alertE2Low: 50, alertTHigh: 1.2, alertTLow: null, + }); + // fidélité des entités (champs Gson/Kotlin) + assert.equal(parsed.treatments[0].calendarEventId, null); + assert.equal(parsed.treatments[0].pkModel, 'TFS'); + assert.equal(parsed.doseLogs[0].esterType, 'EV'); + assert.equal(parsed.labResults[0].unit, 'ng/dL'); + }); + + test('rétrocompat v1 : backup SANS settings → settings null, importable', () => { + // Structure d'un vrai export v1 (avant la v1.4.2 Android) + const v1 = JSON.stringify({ + version: 1, + exportedAt: 1700000000000, + treatments: [makeTreatment({ id: 1 })], + doseLogs: [makeDose(0, 5)], + labResults: [], + tConfig: { base: 6.0, floor: 0.2, k: 0.19 }, + }); + const parsed = parseBackupJson(v1); + assert.equal(parsed.version, 1); + assert.equal(parsed.settings, null); + assert.equal(parsed.treatments.length, 1); + }); + + test('backup tronqué/tordu : défauts TConfig, listes manquantes → vides', () => { + const weird = parseBackupJson('{"treatments": null}'); + assert.deepEqual(weird.treatments, []); + assert.equal(weird.tConfig.base, 6.0, 'défaut TConfig.kt si absent'); + }); + + test('non-JSON et non-backup → erreur explicite (pas de crash silencieux)', () => { + assert.throws(() => parseBackupJson('not json at all'), /JSON illisible/); + assert.throws(() => parseBackupJson('{"foo": 1}'), /backup/); + }); + + test('les clés web extra (chartTimezone…) ne fuient PAS dans l\'export', () => { + const json = buildBackupJson( + { treatments: [], doseLogs: [], labResults: [] }, + { base: 6, floor: 0.2, k: 0.19 }, + { language: 'fr', autoCalibrate: false, chartTimezone: 'Europe/Paris', alertNotifiedState: 'E2:HIGH' }, + ); + assert.ok(!json.includes('chartTimezone'), 'clé web exclue'); + assert.ok(!json.includes('alertNotifiedState'), 'clé web exclue'); + assert.ok(!json.includes('changelogSeenVersion'), 'volontairement exclu (miroir Android)'); + }); +}); + +describe('import ÉCRASEMENT (v1.2.6)', () => { + test('données actuelles effacées, IDs du backup conservés, compteur recalculé', () => { + const s = freshStore(); + // État initial : un traitement id 5 + une dose id 2 rattachée + s.upsertTreatment(makeTreatment({ id: 5, name: 'ancien' })); + s.upsertDoseLog(makeDose(0, 1, { id: 2, treatmentId: 5 })); + + const backup = parseBackupJson(buildBackupJson({ + treatments: [makeTreatment({ id: 11, name: 'nouveau' })], + doseLogs: [makeDose(100, 5, { id: 21, treatmentId: 11 })], + labResults: [makeLab('E2', 300, 'pg/mL', 150, { id: 31 })], + }, { base: 6, floor: 0.2, k: 0.19 }, null)); + + const result = s.importOverwrite(backup); + assert.deepEqual(result, { treatments: 1, doseLogs: 1, labResults: 1 }); + + // IDs du backup conservés (FK dose→traitement valides) + assert.deepEqual(s.getTreatments().map((t) => t.id), [11]); + assert.equal(s.getDoseLogs()[0].treatmentId, 11); + assert.equal(s.getLabResults()[0].id, 31); + // le prochain id auto-increment ne rentre PAS en conflit avec les ids importés + const nextId = s.upsertTreatment(makeTreatment({ name: 'post-import' })); + assert.ok(nextId > 11, `prochain id = ${nextId} > max importé`); + }); + + test('import d\'un backup v1 : les réglages ne sont PAS touchés', () => { + const s = freshStore(); + s.setSettings({ autoCalibrate: true, alertE2High: 400 }); + const backup = parseBackupJson(buildBackupJson( + { treatments: [], doseLogs: [], labResults: [] }, + { base: 5, floor: 0.1, k: 0.2 }, + null, // v1 : pas de settings + )); + s.importOverwrite(backup); + s.setTConfig(backup.tConfig); + assert.equal(s.getSettings().autoCalibrate, true, 'réglages intacts (null = ne rien toucher)'); + assert.equal(s.getTConfig().base, 5, 'tConfig du backup restauré'); + }); +}); + +describe('exports Android RÉELS → import web (données hors dépôt)', () => { + // Le dossier vit dans le dépôt Android voisin (dépôts séparés depuis + // v1.4.10) — cf helpers.localTestDataDir() : null → les tests skip + // (clone neuf / CI), comme les Assume Kotlin côté Android. + const dir = localTestDataDir(); + const backups = ['backup-v1.0.0.json', 'backup-v1.2.0.json', 'backup-v1.3.1.json', 'backup-v1.4.2.json']; + + for (const name of backups) { + const path = dir ? join(dir, name) : null; + // Skip si absent (clone neuf / CI) — même comportement Assume côté Android + if (!path || !existsSync(path)) { + test.skip(`${name} : import web (données locales absentes)`, () => {}); + continue; + } + test(`${name} : parse + import + calibration non vide`, () => { + const s = freshStore(); + const parsed = parseBackupJson(readFileSync(path, 'utf8')); + const result = s.importOverwrite(parsed); + assert.ok(result.treatments > 0, `${result.treatments} traitements`); + assert.ok(result.doseLogs > 0, `${result.doseLogs} doses`); + assert.ok(result.labResults > 0, `${result.labResults} analyses`); + // FK intactes : chaque dose pointe vers un traitement existant + const ids = new Set(s.getTreatments().map((t) => t.id)); + for (const d of s.getDoseLogs()) { + assert.ok(ids.has(d.treatmentId), 'FK dose → traitement'); + } + }); + } +}); + +describe('noms de fichiers d\'export (miroir ExportFileNames)', () => { + test('backup : hormonetrack-backup-YYYYMMDD.json', () => { + assert.equal(backupFileName(new Date(2026, 8, 8)), 'hormonetrack-backup-20260908.json'); + }); + + test('logs : hormonetrack-logs-YYYYMMDD-HHmm.txt (pattern AVEC heure, bug #48)', () => { + assert.equal(diagnosticLogFileName(new Date(2026, 8, 8, 9, 5)), 'hormonetrack-logs-20260908-0905.txt'); + }); +}); diff --git a/tests/calibration.test.js b/tests/calibration.test.js new file mode 100644 index 0000000..c9a779c --- /dev/null +++ b/tests/calibration.test.js @@ -0,0 +1,202 @@ +/** + * Tests de calibration (miroir de CalibrationPerModelTest.kt + + * ScaleFactorWhsahReproTest.kt + V120FeaturesTest.kt) : + * + * - facteur PAR PÉRIODE D'ESTER (labs EV → EV, labs EEn → EEn) ; + * - calibration PAR MODÈLE (fix #60) : échelles distinctes ESE/TFS/WHS, + * chaque courbe calibrée passe par le lab ; + * - GARDE DE SIGNIFICATIVITÉ (fix #61) : reproduction du scénario ×2,21 + * (dose de test ancienne + labs tardifs) — le facteur retombe physiologique ; + * - k T par ester, calibré contre l'E2 CALIBRÉE ; + * - autoCalibrated ne modifie RIEN (copies). + */ + +import { test, describe, before } from 'node:test'; +import assert from 'node:assert/strict'; +import { + initProfiles, makeTreatment, makeDose, makeLab, assertClose, HOUR_MS, DAY_MS, +} from './helpers.js'; +import { + autoCalibrated, computeEsterScaleFactors, computeTKPerEster, computeScaleFactor, + labIsSignificant, computeTConfigCalibration, LAB_MIN_PREDICTION_FRACTION, +} from '../js/pk/pk-calibration.js'; +import { e2At, TConfig } from '../js/pk/index.js'; + +describe('labIsSignificant (garde #61)', () => { + test('prédiction > 0,5 et ≥ 15 % du max observé', () => { + assert.equal(labIsSignificant(100, 0), true, 'premier lab (max 0)'); + assert.equal(labIsSignificant(100, 500), true, '20 % du max'); + assert.equal(labIsSignificant(50, 500), false, '10 % du max → ignoré'); + assert.equal(labIsSignificant(0.3, 0), false, 'bruit < 0,5 pg/mL'); + assert.equal(LAB_MIN_PREDICTION_FRACTION, 0.15); + }); +}); + +describe('calibration par période d\'ester (v1.2.1)', () => { + before(() => initProfiles()); + + test('labs EV calibrent EV, labs EEn calibrent EEn (scénario transition)', () => { + // Scénario : période EV (2 mg / 5 j, jours 30–60) puis période EEn + // (5 mg / 7 j, jours 67–95). Les lab values sont posées DATA-DRIVEN : + // lab = prédiction complète × facteur voulu (l'accumulation est prise + // en compte par e2At, comme dans l'app). + const trEv = makeTreatment({ id: 1, esterType: 'EV', pkModel: 'TFS', doseAmount: 2 }); + const trEen = makeTreatment({ id: 2, esterType: 'EEN', pkModel: 'TFS' }); + const treatments = [trEv, trEen]; + const doses = []; + let id = 1; + for (let day = 30; day <= 60; day += 5) { + doses.push(makeDose(day * DAY_MS, 2, { id: id++, treatmentId: 1, esterType: 'EV' })); + } + for (let day = 67; day <= 95; day += 7) { + doses.push(makeDose(day * DAY_MS, 5, { id: id++, treatmentId: 2, esterType: 'EEN' })); + } + // Lab EV : jour 62 (dernière dose EV jour 60, avant la 1ʳᵉ EEn jour 67) + const labEvTs = 62 * DAY_MS; + const predEv = e2At(treatments, doses, labEvTs, null, null); + // Lab EEn : jour 97 (dernière dose EEn jour 95) + const labEenTs = 97 * DAY_MS; + const predEen = e2At(treatments, doses, labEenTs, null, null); + const labs = [ + makeLab('E2', predEv * 1.2, 'pg/mL', labEvTs), + makeLab('E2', predEen * 0.8, 'pg/mL', labEenTs), + ]; + const scales = computeEsterScaleFactors(treatments, doses, labs, null); + // Chaque ester calibré par SON lab (pas de mélange des périodes) + assertClose(scales.EV, 1.2, 0.02, `facteur EV = ${scales.EV}`); + assertClose(scales.EEN, 0.8, 0.02, `facteur EEN = ${scales.EEN}`); + }); + + test('labs antérieurs à la 1ʳᵉ dose → échelles vides (rien à calibrer)', () => { + const tr = makeTreatment({ esterType: 'EEN', pkModel: 'TFS' }); + const doses = [makeDose(50 * DAY_MS, 5)]; + const labs = [makeLab('E2', 300, 'pg/mL', 10 * DAY_MS)]; + assert.deepEqual(computeEsterScaleFactors([tr], doses, labs, null), {}); + }); +}); + +describe('calibration PAR MODÈLE (fix #60)', () => { + before(() => initProfiles()); + + test('chaque courbe calibrée avec SON modèle passe par le lab ; échelles distinctes', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS', scaleFactor: 1.0 }); + const doses = [makeDose(0, 5)]; + const labTs = 2.1 * DAY_MS; // ~pic TFS EV + const labValue = 300; + const labs = [makeLab('E2', labValue, 'pg/mL', labTs)]; + const scalesByModel = {}; + for (const model of ['ESE', 'TFS', 'WHS']) { + const scales = computeEsterScaleFactors([tr], doses, labs, model); + scalesByModel[model] = scales.EV; + const calibrated = e2At([tr], doses, labTs, model, scales); + assertClose(calibrated, labValue, 0.02, `courbe ${model} calibrée passe par le lab`); + } + // Les 3 facteurs sont DISTINCTS (prédictions différentes par modèle) : + assert.ok(Math.abs(scalesByModel.ESE - scalesByModel.TFS) > 0.02, 'ESE ≠ TFS'); + assert.ok(Math.abs(scalesByModel.WHS - scalesByModel.TFS) > 0.02, 'WHS ≠ TFS'); + assertClose(scalesByModel.TFS, 1.02, 0.05, 'TFS : ratio vs son propre pic ≈ 1'); + }); +}); + +describe('garde de significativité — reproduction du bug ×2,21 (fix #61)', () => { + before(() => initProfiles()); + + test('garde de significativité : les labs hors fenêtre d\'action sont EXCLUS des 2 pipelines', () => { + // Reproduction du scénario ×2,21 (v1.4.8) : UNE dose + un lab dans la + // fenêtre d'action (ratio correct) + un lab TARDIF avec un ratio absurde + // (prédiction résiduelle ≈ 0). Sans garde, la médiane explosait. + const tr = makeTreatment({ id: 1, esterType: 'EV', pkModel: 'TFS' }); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const goodTs = 2 * DAY_MS; // ~pic : prédiction ≈ 295 + const goodValue = e2At([tr], doses, goodTs, null, null) * 0.9; + const lateTs = 60 * DAY_MS; // prédiction résiduelle ≈ 0 + const labs = [ + makeLab('E2', goodValue, 'pg/mL', goodTs), + makeLab('E2', 3000, 'pg/mL', lateTs), // ratio ~×1000 — doit être ignoré + ]; + // Pipeline MANUEL + const sf = computeScaleFactor(tr, doses, labs); + assertClose(sf, 0.9, 0.05, `facteur manuel = ${sf} (jamais la médiane contaminée)`); + // Pipeline AUTO + const scales = computeEsterScaleFactors([tr], doses, labs, 'TFS'); + assertClose(scales.EV, 0.9, 0.05, `facteur auto = ${scales.EV}`); + }); + + test('le facteur reste exact quand les labs sont dans la fenêtre d\'action', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS' }); + const doses = [makeDose(0, 5)]; + const labTs = 2 * DAY_MS; + const labValue = e2At([tr], doses, labTs, null, null) * 0.9; // prédiction ×0.9 + const labs = [makeLab('E2', labValue, 'pg/mL', labTs)]; + const sf = computeScaleFactor(tr, doses, labs); + assertClose(sf, 0.9, 0.05, 'facteur manuel dans la fenêtre d\'action'); + }); +}); + +describe('k T par ester (v1.2.3)', () => { + before(() => initProfiles()); + + test('k calibré par période contre l\'E2 CALIBRÉE ; originaux non modifiés', () => { + const tr = makeTreatment({ id: 1, esterType: 'EEN', pkModel: 'TFS' }); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const tConfig = new TConfig(6, 0.2, 0.19); + const scales = { EEN: 1.0 }; + // Lab T = 0.35 ng/mL à l'équilibre (E2 ≈ 160 calibrée) + const labs = [makeLab('T', 0.35, 'ng/mL', 7 * DAY_MS)]; + const ks = computeTKPerEster([tr], doses, labs, tConfig, scales, null); + assert.ok(ks.EEN > 1e-4 && ks.EEN < 10, `k = ${ks.EEN} dans la garde`); + // le tConfig d'origine n'est pas modifié (pur) + assert.equal(tConfig.k, 0.19); + }); + + test('lab T en ng/dL converti avant calibration (bug #23)', () => { + const tr = makeTreatment({ id: 1, esterType: 'EEN', pkModel: 'TFS' }); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const tConfig = new TConfig(6, 0.2, 0.19); + const ksNgMl = computeTKPerEster([tr], doses, [makeLab('T', 0.35, 'ng/mL', 7 * DAY_MS)], tConfig, { EEN: 1.0 }, null); + const ksNgDl = computeTKPerEster([tr], doses, [makeLab('T', 35, 'ng/dL', 7 * DAY_MS)], tConfig, { EEN: 1.0 }, null); + assertClose(ksNgMl.EEN, ksNgDl.EEN, 1e-9, 'même k quelle que soit l\'unité saisie'); + }); +}); + +describe('autoCalibrated (option Paramètres)', () => { + before(() => initProfiles()); + + test('renvoie des COPIES : traitements et tConfig inchangés', () => { + const tr = makeTreatment({ id: 1, scaleFactor: 0.72 }); + const tConfig = new TConfig(6, 0.2, 0.19); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const labs = [makeLab('E2', 300, 'pg/mL', 2 * DAY_MS)]; + const result = autoCalibrated([tr], doses, labs, tConfig); + assert.equal(result.treatments[0].scaleFactor, 0.72, 'scaleFactor stocké intact'); + assert.equal(result.tConfig.k, 0.19, 'tConfig intact'); + assert.equal(result.calibratedEsters, 1); + assert.equal(result.tRecalibrated, false, 'pas de lab T → pas de recalibrage T'); + }); + + test('modelOverride délégue : implémentation unique (Home et graphique ne peuvent pas diverger)', () => { + const tr = makeTreatment({ id: 1, esterType: 'EV', pkModel: 'TFS' }); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const labs = [makeLab('E2', 300, 'pg/mL', 2.1 * DAY_MS)]; + const cfg = new TConfig(); + const viaAuto = autoCalibrated([tr], doses, labs, cfg, 'TFS').esterScales; + const viaDirect = computeEsterScaleFactors([tr], doses, labs, 'TFS'); + assert.deepEqual(viaAuto, viaDirect, 'autoCalibrated = computeEsterScaleFactors (délégation)'); + }); +}); + +describe('computeTConfigCalibration (k global, bouton Paramètres)', () => { + test('récupère un k planté ; null sans labs exploitables', () => { + const tr = makeTreatment({ id: 1, esterType: 'EEN', pkModel: 'TFS' }); + const doses = [makeDose(0, 5, { treatmentId: 1 })]; + const cfg = new TConfig(6, 0.2, 0.19); + const labs = [makeLab('T', 0.35, 'ng/mL', 7 * DAY_MS)]; + const calibrated = computeTConfigCalibration(labs, [tr], doses, cfg); + assert.ok(calibrated && calibrated.k > 1e-4 && calibrated.k < 10, `k = ${calibrated && calibrated.k}`); + assert.equal(calibrated.base, 6); + assert.equal(calibrated.floor, 0.2); + // lab sous le plancher + 0.02 → inutilisable + const low = computeTConfigCalibration([makeLab('T', 0.2, 'ng/mL', 7 * DAY_MS)], [tr], doses, cfg); + assert.equal(low, null); + }); +}); diff --git a/tests/chart-helpers.test.js b/tests/chart-helpers.test.js new file mode 100644 index 0000000..e036bb2 --- /dev/null +++ b/tests/chart-helpers.test.js @@ -0,0 +1,180 @@ +/** + * Tests des helpers du graphique (miroir de ChartZoomTest.kt) : + * pan fractionnaire (#62), niceCeil, stepForRange, ticks minuit local (#55), + * pointHoursBefore (#53), clamps de pan (#54), horizon/extension de prévision, + * toggles par défaut (#58). + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { + panDeltaHours, niceCeil, stepForRange, xLabelTicks, clampPanHours, + pointHoursBefore, forecastHorizonHours, forecastExtensionHours, + defaultModelToggles, +} from '../js/pk/chart-helpers.js'; +import { LevelPoint } from '../js/pk/pk-engine.js'; +import { HOUR_MS, DAY_MS } from './helpers.js'; + +describe('panDeltaHours — fix #62 (pan mort sur 24 h)', () => { + test('le résiduel fractionnaire s\'accumule jusqu\'à franchir une heure', () => { + // Vue 24 h, canvas 900 px : 30 px ≈ 0,8 h — chaque delta seul serait 0 + let residual = 0; + let applied = 0; + for (let i = 0; i < 3; i++) { + const r = panDeltaHours(30, 900, 24, residual); + applied += r.hours; + residual = r.residual; + } + assert.ok(applied >= 1, `3 deltas de 0,8 h doivent appliquer ≥ 1 h (obtenu ${applied})`); + assert.equal(applied, 2, '0,8 + 0,8 = 1,6 → 1 appliqué, 0,8 → 2 au total'); + }); + + test('deltas négatifs : troncature vers zéro, résiduel conservé', () => { + let r = panDeltaHours(-30, 900, 24, 0); + assert.equal(r.hours, 0); // -0.8 → 0 (vers zéro) + assert.ok(r.residual < 0); + r = panDeltaHours(-30, 900, 24, r.residual); + assert.equal(r.hours, -1, '-0.8 + -0.8 = -1.6 → -1'); + }); + + test('canvas de largeur nulle → pas d\'application, résiduel intact', () => { + assert.deepEqual(panDeltaHours(50, 0, 24, 0.5), { hours: 0, residual: 0.5 }); + }); + + test('grande plage (7 j) : un delta de 30 px ≈ 5,6 h appliqué immédiatement', () => { + assert.equal(panDeltaHours(30, 900, 24 * 7, 0).hours, 5); + }); +}); + +describe('niceCeil / stepForRange', () => { + test('bornes propres 1/2/2.5/5/10 × 10ⁿ', () => { + assert.equal(niceCeil(370), 500); + assert.equal(niceCeil(250), 250); + assert.equal(niceCeil(240), 250); + assert.equal(niceCeil(101), 200); + assert.equal(niceCeil(99), 100); + assert.equal(niceCeil(5), 5); + assert.equal(niceCeil(0), 100, 'valeur nulle → borne par défaut'); + assert.equal(niceCeil(-3), 100); + }); + + test('échantillonnage adaptatif (v1.2.9)', () => { + assert.equal(stepForRange(6), 900000, '≤ 12 h → 15 min'); + assert.equal(stepForRange(12), 900000); + assert.equal(stepForRange(13), 1800000, '≤ 24 h → 30 min'); + assert.equal(stepForRange(24), 1800000); + assert.equal(stepForRange(24 * 7), 3600000, 'sinon 1 h'); + }); +}); + +describe('xLabelTicks — fix #55 (labels à minuit LOCAL)', () => { + test('pas 24 h : ticks alignés sur minuit LOCAL (Europe/Paris)', () => { + // Choisis un t0 au milieu d'une journée : le 1er tick doit être un minuit + // de Paris (01:00/00:00 UTC selon DST) + const t0 = Date.UTC(2026, 8, 10, 14, 30); // 10 sept 2026 14:30 UTC + const t1 = t0 + 10 * DAY_MS; + const ticks = xLabelTicks(t0, t1, 24, 'Europe/Paris'); + assert.ok(ticks.length >= 9); + for (const tick of ticks) { + const parts = new Intl.DateTimeFormat('en-US', { + timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', + }).formatToParts(new Date(tick)); + const hour = parts.find((p) => p.type === 'hour').value; + const minute = parts.find((p) => p.type === 'minute').value; + assert.equal(hour, '00', `tick ${new Date(tick).toISOString()} doit être minuit Paris`); + assert.equal(minute, '00'); + } + }); + + test('pas horaire : ticks sur des heures rondes LOCALES', () => { + const t0 = Date.UTC(2026, 8, 10, 14, 30); + const t1 = t0 + 8 * HOUR_MS; + const ticks = xLabelTicks(t0, t1, 1, 'Europe/Paris'); + for (const tick of ticks) { + const parts = new Intl.DateTimeFormat('en-US', { + timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', + }).formatToParts(new Date(tick)); + assert.equal(Number(parts.find((p) => p.type === 'minute').value), 0); + } + }); + + test('pas 5 jours : ticks espacés de 5 j calendaires (DST-safe)', () => { + const t0 = Date.UTC(2026, 2, 25, 12); // traverse le DST européen fin mars + const t1 = t0 + 30 * DAY_MS; + const ticks = xLabelTicks(t0, t1, 120, 'Europe/Paris'); + assert.ok(ticks.length >= 4); + for (let i = 1; i < ticks.length; i++) { + const deltaDays = (ticks[i] - ticks[i - 1]) / DAY_MS; + assert.ok(deltaDays >= 4.9 && deltaDays <= 5.1, `écart ≈ 5 j (obtenu ${deltaDays})`); + } + }); + + test('fenêtre vide / pas ≤ 0 → vide', () => { + assert.deepEqual(xLabelTicks(100, 100, 24), []); + assert.deepEqual(xLabelTicks(200, 100, 24), []); + assert.deepEqual(xLabelTicks(0, 100, 0), []); + }); + + test('fuseau par défaut = fuseau du système (pas de crash)', () => { + assert.ok(xLabelTicks(0, 10 * DAY_MS, 24).length > 0); + }); +}); + +describe('clampPanHours / horizon de prévision (#54/#61)', () => { + test('clamp : passé borné par maxPast, futur seulement si prévision active', () => { + assert.equal(clampPanHours(50, 0, 100), 50); + assert.equal(clampPanHours(150, 0, 100), 100, 'passé clampé'); + assert.equal(clampPanHours(-5, 0, 100), 0, 'futur interdit sans prévision'); + assert.equal(clampPanHours(-5, 48, 100), -5, 'futur autorisé avec prévision'); + assert.equal(clampPanHours(-100, 48, 100), -48, 'futur clampé à l\'horizon'); + }); + + test('forecastHorizonHours : 12 × Posologie borné [30 j ; 1 an]', () => { + assert.equal(forecastHorizonHours(7), 12 * 7 * 24 / 24 * 24); // 7 j × 12 = 84 j → non borné + assert.equal(forecastHorizonHours(7), 7 * 24 * 12); + assert.equal(forecastHorizonHours(1), 24 * 30, 'borné bas à 30 j'); + assert.equal(forecastHorizonHours(60), 24 * 365, 'borné haut à 1 an'); + assert.equal(forecastHorizonHours(null), null); + assert.equal(forecastHorizonHours(0), null); + }); + + test('forecastExtensionHours : au moins la prochaine dose (+12 h), sinon 1/3 de plage', () => { + assert.equal(forecastExtensionHours(24, 84 * 24, 48), 60, '48 + 12 = 60 h'); + assert.equal(forecastExtensionHours(720, 84 * 24, null), 240, '1/3 de la plage'); + assert.equal(forecastExtensionHours(24, 0, 48), 0, 'horizon 0 → rien'); + assert.equal(forecastExtensionHours(24, null, 48), 0, 'pas de prévision → rien'); + }); +}); + +describe('pointHoursBefore — fix #53 (delta « 6 h » qui comparait 24 h)', () => { + const curve = []; + for (let h = 0; h <= 24; h++) { + curve.push(new LevelPoint(h * HOUR_MS, h, 1)); + } + + test('le point le plus PROCHE de −6 h (par excès)', () => { + const p = pointHoursBefore(curve, 6); + assert.equal((curve[24].timestamp - p.timestamp) / HOUR_MS, 6); + }); + + test('retourne null si aucun point au-delà de N heures (courbe trop courte)', () => { + const short = [new LevelPoint(0, 1, 1), new LevelPoint(2 * HOUR_MS, 2, 1)]; + assert.equal(pointHoursBefore(short, 6), null); + assert.equal(pointHoursBefore([], 6), null); + }); +}); + +describe('defaultModelToggles — fix #58 (toggles alignés sur les traitements)', () => { + test('seuls les modèles utilisés sont ON (EV TFS + EEn WHSAH → ESE off)', () => { + const toggles = defaultModelToggles(new Set(['TFS', 'WHS'])); + assert.deepEqual(toggles, { ESE: false, TFS: true, WHS: true }); + }); + + test('garde-fou : aucun profil → TFS forcé ON (Bateman tracé dans chaque série)', () => { + assert.deepEqual(defaultModelToggles(new Set()), { ESE: false, TFS: true, WHS: false }); + }); + + test('un seul modèle utilisé → les autres restent off', () => { + assert.deepEqual(defaultModelToggles(new Set(['ESE'])), { ESE: true, TFS: false, WHS: false }); + }); +}); diff --git a/tests/helpers.js b/tests/helpers.js new file mode 100644 index 0000000..4fa30f5 --- /dev/null +++ b/tests/helpers.js @@ -0,0 +1,134 @@ +/** + * Helpers de test partagés (chargement de l'asset PK, fabriques de fixtures). + * + * Les tests tournent en Node pur (`node --test tests/`) — aucun DOM ni + * localStorage : la couche pure (pk/, backup) est testée directement, le + * store est testé avec un backend Map injectable. + */ + +import { readFileSync, existsSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { initWithJson } from '../js/pk/pk-profile-store.js'; + +/** Racine du dépôt web. */ +export const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); + +/** + * Dossier des données de test RÉELLES (exports Android de l'utilisatrice) — + * HORS dépôt (données de santé, cf docs/DEVELOPPEMENT.md §8). + * + * Depuis la séparation des dépôts (v1.4.10), ce dossier vit dans le dépôt + * ANDROID voisin (`../HormoneTrack/local-test-data/`) : le web ne duplique + * rien, il ne fait que le CONSOMMER en lecture (skip des tests s'il est + * absent — clone neuf, CI, autre machine). Candidats par ordre de priorité : + * 1. le dépôt Android voisin (layout standard ~/projects/HormoneTrack*) + * 2. un sibling direct `../local-test-data` (ancien layout repo unique) + * 3. un dossier local-test-data/ dans le dépôt web lui-même (si l'utilisatrice + * préfère y déposer ses exports — gitigné) + */ +export function localTestDataDir() { + const candidates = [ + join(WEB_ROOT, '..', 'HormoneTrack', 'local-test-data'), + join(WEB_ROOT, '..', 'local-test-data'), + join(WEB_ROOT, 'local-test-data'), + ]; + for (const dir of candidates) { + if (existsSync(dir)) return dir; + } + return null; +} + +/** Candidats de fichiers de backup réels pour les régressions data-driven. */ +export function localBackupPath(name) { + const dir = localTestDataDir(); + return dir ? join(dir, name) : null; +} + +/** + * Charge l'asset PK réel (le MÊME fichier que l'app Android embarque) et + * initialise PKProfileStore. À appeler dans les suites qui touchent aux + * profils ODS / au dispatch moteur. + */ +export function initProfiles() { + const json = readFileSync(join(WEB_ROOT, 'assets/pk_profiles.json'), 'utf8'); + initWithJson(json); + return JSON.parse(json); +} + +/** Contenu brut parsé de l'asset (sans initialiser le store). */ +export function rawProfiles() { + return JSON.parse(readFileSync(join(WEB_ROOT, 'assets/pk_profiles.json'), 'utf8')); +} + +/** Heure en ms. */ +export const HOUR_MS = 3600000; +/** Jour en ms. */ +export const DAY_MS = 86400000; + +/** + * Traitement injectable E2 par défaut (EEn TFS 5 mg) — valeurs des presets + * `preset_een_tfs`. id=0 = « pas encore en base » (insertion côté store). + */ +export function makeTreatment(overrides = {}) { + return { + id: 0, + name: 'EEn test', + type: 'ESTRADIOL', + route: 'INJECTION_SUBCUT', + doseAmount: 5, + doseUnit: 'mg', + isActive: true, + notes: null, + esterType: 'EEN', + pkModel: 'TFS', + absorptionHours: 156, + eliminationHalfLifeHours: 110, + bioavailabilityFraction: 1, + scaleFactor: 1.0, + forecastIntervalDays: null, + reminderHour: null, + reminderMinute: null, + reminderEnabled: false, + calendarEventId: null, + createdAt: 0, + ...overrides, + }; +} + +/** Dose simple rattachée au traitement par défaut (id 0 — celui de makeTreatment). */ +export function makeDose(timestamp, doseAmount = 5, overrides = {}) { + return { + id: 0, + treatmentId: 0, + timestamp, + doseAmount, + notes: null, + esterType: null, + ...overrides, + }; +} + +/** Lab simple. */ +export function makeLab(marker, value, unit, timestamp, overrides = {}) { + return { + id: 0, + marker, + value, + unit, + timestamp, + notes: null, + ...overrides, + }; +} + +/** assert proche (tolérance absolue ou relative) — les tests PK pin des valeurs d'article. */ +export function assertClose(actual, expected, tolerance, message = '') { + const diff = Math.abs(actual - expected); + const tol = typeof tolerance === 'number' && tolerance <= 1 && tolerance > 0 && expected !== 0 + ? Math.abs(expected) * tolerance + : tolerance; + if (diff > tol) { + throw new Error(`${message}\n attendu ≈ ${expected} (±${tol}) — obtenu ${actual}`); + } +} diff --git a/tests/misc.test.js b/tests/misc.test.js new file mode 100644 index 0000000..0b41635 --- /dev/null +++ b/tests/misc.test.js @@ -0,0 +1,214 @@ +/** + * Tests rappels (grille Posologie, miroir ReminderScheduleTest.kt — fix #52) + * + extrema (miroir ExtremaTest.kt) + changelog (miroir ChangelogHelperTest.kt) + * + HrtDuration (miroir HrtDurationTest.kt). + * + * ⚠️ Les tests de rappel sont indépendants du fuseau : ils construisent les + * heures attendues avec LES MÊMES primitives (Date locale) que le module + * testé, sur des dates hors changement d'heure. + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { nextReminderFireFor, nextReminderFireMs } from '../js/pk/pk-reminders.js'; +import { detectExtrema } from '../js/pk/pk-extrema.js'; +import { LevelPoint } from '../js/pk/pk-engine.js'; +import { isVersionNewer, sectionsSince } from '../js/util/changelog.js'; +import { HrtDuration } from '../js/util/format.js'; +import { makeTreatment, makeDose, DAY_MS, HOUR_MS, assertClose } from './helpers.js'; + +/** Met (h, m) sur le jour calendaire de [ms] (locale — miroir du module testé). */ +function atTimeSameDay(ms, h, m) { + const d = new Date(ms); + d.setHours(h, m, 0, 0); + return d.getTime(); +} + +describe('rappels — grille Posologie (fix #52)', () => { + test('dose il y a 2 j + intervalle 7 j → alarme dans 5 j à l\'heure choisie (PAS demain)', () => { + const now = Date.now(); + const tr = makeTreatment({ reminderEnabled: true, reminderHour: 18, reminderMinute: 0, forecastIntervalDays: 7 }); + const fire = nextReminderFireFor(tr, [makeDose(now - 2 * DAY_MS, 5)], now); + // prochaine occurrence de la grille : J+5 à 18:00 + const expected = atTimeSameDay(now + 5 * DAY_MS, 18, 0); + assert.equal(fire, expected, `attendu ${new Date(expected)}, obtenu ${new Date(fire)}`); + }); + + test('heure de rappel déjà passée le jour du créneau → créneau suivant', () => { + const now = Date.now(); + const hour = new Date(now).getHours(); + const tr = makeTreatment({ + reminderEnabled: true, reminderHour: hour, reminderMinute: 0, + forecastIntervalDays: 7, + }); + // créneau théorique (dernière dose + 7 j) = il y a 2 h → son HH:00 est passé + const dose = makeDose(now - 7 * DAY_MS - 2 * HOUR_MS, 5); + const slot = dose.timestamp + 7 * DAY_MS; + const fire = nextReminderFireFor(tr, [dose], now); + + assert.ok(fire > now, 'jamais dans le passé'); + const fired = new Date(fire); + assert.equal(fired.getHours(), hour, 'à l\'heure de rappel choisie'); + assert.equal(fired.getMinutes(), 0); + // fire est un créneau de la grille (k × 7 j après le créneau initial, + // ± le recadrage HH:mm — le snap à l'heure fait varier l'écart de < 24 h) + const k = Math.round((fire - slot) / (7 * DAY_MS)); + assert.ok(k >= 1, `créneau suivant (k=${k})`); + assert.ok(Math.abs(fire - slot - k * 7 * DAY_MS) < DAY_MS, 'aligné sur la grille (± recadrage horaire)'); + // le créneau SAUTÉ (k-1) était bien passé à l'heure de rappel + assert.ok(atTimeSameDay(slot + (k - 1) * 7 * DAY_MS, hour, 0) <= now); + }); + + test('fallback QUOTIDIEN sans Posologie (gel/oral) : prochaine occurrence HH:mm', () => { + const now = Date.now(); + const tr = makeTreatment({ reminderEnabled: true, reminderHour: 9, reminderMinute: 30, forecastIntervalDays: null }); + const fire = nextReminderFireFor(tr, [], now); + const todayAt = atTimeSameDay(now, 9, 30); + const expected = todayAt > now ? todayAt : todayAt + DAY_MS; + assert.equal(fire, expected); + }); + + test('créneau manqué sauté : jamais dans le passé (oubli d\'injection)', () => { + const now = Date.now(); + const tr = makeTreatment({ reminderEnabled: true, reminderHour: 18, reminderMinute: 0, forecastIntervalDays: 7 }); + // dernière dose il y a 10 jours : créneaux J-3 et J+4 → J+4 + const fire = nextReminderFireFor(tr, [makeDose(now - 10 * DAY_MS, 5)], now); + assert.ok(fire > now, 'jamais dans le passé'); + assert.equal(fire, atTimeSameDay(now + 4 * DAY_MS, 18, 0)); + }); + + test('agrégat multi-traitements : le plus tôt gagne', () => { + const now = Date.now(); + const a = makeTreatment({ + id: 1, reminderEnabled: true, reminderHour: 23, reminderMinute: 0, + forecastIntervalDays: 7, + }); + const b = makeTreatment({ + id: 2, reminderEnabled: true, reminderHour: 8, reminderMinute: 0, + forecastIntervalDays: 3, + }); + const next = nextReminderFireMs([a, b], [makeDose(now - 2 * DAY_MS, 1, { treatmentId: 1 }), makeDose(now - 2 * DAY_MS, 1, { treatmentId: 2 })], now); + assert.equal(next, Math.min( + nextReminderFireFor(a, [makeDose(now - 2 * DAY_MS, 1, { treatmentId: 1 })], now), + nextReminderFireFor(b, [makeDose(now - 2 * DAY_MS, 1, { treatmentId: 2 })], now), + )); + }); + + test('traitements inactifs / rappel désactivé / heure absente → null', () => { + const now = Date.now(); + assert.equal(nextReminderFireFor(makeTreatment({ reminderEnabled: false }), [], now), null); + assert.equal(nextReminderFireFor(makeTreatment({ reminderEnabled: true, isActive: false, reminderHour: 8, reminderMinute: 0 }), [], now), null); + assert.equal(nextReminderFireFor(makeTreatment({ reminderEnabled: true, reminderHour: null, reminderMinute: null }), [], now), null); + }); +}); + +describe('detectExtrema (pics/creux du graphique)', () => { + test('alternance stricte pic/creux en régime d\'équilibre (4 doses hebdo)', () => { + // Superposition de 4 doses hebdo EV TFS : oscillation ≥ 4 extrema + const points = []; + for (let h = 0; h <= 4 * 7 * 24; h += 6) { + let e2 = 0; + for (let k = 0; k < 4; k++) { + const dtH = h - k * 7 * 24; + if (dtH > 0) { + // profil EV TFS approx (pic 59 @ 51 h, t½ 3 j) — suffisant pour l'oscillation + const t = dtH / 24; + e2 += 5 * 59 * Math.exp(-(Math.abs(Math.log((t + 0.3) / 2.4)) ** 2) * 0.5); + } + } + points.push(new LevelPoint(h * HOUR_MS, e2, 0.5)); + } + const extrema = detectExtrema(points, (p) => p.e2, 2.0); + assert.ok(extrema.length >= 4, `≥ 4 extrema (obtenu ${extrema.length})`); + for (let i = 1; i < extrema.length; i++) { + assert.notEqual(extrema[i].isPeak, extrema[i - 1].isPeak, 'alternance stricte'); + } + // un pic est au-dessus de ses voisins (dans la liste alternée) + const peak = extrema.find((e) => e.isPeak); + const trough = extrema.find((e) => !e.isPeak); + assert.ok(peak.value > trough.value); + }); + + test('courbe monotone → vide ; série plate → vide ; trop courte → vide', () => { + const mono = [new LevelPoint(0, 1, 0), new LevelPoint(1, 2, 0), new LevelPoint(2, 3, 0), new LevelPoint(3, 4, 0)]; + assert.deepEqual(detectExtrema(mono, (p) => p.e2), []); + const flat = [new LevelPoint(0, 5, 0), new LevelPoint(1, 5, 0), new LevelPoint(2, 5, 0), new LevelPoint(3, 5, 0)]; + assert.deepEqual(detectExtrema(flat, (p) => p.e2), []); + assert.deepEqual(detectExtrema([new LevelPoint(0, 1, 0)], (p) => p.e2), []); + }); + + test('filtre d\'amplitude : une oscillation sous le seuil produit UN pivot', () => { + const tiny = [ + new LevelPoint(0, 100, 0), + new LevelPoint(1, 100.5, 0), // pic local < minSwing 2 + new LevelPoint(2, 100, 0), + new LevelPoint(3, 100.5, 0), + new LevelPoint(4, 100, 0), + ]; + const filtered = detectExtrema(tiny, (p) => p.e2, 2.0); + assert.ok(filtered.length <= 1, 'l\'alternance complète est filtrée'); + const kept = detectExtrema(tiny, (p) => p.e2, 0.1); + assert.ok(kept.length >= 2, 'avec un seuil bas, l\'alternance apparaît'); + }); +}); + +describe('ChangelogHelper (comparaison numérique — piège lexicographique #38)', () => { + test('1.2.9 < 1.2.10 (la comparaison de chaînes serait fausse)', () => { + assert.equal(isVersionNewer('1.2.10', '1.2.9'), true); + assert.equal(isVersionNewer('1.2.9', '1.2.10'), false); + assert.equal(isVersionNewer('1.4.2', '1.4.2'), false); + assert.equal(isVersionNewer('0.2.0', '0.1.9'), true); + assert.equal(isVersionNewer('1.0.0', null), true, 'première installation'); + assert.equal(isVersionNewer('1.0.0', ''), true); + assert.equal(isVersionNewer('1.1.0', 'bogus'), true, 'référence non parsable → true'); + assert.equal(isVersionNewer('v1.1.0', '1.0.9'), true, 'préfixe v toléré'); + }); + + test('sectionsSince : uniquement les versions plus récentes que la dernière vue', () => { + const md = [ + '## [0.3.0] — la plus récente', + 'Corrige le pan.', + '', + '## [0.2.0] — milieu', + 'Ajoute le zoom.', + '', + '## [0.1.0] — la plus ancienne', + 'Première version.', + ].join('\n'); + // vue 0.1.0 → 0.2.0 + 0.3.0 + const out = sectionsSince(md, '0.1.0', '0.3.0'); + assert.ok(out.includes('[0.3.0]') === false && out.includes('Version 0.3.0'), 'titre réécrit'); + assert.ok(out.includes('Corrige le pan.')); + assert.ok(out.includes('Ajoute le zoom.')); + assert.ok(!out.includes('Première version.'), 'version déjà vue exclue'); + // première installation → section courante seule + const first = sectionsSince(md, null, '0.3.0'); + assert.ok(first.includes('Corrige le pan.')); + assert.ok(!first.includes('Ajoute le zoom.')); + // rien de nouveau + assert.equal(sectionsSince(md, '0.3.0', '0.3.0'), ''); + }); +}); + +describe('HrtDuration (temps sous THS)', () => { + test('daysSince : 0 si futur/invalide, sinon jours entiers', () => { + assert.equal(HrtDuration.daysSince(10 * DAY_MS, 9 * DAY_MS), 0); + assert.equal(HrtDuration.daysSince(10 * DAY_MS, 10 * DAY_MS + 3 * DAY_MS + 12 * HOUR_MS), 3); + }); + + test('monthsAndDays : mois de 30 jours (estimation assumée)', () => { + assert.deepEqual(HrtDuration.monthsAndDays(95), [3, 5]); + assert.deepEqual(HrtDuration.monthsAndDays(0), [0, 0]); + }); + + test('daysAndHours : carte « prochaine dose » au-delà de 24 h (v1.4.1)', () => { + assert.deepEqual(HrtDuration.daysAndHours(122), [5, 2]); + assert.deepEqual(HrtDuration.daysAndHours(24), [1, 0]); + assert.deepEqual(HrtDuration.daysAndHours(5), [0, 5]); + }); + + test('assertClose helper (sanity)', () => { + assertClose(300, 295, 0.02); + assert.throws(() => assertClose(300, 100, 0.02)); + }); +}); diff --git a/tests/pk-engine.test.js b/tests/pk-engine.test.js new file mode 100644 index 0000000..8a2b0c2 --- /dev/null +++ b/tests/pk-engine.test.js @@ -0,0 +1,272 @@ +/** + * Tests du moteur PK (miroir de PharmacokineticEngineTest.kt) : + * Bateman (bisection — attrape la bisection inversée #19), superposition, + * dispatch des 3 modèles, ester override par dose, modèle T monotone/borné, + * prévision (créneaux passés sautés #35), coupures. + */ + +import { test, describe, before } from 'node:test'; +import assert from 'node:assert/strict'; +import { + initProfiles, makeTreatment, makeDose, assertClose, HOUR_MS, DAY_MS, +} from './helpers.js'; +import { + computeKa, batemanParams, concentrationOfDose, e2At, testosteroneAt, + convertTToNgMl, computeCurve, generateForecastDoses, activeEsterAt, + levelAt, currentLevel, doseEster, cutoffHours, Esters, PKModels, TConfig, +} from '../js/pk/index.js'; +import * as TFS from '../js/pk/transfem-science-models.js'; + +describe('computeKa (bisection Bateman)', () => { + test('pic Bateman ≈ Tmax (attrape la bisection inversée, bug #19)', () => { + const treatment = makeTreatment({ + route: 'TRANSDERMAL_GEL', esterType: 'NONE', + absorptionHours: 8, eliminationHalfLifeHours: 24, bioavailabilityFraction: 0.9, + }); + const p = batemanParams(treatment); + // Le max de C(dt) doit tomber à ~8 h, pas à ~0 h + let peakH = 0; + let peakV = 0; + for (let h = 0; h <= 48; h += 0.1) { + const dose = makeDose(0, 1); + const c = concentrationOfDose(treatment, dose, h * HOUR_MS, p); + if (c > peakV) { + peakV = c; + peakH = h; + } + } + assert.ok(Math.abs(peakH - 8) < 0.6, `pic Bateman = ${peakH.toFixed(1)} h, attendu ≈ 8 h`); + }); + + test('ke = ln2/t½', () => { + const p = batemanParams(makeTreatment({ absorptionHours: 8, eliminationHalfLifeHours: 24 })); + assertClose(p.ke, Math.log(2) / 24, 1e-9); + }); + + test('cas dégénéré ka≈ke : pas de division par ~0, courbe en cloche', () => { + const t1 = makeTreatment({ route: 'ORAL', esterType: 'NONE', absorptionHours: 10, eliminationHalfLifeHours: 10.0005, bioavailabilityFraction: 0.5 }); + const c1 = concentrationOfDose(t1, makeDose(0, 2), 5 * HOUR_MS); + const c2 = concentrationOfDose(t1, makeDose(0, 2), 10 * HOUR_MS); + assert.ok(c1 > 0 && c2 > 0, 'valeurs finies positives'); + assert.ok(Number.isFinite(c1) && Number.isFinite(c2)); + }); +}); + +describe('dispatch concentrationOfDose (3 modèles superposables)', () => { + before(() => initProfiles()); + + test('TFS : EV 5 mg → pic ≈ 5×59 pg/mL (via le MOTEUR, pas directement TFS)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS', doseAmount: 5 }); + let peakV = 0; + for (let h = 0; h <= 14 * 24; h += 0.5) { + const c = concentrationOfDose(tr, makeDose(0, 5), h * HOUR_MS); + if (c > peakV) peakV = c; + } + assertClose(peakV, 295, 0.05, 'pic EV TFS via moteur'); + }); + + test('ESE : EV 5 mg → pic ≈ 5×61,1 pg/mL (tables ODS)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'ESE', doseAmount: 5 }); + let peakV = 0; + for (let h = 0; h <= 14 * 24; h += 0.5) { + const c = concentrationOfDose(tr, makeDose(0, 5), h * HOUR_MS); + if (c > peakV) peakV = c; + } + assertClose(peakV, 5 * 61.12, 0.5, 'pic EV ESE via moteur'); + }); + + test('modelOverride : un traitement ESE force en TFS donne le pic TFS (et réciproquement)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'ESE', doseAmount: 5 }); + const cTfs = concentrationOfDose(tr, makeDose(0, 5), 51 * HOUR_MS, null, 'TFS'); + const cEse = concentrationOfDose(tr, makeDose(0, 5), 51 * HOUR_MS); + assert.ok(Math.abs(cTfs - cEse) > 1, `override doit changer la valeur (${cEse} → ${cTfs})`); + // TFS à 51 h ≈ autour du pic 2,1 j ≈ 295 ; contrôle de cohérence directe + assertClose(cTfs, TFS.sample('EV', 51) * 5, 1e-9); + }); + + test('WHS dispatch (v1.4.6) : coupure 10 × t½, PEP → 0', () => { + const tr = makeTreatment({ esterType: 'EEN', pkModel: 'WHS', doseAmount: 5 }); + const c = concentrationOfDose(tr, makeDose(0, 5), 5 * DAY_MS); + assertClose(c, 188, 2, 'pic EEn WHSAH via moteur ~5 j'); + // coupure : t½ EEn WHS 7,34 j → 10 t½ = 73,4 j ; à 80 j → 0 (cutoff) + const cut = cutoffHours(tr); + assertClose(cut, 7.34 * 24 * 10, 0.5, 'cutoff WHSAH = 10 t½'); + const trPep = makeTreatment({ esterType: 'PEP', pkModel: 'WHS', doseAmount: 5 }); + assert.equal(concentrationOfDose(trPep, makeDose(0, 5), 10 * DAY_MS), 0); + }); + + test('anti-androgène → 0 en E2 (type != ESTRADIOL ignoré par e2At)', () => { + const tr = makeTreatment({ type: 'ANTI_ANDROGEN', route: 'ORAL', esterType: 'NONE' }); + assert.equal(e2At([tr], [makeDose(0, 10)], 12 * HOUR_MS), 0); + }); + + test('superposition de deux doses + linéarité du scaleFactor', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS', doseAmount: 5, scaleFactor: 1.0 }); + const t = 100 * HOUR_MS; + const one = e2At([tr], [makeDose(0, 5)], t); + const two = e2At([tr], [makeDose(0, 5), makeDose(HOUR_MS, 5)], t + HOUR_MS); + assert.ok(two > one, 'deux doses > une dose'); + + const trScaled = { ...tr, scaleFactor: 0.72 }; + assertClose(e2At([trScaled], [makeDose(0, 5)], t), one * 0.72, 1e-9, 'scaleFactor linéaire'); + }); + + test('override d\'ESTER par dose (EV≫EU à 45 h)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS' }); + const doseEv = makeDose(0, 1, { esterType: 'EV' }); + const doseEu = makeDose(0, 1, { esterType: 'EU' }); + const cEv = concentrationOfDose(tr, doseEv, 45 * HOUR_MS); + const cEu = concentrationOfDose(tr, doseEu, 45 * HOUR_MS); + assert.ok(cEv > cEu * 4, `EV à 45 h (${cEv}) doit ≫ EU (${cEu})`); + assert.equal(doseEster(tr, { ...doseEu, esterType: null }), 'EV', 'null = ester du traitement'); + }); + + test('dt ≤ 0 (dose future) et dose ≤ 0 → 0', () => { + const tr = makeTreatment(); + assert.equal(concentrationOfDose(tr, makeDose(Date.now() + HOUR_MS, 5), Date.now()), 0); + assert.equal(concentrationOfDose(tr, makeDose(0, 0), 5 * HOUR_MS), 0); + }); +}); + +describe('modèle T empirique', () => { + const cfg = new TConfig(6.0, 0.2, 0.19); + + test('monotone décroissante en E2, bornée (floor, base)', () => { + let prev = testosteroneAt(0, cfg); + assert.equal(prev, 6.0, 'T(0) = base'); + for (let e2 = 10; e2 <= 2000; e2 += 10) { + const t = testosteroneAt(e2, cfg); + assert.ok(t < prev, `décroissance attendue à E2=${e2}`); + assert.ok(t > cfg.floor, 'au-dessus du plancher'); + prev = t; + } + // T ≈ 0,4 à E2 ≈ 150 (valeur de référence de la doc) + assertClose(testosteroneAt(150, cfg), 0.42, 0.08, 'T à E2=150'); + }); + + test('convertTToNgMl : ng/dL ÷100, ng/L ÷1000, nmol/L ×0,2884, pg/mL défensif ÷1000 (bugs #23/#26)', () => { + assertClose(convertTToNgMl(44, 'ng/dL'), 0.44, 1e-9); + assertClose(convertTToNgMl(0.44, 'ng/mL'), 0.44, 1e-9); + assertClose(convertTToNgMl(440, 'ng/L'), 0.44, 1e-9); + assertClose(convertTToNgMl(1.5, 'nmol/L'), 0.4326, 1e-9); + assertClose(convertTToNgMl(38, 'pg/mL'), 0.038, 1e-9, 'lab aberrant neutralisé'); + assertClose(convertTToNgMl(0.44, 'NG/ML'), 0.44, 1e-9, 'insensible à la casse'); + }); + + test('activeEsterAt : la dernière dose ≤ t définit l\'ester actif', () => { + const trEv = makeTreatment({ id: 1, esterType: 'EV' }); + const trEen = makeTreatment({ id: 2, esterType: 'EEN' }); + const doses = [ + makeDose(0, 2, { id: 10, treatmentId: 1 }), + makeDose(30 * DAY_MS, 5, { id: 11, treatmentId: 2 }), + ]; + assert.equal(activeEsterAt([trEv, trEen], doses, 10 * DAY_MS), 'EV'); + assert.equal(activeEsterAt([trEv, trEen], doses, 31 * DAY_MS), 'EEN'); + assert.equal(activeEsterAt([trEv, trEen], doses, 0), 'EV', 'la dose à t=0 compte (≤ t)'); + assert.equal(activeEsterAt([trEv, trEen], doses, -1000), null, 'avant la 1ʳᵉ dose'); + }); + + test('levelAt : k de l\'ester actif appliqué au modèle T', () => { + const tr = makeTreatment({ esterType: 'EEN' }); + const doses = [makeDose(0, 5)]; + const tMs = 5 * DAY_MS; + const base = levelAt([tr], doses, tMs, new TConfig(6, 0.2, 0.19), { EEN: 0.05 }); + const withDefaultK = levelAt([tr], doses, tMs, new TConfig(6, 0.2, 0.19), null); + assert.ok(base.t > withDefaultK.t, 'k plus petit → T plus haute'); + }); +}); + +describe('computeCurve', () => { + before(() => initProfiles()); + + test('grille horaire clampée à la 1ʳᵉ dose (rien avant)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS' }); + const start = 10 * DAY_MS; + const firstDose = 20 * DAY_MS; + const curve = computeCurve([tr], [makeDose(firstDose, 5)], start, 30 * DAY_MS, HOUR_MS, new TConfig()); + assert.equal(curve.length, (30 - 20) * 24 + 1, 'commence à la 1ʳᵉ dose'); + assert.ok(curve.every((p) => p.timestamp >= firstDose)); + }); + + test('vide sans doses / sans traitements / fenêtre inversée', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS' }); + assert.deepEqual(computeCurve([tr], [], 0, DAY_MS, HOUR_MS, new TConfig()), []); + assert.deepEqual(computeCurve([], [makeDose(0)], 0, DAY_MS, HOUR_MS, new TConfig()), []); + assert.deepEqual(computeCurve([tr], [makeDose(0)], DAY_MS, 0, HOUR_MS, new TConfig()), []); + }); + + test('inactivation d\'un traitement : la simulation RESTE (bug v1.2.4, régression #3)', () => { + const trInactive = makeTreatment({ isActive: false, esterType: 'EV', pkModel: 'TFS' }); + const curve = computeCurve([trInactive], [makeDose(0, 5)], 0, 5 * DAY_MS, HOUR_MS, new TConfig()); + assert.ok(curve.length > 0); + assert.ok(Math.max(...curve.map((p) => p.e2)) > 50, 'EV inactif doit être simulé'); + }); + + test('les 3 modèles donnent 3 courbes distinctes (seuil 2 %)', () => { + const tr = makeTreatment({ esterType: 'EV', pkModel: 'TFS' }); + const at = 2 * DAY_MS; + const cfg = new TConfig(); + const ese = computeCurve([tr], [makeDose(0, 5)], 0, at, DAY_MS / 4, cfg, { modelOverride: 'ESE' }); + const tfs = computeCurve([tr], [makeDose(0, 5)], 0, at, DAY_MS / 4, cfg, { modelOverride: 'TFS' }); + const whs = computeCurve([tr], [makeDose(0, 5)], 0, at, DAY_MS / 4, cfg, { modelOverride: 'WHS' }); + const e2Of = (c) => c[Math.floor(c.length / 2)].e2; + assert.ok(Math.abs(e2Of(ese) - e2Of(tfs)) / e2Of(tfs) > 0.02, 'ESE ≠ TFS'); + assert.ok(Math.abs(e2Of(whs) - e2Of(tfs)) / e2Of(tfs) > 0.02, 'WHS ≠ TFS'); + }); +}); + +describe('generateForecastDoses (prévision)', () => { + test('grille exacte depuis la dernière dose, strictement future', () => { + const now = 100 * DAY_MS; + const tr = makeTreatment({ forecastIntervalDays: 7 }); + const doses = [makeDose(now - 10 * DAY_MS, 5)]; + const forecast = generateForecastDoses(tr, doses, now + 30 * DAY_MS, now); + // Dernière dose J-10 → créneaux J-3 (passé, sauté), J+4, J+11, J+18, J+25 + assert.deepEqual( + forecast.map((d) => Math.round((d.timestamp - now) / DAY_MS)), + [4, 11, 18, 25], + 'créneaux passés sautés (bug #35), rythme exact 7 j', + ); + }); + + test('vide sans intervalle / intervalle ≤ 0 / sans doses', () => { + const now = 100 * DAY_MS; + assert.deepEqual(generateForecastDoses(makeTreatment({ forecastIntervalDays: null }), [makeDose(now - 1)], now + 7 * DAY_MS, now), []); + assert.deepEqual(generateForecastDoses(makeTreatment({ forecastIntervalDays: 0 }), [makeDose(now - 1)], now + 7 * DAY_MS, now), []); + assert.deepEqual(generateForecastDoses(makeTreatment({ forecastIntervalDays: 7 }), [], now + 7 * DAY_MS, now), []); + }); + + test('l\'override d\'ester de la DERNIÈRE injection est projeté', () => { + const now = 100 * DAY_MS; + const tr = makeTreatment({ forecastIntervalDays: 7, esterType: 'EEN' }); + const doses = [makeDose(now - 1 * DAY_MS, 5, { esterType: 'EV' })]; + const forecast = generateForecastDoses(tr, doses, now + 14 * DAY_MS, now); + assert.equal(forecast.length, 2, 'créneaux J+6 et J+13'); + assert.equal(forecast[0].esterType, 'EV', 'la projection suit la dernière injection réelle'); + assert.equal(forecast[0].doseAmount, 5, 'dose standard du traitement'); + }); + + test('un RETARD décale toute la prévision (comportement voulu)', () => { + const now = 100 * DAY_MS; + const tr = makeTreatment({ forecastIntervalDays: 7 }); + // dernière prise il y a 9 jours (2 jours de retard) + // créneaux théoriques : J-2 (passé, SAUTÉ) puis J+5, J+12… + const forecast = generateForecastDoses(tr, [makeDose(now - 9 * DAY_MS, 5)], now + 10 * DAY_MS, now); + assert.deepEqual(forecast.map((d) => Math.round((d.timestamp - now) / DAY_MS)), [5]); + }); +}); + +describe('currentLevel', () => { + before(() => initProfiles()); + + test('niveau combiné cohérent avec computeCurve (même calibration)', () => { + const tr = makeTreatment({ esterType: 'EEN', pkModel: 'TFS' }); + const now = 6 * DAY_MS; + // Grille 1 h sur [now−1 h ; now] → le dernier point est exactement « now » + const curve = computeCurve([tr], [makeDose(0, 5)], now - HOUR_MS, now, HOUR_MS, new TConfig()); + const lvl = currentLevel([tr], [makeDose(0, 5)], new TConfig(), null, now); + assert.ok(curve.length >= 1); + assertClose(lvl.e2, curve[curve.length - 1].e2, 1e-9); + assertClose(lvl.t, curve[curve.length - 1].t, 1e-9); + }); +}); diff --git a/tests/pk-profiles.test.js b/tests/pk-profiles.test.js new file mode 100644 index 0000000..1aa9376 --- /dev/null +++ b/tests/pk-profiles.test.js @@ -0,0 +1,100 @@ +/** + * Tests de fidélité de l'asset pk_profiles.json + échantillonnage + * (miroir de PKProfileStoreTest.kt côté Android). + * + * ⚠️ L'asset web est une COPIE de l'asset Android : ces tests garantissent + * que les deux apps lisent EXACTEMENT les mêmes tables (toute divergence = + * courbes différentes entre Android et web). + */ + +import { test, describe, before } from 'node:test'; +import assert from 'node:assert/strict'; +import { initProfiles, rawProfiles } from './helpers.js'; +import * as PKProfileStore from '../js/pk/pk-profile-store.js'; + +describe('pk_profiles.json (asset partagé avec Android)', () => { + before(() => initProfiles()); + + test('6 profils présents × 8001 points', () => { + const raw = rawProfiles(); + const expected = ['EV_ese', 'EU_ese', 'EEn_ese', 'EV_tfs', 'EU_tfs', 'EEn_tfs']; + for (const key of expected) { + assert.ok(raw.profiles[key], `profil manquant : ${key}`); + assert.equal(raw.profiles[key].length, 8001, `${key} doit avoir 8001 points`); + } + }); + + test('pics == valeurs ODS de référence (pg/mL par mg)', () => { + // Mêmes valeurs épinglées côté Android (doc §7.1 / PKProfileStoreTest) + const refs = [ + ['EV', 'ESE', 61.12], + ['EU', 'ESE', 3.44], + ['EEn', 'ESE', 31.35], + ['EV', 'TFS', 58.96], + ['EU', 'TFS', 10.11], + ['EEn', 'TFS', 31.97], + ]; + for (const [ester, mod, peak] of refs) { + let max = 0; + for (let h = 0; h <= 8000; h++) { + const v = PKProfileStore.sample(ester, mod, h); + if (v > max) max = v; + } + assert.ok(Math.abs(max - peak) < 0.02, `pic ${ester}_${mod} = ${max}, attendu ${peak}`); + } + }); + + test('EV_ese : pic à ~45 h', () => { + let peakH = 0; + let peakV = 0; + for (let h = 0; h <= 500; h++) { + const v = PKProfileStore.sample('EV', 'ESE', h); + if (v > peakV) { + peakV = v; + peakH = h; + } + } + assert.ok(peakH >= 40 && peakH <= 50, `Tmax EV_ese = ${peakH} h, attendu ~45 h`); + }); + + test('zéro avant injection / à dt=0', () => { + assert.equal(PKProfileStore.sample('EV', 'ESE', 0), 0); + assert.equal(PKProfileStore.sample('EV', 'ESE', -5), 0); + }); + + test('modèle STRICT : modèle inconnu → 0 (jamais de fallback silencieux, bug §14 #21)', () => { + assert.equal(PKProfileStore.sample('EV', 'WHS', 100), 0); // WHS n'a pas de tables ODS + assert.equal(PKProfileStore.sample('EV', 'XXX', 100), 0); + }); + + test('lookup INSENSIBLE À LA CASSE (bug #22 — courbes EEn vides)', () => { + // Esters.EEN = "EEN" (constante app) vs "EEn" (casse biologique de l'asset) + assert.equal(PKProfileStore.sample('EEN', 'ESE', 152), PKProfileStore.sample('EEn', 'ESE', 152)); + assert.ok(PKProfileStore.sample('EEN', 'ESE', 152) > 1); + }); + + test('extrapolation terminale : au-delà de la table, décroissante (EV) et jamais croissante (EU)', () => { + // La table fait 8001 h : l'extrapolation ne s'applique qu'AU-DELÀ. + // EV (t½ ~4 j ODS) : décroissance exponentielle mesurable après 8000 h. + const ev8500 = PKProfileStore.sample('EV', 'ESE', 8500); + const ev9000 = PKProfileStore.sample('EV', 'ESE', 9000); + assert.ok(ev8500 > 0, 'queue extrapolée mesurable'); + assert.ok(ev9000 < ev8500, `décroissance attendue (${ev8500} → ${ev9000})`); + // EU (ester ultra-lent, tail plate au plancher 0,04) : taux log ≈ 0 → + // plateau constant — en tout cas JAMAIS croissante (bug #20 : extrapoler + // depuis le plancher 0,01/0,00 donnait 0 à vie ou une constante plate). + const eu8000 = PKProfileStore.sample('EU', 'ESE', 8000); + const eu8500 = PKProfileStore.sample('EU', 'ESE', 8500); + assert.ok(eu8000 > 0, 'EU mesurable au-delà de la table'); + assert.ok(eu8500 <= eu8000, 'jamais croissante'); + }); + + test('interpolation linéaire entre points entiers', () => { + // Le plateau 45/46 h d'EV_ese est plat (piège du test Android) — prendre + // une heure en montée : la valeur à 100.5 h = moyenne de 100 et 101 + const raw = rawProfiles(); + const arr = raw.profiles.EV_ese; + const expected = (arr[100] + arr[101]) / 2; + assert.ok(Math.abs(PKProfileStore.sample('EV', 'ESE', 100.5) - expected) < 1e-9); + }); +}); diff --git a/tests/store.test.js b/tests/store.test.js new file mode 100644 index 0000000..9fb579b --- /dev/null +++ b/tests/store.test.js @@ -0,0 +1,96 @@ +/** + * Tests du store (localStorage) avec backend Map injectable : CRUD, IDs + * auto-incrémentés, CASCADE, pub/sub, réglages fusionnés. + * (Le module est identique en production — seul le backend change.) + */ + +import { test, describe, beforeEach } from 'node:test'; +import assert from 'node:assert/strict'; +import { store } from '../js/data/store.js'; +import { makeTreatment, makeDose, makeLab } from './helpers.js'; + +describe('store (localStorage → backend Map en test)', () => { + beforeEach(() => { + store.setBackend(new Map()); + }); + + test('upsert : id 0 = insertion (id auto), id > 0 = mise à jour', () => { + const id = store.upsertTreatment(makeTreatment({ id: 0, name: 'A' })); + assert.equal(id, 1); + store.upsertTreatment(makeTreatment({ id, name: 'A modifié' })); + assert.equal(store.getTreatments().length, 1); + assert.equal(store.getTreatments()[0].name, 'A modifié'); + const id2 = store.upsertTreatment(makeTreatment({ id: 0, name: 'B' })); + assert.equal(id2, 2, 'auto-increment'); + }); + + test('IDs auto-incrémentés par table (miroir Room autoGenerate)', () => { + const d1 = store.upsertDoseLog(makeDose(0, 1)); + const d2 = store.upsertDoseLog(makeDose(1, 1)); + const l1 = store.upsertLabResult(makeLab('E2', 100, 'pg/mL', 0)); + assert.equal(d1, 1); + assert.equal(d2, 2); + assert.equal(l1, 1, 'compteurs indépendants par table'); + }); + + test('deleteTreatment : CASCADE sur ses doses (miroir Room FK)', () => { + const trId = store.upsertTreatment(makeTreatment({})); + store.upsertDoseLog(makeDose(0, 1, { treatmentId: trId })); + store.upsertDoseLog(makeDose(1, 1, { treatmentId: trId })); + const other = store.upsertTreatment(makeTreatment({ name: 'autre' })); + store.upsertDoseLog(makeDose(2, 1, { treatmentId: other })); + store.deleteTreatment(trId); + assert.equal(store.getTreatments().length, 1); + assert.equal(store.getDoseLogs().length, 1, 'les doses du traitement supprimé sont parties'); + assert.equal(store.getDoseLogs()[0].treatmentId, other); + }); + + test('pub/sub : les écrans sont notifiés à chaque mutation', () => { + let calls = 0; + const unsub = store.subscribe(() => calls++); + store.upsertTreatment(makeTreatment({})); + store.upsertDoseLog(makeDose(0, 1)); + store.deleteDoseLog(1); + assert.equal(calls, 3); + unsub(); + store.upsertLabResult(makeLab('T', 0.4, 'ng/mL', 0)); + assert.equal(calls, 3, 'désabonné'); + }); + + test('un listener qui plante ne bloque pas les autres ni la donnée', () => { + let ok = 0; + store.subscribe(() => { + throw new Error('listener cassé'); + }); + store.subscribe(() => ok++); + store.upsertTreatment(makeTreatment({})); + assert.equal(ok, 1, 'le second listener a bien tourné'); + assert.equal(store.getTreatments().length, 1, 'la donnée est écrite'); + }); + + test('setSettings fusionne partiellement (pas d\'écrasement des autres clés)', () => { + store.setSettings({ language: 'fr' }); + store.setSettings({ autoCalibrate: true }); + const s = store.getSettings(); + assert.equal(s.language, 'fr', 'la langue est conservée'); + assert.equal(s.autoCalibrate, true); + assert.equal(s.alertE2High, null, 'défaut opt-in'); + }); + + test('donnée corrompue dans le storage → valeurs par défaut (jamais de crash au boot)', () => { + store.setBackend(new Map([['hormonetrack.treatments', '}}}pas du json']])); + assert.deepEqual(store.getTreatments(), []); + store.setBackend(new Map([['hormonetrack.tConfig', '{base:']])); + assert.deepEqual(store.getTConfig(), { base: 6.0, floor: 0.2, k: 0.19 }, 'défauts TConfig.kt'); + }); + + test('clearAll remet tout à zéro', () => { + store.upsertTreatment(makeTreatment({})); + store.upsertLabResult(makeLab('E2', 1, 'pg/mL', 0)); + store.setTConfig({ base: 5, floor: 0.1, k: 0.2 }); + store.clearAll(); + assert.deepEqual(store.getTreatments(), []); + assert.deepEqual(store.getLabResults(), []); + assert.deepEqual(store.getTConfig(), { base: 6.0, floor: 0.2, k: 0.19 }); + }); +}); diff --git a/tests/tfs-models.test.js b/tests/tfs-models.test.js new file mode 100644 index 0000000..9de2fb9 --- /dev/null +++ b/tests/tfs-models.test.js @@ -0,0 +1,116 @@ +/** + * Fidélité du modèle TFS V3C à l'article Transfem Science + * (miroir de TransfemScienceModelsTest.kt — toute retouche de + * transfem-science-models.js passe par CE test). + * + * Valeurs épinglées = Tableaux 9–10 + Figure 11 de + * transfemscience.org/articles/injectable-e2-meta-analysis/ : + * EV 295 pg/mL @ 2,1 j · EEn 160 @ 6,5 j · EB 971 @ 0,65 j · + * EC 155 @ 4,3 j · ECS 241 @ 1,2 j · PEP ~34 @ 32,5 mg @ ~18 j + * Équilibre EV 5 mg/7 j : Cmax 384 / Cmin 142 / Cavg 269. + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { assertClose, DAY_MS, HOUR_MS } from './helpers.js'; +import * as TFS from '../js/pk/transfem-science-models.js'; + +/** Cherche le pic d'un ester (mg) sur une fenêtre de jours. */ +function findPeak(ester, doseMg, maxDays) { + let best = { v: 0, tDays: 0 }; + for (let h = 0; h <= maxDays * 24; h += 0.25) { + const v = TFS.sample(ester, h) * doseMg; + if (v > best.v) best = { v, tDays: h / 24 }; + } + return best; +} + +describe('TransfemScienceModels (V3C, méta-analyse)', () => { + test('pics / Tmax de l\'article (±2 %)', () => { + const ev = findPeak('EV', 1, 15); + assertClose(ev.v, 59.0, 0.03, 'pic EV par mg'); // 295/5 + assertClose(ev.tDays, 2.1, 0.25, 'Tmax EV'); + + const een = findPeak('EEN', 1, 20); + assertClose(een.v, 32.0, 0.03, 'pic EEn par mg'); // 160/5 + assertClose(een.tDays, 6.5, 0.5, 'Tmax EEn'); + + const eb = findPeak('EB', 1, 5); + assertClose(eb.v, 194.2, 0.03, 'pic EB par mg'); // 971/5 + assertClose(eb.tDays, 0.65, 0.2, 'Tmax EB'); + + const ec = findPeak('EC', 1, 20); + assertClose(ec.v, 31.0, 0.03, 'pic EC par mg'); // 155/5 + assertClose(ec.tDays, 4.3, 0.5, 'Tmax EC'); + + const ecs = findPeak('ECS', 1, 10); + assertClose(ecs.v, 48.2, 0.03, 'pic ECS par mg'); // 241/5 + assertClose(ecs.tDays, 1.2, 0.3, 'Tmax ECS'); + }); + + test('demi-vies terminales (t½ = ln2/min(k1,k2,k3))', () => { + assertClose(TFS.model('EV').terminalHalfLifeDays, 3.0, 0.05); + assertClose(TFS.model('EEN').terminalHalfLifeDays, 4.53, 0.05); + assertClose(TFS.model('EB').terminalHalfLifeDays, 1.18, 0.05); + assertClose(TFS.model('EC').terminalHalfLifeDays, 6.7, 0.1); + assertClose(TFS.model('ECS').terminalHalfLifeDays, 5.1, 0.1); + assertClose(TFS.model('PEP').terminalHalfLifeDays, 28.4, 0.3); + }); + + test('PEP : pic faible par mg mais dose ~6,5× → 32,5 mg ≈ 34 pg/mL @ ~18 j', () => { + const peak = findPeak('PEP', 32.5, 60); + assertClose(peak.v, 34, 0.06, 'pic PEP à 32,5 mg'); + assertClose(peak.tDays, 18, 2, 'Tmax PEP'); + // PEP est présent dans choicesForModel('TFS') mais l'est UNIQUEMENT là + assert.ok(TFS.hasModel('PEP')); + }); + + test('linéarité par mg : 2× dose = 2× niveau (aux pics comme en queue)', () => { + const at50h = TFS.sample('EEN', 50); + assertClose(at50h * 2, TFS.sample('EEN', 50) * 2, 1e-12); // trivial par construction + // plus utile : 5 mg à 50 h == 5 × (1 mg à 50 h) + assertClose(TFS.sample('EEN', 50) * 5, TFS.sample('EEN', 50) * 5, 1e-12); + // contrôle réel de linéarité du dispatch dans le moteur (doseAmount) + }); + + test('état d\'équilibre EV 5 mg/7 j ≈ Figure 11 (Cmax 384 / Cmin 142 / Cavg 269)', () => { + // Superposition de 8 doses hebdomadaires (au-delà : contribution négligeable) + // → régime d'équilibre sur le dernier cycle + const doses = []; + for (let k = 8; k >= 1; k--) doses.push((8 - k) * 7 * DAY_MS); + const cycleStart = doses[doses.length - 1]; // dernière dose + let cmax = 0; + let cmin = Infinity; + let sum = 0; + let n = 0; + for (let h = 0; h <= 7 * 24; h += 1) { + const t = cycleStart + h * HOUR_MS; + let level = 0; + for (const d of doses) { + const dtH = (t - d) / 3600000; + if (dtH > 0) level += TFS.sample('EV', dtH) * 5; + } + cmax = Math.max(cmax, level); + cmin = Math.min(cmin, level); + sum += level; + n++; + } + assertClose(cmax, 384, 0.04, 'Cmax équilibre EV 5/7'); + assertClose(cmin, 142, 0.06, 'Cmin équilibre EV 5/7'); + assertClose(sum / n, 269, 0.04, 'Cavg équilibre EV 5/7'); + }); + + test('esters sans modèle → 0 ; dt ≤ 0 → 0', () => { + assert.equal(TFS.sample('XXX', 50), 0); + assert.equal(TFS.sample('EV', 0), 0); + assert.equal(TFS.sample('EV', -1), 0); + }); + + test('compartiment ultra-rapide (EU k2 ~4,8×10⁶ j⁻¹) : double précision suffisante', () => { + // EU : le terme k2 décroît instantanément — le pic doit rester ~10 pg/mL/mg, + // Tmax ~198 h (≈ 8,25 j — valeur du README, t½ terminale ~22 j) + const eu = findPeak('EU', 1, 60); + assertClose(eu.v, 10.1, 0.05, 'pic EU par mg'); + assertClose(eu.tDays, 8.25, 0.5, `Tmax EU ≈ 198 h, obtenu ${eu.tDays} j`); + }); +}); diff --git a/tests/whsah-models.test.js b/tests/whsah-models.test.js new file mode 100644 index 0000000..4620fa6 --- /dev/null +++ b/tests/whsah-models.test.js @@ -0,0 +1,65 @@ +/** + * Fidélité du modèle WHSAH au comportement de Mona + * (miroir de WhsahModelsTest.kt — fit « license-free » du WHSAH Collective). + * + * Valeurs épinglées (±2 %) : EV Cmax 367,5 @ 1,69 j · EEn 187,9 @ 4,99 j · + * t½ EEn 7,34 j · PEP NON COUVERT · linéarité par mg. + */ + +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { assertClose } from './helpers.js'; +import * as WHS from '../js/pk/whsah-models.js'; + +function findPeak(ester, doseMg, maxDays) { + let best = { v: 0, tDays: 0 }; + for (let h = 0; h <= maxDays * 24; h += 0.25) { + const v = WHS.sample(ester, h) * doseMg; + if (v > best.v) best = { v, tDays: h / 24 }; + } + return best; +} + +describe('WhsahModels (fit Mona, license-free)', () => { + test('pics Mona : EV 367,5 @ 1,69 j (5 mg) · EEn 187,9 @ 4,99 j (5 mg)', () => { + const ev = findPeak('EV', 5, 10); + assertClose(ev.v, 367.5, 0.02, 'pic EV WHSAH 5 mg'); + assertClose(ev.tDays, 1.69, 0.1, 'Tmax EV WHSAH'); + + const een = findPeak('EEN', 5, 20); + assertClose(een.v, 187.9, 0.02, 'pic EEn WHSAH 5 mg'); + assertClose(een.tDays, 4.99, 0.15, 'Tmax EEn WHSAH'); + }); + + test('t½ terminale EEn ≈ 7,34 j (plus longue que TFS 4,5 j)', () => { + assertClose(WHS.model('EEN').terminalHalfLifeDays, 7.34, 0.05); + }); + + test('EEn à J+1 ~3× plus haut que TFS (70 vs 22 pg/mL) — la « montée rapide » WHSAH', () => { + // Sémantique épinglée côté Android (différence visible entre les apps) + const eenJ1 = WHS.sample('EEN', 24) * 5; + assertClose(eenJ1, 70, 0.15, 'EEn WHSAH à J+1 (5 mg)'); + }); + + test('PEP non couvert → 0 (retiré du fit WHSAH)', () => { + assert.equal(WHS.sample('PEP', 100), 0); + assert.equal(WHS.hasModel('PEP'), false); + }); + + test('linéarité par mg', () => { + const a = WHS.sample('EV', 40); + assertClose(a * 3, WHS.sample('EV', 40) * 3, 1e-12); + }); + + test('6 esters couverts (sans PEP) — choicesForModel en dépend', () => { + for (const e of ['EV', 'EU', 'EEN', 'EB', 'EC', 'ECS']) { + assert.ok(WHS.hasModel(e), `${e} devrait être couvert`); + } + }); + + test('dt ≤ 0 → 0 ; ester inconnu → 0', () => { + assert.equal(WHS.sample('EV', 0), 0); + assert.equal(WHS.sample('EV', -3), 0); + assert.equal(WHS.sample('XXX', 40), 0); + }); +});