mergeron 2024.738953.1__py3-none-any.whl → 2025.739265.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mergeron might be problematic. Click here for more details.

Files changed (39) hide show
  1. mergeron/__init__.py +26 -6
  2. mergeron/core/__init__.py +5 -65
  3. mergeron/core/{damodaran_margin_data.py → empirical_margin_distribution.py} +74 -58
  4. mergeron/core/ftc_merger_investigations_data.py +147 -101
  5. mergeron/core/guidelines_boundaries.py +290 -1078
  6. mergeron/core/guidelines_boundary_functions.py +1128 -0
  7. mergeron/core/{guidelines_boundaries_specialized_functions.py → guidelines_boundary_functions_extra.py} +87 -55
  8. mergeron/core/pseudorandom_numbers.py +16 -22
  9. mergeron/data/__init__.py +3 -0
  10. mergeron/data/damodaran_margin_data.xls +0 -0
  11. mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  12. mergeron/demo/__init__.py +3 -0
  13. mergeron/demo/visualize_empirical_margin_distribution.py +86 -0
  14. mergeron/gen/__init__.py +258 -246
  15. mergeron/gen/data_generation.py +473 -224
  16. mergeron/gen/data_generation_functions.py +876 -0
  17. mergeron/gen/enforcement_stats.py +355 -0
  18. mergeron/gen/upp_tests.py +171 -259
  19. mergeron-2025.739265.0.dist-info/METADATA +115 -0
  20. mergeron-2025.739265.0.dist-info/RECORD +23 -0
  21. {mergeron-2024.738953.1.dist-info → mergeron-2025.739265.0.dist-info}/WHEEL +1 -1
  22. mergeron/License.txt +0 -16
  23. mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
  24. mergeron/core/excel_helper.py +0 -257
  25. mergeron/core/proportions_tests.py +0 -520
  26. mergeron/ext/__init__.py +0 -5
  27. mergeron/ext/tol_colors.py +0 -851
  28. mergeron/gen/_data_generation_functions_nonpublic.py +0 -623
  29. mergeron/gen/investigations_stats.py +0 -709
  30. mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -121
  31. mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -82
  32. mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -57
  33. mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -104
  34. mergeron/jinja_LaTex_templates/mergeron.cls +0 -161
  35. mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -90
  36. mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -84
  37. mergeron-2024.738953.1.dist-info/METADATA +0 -93
  38. mergeron-2024.738953.1.dist-info/RECORD +0 -30
  39. /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.3
