mergeron 2025.739355.19__tar.gz → 2025.739413.1__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.

Files changed (20) hide show
  1. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/PKG-INFO +1 -1
  2. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/pyproject.toml +3 -3
  3. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/__init__.py +39 -1
  4. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/empirical_margin_distribution.py +6 -5
  5. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/ftc_merger_investigations_data.py +4 -3
  6. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/guidelines_boundaries.py +12 -10
  7. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/guidelines_boundary_functions.py +4 -4
  8. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/data/__init__.py +1 -1
  9. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/gen/__init__.py +2 -1
  10. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/gen/data_generation_functions.py +2 -2
  11. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/README.rst +0 -0
  12. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/__init__.py +0 -0
  13. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/guidelines_boundary_functions_extra.py +0 -0
  14. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/core/pseudorandom_numbers.py +0 -0
  15. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/data/damodaran_margin_data_serialized.zip +0 -0
  16. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/data/ftc_merger_investigations_data.zip +0 -0
  17. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/gen/data_generation.py +0 -0
  18. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/gen/enforcement_stats.py +0 -0
  19. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/gen/upp_tests.py +0 -0
  20. {mergeron-2025.739355.19 → mergeron-2025.739413.1}/src/mergeron/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mergeron
3
- Version: 2025.739355.19
3
+ Version: 2025.739413.1
4
4
  Summary: Python for analyzing merger enforcement policy
5
5
  License: MIT
6
6
  Keywords: merger enforcement policy,merger guidelines,merger screening,enforcement presumptions,concentration standards,diversion ratio,upward pricing pressure,GUPPI
@@ -15,7 +15,7 @@ keywords = [
15
15
  "upward pricing pressure",
16
16
  "GUPPI",
17
17
  ]
18
- version = "2025.739355.19"
18
+ version = "2025.739413.1"
19
19
  requires-python = ">=3.12,<4.0"
20
20
 
21
21
  # Classifiers list: https://pypi.org/classifiers/
@@ -189,6 +189,8 @@ preview = true
189
189
  cache_fine_grained = true
190
190
  ignore_missing_imports = false
191
191
  strict = true
192
+ local_partial_types = true
193
+ allow_redefinition_new = true
192
194
  enable_incomplete_feature = ["PreciseTupleTypes"]
193
195
 
194
196
  show_column_numbers = true
@@ -200,8 +202,6 @@ no_implicit_reexport = true
200
202
 
201
203
  allow_redefinition = true
202
204
 
203
- plugins = "numpy.typing.mypy_plugin"
204
-
205
205
  [tool.pytest.ini_options]
206
206
  log_auto_indent = 4
207
207
  minversion = "8.0"
@@ -15,7 +15,7 @@ from ruamel import yaml
15
15
 
16
16
  _PKG_NAME: str = Path(__file__).parent.name
17
17
 
18
- VERSION = "2025.739355.19"
18
+ VERSION = "2025.739413.1"
19
19
 
20
20
  __version__ = VERSION
21
21
 
@@ -51,6 +51,44 @@ type ArrayDouble = NDArray[np.float64]
51
51
  type ArrayBIGINT = NDArray[np.int64]
52
52
 
53
53
 
54
+ def allclose(
55
+ _a: ArrayFloat | ArrayINT | float | int,
56
+ _b: ArrayFloat | ArrayINT | float | int,
57
+ /,
58
+ *,
59
+ rtol: float = 1e-14,
60
+ atol: float = 1e-15,
61
+ equal_nan: bool = True,
62
+ ) -> bool:
63
+ """Redefine native numpy function with updated default tolerances."""
64
+ return np.allclose(_a, _b, atol=atol, rtol=rtol, equal_nan=equal_nan)
65
+
66
+
67
+ def assert_allclose( # noqa: PLR0913
68
+ _a: ArrayFloat | ArrayINT | float | int,
69
+ _b: ArrayFloat | ArrayINT | float | int,
70
+ /,
71
+ *,
72
+ rtol: float = 1e-14,
73
+ atol: float = 1e-15,
74
+ equal_nan: bool = True,
75
+ err_msg: str = "",
76
+ verbose: bool = False,
77
+ strict: bool = True,
78
+ ) -> None:
79
+ """Redefine native numpy function with updated default tolerances, type-enforcing."""
80
+ return np.testing.assert_allclose(
81
+ _a,
82
+ _b,
83
+ atol=1e-15,
84
+ rtol=1e-14,
85
+ equal_nan=equal_nan,
86
+ err_msg=err_msg,
87
+ verbose=verbose,
88
+ strict=True,
89
+ )
90
+
91
+
54
92
  this_yaml = yaml.YAML(typ="rt")
