haystack-ml-stack 0.4.7__tar.gz → 0.4.9__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.
Files changed (27) hide show
  1. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/PKG-INFO +1 -1
  2. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/pyproject.toml +1 -1
  3. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/_serializers.py +2 -0
  4. haystack_ml_stack-0.4.9/src/haystack_ml_stack/_version.py +1 -0
  5. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/generated/v1/features_pb2.py +38 -38
  6. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/generated/v1/features_pb2.pyi +28 -24
  7. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/utils.py +2 -0
  8. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack.egg-info/PKG-INFO +1 -1
  9. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/tests/test_serializers.py +61 -0
  10. haystack_ml_stack-0.4.7/src/haystack_ml_stack/_version.py +0 -1
  11. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/README.md +0 -0
  12. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/setup.cfg +0 -0
  13. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/__init__.py +0 -0
  14. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/_kafka.py +0 -0
  15. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/app.py +0 -0
  16. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/cache.py +0 -0
  17. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/dynamo.py +0 -0
  18. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/exceptions.py +0 -0
  19. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/generated/__init__.py +0 -0
  20. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/generated/v1/__init__.py +0 -0
  21. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/model_store.py +0 -0
  22. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack/settings.py +0 -0
  23. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack.egg-info/SOURCES.txt +0 -0
  24. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack.egg-info/dependency_links.txt +0 -0
  25. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack.egg-info/requires.txt +0 -0
  26. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/src/haystack_ml_stack.egg-info/top_level.txt +0 -0
  27. {haystack_ml_stack-0.4.7 → haystack_ml_stack-0.4.9}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haystack-ml-stack
3
- Version: 0.4.7
3
+ Version: 0.4.9
4
4
  Summary: Functions related to Haystack ML
5
5
  Author-email: Oscar Vega <oscar@haystack.tv>
6
6
  License: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "haystack-ml-stack"
8
- version = "0.4.7"
8
+ version = "0.4.9"
9
9
  description = "Functions related to Haystack ML"
10
10
  readme = "README.md"
11
11
  authors = [{ name = "Oscar Vega", email = "oscar@haystack.tv" }]
@@ -275,6 +275,8 @@ class UserPlaylistStatsSerializerV1(SimpleSerializer):
275
275
  category_msg.active_days = int(stats["active_days"])
276
276
  category_msg.total_watched = float(stats["total_watched"])
277
277
  category_msg.capped_total_watched = float(stats["capped_total_watched"])
278
+ category_msg.capped_total_log_watched = float(stats.get("capped_total_log_watched", 0.0))
279
+ category_msg.first_active_date = str(stats.get("first_active_date", ""))
278
280
  return root_msg
279
281
 
280
282
 
@@ -0,0 +1 @@
1
+ __version__ = "0.4.9"
@@ -2,7 +2,7 @@
2
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
3
3
  # NO CHECKED-IN PROTOBUF GENCODE
4
4
  # source: features.proto
5
- # Protobuf Python Version: 7.34.1
5
+ # Protobuf Python Version: 6.33.2
6
6
  """Generated protocol buffer code."""
7
7
  from google.protobuf import descriptor as _descriptor
8
8
  from google.protobuf import descriptor_pool as _descriptor_pool
@@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database
11
11
  from google.protobuf.internal import builder as _builder