2
+ Name: mergeron
3
+ Version: 2025.739265.0
4
+ Summary: Analyze merger enforcement policy using Python
5
+ License: MIT
6
+ Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
7
+ Author: Murthy Kambhampaty
8
+ Author-email: smk@capeconomics.com
9
+ Requires-Python: >=3.12,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Requires-Dist: aenum (>=3.1.15,<4.0.0)
23
+ Requires-Dist: attrs (>=23.2)
24
+ Requires-Dist: bs4 (>=0.0.1)
25
+ Requires-Dist: certifi (>=2023.11.17)
26
+ Requires-Dist: google-re2 (>=1.1)
27
+ Requires-Dist: jinja2 (>=3.1)
28
+ Requires-Dist: joblib (>=1.3)
29
+ Requires-Dist: matplotlib (>=3.8)
30
+ Requires-Dist: mpmath (>=1.3)
31
+ Requires-Dist: msgpack (>=1.0)
32
+ Requires-Dist: msgpack-numpy (>=0.4)
33
+ Requires-Dist: scipy (>=1.12)
34
+ Requires-Dist: sympy (>=1.12)
35
+ Requires-Dist: tables (>=3.10.1)
36
+ Requires-Dist: types-beautifulsoup4 (>=4.11.2)
37
+ Requires-Dist: urllib3 (>=2.2.2,<3.0.0)
38
+ Requires-Dist: xlrd (>=2.0.1,<3.0.0)
39
+ Requires-Dist: xlsxwriter (>=3.1)
40
+ Description-Content-Type: text/x-rst
41
+
42
+ mergeron: Merger Policy Analysis using Python
43
+ =============================================
44
+
45
+ Visualize the sets of mergers conforming to concentration and diversion-ratio standards. Estimate intrinsic enforcement rates, and intrinsic clearance rates, under concentration, diversion ratio, GUPPI, CMCR, and IPR bounds using generated data with specified distributions of market shares, price-cost margins, firm counts, and prices, optionally imposing restrictions implied by statutory filing thresholds and/or Bertrand-Nash oligopoly with MNL demand. Download and analyze merger investigations data published by the U.S. Federal Trade Commission in various reports on extended merger investigations (Second Requests) during 1996 to 2011.
46
+
47
+ Here, enforcement rates derived with merger enforcement as being exogenous to firm conduct are defined as intrinsic enforcement rates, and similarly intrinsic clearance rates. Depending on the merger enforcement regime, or merger control regime, intrinsic enforcement rates may also not be the complement of intrinsic clearance rates, i.e, it is not necessarily true that the intrinsic clearance rate estimate for a given enforcement regime is 1 minus the intrinsic enforcement rate. In contrast, observed enforcement rates reflect the deterrent effects of merger enforcement on firm conduct as well as the effects of merger screening on the level of enforcement; and, by definition, the observed clearance rate is 1 minus the observed enforcement rate.
48
+
49
+ Introduction
50
+ ------------
51
+
52
+ Module :code:`.core.guidelines_boundaries` includes classes for specifying concentration bounds (:code:`.core.guidelines_boundaries.ConcentrationBoundary`) and diversion-ratio bounds (:code:`.core.guidelines_boundaries.DiversionRatioBoundary`), with automatic generation of boundary, as an array of share-pairs, and area. This module also includes a function for generating plots of concentration and diversion-ratio boundaries, and functions for mapping GUPPI standards to concentration (ΔHHI) standards, and vice-versa.
53
+
54
+ Module :code:`.gen.data_generation` includes the :code:`.gen.data_generation.MarketSample` which provides for a rich specification of shares and diversion ratios (:code:`.gen.data_generation.MarketSample.share_spec`), margins (:code:`.gen.data_generation.MarketSample.pcm_spec`, prices (:code:`.gen.data_generation.MarketSample.price_spec`), and HSR filing requirements (:code:`.gen.data_generation.MarketSample.hsr_filing_test_type`), and with methods for, (i) generating sample data (:code:`.gen.data_generation.MarketSample.generate_sample`), and (ii) computing the intrinsic enforcement rate and intrinsic clearance rate for the generated sample, given a method (:code:`.UPPAggrSelector`) of aggregating diversion ratio or GUPPI estimates for the firms in a merger (:code:`.gen.data_generation.MarketSample.estimate_enf_counts`). While the latter populate the properties, :code:`.gen.data_generation.MarketSample.data`
55
+ and :code:`.gen.data_generation.MarketSample.enf_counts`, respectively, the underlying methods for generating standalone :code:`MarketDataSample` and :code:`UPPTestCounts` objects are included in the class definition, with helper functions defined in the modules, :code:`.gen.data_generation_functions` and :code:`.gen.upp_tests`. Notably, market shares are generated for a sample of markets with firm-count distributed as specified in :code:`.gen.data_generation.MarketSample.share_spec.firm_count_weights`, with defaults as discussed below (also see, :code:`.gen.ShareSpec.firm_count_weights`.
56
+
57
+ By default, merging-firm shares are drawn with uniform distribution over the space :math:`s_1 + s_2 \leqslant 1` for an unspecified number of firms. Alternatively, shares may be drawn from the Dirichlet distribution (see property `dist_type` of :code:`.gen.data_generation.MarketSample.share_spec`, of type, :code:`.gen.SHRDistribution`), with specified shape parameters (property `dist_parms` of :code:`.gen.data_generation.MarketSample.share_spec`. When drawing shares from the Dirichlet distribution, the user specifies the `firm_count_weights` property of :code:`.gen.data_generation.MarketSample.share_spec`, as a vector of weights specifying the frequency distribution over sequential firm counts, e.g., :code:`[133, 184, 134, 52, 32, 10, 12, 4, 3]` to specify shares drawn from Dirichlet distributions with 2 to 10 pre-merger firms distributed as in data for FTC merger investigations during 1996--2003 (See, for example, Table 4.1 of `FTC, Horizontal Merger Investigations Data, Fiscal Years 1996--2003 (Revised: August 31, 2004) <https://www.ftc.gov/sites/default/files/documents/reports/horizontal-merger-investigation-data-fiscal-years-1996-2003/040831horizmergersdata96-03.pdf>`_). If the property `firm_count_weights` is not explicitly assigned a value when defining :code:`.gen.data_generation.MarketSample.share_spec`, the default values is used, which results in a sample of markets with 2 to 7 firms with relative frequency in inverse proportion to firm-count, with 2-firm markets being 6 times as likely to be drawn as 7-firm markets.
58
+
59
+ Recapture ratios can be specified as, "proportional", "inside-out", or "outside-in" (see :code:`.RECForm`). The "inside-out" specification (assigning :code:`.RECForm.INOUT` to the `recapture_form` property of :code:`.gen.data_generation.MarketSample.share_spec`) results in recapture ratios consistent with MNL demand, given merging-firms' in-market shares and a default recapture ratio. The "outside-in" specification (assigning :code:`.RECForm.INOUT` to the `recapture_form` property of :code:`.gen.data_generation.MarketSample.share_spec`) yields diversion ratios from purchase probabilities drawn at random for :math:`N+1` goods, with market shares and recapture ratios for the :math:`N` goods in the putative market (see, :code:`.gen.ShareSpec`) computed from the simulated choice probabilities. The "outside-in" specification requires specification of the distribution of markets over firm counts (the default being uniform distirbution over markets with 2 to 7 firms pre-merger), and Dirichlet-distributed shares, with optional parameters (the default being a "flat" Dirichlet distribution, i.e., one with all parameters being 1). The parameters of the Dirichlet distribution can, for example, be specified to increase (decrease) the probability of drawing mergers to monopoly relative to that probability associated with the Flat Dirichlet specification, by setting the first 2 specified parameters at higher (lower) values relative to the others. Lastly, the "proportional" form of recapture ratio (`recapture_form` = :code:`.RECForm.FIXED`) is often used in the literature, as an approximation to the "inside-out" calibration. See, for example, Coate (2011).
60
+
61
+ Price-cost-margins may be specified as having uniform distribution, Beta distribution (including a bounded Beta distribution with specified mean and variance), or a built-in empirical distribution (see, :code:`.gen.PCMSpec`). The in-built empirical margin distribution is based on resampling margin data published by Prof. Damodaran of NYU Stern School of Business (see Notes), using an estimated Gaussian KDE. The second merging firm's margin (per the property `firm2_pcm_constraint` of :code:`.gen.data_generation.MarketSample.pcm_spec`) may be specified as symmetric, i.i.d., or subject to equilibrium conditions for (profit-maximization in) Bertrand-Nash oligopoly with MNL demand (:code:`.gen.FM2Constraint`).
62
+
63
+ Prices may be specified as symmetric or asymmetric, and in the latter case, the direction of correlation between merging firm prices, if any, can also be specified (see, :code:`.gen.PriceSpec`). Prices may also be defined by imposing cost symmetry on firms in the sample, with fixed unit marginal costs normalized to 1 unit, such that prices equal :math:`1 / (1 - \pmb{m})`, where :math:`\pmb{m}` represents the array of margins for firms in the sample.
64
+
65
+ The market sample may be restricted to mergers meeting the HSR filing requirement under two alternative approaches: in the one, the smaller of the two merging firms meets the lower HSR size threshold ($10 million, as adjusted) and the larger of the two merging firms meets the size test if it's share is no less than 10 times the share of the smaller firm. In the other, the :math:`n`-th firm's size is maintained as $10 million, as adjusted (see, :code:`.gen.SSZConstant`), and a merger meets the HSR filing test if either, (a.) the smaller merging firm is no smaller than the n-th firm and the larger merging firm is at 10-times as large as the n-th firm, or (b.) the smaller merging firm's market share is in excess of 10%; in effect this version of the test maintains that if the smaller merging firm's market share exceeds 10%, the value of the transaction exceeds $200 million, as adjusted, and the size-of-person test is eliminated (see, FTC (2008, p. 12); the above are simplifications of the statutory HSR filing requirements). The second assumption avoids the unfortunate assumption in the first that, within the resulting sample, the larger merging firm be at least 10 times as large as the smaller merging firm, as a consequence of the full definition of the HSR filing requirement.
66
+
67
+ The full specification of a market sample is given in a :code:`.gen.data_generation.MarketSample` object, including the above parameters. Data are drawn by invoking :code:`.gen.data_generation.MarketSample.generate_sample` which adds a :code:`data` property of class, :code:`.gen.MarketDataSample`. Enforcement or clearance counts are computed by invoking :code:`.gen.data_generation.MarketSample.estimate_enf_counts`, which adds an :code:`enf_counts` property of class :code:`.gen.UPPTestsCounts`. For fast, parallel generation of enforcement or clearance counts over large market data samples that ordinarily would exceed available limits on machine memory, the user can invoke the method :code:`.gen.data_generation.MarketSample.estimate_enf_counts` on a :code:`.gen.data_generation.MarketSample` object without first invoking :code:`.gen.data_generation.MarketSample.generate_sample`. Note, however, that this strategy does not retain the market sample in memory in the interests of conserving memory and maintaining high performance (the user can specify that the market sample and enforcement statistics be stored to permanent storage; when saving to current PCIe NVMe storage, the performance penalty is slight, but can be considerable if saving to SATA storage).
68
+
69
+ Enforcement statistics based on FTC investigations data and test data are tabulated using methods provided in :code:`.gen.enforcement_stats`.
70
+
71
+ Programs demonstrating the use of this package are included in the sub-package, :code:`.demo`.
72
+
73
+ This package includes a class, :code:`.core.pseudorandom_numbers.MultithreadedRNG` for generating random numbers with selected continuous distribution over specified parameters, and with CPU multithreading on machines with multiple CPU cores, be they virtual, logical, or physical cores. This class is an adaptation from the documentation for the external :code:`numpy.random` subpackage, from the discussion on, "`Multithreaded generation <https://numpy.org/doc/stable/reference/random/multithreading.html>`_"; the version included here permits selection of the distribution with pre-tests to catch and inform on common errors. To access these directly:
74
+
75
+ .. code-block:: python
76
+
77
+ import mergeron.core.pseudorandom_numbers as prng
78
+
79
+ Documentation for this package is in the form of the API Reference. Documentation for individual functions and classes is accessible within a python shell. For example:
80
+
81
+ .. code-block:: python
82
+
83
+ import mergeron.core.data_generation as dgl
84
+
85
+ help(dgl.MarketSample)
86
+
87
+ .. rubric:: References
88
+
89
+ .. _coate2011:
90
+
91
+ Coate, M. B. (2011). Benchmarking the upward pricing pressure model with Federal Trade
92
+ Commission evidence. Journal of Competition Law & Economics, 7(4), 825--846. URL: https://doi.org/10.1093/joclec/nhr014.
93
+
94
+ .. _ftc_premerger_guide2:
95
+
96
+ FTC Premerger Notification Office. “To File or Not to File: When You Must File a Premerger Notification Report Form”. 2008 (September, revised). URL: https://www.ftc.gov/sites/default/files/attachments/premerger-introductory-guides/guide2.pdf
97
+
98
+
99
+ .. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
100
+ :alt: Poetry
101
+ :target: https://python-poetry.org/
102
+
103
+ .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
104
+ :alt: Ruff
105
+ :target: https://github.com/astral-sh/ruff/
106
+
107
+ .. image:: https://www.mypy-lang.org/static/mypy_badge.svg
108
+ :alt: Checked with mypy
109
+ :target: https://mypy-lang.org/
110
+
111
+ .. image:: https://img.shields.io/badge/License-MIT-yellow.svg
112
+ :alt: License: MIT
113
+ :target: https://opensource.org/licenses/MIT/
114
+
115
+
@@ -0,0 +1,23 @@
1
+ mergeron/__init__.py,sha256=TA29lm9xq1AOShjbOs4dXujCClb_MMCLlKN6ej8VC08,1581
2
+ mergeron/core/__init__.py,sha256=jPGd0okmvNOdWnTu4biR4hqnL29IERaY4Olv8mS02ko,188
3
+ mergeron/core/empirical_margin_distribution.py,sha256=uDgXXNNL4ln_f1rnvfOxrpokeuha6qQXpi0c7ZGE58U,8726
4
+ mergeron/core/ftc_merger_investigations_data.py,sha256=_Qno1_oWcHmDd60j8YX6BLUSLeAn-DU6KpvMOcbCIaE,28728
5
+ mergeron/core/guidelines_boundaries.py,sha256=ci6bNVNKGynCnXFoQXit0jyhpQW8hw-Y37DpozuQETg,15245
6
+ mergeron/core/guidelines_boundary_functions.py,sha256=SoTamDsKrThqDfoNTHeGp2ntiwhzscWmkAbIWrBtUq4,34885
7
+ mergeron/core/guidelines_boundary_functions_extra.py,sha256=ytTWxYk7FkGLQubkQXrniwP_2ZlYssp4rmOxy4_7480,11292
8
+ mergeron/core/pseudorandom_numbers.py,sha256=VepI_YU5oMkTExjyN9OyfiB3UmTDR2cq4N1DAmYZbm4,9233
9
+ mergeron/data/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
10
+ mergeron/data/damodaran_margin_data.xls,sha256=Qggl1p5nkOMJI8YUXhkwXQRz-OhRSqBTzz57N0JQyYA,79360
11
+ mergeron/data/damodaran_margin_data_dict.msgpack,sha256=sr6s4L69kposEpzGI7jpPb4ULz0UpY-bEYfeNi6UlRA,57621
12
+ mergeron/data/ftc_invdata.msgpack,sha256=WBFHgi7Ld4R-h2zL2Zc3TOIlKqVrbVFMH1LoI4-T-M0,264664
13
+ mergeron/demo/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
14
+ mergeron/demo/visualize_empirical_margin_distribution.py,sha256=_1SWKqFJNqKV_yA3v2tmzl8Th3CC3SXpb6VGiwGGuN0,2373
15
+ mergeron/gen/__init__.py,sha256=6A1HVewEEwLo4JtZZ61ShzhU2phEeNfTxmTPlpi5LXY,17215
16
+ mergeron/gen/data_generation.py,sha256=nzCWVJBXYxloR6J6MamFM0MUbDzguhARCAEksYkX1Dw,16872
17
+ mergeron/gen/data_generation_functions.py,sha256=cIYkE4DcBXdEbEIeCk2lG9rLKblhdYZPDMJdGMS2dKE,29022
18
+ mergeron/gen/enforcement_stats.py,sha256=ZjrV_VkFMF0D1myc-fj-W99M1EhJMA9-nCfyE5g9e54,10890
19
+ mergeron/gen/upp_tests.py,sha256=bQfxigqHc8bdsd5SjMyKKa07weTEcAdJvLCiTrhgzAE,12547
20
+ mergeron/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
21
+ mergeron-2025.739265.0.dist-info/METADATA,sha256=nkTdrTqjVT4tc6ljYRuCFykz0X-TsABN3zxyEe9ALnE,14494
22
+ mergeron-2025.739265.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
23
+ mergeron-2025.739265.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
mergeron/License.txt DELETED
@@ -1,16 +0,0 @@
1
- mergeron
2
- ========
3
-
4
- Copyright 2017-2023 S. Murthy Kambhampaty
5
-
6
- MIT License
7
- -----------
8
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
-
12
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
-
14
- Exceptions
15
- ----------
16
- Modules provided in the mergeron.ext subpackage are redistributed under the license published within each module.
@@ -1,68 +0,0 @@
1
- -----BEGIN CERTIFICATE-----
2
- MIIF+TCCA+GgAwIBAgIQRyDQ+oVGGn4XoWQCkYRjdDANBgkqhkiG9w0BAQwFADCB
3
- iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
4
- cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
5
- BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQx
6
- MDA2MDAwMDAwWhcNMjQxMDA1MjM1OTU5WjB2MQswCQYDVQQGEwJVUzELMAkGA1UE
7
- CBMCTUkxEjAQBgNVBAcTCUFubiBBcmJvcjESMBAGA1UEChMJSW50ZXJuZXQyMREw
8
- DwYDVQQLEwhJbkNvbW1vbjEfMB0GA1UEAxMWSW5Db21tb24gUlNBIFNlcnZlciBD
9
- QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwb8bsvf2MYFVFRVA+e
10
- xU5NEFj6MJsXKZDmMwysE1N8VJG06thum4ltuzM+j9INpun5uukNDBqeso7JcC7v
11
- HgV9lestjaKpTbOc5/MZNrun8XzmCB5hJ0R6lvSoNNviQsil2zfVtefkQnI/tBPP
12
- iwckRR6MkYNGuQmm/BijBgLsNI0yZpUn6uGX6Ns1oytW61fo8BBZ321wDGZq0GTl
13
- qKOYMa0dYtX6kuOaQ80tNfvZnjNbRX3EhigsZhLI2w8ZMA0/6fDqSl5AB8f2IHpT
14
- eIFken5FahZv9JNYyWL7KSd9oX8hzudPR9aKVuDjZvjs3YncJowZaDuNi+L7RyML
15
- fzcCAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bL
16
- MB0GA1UdDgQWBBQeBaN3j2yW4luHS6a0hqxxAAznODAOBgNVHQ8BAf8EBAMCAYYw
17
- EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
18
- AwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAECAjBQBgNVHR8ESTBHMEWgQ6BB
19
- hj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2VydGlmaWNh
20
- dGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNo
21
- dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5j
22
- cnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZI
23
- hvcNAQEMBQADggIBAC0RBjjW29dYaK+qOGcXjeIT16MUJNkGE+vrkS/fT2ctyNMU
24
- 11ZlUp5uH5gIjppIG8GLWZqjV5vbhvhZQPwZsHURKsISNrqOcooGTie3jVgU0W+0
25
- +Wj8mN2knCVANt69F2YrA394gbGAdJ5fOrQmL2pIhDY0jqco74fzYefbZ/VS29fR
26
- 5jBxu4uj1P+5ZImem4Gbj1e4ZEzVBhmO55GFfBjRidj26h1oFBHZ7heDH1Bjzw72
27
- hipu47Gkyfr2NEx3KoCGMLCj3Btx7ASn5Ji8FoU+hCazwOU1VX55mKPU1I2250Lo
28
- RCASN18JyfsD5PVldJbtyrmz9gn/TKbRXTr80U2q5JhyvjhLf4lOJo/UzL5WCXED
29
- Smyj4jWG3R7Z8TED9xNNCxGBMXnMete+3PvzdhssvbORDwBZByogQ9xL2LUZFI/i
30
- eoQp0UM/L8zfP527vWjEzuDN5xwxMnhi+vCToh7J159o5ah29mP+aJnvujbXEnGa
31
- nrNxHzu+AGOePV8hwrGGG7hOIcPDQwkuYwzN/xT29iLp/cqf9ZhEtkGcQcIImH3b
32
- oJ8ifsCnSbu0GB9L06Yqh7lcyvKDTEADslIaeSEINxhO2Y1fmcYFX/Fqrrp1WnhH
33
- OjplXuXE0OPa0utaKC25Aplgom88L2Z8mEWcyfoB7zKOfD759AN7JKZWCYwk
34
- -----END CERTIFICATE-----
35
- -----BEGIN CERTIFICATE-----
36
- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
37
- iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
38
- cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
39
- BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
40
- MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
41
- BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
42
- aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
43
- dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
44
- AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
45
- 3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
46
- tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
47
- Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
48
- VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
49
- 79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
50
- c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
51
- Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
52
- c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
53
- UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
54
- Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
55
- BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
56
- A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
57
- Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
58
- VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
59
- ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
60
- 8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
61
- iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
62
- Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
63
- XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
64
- qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
65
- VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
66
- L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
67
- jjxDah2nGN59PRbxYvnKkKj9
68
- -----END CERTIFICATE-----
@@ -1,257 +0,0 @@
1
- """
2
- Routines for writing data from Python to fresh Excel workbooks using
3
- the third-party package, `xlsxwriter`.
4
-
5
- Includes a flexible system of defining cell formats.
6
-
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import enum
12
- from collections.abc import Mapping, Sequence
13
- from importlib.metadata import version
14
- from types import MappingProxyType
15
- from typing import Any
16
-
17
- import numpy as np
18
- import numpy.typing as npt
19
- import xlsxwriter # type: ignore
20
-
21
- from .. import _PKG_NAME # noqa: TID252
22
-
23
- __version__ = version(_PKG_NAME)
24
-
25
-
26
- @enum.unique
27
- class CFmtParent(dict[str, Any], enum.ReprEnum): # type: ignore
28
- """Unique mappings defining xlsxwirter Workbook formats"""
29
-
30
-
31
- class CFmt(CFmtParent):
32
- """
33
- Initialize cell formats for xlsxwriter.
34
-
35
- The mappings included here, and unions, etc. of them
36
- and any others added at runtime, will be rendered
37
- as xlsxWriter.Workbook.Format objects for writing
38
- cell values to formatted cells in a spreadsheet.
39
-
40
- See, https://xlsxwriter.readthedocs.io/format.html
41
- """
42
-
43
- XL_DEFAULT = MappingProxyType({"font_name": "Calibri", "font_size": 11})
44
- XL_DEFAULT_2003 = MappingProxyType({"font_name": "Arial", "font_size": 10})
45
-
46
- A_CTR = MappingProxyType({"align": "center"})
47
- A_CTR_ACROSS = MappingProxyType({"align": "center_across"})
48
- A_LEFT = MappingProxyType({"align": "left"})
49
- A_RIGHT = MappingProxyType({"align": "right"})
50
-
51
- BOLD = MappingProxyType({"bold": True})
52
- ITALIC = MappingProxyType({"italic": True})
53
- ULINE = MappingProxyType({"underline": True})
54
-
55
- TEXT_WRAP = MappingProxyType({"text_wrap": True})
56
- IND_1 = MappingProxyType({"indent": 1})
57
-
58
- DOLLAR_NUM = MappingProxyType({"num_format": "[$$-409]#,##0.00"})
59
- DT_NUM = MappingProxyType({"num_format": "mm/dd/yyyy"})
60
- QTY_NUM = MappingProxyType({"num_format": "#,##0.0"})
61
- PCT_NUM = MappingProxyType({"num_format": "##0.000000%"})
62
- AREA_NUM = MappingProxyType({"num_format": "0.00000000"})
63
-
64
- BAR_FILL = MappingProxyType({"pattern": 1, "bg_color": "dfeadf"})
65
- BOT_BORDER = MappingProxyType({"bottom": 1, "bottom_color": "000000"})
66
- TOP_BORDER = MappingProxyType({"top": 1, "top_color": "000000"})
67
- HDR_BORDER = TOP_BORDER | BOT_BORDER
68
-
69
-
70
- def matrix_to_sheet(
71
- _xl_book: xlsxwriter.workbook.Workbook,
72
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
73
- _data_table: npt.ArrayLike,
74
- _row_id: int,
75
- _col_id: int = 0,
76
- /,
77
- *,
78
- cell_format: Sequence[CFmt] | CFmt | None = None,
79
- green_bar_flag: bool = True,
80
- ) -> tuple[int, int]:
81
- """
82
- Write a 2-D array to a worksheet.
83
-
84
- The given array is required be a two-dimensional array, whether
85
- a nested list, nested tuple, or a 2-D numpy ndarray.
86
-
87
- Parameters
88
- ----------
89
- _xl_book
90
- Workbook object
91
-
92
- _xl_sheet
93
- Worksheet object to which to write the give array
94
-
95
- _data_table
96
- Array to be written
97
-
98
- _row_id
99
- Row number of top left corner of range to write to
100
-
101
- _col_id
102
- Column number of top left corner of range to write to
103
-
104
- cell_format
105
- Format specification for range to be written
106
-
107
- green_bar_flag
108
- Whether to highlight alternating rows as in green bar paper
109
-
110
- Returns
111
- -------
112
- Tuple giving address of cell (at left) below range written
113
-
114
- """
115
- _data_array: npt.NDArray[Any] = np.array(_data_table)
116
- del _data_table
117
-
118
- if not len(_data_array.shape) == 2:
119
- raise ValueError(
120
- "Array to write must be a 2-D array, but"
121
- f"the given array has shape, {_data_array.shape}."
122
- )
123
-
124
- # Get the array dimensions and row and column numbers for Excel
125
- _bottom_row_id: int = _row_id + _data_array.shape[0]
126
- _right_column_id: int = _col_id + _data_array.shape[1]
127
-
128
- if isinstance(cell_format, tuple):
129
- ensure_cell_format_spec_tuple(cell_format)
130
- if not len(cell_format) == len(_data_array[0]):
131
- raise ValueError("Format tuple does not match data in length.")
132
- _cell_format: Sequence[CFmt] = cell_format
133
- elif isinstance(cell_format, CFmt):
134
- _cell_format = (cell_format,) * len(_data_array[0])
135
- else:
136
- _cell_format = (CFmt.XL_DEFAULT,) * len(_data_array[0])
137
-
138
- for _cell_row in range(_row_id, _bottom_row_id):
139
- for _cell_col in range(_col_id, _right_column_id):
140
- _cell_fmt = (
141
- (_cell_format[_cell_col - _col_id], CFmt.BAR_FILL)
142
- if green_bar_flag and (_cell_row - _row_id) % 2
143
- else _cell_format[_cell_col - _col_id]
144
- )
145
-
146
- scalar_to_sheet(
147
- _xl_book,
148
- _xl_sheet,
149
- _cell_row,
150
- _cell_col,
151
- _data_array[_cell_row - _row_id, _cell_col - _col_id],
152
- _cell_fmt,
153
- )
154
-
155
- return _bottom_row_id, _right_column_id
156
-
157
-
158
- def scalar_to_sheet(
159
- _xl_book: xlsxwriter.workbook.Workbook,
160
- _xl_sheet: xlsxwriter.worksheet.Worksheet,
161
- _cell_addr_0: str | int | float = "A1",
162
- /,
163
- *_s2s_args: Any,
164
- ) -> None:
165
- """
166
- Write to a single cell in a worksheet.
167
-
168
- Parameters
169
- ----------
170
- _xl_book
171
- Workbook object
172
-
173
- _xl_sheet
174
- Worksheet object to which to write the give array
175
-
176
- _cell_addr_0
177
- First element of a cell address, which may be the entire address
178
- in 'A1' format or the row-part in 'R1,C1' format
179
-
180
- _s2s_args
181
- Other arguments, which may be just the cell value to be written and the
182
- cell format, or the column-part of the 'R1,C1' address along with
183
- cell value and cell format.
184
-
185
- """
186
-
187
- if isinstance(_cell_addr_0, str):
188
- if len(_s2s_args) not in (1, 2):
189
- raise ValueError("Too many or too few arguments.")
190
- _cell_addr: tuple[int | str, ...] = (_cell_addr_0,)
191
- _cell_val: Any = _s2s_args[0]
192
- _cell_fmt: CFmt | Sequence[CFmt] = _s2s_args[1] if len(_s2s_args) == 2 else None # type: ignore
193
- elif isinstance(_cell_addr_0, int):
194
- if len(_s2s_args) not in (2, 3):
195
- raise ValueError("Too many or too few arguments.")
196
- _cell_addr = (_cell_addr_0, _s2s_args[0])
197
- _cell_val = _s2s_args[1]
198
- _cell_fmt = _s2s_args[2] if len(_s2s_args) == 3 else None # type: ignore
199
- else:
200
- raise ValueError("Incorrect specification for Excel cell data.")
201
-
202
- _xl_sheet.write(*_cell_addr, _cell_val, xl_fmt(_xl_book, _cell_fmt))
203
-
204
-
205
- def xl_fmt(
206
- _xl_book: xlsxwriter.Workbook, _cell_fmt: Sequence[CFmt] | CFmt | None, /
207
- ) -> xlsxwriter.format.Format:
208
- """
209
- Return :code:`xlsxwriter` `Format` object given a CFmt enum, or tuple thereof.
210
-
211
- Parameters
212
- ----------
213
- _xl_book
214
- :code:`xlsxwriter.Workbook` object
215
-
216
- _cell_fmt
217
- :code:`CFmt` enum object, or tuple thereof
218
-
219
- Returns
220
- -------
221
- :code:`xlsxwriter` `Format` object
222
-
223
- """
224
- _cell_fmt_dict: Mapping[str, Any] = MappingProxyType({})
225
- if isinstance(_cell_fmt, tuple):
226
- ensure_cell_format_spec_tuple(_cell_fmt)
227
- for _cf in _cell_fmt:
228
- _cell_fmt_dict = _cell_fmt_dict | _cf.value
229
- elif isinstance(_cell_fmt, CFmt):
230
- _cell_fmt_dict = _cell_fmt.value
231
- else:
232
- _cell_fmt_dict = CFmt.XL_DEFAULT.value
233
-
234
- return _xl_book.add_format(_cell_fmt_dict)
235
-
236
-
237
- def ensure_cell_format_spec_tuple(_cell_formats: Sequence[CFmt], /) -> None:
238
- """
239
- Test that a given format specification is tuple of CFmt enums
240
-
241
- Parameters
242
- ----------
243
- _cell_formats
244
- Format specification
245
-
246
- Returns
247
- -------
248
- True if format specification passes, else False
249
-
250
- """
251
-
252
- for _cell_format in _cell_formats:
253
- if isinstance(_cell_format, tuple):
254
- ensure_cell_format_spec_tuple(_cell_format)
255
-
256
- if not (isinstance(_cell_format, CFmt),):
257
- raise ValueError("Improperly specified format tuple.")