rtbnext 1.0.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.
@@ -0,0 +1,266 @@
1
+ """
2
+ Time Series Resource
3
+
4
+ Implements the resource wrapper for time-series endpoints.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections import defaultdict
10
+ from datetime import date as date_type
11
+ from statistics import mean, median
12
+ from typing import Callable, Generic, Literal, TypedDict, TypeVar, cast
13
+
14
+ from rtbnext.core.loader import ResourceStateLoader
15
+ from rtbnext.core.parser import ParserFn
16
+ from rtbnext.resource.base import Resource
17
+ from rtbnext.resource.collection import DateCollectionBase
18
+
19
+ TimePoint = TypedDict( "TimePoint", { "date": str } )
20
+ type TimeSeriesRow = tuple
21
+
22
+ AggregateValue = TypedDict( "AggregateValue", {
23
+ "first": float,
24
+ "last": float,
25
+ "min": float,
26
+ "max": float,
27
+ "avg": float,
28
+ "median": float,
29
+ "sum": float
30
+ } )
31
+
32
+ AggregateRange = TypedDict( "AggregateRange", {
33
+ "from": str,
34
+ "to": str
35
+ } )
36
+
37
+ AggregatePoint = TypedDict( "AggregatePoint", {
38
+ "date": str,
39
+ "label": str,
40
+ "range": AggregateRange
41
+ } )
42
+
43
+ A = TypeVar( "A", bound= AggregatePoint )
44
+ D = TypeVar( "D", bound= list[ TimeSeriesRow ] )
45
+ R = TypeVar( "R", bound= TimePoint )
46
+
47
+ type AggregatePeriod = Literal[ "week", "month", "quarter", "year" ]
48
+ type NumberCallback[ R ] = Callable[ [ R ], int | float ]
49
+ type PointFn[ D, R ] = Callable[ [ D ], R ]
50
+
51
+
52
+ class TimeSeriesCollection( DateCollectionBase[ R, R ], Generic[ R, A ] ):
53
+ """
54
+ Collection wrapper for time-series resources.
55
+
56
+ This class is the base for CSV resource collections, providing methods
57
+ to access dated data points, aggregate them and get statistical values
58
+ like min, max, median etc.
59
+ """
60
+
61
+ def _numbers( self, callback: NumberCallback | None = None ) -> list[ float ]:
62
+ """Returns numeric values from points."""
63
+
64
+ return (
65
+ [ float( callback( point ) ) for point in self ]
66
+ if callback is not None else
67
+ [
68
+ float( value ) for point in self for key, value in point.items()
69
+ if key != "date" and isinstance( value, ( int, float ) )
70
+ ]
71
+ )
72
+
73
+ def _period( self, date: str, period: AggregatePeriod ) -> str:
74
+ """Creates an aggregation key from a date."""
75
+
76
+ year, month, day = map( int, date.split( "-" ) )
77
+
78
+ if period == "year":
79
+ return str( year )
80
+
81
+ if period == "quarter":
82
+ return f"{ year }-Q{ ( month - 1 ) // 3 + 1 }"
83
+
84
+ if period == "month":
85
+ return f"{ year }-{month:02d}"
86
+
87
+ if period == "week":
88
+ iso_year, iso_week, _ = date_type( year, month, day ).isocalendar()
89
+ return f"{ iso_year }-W{iso_week:02d}"
90
+
91
+ raise ValueError( f"Invalid aggregate period: { period }" )
92
+
93
+ def _aggregate( self, points: list[ R ], label: str | None = None ) -> A:
94
+ """Aggregates a group of points."""
95
+
96
+ sorted_points = sorted( points, key= lambda point: point[ "date" ] )
97
+
98
+ result: dict[ str, object ] = {
99
+ "date": sorted_points[ -1 ][ "date" ],
100
+ "label": label or sorted_points[ -1 ][ "date" ],
101
+ "range": {
102
+ "from": sorted_points[ 0 ][ "date" ],
103
+ "to": sorted_points[ -1 ][ "date" ]
104
+ }
105
+ }
106
+
107
+ keys = sorted_points[ 0 ].keys()
108
+
109
+ for key in keys:
110
+ if key == "date":
111
+ continue
112
+
113
+ values = [
114
+ float( point[ key ] ) for point in sorted_points
115
+ if isinstance( point.get( key ), ( int, float ) )
116
+ ]
117
+
118
+ if not values:
119
+ continue
120
+
121
+ result[ key ] = {
122
+ "first": values[ 0 ],
123
+ "last": values[ -1 ],
124
+ "min": min( values ),
125
+ "max": max( values ),
126
+ "avg": float( mean( values ) ),
127
+ "median": float( median( values ) ),
128
+ "sum": sum( values )
129
+ }
130
+
131
+ return cast( A, result )
132
+
133
+ def _aggregated_series( self, points: list[ A ] ) -> TimeSeriesCollection[ A, A ]:
134
+ """Returns a time series collection of aggregated points."""
135
+
136
+ return TimeSeriesCollection(
137
+ points,
138
+ factory= lambda item: item,
139
+ date= lambda item: item[ "date" ]
140
+ )
141
+
142
+ @property
143
+ def points( self ) -> list[ R ]:
144
+ """Returns all time series points."""
145
+
146
+ return self._items
147
+
148
+ def min( self, callback: NumberCallback[ R ] | None = None ) -> float:
149
+ """Returns the minimum value."""
150
+
151
+ return min( self._numbers( callback ) )
152
+
153
+ def max( self, callback: NumberCallback[ R ] | None = None ) -> float:
154
+ """Returns the maximum value."""
155
+
156
+ return max( self._numbers( callback ) )
157
+
158
+ def sum( self, callback: NumberCallback[ R ] | None = None ) -> float:
159
+ """Returns the sum of values."""
160
+
161
+ return sum( self._numbers( callback ) )
162
+
163
+ def avg( self, callback: NumberCallback[ R ] | None = None ) -> float:
164
+ """Returns the average value."""
165
+
166
+ return float( mean( self._numbers( callback ) ) )
167
+
168
+ def median( self, callback: NumberCallback[ R ] | None = None ) -> float:
169
+ """Returns the median value."""
170
+
171
+ return float( median( self._numbers( callback ) ) )
172
+
173
+ @property
174
+ def labels( self ) -> list[ str ]:
175
+ """Returns the date labels."""
176
+
177
+ return [ point[ "date" ] for point in self ]
178
+
179
+ @property
180
+ def columns( self ) -> dict[ str, list[ object ] ]:
181
+ """Returns the values grouped by column."""
182
+
183
+ result: dict[ str, list[ object ] ] = {}
184
+
185
+ for point in self:
186
+ for key, value in point.items():
187
+ result.setdefault( key, [] ).append( value )
188
+
189
+ return result
190
+
191
+ def values( self, callback: NumberCallback ) -> list[ int | float ]:
192
+ """Returns the mapped numeric values."""
193
+
194
+ return [ callback( point ) for point in self ]
195
+
196
+ def column( self, key: str ) -> list[ object ]:
197
+ """Returns all values of a column."""
198
+
199
+ return [ point[ key ] for point in self ]
200
+
201
+ def aggregate(
202
+ self,
203
+ period: AggregatePeriod | Callable[ [ R ], str ]
204
+ ) -> TimeSeriesCollection[ A, A ]:
205
+ """Aggregates points by period."""
206
+
207
+ groups: dict[ str, list[ R ] ] = defaultdict( list )
208
+
209
+ for point in self:
210
+ groups[ (
211
+ period( point ) if callable( period )
212
+ else self._period( point[ "date" ], period )
213
+ ) ].append( point )
214
+
215
+ return self._aggregated_series( [
216
+ self._aggregate( group, label )
217
+ for label, group in groups.items()
218
+ ] )
219
+
220
+ def buckets( self, count: int ) -> TimeSeriesCollection[ A, A ]:
221
+ """Splits points into equally sized buckets."""
222
+
223
+ if count >= self.count:
224
+ return self._aggregated_series( [
225
+ self._aggregate( [ point ], f"{ index + 1 }/{ self.count }" )
226
+ for index, point in enumerate( self )
227
+ ] )
228
+
229
+ size, points = self.count / count, self.items
230
+ result: list[ A ] = []
231
+
232
+ for index in range( count ):
233
+ start, end = int( index * size ), int( ( index + 1 ) * size )
234
+ result.append( self._aggregate( points[ start : end ], f"{ index + 1 }/{ count }" ) )
235
+
236
+ return self._aggregated_series( result )
237
+
238
+
239
+ class TimeSeriesResource( Resource[ D ], Generic[ D, R, A ] ):
240
+ """
241
+ Resource wrapper for time-series endpoints.
242
+
243
+ This class converts raw CSV rows into typed time-series points and
244
+ exposes them through a time-series collection.
245
+ """
246
+
247
+ def __init__(
248
+ self, path: str, loader: ResourceStateLoader, parser: ParserFn[ D ], *,
249
+ point: PointFn[ TimeSeriesRow, R ]
250
+ ) -> None:
251
+ super().__init__( path, loader, parser )
252
+ self._point = point
253
+
254
+ def _collect_points( self, rows: D ) -> TimeSeriesCollection[ R, A ]:
255
+ """Creates a time-series collection from raw rows."""
256
+
257
+ return TimeSeriesCollection(
258
+ [ self._point( row ) for row in reversed( rows ) ],
259
+ factory= lambda item: item,
260
+ date= lambda point: point[ "date" ]
261
+ )
262
+
263
+ async def series( self ) -> TimeSeriesCollection[ R, A ]:
264
+ """Returns the parsed time-series data as a typed collection."""
265
+
266
+ return await self._transform( self._collect_points )
rtbnext/rtbnext.py ADDED
@@ -0,0 +1,107 @@
1
+ """
2
+ RTBNext
3
+
4
+ Implements the main entry point class for the RTBNext Python SDK exposing
5
+ all API endpoints.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Self
12
+
13
+ from rtbnext.core.http_client import ClientIdentity, HttpClient
14
+ from rtbnext.core.loader import CacheMode, CacheType, ResourceStateLoader
15
+ from rtbnext.defaults import (
16
+ DEFAULT_API_URL, DEFAULT_CACHE_MODE, DEFAULT_CACHE_TYPE, DEFAULT_TIMEOUT
17
+ )
18
+ from rtbnext.endpoint.filter import FilterEndpoint
19
+ from rtbnext.endpoint.list import ListEndpoint
20
+ from rtbnext.endpoint.mover import MoverEndpoint
21
+ from rtbnext.endpoint.profile import ProfileEndpoint
22
+ from rtbnext.endpoint.stats import StatsEndpoint
23
+ from rtbnext.endpoint.system import SystemEndpoint
24
+ from rtbnext.resource.base import ResourcePool
25
+
26
+
27
+ @dataclass( slots= True, kw_only= True )
28
+ class Endpoints:
29
+ """Endpoints available in the RTBNext SDK."""
30
+
31
+ profile: ProfileEndpoint
32
+ list: ListEndpoint
33
+ mover: MoverEndpoint
34
+ filter: FilterEndpoint
35
+ stats: StatsEndpoint
36
+ system: SystemEndpoint
37
+
38
+
39
+ class RTBNext:
40
+ """
41
+ Main entry point of the RTBNext SDK.
42
+
43
+ Provides access to all RTBNext API endpoints through a single client
44
+ instance while internally managing HTTP communication, resource loading,
45
+ caching, and endpoint initialization.
46
+ """
47
+
48
+ def __init__(
49
+ self, client: ClientIdentity, *,
50
+ base_url: str = DEFAULT_API_URL,
51
+ timeout: float = DEFAULT_TIMEOUT,
52
+ cache: CacheType = DEFAULT_CACHE_TYPE,
53
+ mode: CacheMode = DEFAULT_CACHE_MODE
54
+ ) -> None:
55
+ self._client = HttpClient( client= client, base_url= base_url, timeout= timeout )
56
+ self._loader = ResourceStateLoader( client= self._client, cache= cache, mode= mode )
57
+ self._pool = ResourcePool()
58
+
59
+ endpoints = Endpoints.__new__( Endpoints )
60
+ args = ( self._loader, self._pool, endpoints )
61
+
62
+ self.profile = endpoints.profile = ProfileEndpoint( *args )
63
+ self.list = endpoints.list = ListEndpoint( *args )
64
+ self.mover = endpoints.mover = MoverEndpoint( *args )
65
+ self.filter = endpoints.filter = FilterEndpoint( *args )
66
+ self.stats = endpoints.stats = StatsEndpoint( *args )
67
+ self.system = endpoints.system = SystemEndpoint( *args )
68
+
69
+ self.endpoints = endpoints
70
+
71
+ async def __aenter__( self ) -> Self:
72
+ return self
73
+
74
+ async def __aexit__( self, *_ ) -> None:
75
+ await self.close()
76
+
77
+ async def close( self ) -> None:
78
+ """Closes the underlying HTTP client and release network resources."""
79
+
80
+ await self._loader.aclose()
81
+
82
+
83
+ def rtbnext(
84
+ client: ClientIdentity, *,
85
+ base_url: str = DEFAULT_API_URL,
86
+ timeout: float = DEFAULT_TIMEOUT,
87
+ cache: CacheType = DEFAULT_CACHE_TYPE,
88
+ mode: CacheMode = DEFAULT_CACHE_MODE
89
+ ) -> RTBNext:
90
+ """
91
+ Creates a new RTBNext SDK instance.
92
+
93
+ Args:
94
+ client: Client identity information.
95
+ base_url: RTBNext API base URL.
96
+ timeout: HTTP request timeout.
97
+ cache: Cache implementation.
98
+ mode: Cache mode.
99
+
100
+ Returns:
101
+ A configured RTBNext SDK instance.
102
+ """
103
+
104
+ return RTBNext(
105
+ client, base_url= base_url, timeout= timeout,
106
+ cache= cache, mode= mode
107
+ )
@@ -0,0 +1,101 @@
1
+ """
2
+ Assets
3
+
4
+ Declares shared types for assets, net worth, rankings, performance metrics,
5
+ and historical financial data.
6
+ """
7
+
8
+ from typing import Literal, NotRequired, TypedDict
9
+
10
+ from rtbnext.schema.generic import AssetType, ChangeFlag
11
+
12
+ type ReturnsPeriod = Literal[
13
+ "week", "month", "quarter", "halfYear", "year", "twoYear", "fiveYear"
14
+ ]
15
+
16
+ ChangeItem = TypedDict( "ChangeItem", {
17
+ "value": float,
18
+ "percent": float
19
+ } )
20
+
21
+ AssetInfo = TypedDict( "AssetInfo", {
22
+ "exchange": str,
23
+ "ticker": str,
24
+ "shares": NotRequired[ float ],
25
+ "price": float,
26
+ "currency": str,
27
+ "exRate": float
28
+ } )
29
+
30
+ Asset = TypedDict( "Asset", {
31
+ "type": AssetType,
32
+ "label": str,
33
+ "value": NotRequired[ float ],
34
+ "info": NotRequired[ AssetInfo ]
35
+ } )
36
+
37
+ DataPoint = TypedDict( "DataPoint", {
38
+ "date": str,
39
+ "networth": float,
40
+ "rank": NotRequired[ int ]
41
+ } )
42
+
43
+ Extrema = TypedDict( "Extrema", {
44
+ "high": NotRequired[ DataPoint ],
45
+ "low": NotRequired[ DataPoint ]
46
+ } )
47
+
48
+ type Returns = dict[ ReturnsPeriod, ChangeItem ]
49
+
50
+ Performance = TypedDict( "Performance", {
51
+ "extrema": NotRequired[ Extrema ],
52
+ "returns": NotRequired[ Returns ]
53
+ } )
54
+
55
+ RankingHistoryItem = TypedDict( "RankingHistoryItem", {
56
+ "date": str,
57
+ "rank": NotRequired[ int ],
58
+ "networth": NotRequired[ float ],
59
+ "prev": NotRequired[ str ],
60
+ "next": NotRequired[ str ]
61
+ } )
62
+
63
+ Ranking = TypedDict( "Ranking", {
64
+ "list": str,
65
+ "name": str,
66
+ "date": str,
67
+ "rank": NotRequired[ int ],
68
+ "networth": NotRequired[ float ],
69
+ "prev": NotRequired[ str ],
70
+ "next": NotRequired[ str ],
71
+ "history": NotRequired[ list[ RankingHistoryItem ] ]
72
+ } )
73
+
74
+ Realtime = TypedDict( "Realtime", {
75
+ "date": str,
76
+ "rank": NotRequired[ int ],
77
+ "networth": NotRequired[ float ],
78
+ "prev": NotRequired[ str ],
79
+ "next": NotRequired[ str ],
80
+ "today": NotRequired[ ChangeItem ],
81
+ "ytd": NotRequired[ ChangeItem ]
82
+ } )
83
+
84
+ AnnualRecord = TypedDict( "AnnualRecord", {
85
+ "first": int | float,
86
+ "last": int | float,
87
+ "diff": int | float,
88
+ "flag": ChangeFlag,
89
+ "mean": float,
90
+ "median": float,
91
+ "max": int | float,
92
+ "min": int | float,
93
+ "range": int | float,
94
+ "stdDev": float
95
+ } )
96
+
97
+ Annual = TypedDict( "Annual", {
98
+ "year": int,
99
+ "rank": NotRequired[ AnnualRecord ],
100
+ "networth": NotRequired[ AnnualRecord ]
101
+ } )
@@ -0,0 +1,35 @@
1
+ """
2
+ Filter
3
+
4
+ Declares filter entries, documents and the index.
5
+ """
6
+
7
+ from typing import Any, TypedDict
8
+
9
+ from rtbnext.schema.generic import (
10
+ AgeGroup, FilterSpecial, Gender, Industry, MaritalStatus, MetaData
11
+ )
12
+
13
+ FilterItem = TypedDict( "FilterItem", {
14
+ "uri": str,
15
+ "name": str,
16
+ "value": Any
17
+ } )
18
+
19
+ Filter = TypedDict( "Filter", {
20
+ "$metadata": MetaData,
21
+ "count": int,
22
+ "items": list[ FilterItem ]
23
+ } )
24
+
25
+ FilterIndex = TypedDict( "FilterIndex", {
26
+ "$metadata": MetaData,
27
+ "industry": list[ Industry ],
28
+ "citizenship": list[ str ],
29
+ "country": list[ str ],
30
+ "state": list[ str ],
31
+ "gender": list[ Gender ],
32
+ "age": list[ AgeGroup ],
33
+ "maritalStatus": list[ MaritalStatus ],
34
+ "special": list[ FilterSpecial ]
35
+ } )
@@ -0,0 +1,105 @@
1
+ """
2
+ Literals & Generics
3
+
4
+ Declares literals and generic types.
5
+ """
6
+
7
+ from typing import Literal, NotRequired, TypedDict
8
+
9
+ type Industry = Literal[
10
+ "automotive", "diversified", "energy", "engineering", "finance", "foodstuff",
11
+ "gambling", "healthcare", "logistics", "manufacturing", "media", "mining",
12
+ "property", "retail", "service", "sports", "technology", "telecom"
13
+ ]
14
+
15
+ type AgeGroup = Literal[ "30", "40", "50", "60", "70", "80", "90" ]
16
+ type Gender = Literal[ "m", "f", "d" ]
17
+ type MaritalStatus = Literal[
18
+ "single", "relationship", "married", "remarried", "engaged", "separated",
19
+ "divorced", "widowed"
20
+ ]
21
+
22
+ type SelfMadeRank = Literal[ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ]
23
+
24
+ type RelationType = Literal[ "person", "organization", "place", "unknown" ]
25
+ type AssetType = Literal[ "public", "private", "misc" ]
26
+ type ChangeFlag = Literal[ "up", "down", "unchanged", "new", "returned", "dropoff", "unknown" ]
27
+
28
+ type ChildrenGroup = Literal[ "none", "one", "two", "three", "four", "5-to-10", "over-10" ]
29
+ type Percentile = Literal[ "10th", "25th", "50th", "75th", "90th", "95th", "99th" ]
30
+ type WealthSpread = Literal[ "1", "2", "5", "10", "20", "50", "100", "200", "500", "1000" ]
31
+
32
+ type FilterSpecial = Literal[ "deceased", "dropOff", "family", "selfMade" ]
33
+ type FilterGroup = Literal[
34
+ "industry", "citizenship", "country", "state", "gender", "age", "maritalStatus",
35
+ "special"
36
+ ]
37
+
38
+ type Service = Literal[ "profile", "list", "mover", "filter", "stats", "system" ]
39
+ type StatusFlag = Literal[ "healthy", "degraded", "maintenance", "outage", "unknown" ]
40
+ type ProfileStatusFlag = Literal[ "healthy", "invalid", "missing", "unknown" ]
41
+
42
+ MetaData = TypedDict( "MetaData", {
43
+ "schemaVersion": Literal[ "2" ],
44
+ "generator": str,
45
+ "lastModified": str
46
+ } )
47
+
48
+ DateIndex = TypedDict( "DateIndex", {
49
+ "$metadata": MetaData,
50
+ "dates": list[ str ],
51
+ "latest": NotRequired[ str ]
52
+ } )
53
+
54
+ Location = TypedDict( "Location", {
55
+ "country": str,
56
+ "state": NotRequired[ str ],
57
+ "city": NotRequired[ str ]
58
+ } )
59
+
60
+ Education = TypedDict( "Education", {
61
+ "school": str,
62
+ "degree": NotRequired[ str ]
63
+ } )
64
+
65
+ Organization = TypedDict( "Organization", {
66
+ "name": str,
67
+ "title": NotRequired[ str ]
68
+ } )
69
+
70
+ SelfMade = TypedDict( "SelfMade", {
71
+ "type": NotRequired[ str ],
72
+ "is": bool,
73
+ "rank": NotRequired[ SelfMadeRank ]
74
+ } )
75
+
76
+ Relation = TypedDict( "Relation", {
77
+ "type": RelationType,
78
+ "name": str,
79
+ "relation": NotRequired[ str ],
80
+ "uri": NotRequired[ str ]
81
+ } )
82
+
83
+ Image = TypedDict( "Image", {
84
+ "url": str,
85
+ "credits": str,
86
+ "file": str,
87
+ "thumb": NotRequired[ str ],
88
+ "caption": NotRequired[ str ],
89
+ "desc": NotRequired[ str ],
90
+ "date": NotRequired[ str ]
91
+ } )
92
+
93
+ Wiki = TypedDict( "Wiki", {
94
+ "uri": str,
95
+ "pageId": int,
96
+ "refId": int,
97
+ "confidence": float,
98
+ "name": str,
99
+ "lastModified": str,
100
+ "summary": list[ str ],
101
+ "sortKey": NotRequired[ str ],
102
+ "wikidata": NotRequired[ str ],
103
+ "desc": NotRequired[ str ],
104
+ "image": NotRequired[ Image ]
105
+ } )
rtbnext/schema/list.py ADDED
@@ -0,0 +1,64 @@
1
+ """
2
+ List
3
+
4
+ Declares types for ranking lists and snapshots.
5
+ """
6
+
7
+ from typing import NotRequired, TypedDict
8
+
9
+ from rtbnext.schema.assets import ChangeItem
10
+ from rtbnext.schema.generic import ChangeFlag, Gender, Industry, MetaData, SelfMadeRank
11
+
12
+ ListIndexItem = TypedDict( "ListIndexItem", {
13
+ "uri": str,
14
+ "name": str,
15
+ "text": str,
16
+ "shortName": str,
17
+ "desc": str,
18
+ "columns": list[ str ],
19
+ "filters": list[ str ]
20
+ } )
21
+
22
+ ListIndex = TypedDict( "ListIndex", {
23
+ "$metadata": MetaData,
24
+ "count": int,
25
+ "items": list[ ListIndexItem ]
26
+ } )
27
+
28
+ ListItem = TypedDict( "ListItem", {
29
+ "uri": str,
30
+ "sourceUri": str,
31
+ "name": str,
32
+ "rank": int,
33
+ "networth": NotRequired[ float ],
34
+ "industry": NotRequired[ Industry ],
35
+ "source": NotRequired[ list[ str ] ],
36
+ "gender": NotRequired[ Gender ],
37
+ "age": NotRequired[ int ],
38
+ "citizenship": NotRequired[ str ],
39
+ "flag": NotRequired[ ChangeFlag ],
40
+ "rankDiff": NotRequired[ int ],
41
+ "selfMadeRank": NotRequired[ SelfMadeRank ],
42
+ "philanthropyScore": NotRequired[ int ],
43
+ "today": NotRequired[ ChangeItem ],
44
+ "ytd": NotRequired[ ChangeItem ]
45
+ } )
46
+
47
+ ListStats = TypedDict( "ListStats", {
48
+ "date": str,
49
+ "count": int,
50
+ "total": float,
51
+ "woman": int,
52
+ "quota": float,
53
+ "today": NotRequired[ ChangeItem ],
54
+ "ytd": NotRequired[ ChangeItem ]
55
+ } )
56
+
57
+ List = TypedDict( "List", {
58
+ "$metadata": MetaData,
59
+ "uri": str,
60
+ "date": str,
61
+ "count": int,
62
+ "items": list[ ListItem ],
63
+ "stats": ListStats
64
+ } )