12
12
  _runtime_version.ValidateProtobufRuntimeVersion(
13
13
  _runtime_version.Domain.PUBLIC,
14
- 7,
15
- 34,
16
- 1,
14
+ 6,
15
+ 33,
16
+ 2,
17
17
  '',
18
18
  'features.proto'
19
19
  )
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x66\x65\x61tures.proto\x12\x1ahaystack_ml_stack.features\"7\n\x12\x45ntryContextCounts\x12\x10\n\x08\x61ttempts\x18\x01 \x01(\x05\x12\x0f\n\x07watched\x18\x02 \x01(\x05\"_\n\x0cSelectCounts\x12\x15\n\rtotal_selects\x18\x01 \x01(\x05\x12!\n\x19total_selects_and_watched\x18\x02 \x01(\x05\x12\x15\n\rtotal_browsed\x18\x03 \x01(\x05\"\xf3\x02\n\x14\x45ntryContextPWatched\x12@\n\x08\x61utoplay\x18\x01 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12\x41\n\tsel_thumb\x18\x02 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12\x43\n\x0b\x63hoose_next\x18\x03 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12@\n\x08\x63h_swtch\x18\x04 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12O\n\x17launch_first_in_session\x18\x05 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\"\x85\x02\n\x0fPositionPSelect\x12;\n\tfirst_pos\x18\x01 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12<\n\nsecond_pos\x18\x02 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12;\n\tthird_pos\x18\x03 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12:\n\x08rest_pos\x18\x04 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\"\xa9\x01\n\x1f\x42rowsedDebiasedPositionPSelects\x12\x44\n\x0fup_to_4_browsed\x18\x01 \x01(\x0b\x32+.haystack_ml_stack.features.PositionPSelect\x12@\n\x0b\x61ll_browsed\x18\x02 \x01(\x0b\x32+.haystack_ml_stack.features.PositionPSelect\"\xb8\x01\n\x16PlaylistStatsForGlobal\x12\x15\n\rwatched_count\x18\x01 \x01(\x05\x12\x19\n\x11not_watched_count\x18\x02 \x01(\x05\x12\x1b\n\x13\x63\x61pped_watched_secs\x18\x03 \x01(\x02\x12\x1f\n\x17\x63\x61pped_not_watched_secs\x18\x04 \x01(\x02\x12\x14\n\x0cwatched_secs\x18\x05 \x01(\x02\x12\x18\n\x10not_watched_secs\x18\x06 \x01(\x02\"\x88\x01\n\x14PlaylistStatsForUser\x12\x12\n\ntotal_days\x18\x01 \x01(\x05\x12\x12\n\nstart_days\x18\x02 \x01(\x05\x12\x13\n\x0b\x61\x63tive_days\x18\x03 \x01(\x05\x12\x15\n\rtotal_watched\x18\x04 \x01(\x02\x12\x1c\n\x14\x63\x61pped_total_watched\x18\x05 \x01(\x02\"C\n\x07\x43hannel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0e\x63\x61tegory_group\x18\x02 \x01(\t\x12\x12\n\nstart_date\x18\x03 \x01(\x05\"k\n\rStreamPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12I\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects\"a\n\x0eStreamPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12>\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched\"_\n\x0cUserPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12>\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched\"\xda\x01\n\x19UserPersonalizingPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12M\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32?.haystack_ml_stack.features.UserPersonalizingPWatched.DataEntry\x1a]\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12?\n\x05value\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched:\x02\x38\x01\"i\n\x0bUserPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12I\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects\"\xe3\x01\n\x18UserPersonalizingPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12L\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32>.haystack_ml_stack.features.UserPersonalizingPSelect.DataEntry\x1ah\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12J\n\x05value\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects:\x02\x38\x01\"\xa2\x01\n\x16StreamSimilarityScores\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12J\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32<.haystack_ml_stack.features.StreamSimilarityScores.DataEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x01:\x02\x38\x01\"\xd0\x01\n\x13GlobalPlaylistStats\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12G\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x39.haystack_ml_stack.features.GlobalPlaylistStats.DataEntry\x1a_\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x41\n\x05value\x18\x02 \x01(\x0b\x32\x32.haystack_ml_stack.features.PlaylistStatsForGlobal:\x02\x38\x01\"\xca\x01\n\x11UserPlaylistStats\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x45\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x37.haystack_ml_stack.features.UserPlaylistStats.DataEntry\x1a]\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12?\n\x05value\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.PlaylistStatsForUser:\x02\x38\x01\"T\n\x0eGlobalChannels\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x31\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32#.haystack_ml_stack.features.Channelb\x06proto3')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x66\x65\x61tures.proto\x12\x1ahaystack_ml_stack.features\"7\n\x12\x45ntryContextCounts\x12\x10\n\x08\x61ttempts\x18\x01 \x01(\x05\x12\x0f\n\x07watched\x18\x02 \x01(\x05\"_\n\x0cSelectCounts\x12\x15\n\rtotal_selects\x18\x01 \x01(\x05\x12!\n\x19total_selects_and_watched\x18\x02 \x01(\x05\x12\x15\n\rtotal_browsed\x18\x03 \x01(\x05\"\xf3\x02\n\x14\x45ntryContextPWatched\x12@\n\x08\x61utoplay\x18\x01 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12\x41\n\tsel_thumb\x18\x02 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12\x43\n\x0b\x63hoose_next\x18\x03 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12@\n\x08\x63h_swtch\x18\x04 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\x12O\n\x17launch_first_in_session\x18\x05 \x01(\x0b\x32..haystack_ml_stack.features.EntryContextCounts\"\x85\x02\n\x0fPositionPSelect\x12;\n\tfirst_pos\x18\x01 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12<\n\nsecond_pos\x18\x02 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12;\n\tthird_pos\x18\x03 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\x12:\n\x08rest_pos\x18\x04 \x01(\x0b\x32(.haystack_ml_stack.features.SelectCounts\"\xa9\x01\n\x1f\x42rowsedDebiasedPositionPSelects\x12\x44\n\x0fup_to_4_browsed\x18\x01 \x01(\x0b\x32+.haystack_ml_stack.features.PositionPSelect\x12@\n\x0b\x61ll_browsed\x18\x02 \x01(\x0b\x32+.haystack_ml_stack.features.PositionPSelect\"\xb8\x01\n\x16PlaylistStatsForGlobal\x12\x15\n\rwatched_count\x18\x01 \x01(\x05\x12\x19\n\x11not_watched_count\x18\x02 \x01(\x05\x12\x1b\n\x13\x63\x61pped_watched_secs\x18\x03 \x01(\x02\x12\x1f\n\x17\x63\x61pped_not_watched_secs\x18\x04 \x01(\x02\x12\x14\n\x0cwatched_secs\x18\x05 \x01(\x02\x12\x18\n\x10not_watched_secs\x18\x06 \x01(\x02\"\xc5\x01\n\x14PlaylistStatsForUser\x12\x12\n\ntotal_days\x18\x01 \x01(\x05\x12\x12\n\nstart_days\x18\x02 \x01(\x05\x12\x13\n\x0b\x61\x63tive_days\x18\x03 \x01(\x05\x12\x15\n\rtotal_watched\x18\x04 \x01(\x02\x12\x1c\n\x14\x63\x61pped_total_watched\x18\x05 \x01(\x02\x12 \n\x18\x63\x61pped_total_log_watched\x18\x06 \x01(\x02\x12\x19\n\x11\x66irst_active_date\x18\x07 \x01(\t\"C\n\x07\x43hannel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0e\x63\x61tegory_group\x18\x02 \x01(\t\x12\x12\n\nstart_date\x18\x03 \x01(\x05\"k\n\rStreamPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12I\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects\"a\n\x0eStreamPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12>\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched\"_\n\x0cUserPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12>\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched\"\xda\x01\n\x19UserPersonalizingPWatched\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12M\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32?.haystack_ml_stack.features.UserPersonalizingPWatched.DataEntry\x1a]\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12?\n\x05value\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.EntryContextPWatched:\x02\x38\x01\"i\n\x0bUserPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12I\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects\"\xe3\x01\n\x18UserPersonalizingPSelect\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12L\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32>.haystack_ml_stack.features.UserPersonalizingPSelect.DataEntry\x1ah\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12J\n\x05value\x18\x02 \x01(\x0b\x32;.haystack_ml_stack.features.BrowsedDebiasedPositionPSelects:\x02\x38\x01\"\xa2\x01\n\x16StreamSimilarityScores\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12J\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32<.haystack_ml_stack.features.StreamSimilarityScores.DataEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x01:\x02\x38\x01\"\xd0\x01\n\x13GlobalPlaylistStats\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12G\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x39.haystack_ml_stack.features.GlobalPlaylistStats.DataEntry\x1a_\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x41\n\x05value\x18\x02 \x01(\x0b\x32\x32.haystack_ml_stack.features.PlaylistStatsForGlobal:\x02\x38\x01\"\xca\x01\n\x11UserPlaylistStats\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x45\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x37.haystack_ml_stack.features.UserPlaylistStats.DataEntry\x1a]\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12?\n\x05value\x18\x02 \x01(\x0b\x32\x30.haystack_ml_stack.features.PlaylistStatsForUser:\x02\x38\x01\"T\n\x0eGlobalChannels\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12\x31\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32#.haystack_ml_stack.features.Channelb\x06proto3')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -54,37 +54,37 @@ if not _descriptor._USE_C_DESCRIPTORS:
54
54
  _globals['_PLAYLISTSTATSFORGLOBAL']._serialized_start=1011