55
93
  this_yaml.indent(mapping=2, sequence=4, offset=2)
56
94
 
@@ -221,22 +221,23 @@ def margin_data_getter(
221
221
  elif data_download_flag or not list(data_archive_path.glob("margin*.xls")):
222
222
  margin_data_downloader()
223
223
 
224
- # Whitespace cleanup
225
- ws_pat = re.compile(r"\s+")
226
-
227
224
  # Parse workbooks and save margin data dictionary
228
225
  margin_data_: dict[str, dict[str, MappingProxyType[str, float]]] = {}
229
226
  for _p in (WORK_DIR / "damodaran_margin_data_archive").iterdir():
230
227
  xl_wbk = CalamineWorkbook.from_path(_p)
231
228
  xl_wks = xl_wbk.get_sheet_by_index(
232
- 0 if (_p.stem != "margin" and int(_p.stem[-2:]) in {17, 18, 19}) else 1
229
+ 0
230
+ if (_p.stem.startswith("margin") and _p.stem[-2:] in {"17", "18", "19"})
231
+ else 1
233
232
  ).to_python()
234
233
  if xl_wks[8][2] != "Gross Margin":
235
234
  raise ValueError("Worksheet does not match expected layout.")
236
235
  row_keys: list[str] = [_c.upper() for _c in xl_wks[8][1:]] # type: ignore
237
236
 
238
237
  _u = xl_wks[0][1]
239
- if not isinstance(_u, datetime.datetime):
238
+ if not isinstance(_u, datetime.date):
239
+ print(_u)
240
+ print(xl_wks[:8])
240
241
  raise ValueError("Worksheet does not match expected layout.")
241
242
  update: str = _u.isoformat()[:10]
242
243
 
@@ -434,7 +434,7 @@ def _parse_invdata() -> INVData:
434
434
  " the source code as well as to install an additional package"
435
435
  " not distributed with this package or identified as a requirement."
436
436
  )
437
- import pymupdf # type: ignore # noqa: PLC0415
437
+ import pymupdf # type: ignore
438
438
 
439
439
  invdata_docnames = _download_invdata(FID_WORK_DIR)
440
440
 
