mergeron 2025.739290.5__tar.gz → 2025.739290.7__tar.gz
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.
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/PKG-INFO +1 -1
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/pyproject.toml +4 -4
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/__init__.py +35 -21
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/__init__.py +32 -34
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/empirical_margin_distribution.py +16 -16
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/ftc_merger_investigations_data.py +20 -27
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/guidelines_boundaries.py +25 -24
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/guidelines_boundary_functions.py +113 -113
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/guidelines_boundary_functions_extra.py +207 -0
- mergeron-2025.739290.7/src/mergeron/data/__init__.py +54 -0
- mergeron-2025.739290.7/src/mergeron/data/ftc_merger_investigations_data.zip +0 -0
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/demo/visualize_empirical_margin_distribution.py +5 -2
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/gen/__init__.py +40 -40
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/gen/data_generation.py +1 -13
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/gen/enforcement_stats.py +18 -7
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/gen/upp_tests.py +50 -144
- mergeron-2025.739290.5/src/mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron-2025.739290.5/src/mergeron/data/ftc_invdata.msgpack +0 -0
- mergeron-2025.739290.5/src/mergeron/data/ftc_invdata.zip +0 -0
- mergeron-2025.739290.5/src/mergeron/demo/__init__.py +0 -3
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/README.rst +0 -0
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/pseudorandom_numbers.py +0 -0
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/data/damodaran_margin_data.xls +0 -0
- {mergeron-2025.739290.5/src/mergeron/data → mergeron-2025.739290.7/src/mergeron/demo}/__init__.py +0 -0
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/gen/data_generation_functions.py +0 -0
- {mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mergeron
|
|
3
|
-
Version: 2025.739290.
|
|
3
|
+
Version: 2025.739290.7
|
|
4
4
|
Summary: Analyze merger enforcement policy using Python
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
|
|
@@ -13,7 +13,7 @@ keywords = [
|
|
|
13
13
|
"upward pricing pressure",
|
|
14
14
|
"GUPPI",
|
|
15
15
|
]
|
|
16
|
-
version = "2025.739290.
|
|
16
|
+
version = "2025.739290.7"
|
|
17
17
|
|
|
18
18
|
# Classifiers list: https://pypi.org/classifiers/
|
|
19
19
|
classifiers = [
|
|
@@ -68,11 +68,11 @@ pendulum = ">=3.0.0"
|
|
|
68
68
|
ruff = ">=0.5"
|
|
69
69
|
poetry-plugin-export = "^1.8.0"
|
|
70
70
|
pytest = ">=8.0"
|
|
71
|
-
|
|
71
|
+
sphinx = ">8.2"
|
|
72
72
|
semver = ">=3.0"
|
|
73
73
|
sphinx-autodoc-typehints = ">=2.0.0"
|
|
74
|
-
sphinx-autoapi = ">=3.0"
|
|
75
|
-
sphinx-immaterial = "
|
|
74
|
+
sphinx-autoapi = ">=3.6.0"
|
|
75
|
+
sphinx-immaterial = ">0.11"
|
|
76
76
|
pipdeptree = ">=2.15.1"
|
|
77
77
|
types-openpyxl = ">=3.0.0"
|
|
78
78
|
virtualenv = ">=20.28.0"
|
|
@@ -12,18 +12,20 @@ from ruamel import yaml
|
|
|
12
12
|
|
|
13
13
|
_PKG_NAME: str = Path(__file__).parent.stem
|
|
14
14
|
|
|
15
|
-
VERSION = "2025.739290.
|
|
15
|
+
VERSION = "2025.739290.7"
|
|
16
16
|
|
|
17
17
|
__version__ = VERSION
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
WORK_DIR = globals().get("WORK_DIR", Path.home() / _PKG_NAME)
|
|
20
20
|
"""
|
|
21
|
-
|
|
21
|
+
If defined, the global variable WORK_DIR is used as a data store.
|
|
22
22
|
|
|
23
|
-
If the
|
|
23
|
+
If the user does not define WORK_DIR, a subdirectory in
|
|
24
|
+
the user's home directory, named for this package, is
|
|
25
|
+
created/reused.
|
|
24
26
|
"""
|
|
25
|
-
if not
|
|
26
|
-
|
|
27
|
+
if not WORK_DIR.is_dir():
|
|
28
|
+
WORK_DIR.mkdir(parents=False)
|
|
27
29
|
|
|
28
30
|
DEFAULT_REC_RATIO = 0.85
|
|
29
31
|
|
|
@@ -36,14 +38,14 @@ PKG_ATTRS_MAP: dict[str, object] = {}
|
|
|
36
38
|
|
|
37
39
|
np.set_printoptions(precision=24, floatmode="fixed")
|
|
38
40
|
|
|
39
|
-
type HMGPubYear = Literal[
|
|
41
|
+
type HMGPubYear = Literal[1992, 2010, 2023]
|
|
40
42
|
|
|
41
43
|
type ArrayBoolean = NDArray[np.bool_]
|
|
42
44
|
type ArrayFloat = NDArray[np.floating]
|
|
43
|
-
type ArrayINT = NDArray[np.
|
|
45
|
+
type ArrayINT = NDArray[np.integer]
|
|
44
46
|
|
|
45
47
|
type ArrayDouble = NDArray[np.float64]
|
|
46
|
-
type ArrayBIGINT = NDArray[np.
|
|
48
|
+
type ArrayBIGINT = NDArray[np.int64]
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
this_yaml = yaml.YAML(typ="rt")
|
|
@@ -131,35 +133,47 @@ class Enameled(enum.Enum):
|
|
|
131
133
|
@this_yaml.register_class
|
|
132
134
|
@enum.unique
|
|
133
135
|
class RECForm(str, Enameled):
|
|
134
|
-
"""For derivation of recapture ratio from market shares.
|
|
136
|
+
R"""For derivation of recapture ratio from market shares.
|
|
137
|
+
|
|
138
|
+
With :math:`\mathscr{N}` a set of firms, each supplying a
|
|
139
|
+
single differentiated product, and :math:`\mathscr{M} \subset \mathscr{N}`
|
|
140
|
+
a putative relevant product market, with
|
|
141
|
+
:math:`d_{ij}` denoting diversion ratio from good :math:`i` to good :math:`j`,
|
|
142
|
+
:math:`s_i` denoting market shares, and
|
|
143
|
+
:math:`\overline{r}` the default market recapture ratio,
|
|
144
|
+
market recapture ratios for the respective products may be specified
|
|
145
|
+
as having one of the following forms:
|
|
146
|
+
"""
|
|
135
147
|
|
|
136
|
-
|
|
137
|
-
R"""
|
|
138
|
-
Given, :math:`\overline{r}, s_i {\ } \forall {\ } i \in \set{1, 2, \ldots, m}`, with
|
|
139
|
-
:math:`s_{min} = \min(s_1, s_2)`,
|
|
148
|
+
FIXED = "proportional"
|
|
149
|
+
R"""Given, :math:`\overline{r}`,
|
|
140
150
|
|
|
141
151
|
.. math::
|
|
142
152
|
|
|
143
|
-
REC_i = \
|
|
153
|
+
REC_i = \overline{r} {\ } \forall {\ } i \in \mathscr{M}
|
|
144
154
|
|
|
145
155
|
"""
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
INOUT = "inside-out"
|
|
148
158
|
R"""
|
|
149
|
-
Given, :math:`\
|
|
159
|
+
Given, :math:`\overline{r}, s_i {\ } \forall {\ } i \in \mathscr{M}`, with
|
|
160
|
+
:math:`s_{min} = \min(s_1, s_2)`,
|
|
150
161
|
|
|
151
162
|
.. math::
|
|
152
163
|
|
|
153
|
-
REC_i = \frac{\
|
|
164
|
+
REC_i = \frac{\overline{r} (1 - s_i)}{1 - (1 - \overline{r}) s_{min} - \overline{r} s_i}
|
|
165
|
+
{\ } \forall {\ } i \in \mathscr{M}
|
|
154
166
|
|
|
155
167
|
"""
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
R"""
|
|
169
|
+
OUTIN = "outside-in"
|
|
170
|
+
R"""
|
|
171
|
+
Given, :math:`d_{ij} {\ } \forall {\ } i, j \in \mathscr{M}, i \neq j`,
|
|
159
172
|
|
|
160
173
|
.. math::
|
|
161
174
|
|
|
162
|
-
REC_i = \
|
|
175
|
+
REC_i = {\sum_{j \in \mathscr{M}}^{j \neq i} d_{ij}}
|
|
176
|
+
{\ } \forall {\ } i \in \mathscr{M}
|
|
163
177
|
|
|
164
178
|
"""
|
|
165
179
|
|
|
@@ -9,7 +9,14 @@ import mpmath # type: ignore
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
from attrs import cmp_using, field, frozen
|
|
11
11
|
|
|
12
|
-
from .. import
|
|
12
|
+
from .. import ( # noqa: TID252
|
|
13
|
+
VERSION,
|
|
14
|
+
ArrayBIGINT,
|
|
15
|
+
ArrayDouble,
|
|
16
|
+
this_yaml,
|
|
17
|
+
yamelize_attrs,
|
|
18
|
+
yaml_rt_mapper,
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
__version__ = VERSION
|
|
15
22
|
|
|
@@ -17,6 +24,17 @@ type MPFloat = mpmath.ctx_mp_python.mpf
|
|
|
17
24
|
type MPMatrix = mpmath.matrix # type: ignore
|
|
18
25
|
|
|
19
26
|
|
|
27
|
+
@frozen
|
|
28
|
+
class GuidelinesBoundary:
|
|
29
|
+
"""Output of a Guidelines boundary function."""
|
|
30
|
+
|
|
31
|
+
coordinates: ArrayDouble
|
|
32
|
+
"""Market-share pairs as Cartesian coordinates of points on the boundary."""
|
|
33
|
+
|
|
34
|
+
area: float
|
|
35
|
+
"""Area under the boundary."""
|
|
36
|
+
|
|
37
|
+
|
|
20
38
|
@frozen
|
|
21
39
|
class INVTableData:
|
|
22
40
|
industry_group: str
|
|
@@ -29,6 +47,7 @@ type INVData = MappingProxyType[
|
|
|
29
47
|
]
|
|
30
48
|
type INVData_in = Mapping[str, Mapping[str, Mapping[str, INVTableData]]]
|
|
31
49
|
|
|
50
|
+
yamelize_attrs(INVTableData)
|
|
32
51
|
|
|
33
52
|
(_, _) = (
|
|
34
53
|
this_yaml.representer.add_representer(
|
|
@@ -59,16 +78,26 @@ type INVData_in = Mapping[str, Mapping[str, Mapping[str, INVTableData]]]
|
|
|
59
78
|
),
|
|
60
79
|
)
|
|
61
80
|
|
|
81
|
+
_, _ = (
|
|
82
|
+
this_yaml.representer.add_representer(
|
|
83
|
+
MappingProxyType,
|
|
84
|
+
lambda _r, _d: _r.represent_mapping("!mappingproxy", dict(_d.items())),
|
|
85
|
+
),
|
|
86
|
+
this_yaml.constructor.add_constructor(
|
|
87
|
+
"!mappingproxy", lambda _c, _n: MappingProxyType(dict(**yaml_rt_mapper(_c, _n)))
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
|
|
62
91
|
|
|
63
92
|
def _dict_from_mapping(_p: Mapping[Any, Any], /) -> dict[Any, Any]:
|
|
64
|
-
retval = {}
|
|
93
|
+
retval: dict[Any, Any] = {}
|
|
65
94
|
for _k, _v in _p.items(): # for subit in it:
|
|
66
95
|
retval |= {_k: _dict_from_mapping(_v)} if isinstance(_v, Mapping) else {_k: _v}
|
|
67
96
|
return retval
|
|
68
97
|
|
|
69
98
|
|
|
70
99
|
def _mappingproxy_from_mapping(_p: Mapping[Any, Any], /) -> MappingProxyType[Any, Any]:
|
|
71
|
-
retval = {}
|
|
100
|
+
retval: dict[Any, Any] = {}
|
|
72
101
|
for _k, _v in _p.items(): # for subit in it:
|
|
73
102
|
retval |= (
|
|
74
103
|
{_k: _mappingproxy_from_mapping(_v)}
|
|
@@ -76,34 +105,3 @@ def _mappingproxy_from_mapping(_p: Mapping[Any, Any], /) -> MappingProxyType[Any
|
|
|
76
105
|
else {_k: _v}
|
|
77
106
|
)
|
|
78
107
|
return MappingProxyType(retval)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
_, _ = (
|
|
82
|
-
this_yaml.representer.add_representer(
|
|
83
|
-
MappingProxyType,
|
|
84
|
-
lambda _r, _d: _r.represent_mapping("!mappingproxy", dict(_d.items())),
|
|
85
|
-
),
|
|
86
|
-
this_yaml.constructor.add_constructor(
|
|
87
|
-
"!mappingproxy", lambda _c, _n: MappingProxyType(yaml_rt_mapper(_c, _n))
|
|
88
|
-
),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for _typ in (INVTableData,):
|
|
93
|
-
_, _ = (
|
|
94
|
-
this_yaml.representer.add_representer(
|
|
95
|
-
_typ,
|
|
96
|
-
lambda _r, _d: _r.represent_mapping(
|
|
97
|
-
f"!{_d.__class__.__name__}",
|
|
98
|
-
{
|
|
99
|
-
_a.name: getattr(_d, _a.name)
|
|
100
|
-
for _a in _d.__attrs_attrs__
|
|
101
|
-
if _a.name not in {"coordinates", "area"}
|
|
102
|
-
},
|
|
103
|
-
),
|
|
104
|
-
),
|
|
105
|
-
this_yaml.constructor.add_constructor(
|
|
106
|
-
f"!{_typ.__name__}",
|
|
107
|
-
lambda _c, _n: globals()[_n.tag.lstrip("!")](**yaml_rt_mapper(_c, _n)),
|
|
108
|
-
),
|
|
109
|
-
)
|
{mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/empirical_margin_distribution.py
RENAMED
|
@@ -39,7 +39,6 @@ price-cost margins fall in the interval :math:`[0, 1]`.
|
|
|
39
39
|
import shutil
|
|
40
40
|
import zipfile
|
|
41
41
|
from collections.abc import Mapping
|
|
42
|
-
from importlib import resources
|
|
43
42
|
from pathlib import Path
|
|
44
43
|
from types import MappingProxyType
|
|
45
44
|
|
|
@@ -49,12 +48,17 @@ from numpy.random import PCG64DXSM, Generator, SeedSequence
|
|
|
49
48
|
from scipy import stats # type: ignore
|
|
50
49
|
from xlrd import open_workbook # type: ignore
|
|
51
50
|
|
|
52
|
-
from .. import
|
|
51
|
+
from .. import VERSION, ArrayDouble, this_yaml # noqa: TID252
|
|
52
|
+
from .. import WORK_DIR as PKG_WORK_DIR # noqa: TID252
|
|
53
|
+
from .. import data as mdat # noqa: TID252
|
|
53
54
|
from . import _mappingproxy_from_mapping
|
|
54
55
|
|
|
55
56
|
__version__ = VERSION
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
WORK_DIR = globals().get("WORK_DIR", PKG_WORK_DIR)
|
|
59
|
+
"""Redefined, in case the user defines WORK_DIR betweeen module imports."""
|
|
60
|
+
|
|
61
|
+
MGNDATA_ARCHIVE_PATH = WORK_DIR / "damodaran_margin_data_serialized.zip"
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
u3pm = urllib3.PoolManager()
|
|
@@ -74,14 +78,14 @@ def margin_data_getter( # noqa: PLR0912
|
|
|
74
78
|
data_archive_path = data_archive_path or MGNDATA_ARCHIVE_PATH
|
|
75
79
|
workbook_path = data_archive_path.parent / f"damodaran_{_table_name}_data.xls"
|
|
76
80
|
if data_archive_path.is_file() and not data_download_flag:
|
|
77
|
-
# with data_archive_path_.open("r") as _yfh:
|
|
78
|
-
# margin_data_dict: dict[str, dict[str, float | int]] = this_yaml.load(_yfh)
|
|
79
81
|
with (
|
|
80
82
|
zipfile.ZipFile(data_archive_path) as _yzip,
|
|
81
83
|
_yzip.open(f"{data_archive_path.stem}.yaml") as _yfh,
|
|
82
84
|
):
|
|
83
|
-
margin_data_dict:
|
|
84
|
-
|
|
85
|
+
margin_data_dict: MappingProxyType[
|
|
86
|
+
str, MappingProxyType[str, float | int]
|
|
87
|
+
] = this_yaml.load(_yfh)
|
|
88
|
+
return margin_data_dict
|
|
85
89
|
elif workbook_path.is_file():
|
|
86
90
|
workbook_path.unlink()
|
|
87
91
|
if data_archive_path.is_file():
|
|
@@ -116,19 +120,14 @@ def margin_data_getter( # noqa: PLR0912
|
|
|
116
120
|
"Using bundled copy."
|
|
117
121
|
)
|
|
118
122
|
if not workbook_path.is_file():
|
|
119
|
-
|
|
120
|
-
resources.files(f"{_PKG_NAME}.data").joinpath(
|
|
121
|
-
"empirical_margin_distribution.xls"
|
|
122
|
-
)
|
|
123
|
-
) as margin_data_archive_path:
|
|
124
|
-
shutil.copy2(margin_data_archive_path, workbook_path)
|
|
123
|
+
shutil.copy2(mdat.DAMODARAN_MARGIN_WORKBOOK, workbook_path)
|
|
125
124
|
else:
|
|
126
125
|
raise error_
|
|
127
126
|
|
|
128
127
|
xl_book = open_workbook(workbook_path, ragged_rows=True, on_demand=True)
|
|
129
128
|
xl_sheet = xl_book.sheet_by_name("Industry Averages")
|
|
130
129
|
|
|
131
|
-
|
|
130
|
+
margin_dict_in: dict[str, dict[str, float | int]] = {}
|
|
132
131
|
row_keys: list[str] = []
|
|
133
132
|
read_row_flag = False
|
|
134
133
|
for _ridx in range(xl_sheet.nrows):
|
|
@@ -142,15 +141,16 @@ def margin_data_getter( # noqa: PLR0912
|
|
|
142
141
|
continue
|
|
143
142
|
|
|
144
143
|
xl_row[1] = int(xl_row[1])
|
|
145
|
-
|
|
144
|
+
margin_dict_in[xl_row[0]] = dict(zip(row_keys[1:], xl_row[1:], strict=True))
|
|
146
145
|
|
|
146
|
+
margin_dict = _mappingproxy_from_mapping(margin_dict_in)
|
|
147
147
|
with (
|
|
148
148
|
zipfile.ZipFile(data_archive_path, "w") as _yzip,
|
|
149
149
|
_yzip.open(f"{data_archive_path.stem}.yaml", "w") as _yfh,
|
|
150
150
|
):
|
|
151
151
|
this_yaml.dump(margin_dict, _yfh)
|
|
152
152
|
|
|
153
|
-
return
|
|
153
|
+
return margin_dict
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
def margin_data_builder(
|
|
@@ -12,8 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import re
|
|
14
14
|
import shutil
|
|
15
|
-
from collections.abc import Sequence
|
|
16
|
-
from importlib import resources
|
|
15
|
+
from collections.abc import Mapping, Sequence
|
|
17
16
|
from operator import itemgetter
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from types import MappingProxyType
|
|
@@ -26,14 +25,9 @@ import urllib3
|
|
|
26
25
|
from bs4 import BeautifulSoup
|
|
27
26
|
from numpy.testing import assert_array_equal
|
|
28
27
|
|
|
29
|
-
from .. import
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
EMPTY_ARRAYINT,
|
|
33
|
-
VERSION,
|
|
34
|
-
ArrayBIGINT,
|
|
35
|
-
this_yaml,
|
|
36
|
-
)
|
|
28
|
+
from .. import EMPTY_ARRAYINT, VERSION, ArrayBIGINT, this_yaml # noqa: TID252
|
|
29
|
+
from .. import WORK_DIR as PKG_WORK_DIR # noqa: TID252
|
|
30
|
+
from .. import data as mdat # noqa: TID252
|
|
37
31
|
from . import (
|
|
38
32
|
INVData,
|
|
39
33
|
INVData_in,
|
|
@@ -46,21 +40,16 @@ __version__ = VERSION
|
|
|
46
40
|
|
|
47
41
|
m.patch()
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
FTCDATA_DIR.mkdir(parents=True)
|
|
43
|
+
WORK_DIR = globals().get("WORK_DIR", PKG_WORK_DIR)
|
|
44
|
+
"""Redefined, in case the user defines WORK_DIR betweeen module imports."""
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
).is_file()
|
|
61
|
-
):
|
|
62
|
-
with resources.as_file(_bundled_copy) as _bundled_copy_path:
|
|
63
|
-
shutil.copy2(_bundled_copy_path, INVDATA_ARCHIVE_PATH)
|
|
46
|
+
FID_WORK_DIR = WORK_DIR / "FTCData"
|
|
47
|
+
if not FID_WORK_DIR.is_dir():
|
|
48
|
+
FID_WORK_DIR.mkdir(parents=True)
|
|
49
|
+
|
|
50
|
+
INVDATA_ARCHIVE_PATH = WORK_DIR / mdat.FTC_MERGER_INVESTIGATIONS_DATA.name
|
|
51
|
+
if not INVDATA_ARCHIVE_PATH.is_file():
|
|
52
|
+
shutil.copy2(mdat.FTC_MERGER_INVESTIGATIONS_DATA, INVDATA_ARCHIVE_PATH)
|
|
64
53
|
|
|
65
54
|
TABLE_NO_RE = re.compile(r"Table \d+\.\d+")
|
|
66
55
|
TABLE_TYPES = ("ByHHIandDelta", "ByFirmCount")
|
|
@@ -105,6 +94,10 @@ CNT_FCOUNT_DICT = {
|
|
|
105
94
|
}
|
|
106
95
|
|
|
107
96
|
|
|
97
|
+
def reverse_map(_dict: Mapping[Any, Any]) -> Mapping[Any, Any]:
|
|
98
|
+
return {_v: _k for _k, _v in _dict.items()}
|
|
99
|
+
|
|
100
|
+
|
|
108
101
|
def construct_data(
|
|
109
102
|
_archive_path: Path = INVDATA_ARCHIVE_PATH,
|
|
110
103
|
*,
|
|
@@ -442,12 +435,12 @@ def _parse_invdata() -> INVData:
|
|
|
442
435
|
# )
|
|
443
436
|
import pymupdf # type: ignore # noqa: PLC0415
|
|
444
437
|
|
|
445
|
-
invdata_docnames = _download_invdata(
|
|
438
|
+
invdata_docnames = _download_invdata(FID_WORK_DIR)
|
|
446
439
|
|
|
447
440
|
invdata: INVData_in = {}
|
|
448
441
|
|
|
449
442
|
for invdata_docname in invdata_docnames:
|
|
450
|
-
invdata_pdf_path =
|
|
443
|
+
invdata_pdf_path = FID_WORK_DIR.joinpath(invdata_docname)
|
|
451
444
|
|
|
452
445
|
invdata_doc = pymupdf.open(invdata_pdf_path)
|
|
453
446
|
invdata_meta = invdata_doc.metadata
|
|
@@ -709,7 +702,7 @@ def _process_table_blks_cnt_type(
|
|
|
709
702
|
return invdata_array[np.argsort(invdata_array[:, 0])]
|
|
710
703
|
|
|
711
704
|
|
|
712
|
-
def _download_invdata(_dl_path: Path =
|
|
705
|
+
def _download_invdata(_dl_path: Path = FID_WORK_DIR) -> tuple[str, ...]:
|
|
713
706
|
if not _dl_path.is_dir():
|
|
714
707
|
_dl_path.mkdir(parents=True)
|
|
715
708
|
|
{mergeron-2025.739290.5 → mergeron-2025.739290.7}/src/mergeron/core/guidelines_boundaries.py
RENAMED
|
@@ -51,14 +51,12 @@ class GuidelinesThresholds:
|
|
|
51
51
|
|
|
52
52
|
ΔHHI, Recapture Ratio, GUPPI, Diversion ratio, CMCR, and IPR thresholds
|
|
53
53
|
constructed from concentration standards in Guidelines published in
|
|
54
|
-
|
|
54
|
+
1992, 2010, and 2023.
|
|
55
55
|
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
58
|
pub_year: HMGPubYear = field(
|
|
59
|
-
kw_only=False,
|
|
60
|
-
default=2023,
|
|
61
|
-
validator=validators.in_([1982, 1984, 1992, 2010, 2023]),
|
|
59
|
+
kw_only=False, default=2023, validator=validators.in_([1992, 2010, 2023])
|
|
62
60
|
)
|
|
63
61
|
"""
|
|
64
62
|
Year of publication of the Guidelines
|
|
@@ -97,9 +95,7 @@ class GuidelinesThresholds:
|
|
|
97
95
|
# thus, here, the tentative delta safeharbor under
|
|
98
96
|
# the 2023 Guidelines is 100 points
|
|
99
97
|
hhi_p, dh_s, dh_p = {
|
|
100
|
-
|
|
101
|
-
1984: _s1982,
|
|
102
|
-
1992: _s1982,
|
|
98
|
+
1992: (0.18, 0.005, 0.01),
|
|
103
99
|
2010: (0.25, 0.01, 0.02),
|
|
104
100
|
2023: (0.18, 0.01, 0.01),
|
|
105
101
|
}[self.pub_year]
|
|
@@ -150,6 +146,15 @@ class GuidelinesThresholds:
|
|
|
150
146
|
class ConcentrationBoundary:
|
|
151
147
|
"""Concentration parameters, boundary coordinates, and area under concentration boundary."""
|
|
152
148
|
|
|
149
|
+
threshold: float = field(kw_only=False, default=0.01)
|
|
150
|
+
|
|
151
|
+
@threshold.validator
|
|
152
|
+
def _tv(
|
|
153
|
+
_instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
|
|
154
|
+
) -> None:
|
|
155
|
+
if not 0 <= _value <= 1:
|
|
156
|
+
raise ValueError("Concentration threshold must lie between 0 and 1.")
|
|
157
|
+
|
|
153
158
|
measure_name: Literal[
|
|
154
159
|
"ΔHHI",
|
|
155
160
|
"Combined share",
|
|
@@ -169,17 +174,8 @@ class ConcentrationBoundary:
|
|
|
169
174
|
}:
|
|
170
175
|
raise ValueError(f"Invalid name for a concentration measure, {_value!r}.")
|
|
171
176
|
|
|
172
|
-
threshold: float = field(kw_only=False, default=0.01)
|
|
173
|
-
|
|
174
|
-
@threshold.validator
|
|
175
|
-
def _tv(
|
|
176
|
-
_instance: ConcentrationBoundary, _attribute: Attribute[float], _value: float, /
|
|
177
|
-
) -> None:
|
|
178
|
-
if not 0 <= _value <= 1:
|
|
179
|
-
raise ValueError("Concentration threshold must lie between 0 and 1.")
|
|
180
|
-
|
|
181
177
|
precision: int = field(
|
|
182
|
-
kw_only=
|
|
178
|
+
kw_only=True, default=5, validator=validators.instance_of(int)
|
|
183
179
|
)
|
|
184
180
|
|
|
185
181
|
area: float = field(init=False, kw_only=True)
|
|
@@ -240,7 +236,7 @@ class DiversionRatioBoundary:
|
|
|
240
236
|
)
|
|
241
237
|
|
|
242
238
|
recapture_form: RECForm | None = field(kw_only=True, default=RECForm.INOUT)
|
|
243
|
-
"""
|
|
239
|
+
R"""
|
|
244
240
|
The form of the recapture ratio.
|
|
245
241
|
|
|
246
242
|
When :attr:`mergeron.RECForm.INOUT`, the recapture ratio for
|
|
@@ -251,12 +247,17 @@ class DiversionRatioBoundary:
|
|
|
251
247
|
constructed from the generated purchase-probabilities for products in
|
|
252
248
|
the market and for the outside good, specify :attr:`mergeron.RECForm.OUTIN`.)
|
|
253
249
|
|
|
254
|
-
The GUPPI boundary is a continuum of diversion ratio boundaries
|
|
255
|
-
|
|
256
|
-
|
|
250
|
+
The GUPPI boundary is a continuum of conditional diversion ratio boundaries,
|
|
251
|
+
|
|
252
|
+
.. math::
|
|
253
|
+
|
|
254
|
+
d_{ij} \vert_{p_i, p_j, m_j} \triangleq \frac{g_i p_i}{m_j p_j} = \overline{d}
|
|
255
|
+
|
|
256
|
+
with :math:`d_{ij}` the diversion ratio from product :math:`i` to product :math:`j`;
|
|
257
257
|
:math:`g_i` the GUPPI for product :math:`i`;
|
|
258
|
-
:math:`m_j` the margin
|
|
259
|
-
:math:`p_i, p_j` the prices of goods :math:`i, j`, respectively
|
|
258
|
+
:math:`m_j` the price-cost margin on product :math:`j`;
|
|
259
|
+
:math:`p_i, p_j` the prices of goods :math:`i, j`, respectively; and
|
|
260
|
+
:math:`\overline{d}` the diversion ratio threshold (i.e., bound).
|
|
260
261
|
|
|
261
262
|
"""
|
|
262
263
|
|
|
@@ -283,7 +284,7 @@ class DiversionRatioBoundary:
|
|
|
283
284
|
|
|
284
285
|
agg_method: UPPAggrSelector = field(
|
|
285
286
|
kw_only=True,
|
|
286
|
-
default=UPPAggrSelector.
|
|
287
|
+
default=UPPAggrSelector.MIN,
|
|
287
288
|
validator=validators.instance_of(UPPAggrSelector),
|
|
288
289
|
)
|
|
289
290
|
"""
|