55
55
  _globals['_PLAYLISTSTATSFORGLOBAL']._serialized_end=1195
56
56
  _globals['_PLAYLISTSTATSFORUSER']._serialized_start=1198
57
- _globals['_PLAYLISTSTATSFORUSER']._serialized_end=1334
58
- _globals['_CHANNEL']._serialized_start=1336
59
- _globals['_CHANNEL']._serialized_end=1403
60
- _globals['_STREAMPSELECT']._serialized_start=1405
61
- _globals['_STREAMPSELECT']._serialized_end=1512
62
- _globals['_STREAMPWATCHED']._serialized_start=1514
63
- _globals['_STREAMPWATCHED']._serialized_end=1611
64
- _globals['_USERPWATCHED']._serialized_start=1613
65
- _globals['_USERPWATCHED']._serialized_end=1708
66
- _globals['_USERPERSONALIZINGPWATCHED']._serialized_start=1711
67
- _globals['_USERPERSONALIZINGPWATCHED']._serialized_end=1929
68
- _globals['_USERPERSONALIZINGPWATCHED_DATAENTRY']._serialized_start=1836
69
- _globals['_USERPERSONALIZINGPWATCHED_DATAENTRY']._serialized_end=1929
70
- _globals['_USERPSELECT']._serialized_start=1931
71
- _globals['_USERPSELECT']._serialized_end=2036
72
- _globals['_USERPERSONALIZINGPSELECT']._serialized_start=2039
73
- _globals['_USERPERSONALIZINGPSELECT']._serialized_end=2266
74
- _globals['_USERPERSONALIZINGPSELECT_DATAENTRY']._serialized_start=2162
75
- _globals['_USERPERSONALIZINGPSELECT_DATAENTRY']._serialized_end=2266
76
- _globals['_STREAMSIMILARITYSCORES']._serialized_start=2269
77
- _globals['_STREAMSIMILARITYSCORES']._serialized_end=2431
78
- _globals['_STREAMSIMILARITYSCORES_DATAENTRY']._serialized_start=2388
79
- _globals['_STREAMSIMILARITYSCORES_DATAENTRY']._serialized_end=2431
80
- _globals['_GLOBALPLAYLISTSTATS']._serialized_start=2434
81
- _globals['_GLOBALPLAYLISTSTATS']._serialized_end=2642
82
- _globals['_GLOBALPLAYLISTSTATS_DATAENTRY']._serialized_start=2547
83
- _globals['_GLOBALPLAYLISTSTATS_DATAENTRY']._serialized_end=2642
84
- _globals['_USERPLAYLISTSTATS']._serialized_start=2645
85
- _globals['_USERPLAYLISTSTATS']._serialized_end=2847
86
- _globals['_USERPLAYLISTSTATS_DATAENTRY']._serialized_start=2754
87
- _globals['_USERPLAYLISTSTATS_DATAENTRY']._serialized_end=2847
88
- _globals['_GLOBALCHANNELS']._serialized_start=2849
89
- _globals['_GLOBALCHANNELS']._serialized_end=2933
57
+ _globals['_PLAYLISTSTATSFORUSER']._serialized_end=1395
58
+ _globals['_CHANNEL']._serialized_start=1397
59
+ _globals['_CHANNEL']._serialized_end=1464
60
+ _globals['_STREAMPSELECT']._serialized_start=1466
61
+ _globals['_STREAMPSELECT']._serialized_end=1573
62
+ _globals['_STREAMPWATCHED']._serialized_start=1575
63
+ _globals['_STREAMPWATCHED']._serialized_end=1672
64
+ _globals['_USERPWATCHED']._serialized_start=1674
65
+ _globals['_USERPWATCHED']._serialized_end=1769
66
+ _globals['_USERPERSONALIZINGPWATCHED']._serialized_start=1772
67
+ _globals['_USERPERSONALIZINGPWATCHED']._serialized_end=1990
68
+ _globals['_USERPERSONALIZINGPWATCHED_DATAENTRY']._serialized_start=1897
69
+ _globals['_USERPERSONALIZINGPWATCHED_DATAENTRY']._serialized_end=1990
70
+ _globals['_USERPSELECT']._serialized_start=1992
71
+ _globals['_USERPSELECT']._serialized_end=2097
72
+ _globals['_USERPERSONALIZINGPSELECT']._serialized_start=2100
73
+ _globals['_USERPERSONALIZINGPSELECT']._serialized_end=2327
74
+ _globals['_USERPERSONALIZINGPSELECT_DATAENTRY']._serialized_start=2223
75
+ _globals['_USERPERSONALIZINGPSELECT_DATAENTRY']._serialized_end=2327
76
+ _globals['_STREAMSIMILARITYSCORES']._serialized_start=2330
77
+ _globals['_STREAMSIMILARITYSCORES']._serialized_end=2492
78
+ _globals['_STREAMSIMILARITYSCORES_DATAENTRY']._serialized_start=2449
79
+ _globals['_STREAMSIMILARITYSCORES_DATAENTRY']._serialized_end=2492
80
+ _globals['_GLOBALPLAYLISTSTATS']._serialized_start=2495
81
+ _globals['_GLOBALPLAYLISTSTATS']._serialized_end=2703
82
+ _globals['_GLOBALPLAYLISTSTATS_DATAENTRY']._serialized_start=2608
83
+ _globals['_GLOBALPLAYLISTSTATS_DATAENTRY']._serialized_end=2703
84
+ _globals['_USERPLAYLISTSTATS']._serialized_start=2706
85
+ _globals['_USERPLAYLISTSTATS']._serialized_end=2908
86
+ _globals['_USERPLAYLISTSTATS_DATAENTRY']._serialized_start=2815
87
+ _globals['_USERPLAYLISTSTATS_DATAENTRY']._serialized_end=2908
88
+ _globals['_GLOBALCHANNELS']._serialized_start=2910
89
+ _globals['_GLOBALCHANNELS']._serialized_end=2994
90
90
  # @@protoc_insertion_point(module_scope)
