cooptools 1.76__tar.gz → 1.77__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.
- {cooptools-1.76 → cooptools-1.77}/PKG-INFO +1 -1
- cooptools-1.77/cooptools/currency.py +154 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/PKG-INFO +1 -1
- {cooptools-1.76 → cooptools-1.77}/setup.py +1 -1
- cooptools-1.77/tests/test_currency.py +116 -0
- cooptools-1.76/cooptools/currency.py +0 -134
- cooptools-1.76/tests/test_currency.py +0 -31
- {cooptools-1.76 → cooptools-1.77}/MANIFEST.in +0 -0
- {cooptools-1.76 → cooptools-1.77}/README.md +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/anchor.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/asyncable.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/cli/CliAtomicUserInteraction.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/cli/CliMenu.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/cli/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/cli/fileContentReturn.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/cnxn_info.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/colors.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/commandDesignPattern/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/commandDesignPattern/commandController.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/commandDesignPattern/commandProtocol.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/commandDesignPattern/exceptions.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/common.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/config.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/coopDataclass.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/coopEnum.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/coopthreading.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataRefresher/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataRefresher/dataHub.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataRefresher/dataRefresher.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataStore/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataStore/dataProcessor.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataStore/dataStoreProtocol.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataStore/dbConnectionURI.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dataStore/inMemoryDataStore.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/date_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/decay.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/decor.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/dictPolicies.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/exceptions.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/expertise/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/expertise/expertiseArgs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/expertise/expertiseSchedules.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/dicounted_cashflow_analysis.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/fund_projection.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/fund_projection_renderer.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/futureValueProjector.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/growth_projections.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/reports.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/finance/utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/circle_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/common.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/curve_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/curves.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/line_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/polygon_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/rect_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/triangle_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/geometry_utils/vector_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/astar_results_viewer.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/draw.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/flow_field.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/graph.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/graph_dcs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/graph_definitions.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/graphs/utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/actionItemTracker/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/actionItemTracker/ai_tracker.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/actionItemTracker/dcs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/discreteEventSimulator/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/discreteEventSimulator/discreteEventSimulator.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/jsonIO.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/metrics.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/triggerActionSystem/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/triggerActionSystem/signal.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/ideas/triggerActionSystem/trigger.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/loggingHelpers.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/marchingSquares.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/materialHandling/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/materialHandling/cli.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/materialHandling/dcs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/materialHandling/selectionCriteria.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/matrixManipulation.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/os_manip.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pandasHelpers.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/catmullRomFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/demo/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/demo/server.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/kinematicGoalSeeker.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/motion_profile_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/orbit_fuzzer.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/path_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/straightLineFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/trajectory_provider.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/waypoint.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/pathing/waypointFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/physics/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/physics/kinematic.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/physics/kinematic_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/physics/movement.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/physics/trajectory.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/plotting.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/printing.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/protocols.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/qualifiers/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/qualifiers/cli.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/qualifiers/qualifier.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/randoms.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/register.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/reservation/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/reservation/dcs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/reservation/enums.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/reservation/reservationmanager.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/retry.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/grids/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/grids/gridState.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/grids/grid_base.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/grids/hexGrid.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/grids/rectGrid.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/hex_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/sect_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/sectorTree/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/sectors/sectorTree/sectorTree.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/activityReport.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/controlChart/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/controlChart/controlChart.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/controlChart/plotting.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/oee/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/statistics/oee/oeeHub.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/taskProcessing/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/taskProcessing/dcs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/taskProcessing/taskProcessor.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/timeTracker/__init__.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/timeTracker/decay.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/timeTracker/timeTracker.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/timeWindow.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/toggles.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/transform.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/trends.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/typeProviders.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/typevalidation.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools/version.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/SOURCES.txt +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/dependency_links.txt +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/not-zip-safe +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/requires.txt +0 -0
- {cooptools-1.76 → cooptools-1.77}/cooptools.egg-info/top_level.txt +0 -0
- {cooptools-1.76 → cooptools-1.77}/requirements.txt +0 -0
- {cooptools-1.76 → cooptools-1.77}/setup.cfg +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_CliAtomicUserInteraction.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_asyncable.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_catmullRomFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_cdp_bugs.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_commandDesignPattern.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_common.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_config.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_coopEnum.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_curves.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_date_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_flow_field.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_flow_field_perf.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_fund_projection.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_graph_node_classification.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_kinematicGoalSeeker.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_kinematic_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_os_manip.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_pandas.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_path_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_printing.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_qualifiers.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_sectors.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_sectors_hex.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_sectors_sectorTree.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_statistics.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_straightLineFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_target_seek.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_tasks.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_toggles.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_trajectory.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_trends.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_typevalidation.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_vector_utils.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_version.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/test_waypointFollower.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/tests_graph.py +0 -0
- {cooptools-1.76 → cooptools-1.77}/tests/tests_gridsystem.py +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from dataclasses import dataclass, asdict
|
|
3
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
4
|
+
|
|
5
|
+
CENTS_PER_DOLLAR = 100
|
|
6
|
+
_CENT = Decimal('0.01')
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class USD:
|
|
10
|
+
dollars: int
|
|
11
|
+
cents: int
|
|
12
|
+
|
|
13
|
+
def __post_init__(self):
|
|
14
|
+
""" Canonicalizes to a single representation per amount so that equality and hashing agree
|
|
15
|
+
with value. Both fields carry the sign of the total, and cents always lands in (-100, 100)."""
|
|
16
|
+
total_cents = self.dollars * CENTS_PER_DOLLAR + self.cents
|
|
17
|
+
sign = -1 if total_cents < 0 else 1
|
|
18
|
+
dollars, cents = divmod(abs(total_cents), CENTS_PER_DOLLAR)
|
|
19
|
+
object.__setattr__(self, "dollars", sign * dollars)
|
|
20
|
+
object.__setattr__(self, "cents", sign * cents)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def zero(cls):
|
|
24
|
+
return USD(dollars=0, cents=0)
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_val(cls, val: float | str | int | Decimal):
|
|
28
|
+
if isinstance(val, USD):
|
|
29
|
+
return val
|
|
30
|
+
|
|
31
|
+
# bool is a subclass of int, but a truth value is never an amount of money
|
|
32
|
+
if isinstance(val, bool):
|
|
33
|
+
raise TypeError(f'{val} [{type(val)}] not supported to be converted to CurrencySchema')
|
|
34
|
+
|
|
35
|
+
if isinstance(val, float):
|
|
36
|
+
# str() of a float is the shortest repr that round-trips, which recovers the decimal
|
|
37
|
+
# the caller actually meant rather than the binary approximation stored for it
|
|
38
|
+
dec = Decimal(str(val))
|
|
39
|
+
elif isinstance(val, (str, int, Decimal)):
|
|
40
|
+
dec = Decimal(val)
|
|
41
|
+
else:
|
|
42
|
+
raise TypeError(f'{val} [{type(val)}] not supported to be converted to CurrencySchema')
|
|
43
|
+
|
|
44
|
+
total_cents = int((dec * CENTS_PER_DOLLAR).to_integral_value(rounding=ROUND_HALF_UP))
|
|
45
|
+
return USD(dollars=0, cents=total_cents)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def AmountAsDecimal(self) -> Decimal:
|
|
49
|
+
""" The exact value. This is what should cross a persistence or numeric boundary
|
|
50
|
+
(SQLAlchemy Numeric, pandas, arithmetic outside this class)."""
|
|
51
|
+
return (Decimal(self.dollars * CENTS_PER_DOLLAR + self.cents) / CENTS_PER_DOLLAR).quantize(_CENT)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def AmountFloated(self) -> float:
|
|
55
|
+
return float(self.AmountAsDecimal)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def verify_val(cls, val):
|
|
59
|
+
if isinstance(val, (float, int, str, Decimal)) and not isinstance(val, bool):
|
|
60
|
+
val = USD.from_val(val)
|
|
61
|
+
|
|
62
|
+
if not isinstance(val, USD):
|
|
63
|
+
raise NotImplementedError(f"{val} is not of type [{USD}]")
|
|
64
|
+
|
|
65
|
+
return val
|
|
66
|
+
|
|
67
|
+
def __add__(self, other):
|
|
68
|
+
other = self.verify_val(other)
|
|
69
|
+
return USD(dollars=self.dollars + other.dollars,
|
|
70
|
+
cents=self.cents + other.cents)
|
|
71
|
+
|
|
72
|
+
def __radd__(self, other):
|
|
73
|
+
return self.__add__(other)
|
|
74
|
+
|
|
75
|
+
def __sub__(self, other):
|
|
76
|
+
other = self.verify_val(other)
|
|
77
|
+
return USD(dollars=self.dollars - other.dollars,
|
|
78
|
+
cents=self.cents - other.cents)
|
|
79
|
+
|
|
80
|
+
def __rsub__(self, other):
|
|
81
|
+
return self.verify_val(other).__sub__(self)
|
|
82
|
+
|
|
83
|
+
def __mul__(self, other):
|
|
84
|
+
if isinstance(other, bool) or not isinstance(other, (int, float, Decimal)):
|
|
85
|
+
raise NotImplementedError(f"type {type(other)} cannot be multipled with [{USD}]")
|
|
86
|
+
|
|
87
|
+
factor = Decimal(str(other)) if isinstance(other, float) else Decimal(other)
|
|
88
|
+
return USD.from_val(self.AmountAsDecimal * factor)
|
|
89
|
+
|
|
90
|
+
def __rmul__(self, other):
|
|
91
|
+
return self.__mul__(other)
|
|
92
|
+
|
|
93
|
+
def __truediv__(self, other):
|
|
94
|
+
# dividing two amounts yields a dimensionless ratio rather than money, and callers feed it
|
|
95
|
+
# into ordinary float math
|
|
96
|
+
if isinstance(other, USD):
|
|
97
|
+
return float(self.AmountAsDecimal / other.AmountAsDecimal)
|
|
98
|
+
|
|
99
|
+
if isinstance(other, bool) or not isinstance(other, (int, float, Decimal)):
|
|
100
|
+
raise NotImplementedError(f"cannot divide [{USD}] by type {type(other)} ")
|
|
101
|
+
|
|
102
|
+
divisor = Decimal(str(other)) if isinstance(other, float) else Decimal(other)
|
|
103
|
+
return USD.from_val(self.AmountAsDecimal / divisor)
|
|
104
|
+
|
|
105
|
+
def __gt__(self, other):
|
|
106
|
+
return self.AmountAsDecimal > self.verify_val(other).AmountAsDecimal
|
|
107
|
+
|
|
108
|
+
def __lt__(self, other):
|
|
109
|
+
return self.AmountAsDecimal < self.verify_val(other).AmountAsDecimal
|
|
110
|
+
|
|
111
|
+
def __ge__(self, other):
|
|
112
|
+
return not self.__lt__(other)
|
|
113
|
+
|
|
114
|
+
def __le__(self, other):
|
|
115
|
+
return not self.__gt__(other)
|
|
116
|
+
|
|
117
|
+
def __str__(self):
|
|
118
|
+
return "${:,.2f}".format(self.AmountAsDecimal)
|
|
119
|
+
|
|
120
|
+
def __format__(self, format_spec):
|
|
121
|
+
""" An empty spec gives the currency-decorated str(); any other spec formats the underlying
|
|
122
|
+
decimal, so "{:,.2f}".format(usd) behaves the way callers expect of a numeric type."""
|
|
123
|
+
if format_spec == "":
|
|
124
|
+
return str(self)
|
|
125
|
+
return format(self.AmountAsDecimal, format_spec)
|
|
126
|
+
|
|
127
|
+
def __neg__(self):
|
|
128
|
+
return USD(dollars=-self.dollars, cents=-self.cents)
|
|
129
|
+
|
|
130
|
+
def __abs__(self):
|
|
131
|
+
return USD(dollars=abs(self.dollars), cents=abs(self.cents))
|
|
132
|
+
|
|
133
|
+
def __bool__(self):
|
|
134
|
+
return self.dollars != 0 or self.cents != 0
|
|
135
|
+
|
|
136
|
+
def __float__(self):
|
|
137
|
+
return self.AmountFloated
|
|
138
|
+
|
|
139
|
+
def to_dict(self):
|
|
140
|
+
return asdict(self)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
a = USD.from_val(1.5)
|
|
145
|
+
b = USD.from_val(3.5)
|
|
146
|
+
d = USD(1, 250)
|
|
147
|
+
c = a + b
|
|
148
|
+
print(c)
|
|
149
|
+
print(d)
|
|
150
|
+
print(d.dollars, d.cents)
|
|
151
|
+
e = d + c
|
|
152
|
+
print(e, e.dollars, e.cents)
|
|
153
|
+
print(e.to_dict())
|
|
154
|
+
print(dataclasses.asdict(e))
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from cooptools.currency import USD
|
|
4
|
+
class TestUSD(unittest.TestCase):
|
|
5
|
+
def test_usd_initialization(self):
|
|
6
|
+
usd = USD(5, 150)
|
|
7
|
+
self.assertEqual(usd.dollars, 6)
|
|
8
|
+
self.assertEqual(usd.cents, 50)
|
|
9
|
+
|
|
10
|
+
def test_usd_from_val(self):
|
|
11
|
+
self.assertEqual(USD.from_val(5.75), USD(5, 75))
|
|
12
|
+
self.assertEqual(USD.from_val("3.20"), USD(3, 20))
|
|
13
|
+
self.assertEqual(USD.from_val(2), USD(2, 0))
|
|
14
|
+
|
|
15
|
+
def test_usd_addition(self):
|
|
16
|
+
self.assertEqual(USD(3, 50) + USD(2, 75), USD(6, 25))
|
|
17
|
+
self.assertEqual(USD(1, 90) + 0.15, USD(2, 5))
|
|
18
|
+
|
|
19
|
+
def test_usd_subtraction(self):
|
|
20
|
+
self.assertEqual(USD(5, 50) - USD(2, 25), USD(3, 25))
|
|
21
|
+
self.assertEqual(USD(2, 10) - 1.05, USD(1, 5))
|
|
22
|
+
|
|
23
|
+
def test_usd_multiplication(self):
|
|
24
|
+
self.assertEqual(USD(3, 50) * 2, USD(7, 0))
|
|
25
|
+
self.assertEqual(USD(2, 25) * 3, USD(6, 75))
|
|
26
|
+
|
|
27
|
+
def test_usd_division(self):
|
|
28
|
+
self.assertEqual(USD(5, 0) / 2, USD(2, 50))
|
|
29
|
+
self.assertAlmostEqual(USD(10, 0) / USD(2, 0), 5.0)
|
|
30
|
+
|
|
31
|
+
def test_usd_division__by_usd_yields_a_float_ratio(self):
|
|
32
|
+
ratio = USD(10, 0) / USD(4, 0)
|
|
33
|
+
self.assertIsInstance(ratio, float)
|
|
34
|
+
self.assertEqual(ratio * 2.0, 5.0)
|
|
35
|
+
|
|
36
|
+
def test_usd_from_val__every_cent_round_trips(self):
|
|
37
|
+
""" Values whose binary float lands just under the cent boundary (0.29 -> 28.999...)
|
|
38
|
+
must not truncate down a cent."""
|
|
39
|
+
mismatches = [total_cents / 100
|
|
40
|
+
for total_cents in range(-100000, 100001)
|
|
41
|
+
if USD.from_val(total_cents / 100).AmountAsDecimal != Decimal(total_cents) / 100]
|
|
42
|
+
self.assertEqual(mismatches[:10], [], f"{len(mismatches)} values did not round-trip")
|
|
43
|
+
|
|
44
|
+
def test_usd_from_val__known_truncation_cases(self):
|
|
45
|
+
for val, expected in [(0.29, USD(0, 29)),
|
|
46
|
+
(0.57, USD(0, 57)),
|
|
47
|
+
(1.13, USD(1, 13)),
|
|
48
|
+
(2.01, USD(2, 1)),
|
|
49
|
+
(48.15, USD(48, 15))]:
|
|
50
|
+
with self.subTest(val=val):
|
|
51
|
+
self.assertEqual(USD.from_val(val), expected)
|
|
52
|
+
|
|
53
|
+
def test_usd_from_val__negative(self):
|
|
54
|
+
self.assertEqual(USD.from_val(-0.29), USD(0, -29))
|
|
55
|
+
self.assertEqual(USD.from_val(-48.15), USD(-48, -15))
|
|
56
|
+
self.assertEqual(USD.from_val(-48.15).AmountAsDecimal, Decimal("-48.15"))
|
|
57
|
+
|
|
58
|
+
def test_usd_from_val__decimal_and_usd_passthrough(self):
|
|
59
|
+
self.assertEqual(USD.from_val(Decimal("12.34")), USD(12, 34))
|
|
60
|
+
self.assertEqual(USD.from_val(USD(12, 34)), USD(12, 34))
|
|
61
|
+
|
|
62
|
+
def test_usd_from_val__rejects_bool(self):
|
|
63
|
+
self.assertRaises(TypeError, lambda: USD.from_val(True))
|
|
64
|
+
|
|
65
|
+
def test_usd_normalization__equality_and_hash_agree_with_value(self):
|
|
66
|
+
self.assertEqual(USD(0, 100), USD(1, 0))
|
|
67
|
+
self.assertEqual(hash(USD(0, 100)), hash(USD(1, 0)))
|
|
68
|
+
self.assertEqual(USD(1, -150), USD(0, -50))
|
|
69
|
+
self.assertEqual(hash(USD(1, -150)), hash(USD(0, -50)))
|
|
70
|
+
|
|
71
|
+
def test_usd_normalization__sign_is_consistent_across_both_fields(self):
|
|
72
|
+
neg = USD(0, -150)
|
|
73
|
+
self.assertEqual(neg.dollars, -1)
|
|
74
|
+
self.assertEqual(neg.cents, -50)
|
|
75
|
+
|
|
76
|
+
def test_usd_subtraction__crossing_below_negative_one_dollar(self):
|
|
77
|
+
self.assertEqual(USD(1, 0) - USD(2, 50), USD(-1, -50))
|
|
78
|
+
self.assertEqual((USD(1, 0) - USD(2, 50)).AmountAsDecimal, Decimal("-1.50"))
|
|
79
|
+
|
|
80
|
+
def test_usd_amount_as_decimal__is_exact_and_two_places(self):
|
|
81
|
+
self.assertEqual(USD(48, 15).AmountAsDecimal, Decimal("48.15"))
|
|
82
|
+
self.assertEqual(str(USD(1, 0).AmountAsDecimal), "1.00")
|
|
83
|
+
|
|
84
|
+
def test_usd_accumulation_is_exact(self):
|
|
85
|
+
""" The float-accumulation drift that motivated this type: bare float += lands on
|
|
86
|
+
107.60999999999999, so a request to move 107.61 reads as more than the balance."""
|
|
87
|
+
total = USD.zero()
|
|
88
|
+
for v in [15.04, 33.11, 22.83, 36.63]:
|
|
89
|
+
total = total + v
|
|
90
|
+
self.assertEqual(total, USD.from_val(107.61))
|
|
91
|
+
self.assertFalse(USD.from_val(107.61) > total)
|
|
92
|
+
|
|
93
|
+
def test_usd_format(self):
|
|
94
|
+
self.assertEqual("{:,.2f}".format(USD(1234, 56)), "1,234.56")
|
|
95
|
+
self.assertEqual("{}".format(USD(1234, 56)), "$1,234.56")
|
|
96
|
+
self.assertEqual(f"{USD(1234, 56)}", "$1,234.56")
|
|
97
|
+
|
|
98
|
+
def test_usd_bool(self):
|
|
99
|
+
self.assertFalse(USD.zero())
|
|
100
|
+
self.assertTrue(USD(0, 1))
|
|
101
|
+
|
|
102
|
+
def test_usd_comparisons(self):
|
|
103
|
+
self.assertTrue(USD(1, 0) > USD(0, 99))
|
|
104
|
+
self.assertTrue(USD(0, 99) < USD(1, 0))
|
|
105
|
+
self.assertTrue(USD(1, 0) >= USD(1, 0))
|
|
106
|
+
self.assertTrue(USD(1, 0) <= USD(1, 0))
|
|
107
|
+
self.assertTrue(USD(0, -50) < USD.zero())
|
|
108
|
+
|
|
109
|
+
def test_usd_rsub(self):
|
|
110
|
+
self.assertEqual(5.00 - USD(2, 25), USD(2, 75))
|
|
111
|
+
|
|
112
|
+
def test_usd_abs(self):
|
|
113
|
+
self.assertEqual(abs(USD(-1, -50)), USD(1, 50))
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
unittest.main()
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import math
|
|
3
|
-
from dataclasses import dataclass, asdict
|
|
4
|
-
|
|
5
|
-
@dataclass(frozen=True, slots=True)
|
|
6
|
-
class USD:
|
|
7
|
-
dollars: int
|
|
8
|
-
cents: int
|
|
9
|
-
|
|
10
|
-
def __post_init__(self):
|
|
11
|
-
if self.cents > 100:
|
|
12
|
-
object.__setattr__(self, "dollars", self.dollars + self.cents // 100)
|
|
13
|
-
object.__setattr__(self, 'cents', self.cents % 100)
|
|
14
|
-
|
|
15
|
-
@classmethod
|
|
16
|
-
def zero(cls):
|
|
17
|
-
return USD(dollars=0, cents=0)
|
|
18
|
-
|
|
19
|
-
@classmethod
|
|
20
|
-
def from_val(cls, val: float|str|int):
|
|
21
|
-
if isinstance(val, str):
|
|
22
|
-
val = float(val)
|
|
23
|
-
|
|
24
|
-
if not isinstance(val, (float, int)):
|
|
25
|
-
raise TypeError(f'{val} [{type(val)}] not supported to be converted to CurrencySchema')
|
|
26
|
-
|
|
27
|
-
dollars = int(val)
|
|
28
|
-
cents = int(val * 100 - dollars * 100) # Weird multiplcation to avoid float issues
|
|
29
|
-
|
|
30
|
-
return USD(dollars=dollars, cents=cents)
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def AmountFloated(self) -> float:
|
|
34
|
-
return self.dollars + round(self.cents / 100, 2)
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def verify_val(cls, val):
|
|
38
|
-
if type(val) in [float, int, str]:
|
|
39
|
-
val = USD.from_val(val)
|
|
40
|
-
|
|
41
|
-
if type(val) != USD:
|
|
42
|
-
raise NotImplementedError(f"{val} is not of type [{USD}]")
|
|
43
|
-
|
|
44
|
-
return val
|
|
45
|
-
|
|
46
|
-
def __add__(self, other):
|
|
47
|
-
other = self.verify_val(other)
|
|
48
|
-
|
|
49
|
-
dollars = self.dollars + other.dollars
|
|
50
|
-
cents = self.cents + other.cents
|
|
51
|
-
|
|
52
|
-
if cents >= 100:
|
|
53
|
-
cent_dols = math.floor(cents / 100)
|
|
54
|
-
cents = cents % 100
|
|
55
|
-
dollars += cent_dols
|
|
56
|
-
|
|
57
|
-
if cents < 0:
|
|
58
|
-
factor = math.ceil(- cents / 100)
|
|
59
|
-
dollars -= 1 * factor
|
|
60
|
-
cents += 100 * factor
|
|
61
|
-
|
|
62
|
-
return USD(dollars=dollars, cents=cents)
|
|
63
|
-
|
|
64
|
-
def __radd__(self, other):
|
|
65
|
-
return self.__add__(other)
|
|
66
|
-
|
|
67
|
-
def __sub__(self, other):
|
|
68
|
-
other = self.verify_val(other)
|
|
69
|
-
|
|
70
|
-
dollars = self.dollars - other.dollars
|
|
71
|
-
cents = self.cents - other.cents
|
|
72
|
-
|
|
73
|
-
if cents > -100:
|
|
74
|
-
cent_dols = math.floor(cents / 100)
|
|
75
|
-
cents = cents % 100
|
|
76
|
-
dollars += cent_dols
|
|
77
|
-
return USD(dollars=dollars, cents=cents)
|
|
78
|
-
|
|
79
|
-
def __mul__(self, other):
|
|
80
|
-
if type(other) not in [int, float]:
|
|
81
|
-
raise NotImplementedError(f"type {type(other)} cannot be multipled with [{USD}]")
|
|
82
|
-
|
|
83
|
-
return USD.from_val(float(self) * other)
|
|
84
|
-
|
|
85
|
-
def __rmul__(self, other):
|
|
86
|
-
return self.__mul__(other)
|
|
87
|
-
|
|
88
|
-
def __truediv__(self, other):
|
|
89
|
-
if type(other) in [int, float]:
|
|
90
|
-
return USD.from_val(float(self) / float(other))
|
|
91
|
-
if isinstance(other, USD):
|
|
92
|
-
return float(self) / float(other)
|
|
93
|
-
|
|
94
|
-
raise NotImplementedError(f"cannot divide [{USD}] by type {type(other)} ")
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def __gt__(self, other):
|
|
99
|
-
return (self - other).AmountFloated > 0
|
|
100
|
-
|
|
101
|
-
def __lt__(self, other):
|
|
102
|
-
return (other - self).AmountFloated > 0
|
|
103
|
-
|
|
104
|
-
def __ge__(self, other):
|
|
105
|
-
return not self.__lt__(other)
|
|
106
|
-
|
|
107
|
-
def __le__(self, other):
|
|
108
|
-
return not self.__gt__(other)
|
|
109
|
-
|
|
110
|
-
def __str__(self):
|
|
111
|
-
return "${:,.2f}".format(self.AmountFloated)
|
|
112
|
-
|
|
113
|
-
def __neg__(self):
|
|
114
|
-
return USD(dollars=-self.dollars, cents=-self.cents)
|
|
115
|
-
|
|
116
|
-
def __float__(self):
|
|
117
|
-
return self.AmountFloated
|
|
118
|
-
|
|
119
|
-
def to_dict(self):
|
|
120
|
-
return asdict(self)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if __name__ == "__main__":
|
|
124
|
-
a = USD.from_val(1.5)
|
|
125
|
-
b = USD.from_val(3.5)
|
|
126
|
-
d = USD(1, 250)
|
|
127
|
-
c = a + b
|
|
128
|
-
print(c)
|
|
129
|
-
print(d)
|
|
130
|
-
print(d.dollars, d.cents)
|
|
131
|
-
e = d + c
|
|
132
|
-
print(e, e.dollars, e.cents)
|
|
133
|
-
print(e.to_dict())
|
|
134
|
-
print(dataclasses.asdict(e))
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from cooptools.currency import USD
|
|
3
|
-
class TestUSD(unittest.TestCase):
|
|
4
|
-
def test_usd_initialization(self):
|
|
5
|
-
usd = USD(5, 150)
|
|
6
|
-
self.assertEqual(usd.dollars, 6)
|
|
7
|
-
self.assertEqual(usd.cents, 50)
|
|
8
|
-
|
|
9
|
-
def test_usd_from_val(self):
|
|
10
|
-
self.assertEqual(USD.from_val(5.75), USD(5, 75))
|
|
11
|
-
self.assertEqual(USD.from_val("3.20"), USD(3, 20))
|
|
12
|
-
self.assertEqual(USD.from_val(2), USD(2, 0))
|
|
13
|
-
|
|
14
|
-
def test_usd_addition(self):
|
|
15
|
-
self.assertEqual(USD(3, 50) + USD(2, 75), USD(6, 25))
|
|
16
|
-
self.assertEqual(USD(1, 90) + 0.15, USD(2, 5))
|
|
17
|
-
|
|
18
|
-
def test_usd_subtraction(self):
|
|
19
|
-
self.assertEqual(USD(5, 50) - USD(2, 25), USD(3, 25))
|
|
20
|
-
self.assertEqual(USD(2, 10) - 1.05, USD(1, 5))
|
|
21
|
-
|
|
22
|
-
def test_usd_multiplication(self):
|
|
23
|
-
self.assertEqual(USD(3, 50) * 2, USD(7, 0))
|
|
24
|
-
self.assertEqual(USD(2, 25) * 3, USD(6, 75))
|
|
25
|
-
|
|
26
|
-
def test_usd_division(self):
|
|
27
|
-
self.assertEqual(USD(5, 0) / 2, USD(2, 50))
|
|
28
|
-
self.assertAlmostEqual(USD(10, 0) / USD(2, 0), 5.0)
|
|
29
|
-
|
|
30
|
-
if __name__ == "__main__":
|
|
31
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cooptools-1.76 → cooptools-1.77}/cooptools/ideas/discreteEventSimulator/discreteEventSimulator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|