@@ -656,9 +656,10 @@ def _process_table_blks_conc_type(
656
656
  # Check column totals
657
657
  for _col_tot in col_totals:
658
658
  assert_array_equal(
659
- _col_tot[2:],
659
+ _col_tot[2:], # type: ignore
660
660
  np.einsum(
661
- "ij->j", invdata_array[invdata_array[:, 1] == _col_tot[1]][:, 2:]
661
+ "ij->j",
662
+ invdata_array[invdata_array[:, 1] == _col_tot[1]][:, 2:], # type: ignore
662
663
  ),
663
664
  )
664
665
 
@@ -327,23 +327,25 @@ class DiversionRatioBoundary:
327
327
  share_ratio = critical_share_ratio(
328
328
  self.diversion_ratio, r_bar=self.recapture_ratio
329
329
  )
330
- upp_agg_kwargs: gbfn.ShareRatioBoundaryKeywords = {
331
- "recapture_form": getattr(self.recapture_form, "value", "inside-out"),
332
- "dps": self.precision,
333
- }
330
+
331
+ upp_agg_kwargs: gbfn.ShareRatioBoundaryKeywords = {"dps": self.precision}
332
+ if self.agg_method != UPPAggrSelector.MAX:
333
+ upp_agg_kwargs |= {
334
+ "recapture_form": getattr(self.recapture_form, "value", "inside-out")
335
+ }
334
336
 
335
337
  match self.agg_method:
336
338
  case UPPAggrSelector.DIS:
337
339
  upp_agg_fn = gbfn.diversion_share_boundary_wtd_avg
338
340
  upp_agg_kwargs |= {"agg_method": "distance", "weighting": None}
339
341
  case UPPAggrSelector.AVG:
340
- upp_agg_fn = gbfn.diversion_share_boundary_xact_avg # type: ignore
342
+ upp_agg_fn = gbfn.diversion_share_boundary_xact_avg
341
343
  case UPPAggrSelector.MAX:
342
- upp_agg_fn = gbfn.diversion_share_boundary_max # type: ignore
343
- upp_agg_kwargs = {"dps": 10} # replace here
344
+ upp_agg_fn = gbfn.diversion_share_boundary_max
345
+ upp_agg_kwargs |= {"dps": 10}
344
346
  case UPPAggrSelector.MIN:
345
- upp_agg_fn = gbfn.diversion_share_boundary_min # type: ignore
346
- upp_agg_kwargs |= {"dps": 10} # update here
347
+ upp_agg_fn = gbfn.diversion_share_boundary_min
348
+ upp_agg_kwargs |= {"dps": 10}
347
349
  case _:
348
350
  upp_agg_fn = gbfn.diversion_share_boundary_wtd_avg
349
351
 
@@ -365,7 +367,7 @@ class DiversionRatioBoundary:
365
367
 
366
368
  upp_agg_kwargs |= {"agg_method": aggregator_, "weighting": wgt_type}
367
369
 
368
- boundary_ = upp_agg_fn(share_ratio, self.recapture_ratio, **upp_agg_kwargs)
370
+ boundary_ = upp_agg_fn(share_ratio, self.recapture_ratio, **upp_agg_kwargs) # type: ignore
369
371
  object.__setattr__(self, "area", boundary_.area)
370
372
  object.__setattr__(self, "coordinates", boundary_.coordinates)
371
373
 
@@ -481,7 +481,7 @@ def diversion_share_boundary_xact_avg(
481
481
  _bdry_start = np.array([(_s_mid, _s_mid)])
482
482
  _s_1 = np.arange(_s_mid - _step_size, 0, -_step_size)
483
483
  if recapture_form == "inside-out":
484
- _s_intcpt: float = (
484
+ _s_intcpt = (
485
485
  2 * _delta_star * _r_val + 1 - np.abs(2 * _delta_star * _r_val - 1)
486
486
  ) / (2 * _r_val)
487
487
  nr_t1: ArrayDouble = (
@@ -523,11 +523,11 @@ def diversion_share_boundary_xact_avg(
523
523
  f"with recapture spec, {f'"{recapture_form}"'} is incorrect."
524
524
  )
525
525
 
526
- s_2: ArrayDouble = (nr_t1 - nr_t2_s1**0.5) / (2 * _r_val)
526
+ s_2 = (nr_t1 - nr_t2_s1**0.5) / (2 * _r_val)
527
527
 
528
528
  else:
529
- _s_intcpt: float = _delta_star + 1 / 2 - np.abs(_delta_star - 1 / 2)
530
- s_2: ArrayDouble = (
529
+ _s_intcpt = _delta_star + 1 / 2 - np.abs(_delta_star - 1 / 2)
530
+ s_2 = (
531
531
  0.5
532
532
  + _delta_star
533
533
  - _delta_star * _s_1
@@ -23,7 +23,7 @@ Use for replication/testing.
23
23
  NOTES
24
24
  -----
25
25
  Source data are from Prof. Aswath Damodaran, Stern School of Business, NYU; available online
26
- at https://pages.stern.nyu.edu/~adamodar/pc/datasets/margin.xls
26
+ at https://pages.stern.nyu.edu/~adamodar/pc/datasets/margin.xls
27
27
  and https://pages.stern.nyu.edu/~adamodar/pc/archives/margin*.xls.
28
28
 
29
29
  Gross margins are reported in 2017 data and later.
@@ -25,6 +25,7 @@ from .. import ( # noqa: TID252
25
25
  Enameled,
26
26
  RECForm,
27
27
  UPPAggrSelector,
28
+ allclose,
28
29
  this_yaml,
29
30
  yamelize_attrs,
30
31
  )
@@ -350,7 +351,7 @@ class PCMSpec:
350
351
 
351
352
  dist_parms: ArrayFloat = field(
352
353
  kw_only=True,
353
- eq=cmp_using(eq=np.array_equal),
354
+ eq=cmp_using(eq=allclose),
354
355
  converter=Converter(_pcm_dp_conv, takes_self=True), # type: ignore
355
356
  )
356
357
  """Parameter specification for tailoring PCM distribution
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Literal
6
6
 
7
- import numexpr as ne
7
+ import numexpr as ne # type: ignore
8
8
  import numpy as np
9
9
  from attrs import evolve
10
10
  from numpy.random import SeedSequence
@@ -386,7 +386,7 @@ def gen_market_shares_dirichlet(
386
386
  aggregate_purchase_prob_ = np.empty((_s_size, 1), float)
387
387
  aggregate_purchase_prob_.fill(np.nan)
388
388
  if _recapture_form == RECForm.OUTIN:
389
- aggregate_purchase_prob_ = 1 - mktshr_array[:, [-1]] # type: ignore
389
+ aggregate_purchase_prob_ = 1 - mktshr_array[:, [-1]]
390
390
  mktshr_array = mktshr_array[:, :-1] / aggregate_purchase_prob_
391
391
 
392
392
  return ShareSampleData(