@@ -7,7 +7,7 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
7
7
  DESCRIPTOR: _descriptor.FileDescriptor
8
8
 
9
9
  class EntryContextCounts(_message.Message):
10
- __slots__ = ("attempts", "watched")
10
+ __slots__ = ()
11
11
  ATTEMPTS_FIELD_NUMBER: _ClassVar[int]
12
12
  WATCHED_FIELD_NUMBER: _ClassVar[int]
13
13
  attempts: int
@@ -15,7 +15,7 @@ class EntryContextCounts(_message.Message):
15
15
  def __init__(self, attempts: _Optional[int] = ..., watched: _Optional[int] = ...) -> None: ...
16
16
 
17
17
  class SelectCounts(_message.Message):
18
- __slots__ = ("total_selects", "total_selects_and_watched", "total_browsed")
18
+ __slots__ = ()
19
19
  TOTAL_SELECTS_FIELD_NUMBER: _ClassVar[int]
20
20
  TOTAL_SELECTS_AND_WATCHED_FIELD_NUMBER: _ClassVar[int]
21
21
  TOTAL_BROWSED_FIELD_NUMBER: _ClassVar[int]
@@ -25,7 +25,7 @@ class SelectCounts(_message.Message):
25
25
  def __init__(self, total_selects: _Optional[int] = ..., total_selects_and_watched: _Optional[int] = ..., total_browsed: _Optional[int] = ...) -> None: ...
26
26
 
27
27
  class EntryContextPWatched(_message.Message):
28
- __slots__ = ("autoplay", "sel_thumb", "choose_next", "ch_swtch", "launch_first_in_session")
28
+ __slots__ = ()
29
29
  AUTOPLAY_FIELD_NUMBER: _ClassVar[int]
30
30
  SEL_THUMB_FIELD_NUMBER: _ClassVar[int]
31
31
  CHOOSE_NEXT_FIELD_NUMBER: _ClassVar[int]
@@ -39,7 +39,7 @@ class EntryContextPWatched(_message.Message):
39
39
  def __init__(self, autoplay: _Optional[_Union[EntryContextCounts, _Mapping]] = ..., sel_thumb: _Optional[_Union[EntryContextCounts, _Mapping]] = ..., choose_next: _Optional[_Union[EntryContextCounts, _Mapping]] = ..., ch_swtch: _Optional[_Union[EntryContextCounts, _Mapping]] = ..., launch_first_in_session: _Optional[_Union[EntryContextCounts, _Mapping]] = ...) -> None: ...
40
40
 
41
41
  class PositionPSelect(_message.Message):
42
- __slots__ = ("first_pos", "second_pos", "third_pos", "rest_pos")
42
+ __slots__ = ()
43
43
  FIRST_POS_FIELD_NUMBER: _ClassVar[int]
44
44
  SECOND_POS_FIELD_NUMBER: _ClassVar[int]
45
45
  THIRD_POS_FIELD_NUMBER: _ClassVar[int]
@@ -51,7 +51,7 @@ class PositionPSelect(_message.Message):
51
51
  def __init__(self, first_pos: _Optional[_Union[SelectCounts, _Mapping]] = ..., second_pos: _Optional[_Union[SelectCounts, _Mapping]] = ..., third_pos: _Optional[_Union[SelectCounts, _Mapping]] = ..., rest_pos: _Optional[_Union[SelectCounts, _Mapping]] = ...) -> None: ...
52
52
 
53
53
  class BrowsedDebiasedPositionPSelects(_message.Message):
54
- __slots__ = ("up_to_4_browsed", "all_browsed")
54
+ __slots__ = ()
55
55
  UP_TO_4_BROWSED_FIELD_NUMBER: _ClassVar[int]
56
56
  ALL_BROWSED_FIELD_NUMBER: _ClassVar[int]
57
57
  up_to_4_browsed: PositionPSelect
@@ -59,7 +59,7 @@ class BrowsedDebiasedPositionPSelects(_message.Message):
59
59
  def __init__(self, up_to_4_browsed: _Optional[_Union[PositionPSelect, _Mapping]] = ..., all_browsed: _Optional[_Union[PositionPSelect, _Mapping]] = ...) -> None: ...
60
60
 
61
61
  class PlaylistStatsForGlobal(_message.Message):
62
- __slots__ = ("watched_count", "not_watched_count", "capped_watched_secs", "capped_not_watched_secs", "watched_secs", "not_watched_secs")
62
+ __slots__ = ()
63
63
  WATCHED_COUNT_FIELD_NUMBER: _ClassVar[int]
64
64
  NOT_WATCHED_COUNT_FIELD_NUMBER: _ClassVar[int]
65
65
  CAPPED_WATCHED_SECS_FIELD_NUMBER: _ClassVar[int]
@@ -75,21 +75,25 @@ class PlaylistStatsForGlobal(_message.Message):
75
75
  def __init__(self, watched_count: _Optional[int] = ..., not_watched_count: _Optional[int] = ..., capped_watched_secs: _Optional[float] = ..., capped_not_watched_secs: _Optional[float] = ..., watched_secs: _Optional[float] = ..., not_watched_secs: _Optional[float] = ...) -> None: ...
76
76
 
77
77
  class PlaylistStatsForUser(_message.Message):
78
- __slots__ = ("total_days", "start_days", "active_days", "total_watched", "capped_total_watched")
78
+ __slots__ = ()
79
79
  TOTAL_DAYS_FIELD_NUMBER: _ClassVar[int]
80
80
  START_DAYS_FIELD_NUMBER: _ClassVar[int]
81
81
  ACTIVE_DAYS_FIELD_NUMBER: _ClassVar[int]
82
82
  TOTAL_WATCHED_FIELD_NUMBER: _ClassVar[int]
83
83
  CAPPED_TOTAL_WATCHED_FIELD_NUMBER: _ClassVar[int]
84
+ CAPPED_TOTAL_LOG_WATCHED_FIELD_NUMBER: _ClassVar[int]
85
+ FIRST_ACTIVE_DATE_FIELD_NUMBER: _ClassVar[int]
84
86
  total_days: int
85
87
  start_days: int
86
88
  active_days: int
87
89
  total_watched: float
88
90
  capped_total_watched: float
89
- def __init__(self, total_days: _Optional[int] = ..., start_days: _Optional[int] = ..., active_days: _Optional[int] = ..., total_watched: _Optional[float] = ..., capped_total_watched: _Optional[float] = ...) -> None: ...
91
+ capped_total_log_watched: float
92
+ first_active_date: str
93
+ def __init__(self, total_days: _Optional[int] = ..., start_days: _Optional[int] = ..., active_days: _Optional[int] = ..., total_watched: _Optional[float] = ..., capped_total_watched: _Optional[float] = ..., capped_total_log_watched: _Optional[float] = ..., first_active_date: _Optional[str] = ...) -> None: ...
90
94
 
91
95
  class Channel(_message.Message):
92
- __slots__ = ("name", "category_group", "start_date")
96
+ __slots__ = ()
93
97
  NAME_FIELD_NUMBER: _ClassVar[int]
94
98
  CATEGORY_GROUP_FIELD_NUMBER: _ClassVar[int]
95
99
  START_DATE_FIELD_NUMBER: _ClassVar[int]
@@ -99,7 +103,7 @@ class Channel(_message.Message):
99
103
  def __init__(self, name: _Optional[str] = ..., category_group: _Optional[str] = ..., start_date: _Optional[int] = ...) -> None: ...
100
104
 
101
105
  class StreamPSelect(_message.Message):
102
- __slots__ = ("version", "data")
106
+ __slots__ = ()
103
107
  VERSION_FIELD_NUMBER: _ClassVar[int]
104
108
  DATA_FIELD_NUMBER: _ClassVar[int]
105
109
  version: int
@@ -107,7 +111,7 @@ class StreamPSelect(_message.Message):
107
111
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Union[BrowsedDebiasedPositionPSelects, _Mapping]] = ...) -> None: ...
108
112
 
109
113
  class StreamPWatched(_message.Message):
110
- __slots__ = ("version", "data")
114
+ __slots__ = ()
111
115
  VERSION_FIELD_NUMBER: _ClassVar[int]
112
116
  DATA_FIELD_NUMBER: _ClassVar[int]
113
117
  version: int
@@ -115,7 +119,7 @@ class StreamPWatched(_message.Message):
115
119
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Union[EntryContextPWatched, _Mapping]] = ...) -> None: ...
116
120
 
117
121
  class UserPWatched(_message.Message):
118
- __slots__ = ("version", "data")
122
+ __slots__ = ()
119
123
  VERSION_FIELD_NUMBER: _ClassVar[int]
120
124
  DATA_FIELD_NUMBER: _ClassVar[int]
121
125
  version: int
@@ -123,9 +127,9 @@ class UserPWatched(_message.Message):
123
127
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Union[EntryContextPWatched, _Mapping]] = ...) -> None: ...
124
128
 
125
129
  class UserPersonalizingPWatched(_message.Message):
126
- __slots__ = ("version", "data")
130
+ __slots__ = ()
127
131
  class DataEntry(_message.Message):
128
- __slots__ = ("key", "value")
132
+ __slots__ = ()
129
133
  KEY_FIELD_NUMBER: _ClassVar[int]
130
134
  VALUE_FIELD_NUMBER: _ClassVar[int]
131
135
  key: str
@@ -138,7 +142,7 @@ class UserPersonalizingPWatched(_message.Message):
138
142
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Mapping[str, EntryContextPWatched]] = ...) -> None: ...
139
143
 
140
144
  class UserPSelect(_message.Message):
141
- __slots__ = ("version", "data")
145
+ __slots__ = ()
142
146
  VERSION_FIELD_NUMBER: _ClassVar[int]
143
147
  DATA_FIELD_NUMBER: _ClassVar[int]
144
148
  version: int
@@ -146,9 +150,9 @@ class UserPSelect(_message.Message):
146
150
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Union[BrowsedDebiasedPositionPSelects, _Mapping]] = ...) -> None: ...
147
151
 
148
152
  class UserPersonalizingPSelect(_message.Message):
149
- __slots__ = ("version", "data")
153
+ __slots__ = ()
150
154
  class DataEntry(_message.Message):
151
- __slots__ = ("key", "value")
155
+ __slots__ = ()
152
156
  KEY_FIELD_NUMBER: _ClassVar[int]
153
157
  VALUE_FIELD_NUMBER: _ClassVar[int]
154
158
  key: str
@@ -161,9 +165,9 @@ class UserPersonalizingPSelect(_message.Message):
161
165
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Mapping[str, BrowsedDebiasedPositionPSelects]] = ...) -> None: ...
162
166
 
163
167
  class StreamSimilarityScores(_message.Message):
164
- __slots__ = ("version", "data")
168
+ __slots__ = ()
165
169
  class DataEntry(_message.Message):
166
- __slots__ = ("key", "value")
170
+ __slots__ = ()
167
171
  KEY_FIELD_NUMBER: _ClassVar[int]
168
172
  VALUE_FIELD_NUMBER: _ClassVar[int]
169
173
  key: str
@@ -176,9 +180,9 @@ class StreamSimilarityScores(_message.Message):
176
180
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Mapping[str, float]] = ...) -> None: ...
177
181
 
178
182
  class GlobalPlaylistStats(_message.Message):
179
- __slots__ = ("version", "data")
183
+ __slots__ = ()
180
184
  class DataEntry(_message.Message):
181
- __slots__ = ("key", "value")
185
+ __slots__ = ()
182
186
  KEY_FIELD_NUMBER: _ClassVar[int]
183
187
  VALUE_FIELD_NUMBER: _ClassVar[int]
184
188
  key: str
@@ -191,9 +195,9 @@ class GlobalPlaylistStats(_message.Message):
191
195
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Mapping[str, PlaylistStatsForGlobal]] = ...) -> None: ...
192
196
 
193
197
  class UserPlaylistStats(_message.Message):
194
- __slots__ = ("version", "data")
198
+ __slots__ = ()
195
199
  class DataEntry(_message.Message):
196
- __slots__ = ("key", "value")
200
+ __slots__ = ()
197
201
  KEY_FIELD_NUMBER: _ClassVar[int]
198
202
  VALUE_FIELD_NUMBER: _ClassVar[int]
199
203
  key: str
@@ -206,7 +210,7 @@ class UserPlaylistStats(_message.Message):
206
210
  def __init__(self, version: _Optional[int] = ..., data: _Optional[_Mapping[str, PlaylistStatsForUser]] = ...) -> None: ...
207
211
 
208
212
  class GlobalChannels(_message.Message):
209
- __slots__ = ("version", "data")
213
+ __slots__ = ()
210
214
  VERSION_FIELD_NUMBER: _ClassVar[int]
211
215
  DATA_FIELD_NUMBER: _ClassVar[int]
212
216
  version: int
@@ -715,6 +715,8 @@ def _complete_features_for_channels(
715
715
  "active_days",
716
716
  "total_watched",
717
717
  "capped_total_watched",
718
+ "capped_total_log_watched",
719
+ "first_active_date",
718
720
  ]
719
721
 
720
722
  for ch in channels:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haystack-ml-stack
3
- Version: 0.4.7
3
+ Version: 0.4.9
4
4
  Summary: Functions related to Haystack ML
5
5
  Author-email: Oscar Vega <oscar@haystack.tv>
6
6
  License: MIT
@@ -150,3 +150,64 @@ def test_user_bias_serializers():
150
150
  "User pselect serialization does not match expected value."
151
151
  )
152
152
  return
153
+
154
+
155
+ def test_user_playlist_stats_serializer_backward_compatibility():
156
+ serializer = SerializerRegistry[("USER", "PLAYLIST_CATEGORY_STATS#3M", "v1")]
157
+
158
+ # Case 1: true old binary — built without fields 6 and 7, identical to what
159
+ # the old serializer would have written before the proto change
160
+ old_msg = features_pb2_v1.UserPlaylistStats()
161
+ old_msg.version = 1
162
+ entry = old_msg.data["sports"]
163
+ entry.total_days = 30
164
+ entry.start_days = 5
165
+ entry.active_days = 10
166
+ entry.total_watched = 120.5
167
+ entry.capped_total_watched = 100.0
168
+ old_binary = old_msg.SerializeToString()
169
+
170
+ result = serializer.deserialize(old_binary)
171
+ assert result.data["sports"].total_days == 30
172
+ assert result.data["sports"].capped_total_watched == 100.0
173
+ assert result.data["sports"].capped_total_log_watched == 0.0
174
+ assert result.data["sports"].first_active_date == ""
175
+
176
+ # Case 2: new data with new fields — full round-trip
177
+ new_data = {
178
+ "version": 1,
179
+ "data": {
180
+ "sports": {
181
+ "total_days": 30,
182
+ "start_days": 5,
183
+ "active_days": 10,
184
+ "total_watched": 120.5,
185
+ "capped_total_watched": 100.0,
186
+ "capped_total_log_watched": 4.8,
187
+ "first_active_date": "2024-01-15",
188
+ }
189
+ },
190
+ }
191
+ binary = serializer.serialize(new_data)
192
+ result = serializer.deserialize(binary)
193
+ assert abs(result.data["sports"].capped_total_log_watched - 4.8) < 1e-6
194
+ assert result.data["sports"].first_active_date == "2024-01-15"
195
+
196
+ # Case 3: old-format dict missing the new fields — .get() fallback
197
+ old_format_data = {
198
+ "version": 1,
199
+ "data": {
200
+ "sports": {
201
+ "total_days": 30,
202
+ "start_days": 5,
203
+ "active_days": 10,
204
+ "total_watched": 120.5,
205
+ "capped_total_watched": 100.0,
206
+ }
207
+ },
208
+ }
209
+ binary = serializer.serialize(old_format_data)
210
+ result = serializer.deserialize(binary)
211
+ assert result.data["sports"].total_days == 30
212
+ assert result.data["sports"].capped_total_log_watched == 0.0
213
+ assert result.data["sports"].first_active_date == ""
@@ -1 +0,0 @@
1
- __version__ = "0.4.7"