horde-model-reference 2.1.0__tar.gz → 2.1.2__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 (207) hide show
  1. {horde_model_reference-2.1.0/src/horde_model_reference.egg-info → horde_model_reference-2.1.2}/PKG-INFO +1 -1
  2. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/_version.py +2 -2
  3. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/text_model_parser.py +3 -1
  4. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/integrations/horde_api_models.py +258 -29
  5. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2/src/horde_model_reference.egg-info}/PKG-INFO +1 -1
  6. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference.egg-info/SOURCES.txt +1 -0
  7. horde_model_reference-2.1.2/tests/integrations/test_stats_aggregation.py +247 -0
  8. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/test_text_model_parser.py +40 -0
  9. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.CONTRIBUTING.md +0 -0
  10. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.dockerignore +0 -0
  11. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.env.example +0 -0
  12. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.env.primary.example +0 -0
  13. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.env.sync.example +0 -0
  14. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/codeql.yml +0 -0
  15. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/docker-validation.yml +0 -0
  16. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/lint.yml +0 -0
  17. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/maintests.yml +0 -0
  18. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/prtests.yml +0 -0
  19. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.github/workflows/release.yml +0 -0
  20. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.gitignore +0 -0
  21. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.gitmodules +0 -0
  22. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.pre-commit-config.yaml +0 -0
  23. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/.readthedocs.yaml +0 -0
  24. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/DEPLOYMENT.md +0 -0
  25. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/Dockerfile +0 -0
  26. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/LICENSE +0 -0
  27. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/MANIFEST.in +0 -0
  28. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/README.md +0 -0
  29. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docker-compose.redis.yml +0 -0
  30. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docker-compose.sync.example.yml +0 -0
  31. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docker-compose.yml +0 -0
  32. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/build_docs.py +0 -0
  33. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/.pages +0 -0
  34. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/_version.md +0 -0
  35. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/.pages +0 -0
  36. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/base.md +0 -0
  37. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/filesystem_backend.md +0 -0
  38. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/github_backend.md +0 -0
  39. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/http_backend.md +0 -0
  40. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/redis_backend.md +0 -0
  41. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/backends/replica_backend_base.md +0 -0
  42. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/.pages +0 -0
  43. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/classes/.pages +0 -0
  44. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/classes/legacy_converters.md +0 -0
  45. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/classes/legacy_models.md +0 -0
  46. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/convert_all_legacy_dbs.md +0 -0
  47. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/legacy/validate_sd.md +0 -0
  48. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/meta_consts.md +0 -0
  49. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/model_reference_manager.md +0 -0
  50. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/model_reference_metadata.md +0 -0
  51. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/model_reference_records.md +0 -0
  52. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/path_consts.md +0 -0
  53. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/.pages +0 -0
  54. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/app.md +0 -0
  55. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/shared.md +0 -0
  56. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/.pages +0 -0
  57. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/routers/.pages +0 -0
  58. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/routers/create_update.md +0 -0
  59. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/routers/metadata.md +0 -0
  60. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/routers/references.md +0 -0
  61. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v1/routers/shared.md +0 -0
  62. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v2/.pages +0 -0
  63. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v2/models.md +0 -0
  64. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v2/routers/.pages +0 -0
  65. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v2/routers/metadata.md +0 -0
  66. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/service/v2/routers/references.md +0 -0
  67. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/showcase/.pages +0 -0
  68. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/sync/.pages +0 -0
  69. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/sync/comparator.md +0 -0
  70. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/sync/config.md +0 -0
  71. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/sync/github_client.md +0 -0
  72. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/sync/watch_mode.md +0 -0
  73. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/horde_model_reference/util.md +0 -0
  74. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/index.md +0 -0
  75. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/legacy_csv_conversion.md +0 -0
  76. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/model_reference_backend.md +0 -0
  77. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/primary_deployments.md +0 -0
  78. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/replica_backend_base.md +0 -0
  79. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/docs/stylesheets/extra.css +0 -0
  80. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/mkdocs.yml +0 -0
  81. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/pyproject.toml +0 -0
  82. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/schemas/stable_diffusion.example.json +0 -0
  83. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/schemas/stable_diffusion.schema.json +0 -0
  84. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/README.md +0 -0
  85. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/get_all_names.py +0 -0
  86. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/legacy_text/convert.py +0 -0
  87. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/legacy_text/defaults.json +0 -0
  88. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/legacy_text/generation_params.json +0 -0
  89. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/legacy_text/reverse_convert.py +0 -0
  90. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/sync/README.md +0 -0
  91. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/sync/github_app_auth_example.md +0 -0
  92. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/scripts/sync/sync_github_references.py +0 -0
  93. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/setup.cfg +0 -0
  94. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/__init__.py +0 -0
  95. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/__init__.py +0 -0
  96. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/audit_analysis.py +0 -0
  97. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/audit_cache.py +0 -0
  98. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/base_cache.py +0 -0
  99. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/constants.py +0 -0
  100. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/filter_presets.py +0 -0
  101. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/statistics.py +0 -0
  102. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/statistics_cache.py +0 -0
  103. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/analytics/text_model_grouping.py +0 -0
  104. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/__init__.py +0 -0
  105. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/base.py +0 -0
  106. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/filesystem_backend.py +0 -0
  107. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/github_backend.py +0 -0
  108. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/http_backend.py +0 -0
  109. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/redis_backend.py +0 -0
  110. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/backends/replica_backend_base.py +0 -0
  111. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/integrations/__init__.py +0 -0
  112. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/integrations/data_merger.py +0 -0
  113. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/integrations/horde_api_integration.py +0 -0
  114. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/README.md +0 -0
  115. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/__init__.py +0 -0
  116. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/classes/__init__.py +0 -0
  117. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/classes/legacy_converters.py +0 -0
  118. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/classes/legacy_models.py +0 -0
  119. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/convert_all_legacy_dbs.py +0 -0
  120. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/text_csv_utils.py +0 -0
  121. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/legacy/validate_sd.py +0 -0
  122. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/meta_consts.py +0 -0
  123. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/model_reference_manager.py +0 -0
  124. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/model_reference_metadata.py +0 -0
  125. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/model_reference_records.py +0 -0
  126. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/path_consts.py +0 -0
  127. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/py.typed +0 -0
  128. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/__init__.py +0 -0
  129. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/app.py +0 -0
  130. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/shared.py +0 -0
  131. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/statistics/__init__.py +0 -0
  132. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/statistics/routers/__init__.py +0 -0
  133. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/statistics/routers/audit.py +0 -0
  134. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/statistics/routers/statistics.py +0 -0
  135. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/__init__.py +0 -0
  136. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/routers/__init__.py +0 -0
  137. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/routers/create_update.py +0 -0
  138. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/routers/metadata.py +0 -0
  139. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/routers/references.py +0 -0
  140. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v1/routers/shared.py +0 -0
  141. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v2/__init__.py +0 -0
  142. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v2/models.py +0 -0
  143. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v2/routers/__init__.py +0 -0
  144. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v2/routers/metadata.py +0 -0
  145. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/service/v2/routers/references.py +0 -0
  146. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/showcase/README.md +0 -0
  147. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/__init__.py +0 -0
  148. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/comparator.py +0 -0
  149. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/config.py +0 -0
  150. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/github_client.py +0 -0
  151. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/legacy_text_validator.py +0 -0
  152. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/sync/watch_mode.py +0 -0
  153. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference/util.py +0 -0
  154. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference.egg-info/dependency_links.txt +0 -0
  155. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference.egg-info/entry_points.txt +0 -0
  156. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference.egg-info/requires.txt +0 -0
  157. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/src/horde_model_reference.egg-info/top_level.txt +0 -0
  158. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/test_endpoint.py +0 -0
  159. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/README.md +0 -0
  160. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/__init__.py +0 -0
  161. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/backends/__init__.py +0 -0
  162. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/backends/test_http_backend.py +0 -0
  163. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/backends/test_primary_mode.py +0 -0
  164. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/backends/test_redis_backend.py +0 -0
  165. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/check_model_ref_type_blocks.py +0 -0
  166. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/conftest.py +0 -0
  167. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/create_env_file_example.py +0 -0
  168. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/create_example_json.py +0 -0
  169. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/helpers.py +0 -0
  170. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/__init__.py +0 -0
  171. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/conftest.py +0 -0
  172. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_audit_analysis_live.py +0 -0
  173. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_audit_worker_count.py +0 -0
  174. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_data_merger.py +0 -0
  175. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_horde_api_integration.py +0 -0
  176. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_horde_api_integration_live.py +0 -0
  177. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/horde_api/test_indexed_horde_types.py +0 -0
  178. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/service/__init__.py +0 -0
  179. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/service/test_replica_backend_base.py +0 -0
  180. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/service/test_v1_api.py +0 -0
  181. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/service/test_v2_api.py +0 -0
  182. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/__init__.py +0 -0
  183. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/test_audit_analysis.py +0 -0
  184. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/test_statistics.py +0 -0
  185. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/test_statistics_cache.py +0 -0
  186. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/statistics_and_audit/test_text_model_grouping.py +0 -0
  187. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/sync/__init__.py +0 -0
  188. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/sync/test_comparator.py +0 -0
  189. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/sync/test_comparator_integration.py +0 -0
  190. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/sync/test_config.py +0 -0
  191. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/sync/test_legacy_text_validator.py +0 -0
  192. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_broken_tutu_grouping.py +0 -0
  193. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_canonical_format.py +0 -0
  194. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_combined_model_statistics.py +0 -0
  195. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_consts.py +0 -0
  196. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_convert_legacy_database.py +0 -0
  197. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_converters.py +0 -0
  198. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_env_example.py +0 -0
  199. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_examples.py +0 -0
  200. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_metadata.py +0 -0
  201. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_model_reference_manager.py +0 -0
  202. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_records.py +0 -0
  203. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_scripts.py +0 -0
  204. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_text_generation_csv_conversion.py +0 -0
  205. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_text_generation_file_paths.py +0 -0
  206. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/tests/test_text_model_group.py +0 -0
  207. {horde_model_reference-2.1.0 → horde_model_reference-2.1.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: horde_model_reference
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: A helper library providing a way to work with the lists of generation models, utility models, and any other related files required for the AI-Horde ecosystem.
5
5
  Author-email: tazlin <tazlin.on.github@gmail.com>, db0 <mail@dbzer0.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = 'v2.1.0'
32
- __version_tuple__ = version_tuple = (2, 1, 0)
31
+ __version__ = version = 'v2.1.2'
32
+ __version_tuple__ = version_tuple = (2, 1, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -49,7 +49,9 @@ VARIANT_PATTERNS = [
49
49
 
50
50
  # Quantization patterns
51
51
  QUANT_PATTERNS = [
52
- r"\b(Q[2-8](?:_K)?(?:_[SMLH])?)\b", # Q4_K_M, Q8, Q5_K_S
52
+ r"\b(Q[2-8]_K(?:_[SMLH])?)\b", # Q4_K_M, Q5_K_S, Q6_K (K-quants with optional size)
53
+ r"\b(Q[2-8]_[01])\b", # Q4_0, Q5_0, Q5_1, Q8_0 (legacy/standard quants)
54
+ r"\b(Q[2-8])\b", # Q4, Q8 (bare quant indicators)
53
55
  r"\b(GGUF|GGML|GPTQ|AWQ|EXL2)\b",
54
56
  r"\b(fp16|fp32|int8|int4)\b",
55
57
  ]
@@ -236,12 +236,168 @@ class _StatsLookup(BaseModel):
236
236
  total: dict[str, int] = Field(default_factory=dict)
237
237
 
238
238
 
239
+ def _strip_quantization_suffix(model_name: str) -> str:
240
+ """Strip quantization suffix from a model name, preserving size.
241
+
242
+ This is different from get_base_model_name which strips BOTH size and quantization.
243
+ This function only strips quantization, keeping the size suffix.
244
+
245
+ Args:
246
+ model_name: Model name potentially with quantization suffix.
247
+
248
+ Returns:
249
+ Model name without quantization suffix, but with size preserved.
250
+
251
+ Example:
252
+ "Lumimaid-v0.2-8B-Q8_0" -> "Lumimaid-v0.2-8B"
253
+ "Lumimaid-v0.2-8B" -> "Lumimaid-v0.2-8B"
254
+ "koboldcpp/Lumimaid-v0.2-8B-Q4_K_M" -> "koboldcpp/Lumimaid-v0.2-8B"
255
+ """
256
+ import re
257
+
258
+ # Quantization patterns to strip (same as text_model_parser.QUANT_PATTERNS but as suffix)
259
+ quant_suffix_patterns = [
260
+ r"[-_](Q[2-8]_K(?:_[SMLH])?)$", # -Q4_K_M, -Q5_K_S
261
+ r"[-_](Q[2-8]_[01])$", # -Q4_0, -Q5_0, -Q8_0
262
+ r"[-_](Q[2-8])$", # -Q4, -Q8
263
+ r"[-_](GGUF|GGML|GPTQ|AWQ|EXL2)$",
264
+ r"[-_](fp16|fp32|int8|int4)$",
265
+ ]
266
+
267
+ result = model_name
268
+ for pattern in quant_suffix_patterns:
269
+ result = re.sub(pattern, "", result, flags=re.IGNORECASE)
270
+
271
+ return result
272
+
273
+
274
+ def _build_base_name_index(model_names: list[str]) -> dict[str, list[str]]:
275
+ """Build an index mapping base model names to all matching model names.
276
+
277
+ This enables aggregating stats across quantization variants (e.g., Q4_K_M, Q8_0)
278
+ and different backend prefixes (aphrodite/, koboldcpp/).
279
+
280
+ Args:
281
+ model_names: List of model names from API stats (may include backend prefixes
282
+ and quantization suffixes).
283
+
284
+ Returns:
285
+ Dictionary mapping lowercase base model names to lists of original model names
286
+ (lowercase) that match that base.
287
+
288
+ Example:
289
+ Input: ["koboldcpp/Lumimaid-v0.2-8B", "koboldcpp/Lumimaid-v0.2-8B-Q8_0",
290
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B"]
291
+ Output: {"lumimaid-v0.2": ["koboldcpp/lumimaid-v0.2-8b",
292
+ "koboldcpp/lumimaid-v0.2-8b-q8_0",
293
+ "aphrodite/neversleep/lumimaid-v0.2-8b"]}
294
+ """
295
+ from horde_model_reference.analytics.text_model_parser import get_base_model_name
296
+ from horde_model_reference.meta_consts import strip_backend_prefix
297
+
298
+ base_name_index: dict[str, list[str]] = {}
299
+
300
+ for model_name in model_names:
301
+ # Store lowercase version for case-insensitive matching
302
+ model_name_lower = model_name.lower()
303
+
304
+ # Strip backend prefix first, then extract base name
305
+ stripped = strip_backend_prefix(model_name)
306
+
307
+ # Also strip org prefix for base name extraction (e.g., "NeverSleep/Lumimaid-v0.2-8B" -> "Lumimaid-v0.2-8B")
308
+ if "/" in stripped:
309
+ stripped = stripped.split("/")[-1]
310
+
311
+ base_name = get_base_model_name(stripped).lower()
312
+
313
+ if base_name not in base_name_index:
314
+ base_name_index[base_name] = []
315
+ if model_name_lower not in base_name_index[base_name]:
316
+ base_name_index[base_name].append(model_name_lower)
317
+
318
+ return base_name_index
319
+
320
+
321
+ def _build_model_with_size_index(model_names: list[str]) -> dict[str, list[str]]:
322
+ """Build an index mapping model names (with size, without quant) to all matching names.
323
+
324
+ This enables aggregating stats across quantization variants only (e.g., Q4_K_M, Q8_0)
325
+ while keeping different sizes separate.
326
+
327
+ Unlike _build_base_name_index which groups ALL variants (including different sizes),
328
+ this index only groups quantization variants of the SAME sized model.
329
+
330
+ The key normalizes:
331
+ - Backend prefix (stripped for matching, but preserved in values)
332
+ - Org prefix (stripped for matching)
333
+ - Quantization suffix (stripped for matching)
334
+
335
+ But preserves:
336
+ - Size suffix (8B, 12B, etc.)
337
+
338
+ Args:
339
+ model_names: List of model names from API stats (may include backend prefixes
340
+ and quantization suffixes).
341
+
342
+ Returns:
343
+ Dictionary mapping normalized model names (backend/model-size) to lists of
344
+ original model names (lowercase) that match that model.
345
+
346
+ Example:
347
+ Input: ["koboldcpp/Lumimaid-v0.2-8B", "koboldcpp/Lumimaid-v0.2-8B-Q8_0",
348
+ "koboldcpp/Lumimaid-v0.2-12B", "aphrodite/NeverSleep/Lumimaid-v0.2-8B"]
349
+ Output: {
350
+ "koboldcpp/lumimaid-v0.2-8b": [
351
+ "koboldcpp/lumimaid-v0.2-8b",
352
+ "koboldcpp/lumimaid-v0.2-8b-q8_0"
353
+ ],
354
+ "koboldcpp/lumimaid-v0.2-12b": ["koboldcpp/lumimaid-v0.2-12b"],
355
+ "aphrodite/lumimaid-v0.2-8b": ["aphrodite/neversleep/lumimaid-v0.2-8b"]
356
+ }
357
+ """
358
+ model_with_size_index: dict[str, list[str]] = {}
359
+
360
+ for model_name in model_names:
361
+ model_name_lower = model_name.lower()
362
+
363
+ # Extract backend prefix if present
364
+ backend_prefix = ""
365
+ stripped = model_name_lower
366
+ if stripped.startswith("aphrodite/"):
367
+ backend_prefix = "aphrodite/"
368
+ stripped = stripped[len("aphrodite/") :]
369
+ elif stripped.startswith("koboldcpp/"):
370
+ backend_prefix = "koboldcpp/"
371
+ stripped = stripped[len("koboldcpp/") :]
372
+
373
+ # Strip org prefix (e.g., "neversleep/lumimaid-v0.2-8b" -> "lumimaid-v0.2-8b")
374
+ if "/" in stripped:
375
+ stripped = stripped.split("/")[-1]
376
+
377
+ # Strip quantization suffix
378
+ stripped_no_quant = _strip_quantization_suffix(stripped)
379
+
380
+ # Build key: backend_prefix + model_name (no org, no quant, but with size)
381
+ key = f"{backend_prefix}{stripped_no_quant}"
382
+
383
+ if key not in model_with_size_index:
384
+ model_with_size_index[key] = []
385
+ if model_name_lower not in model_with_size_index[key]:
386
+ model_with_size_index[key].append(model_name_lower)
387
+
388
+ return model_with_size_index
389
+
390
+
239
391
  class IndexedHordeModelStats(RootModel[_StatsLookup]):
240
392
  """Indexed model stats for O(1) lookups by model name.
241
393
 
242
394
  This wraps the stats response and provides case-insensitive dictionary access.
243
395
  Time complexity: O(1) for lookups instead of O(n) for dict iteration.
244
396
 
397
+ Two indexes are built:
398
+ - _base_name_index: Groups ALL variants (including different sizes) for group-level aggregation
399
+ - _model_with_size_index: Groups only quantization variants for per-model stats
400
+
245
401
  Usage:
246
402
  indexed = IndexedHordeModelStats(stats_response)
247
403
  day_count = indexed.get_day("model_name") # Case-insensitive
@@ -249,6 +405,8 @@ class IndexedHordeModelStats(RootModel[_StatsLookup]):
249
405
  """
250
406
 
251
407
  root: _StatsLookup
408
+ _base_name_index: dict[str, list[str]] = {}
409
+ _model_with_size_index: dict[str, list[str]] = {}
252
410
 
253
411
  def __init__(self, stats_response: HordeModelStatsResponse) -> None:
254
412
  """Build indexed lookups from stats response.
@@ -264,6 +422,14 @@ class IndexedHordeModelStats(RootModel[_StatsLookup]):
264
422
  )
265
423
  super().__init__(root=lookups)
266
424
 
425
+ # Build indexes from all unique model names across all time periods
426
+ all_model_names = (
427
+ set(stats_response.day.keys()) | set(stats_response.month.keys()) | set(stats_response.total.keys())
428
+ )
429
+ model_names_list = list(all_model_names)
430
+ self._base_name_index = _build_base_name_index(model_names_list)
431
+ self._model_with_size_index = _build_model_with_size_index(model_names_list)
432
+
267
433
  def get_day(self, model_name: str) -> int | None:
268
434
  """Get day count for a model (case-insensitive). O(1)."""
269
435
  return self.root.day.get(model_name.lower())
@@ -282,12 +448,12 @@ class IndexedHordeModelStats(RootModel[_StatsLookup]):
282
448
  return name_lower in self.root.day or name_lower in self.root.month or name_lower in self.root.total
283
449
 
284
450
  def get_aggregated_stats(self, canonical_name: str) -> tuple[int, int, int]:
285
- """Get aggregated stats across all backend variants of a model.
451
+ """Get aggregated stats across all backend variants and quantization versions of a model.
286
452
 
287
- This method aggregates stats from all possible backend-prefixed variants:
288
- - Canonical name (e.g., "ReadyArt/Broken-Tutu-24B")
289
- - Aphrodite variant (e.g., "aphrodite/ReadyArt/Broken-Tutu-24B")
290
- - KoboldCPP variant (e.g., "koboldcpp/Broken-Tutu-24B")
453
+ This method aggregates stats from:
454
+ - Exact name variants (canonical, aphrodite/, koboldcpp/ prefixed)
455
+ - All quantization variants sharing the same base model name (Q4_K_M, Q8_0, etc.)
456
+ - Different org-prefixed variants (e.g., "NeverSleep/Lumimaid" matches "Lumimaid")
291
457
 
292
458
  Args:
293
459
  canonical_name: The canonical model name from the model reference.
@@ -297,73 +463,136 @@ class IndexedHordeModelStats(RootModel[_StatsLookup]):
297
463
 
298
464
  Example:
299
465
  >>> indexed = IndexedHordeModelStats(stats_response)
300
- >>> day, month, total = indexed.get_aggregated_stats("ReadyArt/Broken-Tutu-24B")
466
+ >>> day, month, total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
467
+ # Will aggregate: Lumimaid-v0.2-8B, koboldcpp/Lumimaid-v0.2-8B,
468
+ # koboldcpp/Lumimaid-v0.2-8B-Q8_0, aphrodite/NeverSleep/Lumimaid-v0.2-8B, etc.
301
469
  """
470
+ from horde_model_reference.analytics.text_model_parser import get_base_model_name
302
471
  from horde_model_reference.meta_consts import get_model_name_variants
303
472
 
304
- variants = get_model_name_variants(canonical_name)
473
+ # Collect all model names to aggregate (use set to avoid double-counting)
474
+ names_to_aggregate: set[str] = set()
305
475
 
476
+ # First, add exact variants from get_model_name_variants
477
+ variants = get_model_name_variants(canonical_name)
478
+ for variant in variants:
479
+ names_to_aggregate.add(variant.lower())
480
+
481
+ # Then, add all model names that share the same base model name
482
+ # This catches quantization variants and org-prefixed variants
483
+ # Strip org prefix from canonical name if present (e.g., "NeverSleep/Lumimaid-v0.2" -> "Lumimaid-v0.2")
484
+ canonical_without_org = canonical_name.split("/")[-1] if "/" in canonical_name else canonical_name
485
+ base_name = get_base_model_name(canonical_without_org).lower()
486
+ if base_name in self._base_name_index:
487
+ for api_model_name in self._base_name_index[base_name]:
488
+ names_to_aggregate.add(api_model_name)
489
+
490
+ # Aggregate stats across all matched names
306
491
  day_total = 0
307
492
  month_total = 0
308
493
  total_total = 0
309
494
 
310
- for variant in variants:
311
- day_total += self.get_day(variant) or 0
312
- month_total += self.get_month(variant) or 0
313
- total_total += self.get_total(variant) or 0
495
+ for name in names_to_aggregate:
496
+ day_total += self.get_day(name) or 0
497
+ month_total += self.get_month(name) or 0
498
+ total_total += self.get_total(name) or 0
314
499
 
315
500
  return (day_total, month_total, total_total)
316
501
 
317
502
  def get_stats_with_variations(
318
503
  self, canonical_name: str
319
504
  ) -> tuple[tuple[int, int, int], dict[str, tuple[int, int, int]]]:
320
- """Get aggregated stats and individual backend variations.
505
+ """Get stats for a specific model broken down by backend.
321
506
 
322
- This method returns both the aggregated stats (same as get_aggregated_stats)
323
- and a dictionary of individual backend stats keyed by backend name.
507
+ Unlike get_aggregated_stats which aggregates across all models with the same
508
+ base name (e.g., all Lumimaid-v0.2 sizes), this method returns stats only for
509
+ the exact model specified (including its quantization variants), broken down
510
+ by backend prefix.
511
+
512
+ This enables showing per-model stats in the UI when displaying grouped models,
513
+ where each model variant (8B, 12B, etc.) shows its own stats by backend.
324
514
 
325
515
  Args:
326
516
  canonical_name: The canonical model name from the model reference.
327
517
 
328
518
  Returns:
329
519
  Tuple of (aggregated_stats, variations_dict) where:
330
- - aggregated_stats: (day_total, month_total, total_total) aggregated
520
+ - aggregated_stats: (day_total, month_total, total_total) for this exact model
331
521
  - variations_dict: Dict of backend_name -> (day, month, total)
332
522
  Keys are 'canonical', 'aphrodite', 'koboldcpp' depending on what's found
333
523
  """
334
524
  from horde_model_reference.meta_consts import get_model_name_variants
335
525
 
526
+ # Collect all model names that are variants of this specific model
527
+ # Use _model_with_size_index to include quantization variants, but NOT size variants
528
+ names_to_aggregate: set[str] = set()
529
+
530
+ # Get exact backend-prefixed variants
336
531
  variants = get_model_name_variants(canonical_name)
337
- variations: dict[str, tuple[int, int, int]] = {}
532
+ for variant in variants:
533
+ variant_lower = variant.lower()
534
+ names_to_aggregate.add(variant_lower)
535
+
536
+ # Build the normalized key to look up in _model_with_size_index
537
+ # The key format is: [backend_prefix/]model_name (no org, no quant)
538
+ backend_prefix = ""
539
+ stripped = variant_lower
540
+ if stripped.startswith("aphrodite/"):
541
+ backend_prefix = "aphrodite/"
542
+ stripped = stripped[len("aphrodite/") :]
543
+ elif stripped.startswith("koboldcpp/"):
544
+ backend_prefix = "koboldcpp/"
545
+ stripped = stripped[len("koboldcpp/") :]
546
+
547
+ # Strip org prefix if present
548
+ if "/" in stripped:
549
+ stripped = stripped.split("/")[-1]
550
+
551
+ # Strip quantization suffix and build key
552
+ stripped_no_quant = _strip_quantization_suffix(stripped)
553
+ key = f"{backend_prefix}{stripped_no_quant}"
554
+
555
+ if key in self._model_with_size_index:
556
+ for api_model_name in self._model_with_size_index[key]:
557
+ names_to_aggregate.add(api_model_name)
558
+
559
+ # Track stats by backend for variations dict
560
+ backend_stats: dict[str, tuple[int, int, int]] = {
561
+ "canonical": (0, 0, 0),
562
+ "aphrodite": (0, 0, 0),
563
+ "koboldcpp": (0, 0, 0),
564
+ }
338
565
 
339
566
  day_total = 0
340
567
  month_total = 0
341
568
  total_total = 0
342
569
 
343
- # Look up each variant and store by backend name
344
- for variant in variants:
345
- day = self.get_day(variant) or 0
346
- month = self.get_month(variant) or 0
347
- total = self.get_total(variant) or 0
570
+ # Look up each name and aggregate by backend
571
+ for name in names_to_aggregate:
572
+ day = self.get_day(name) or 0
573
+ month = self.get_month(name) or 0
574
+ total = self.get_total(name) or 0
348
575
 
349
- # Only store if there's actual data
350
576
  if day > 0 or month > 0 or total > 0:
351
- # Determine backend name from variant
352
- if variant == canonical_name:
353
- backend_name = "canonical"
354
- elif variant.startswith("aphrodite/"):
577
+ # Determine backend name from the model name
578
+ if name.startswith("aphrodite/"):
355
579
  backend_name = "aphrodite"
356
- elif variant.startswith("koboldcpp/"):
580
+ elif name.startswith("koboldcpp/"):
357
581
  backend_name = "koboldcpp"
358
582
  else:
359
- backend_name = "unknown"
583
+ backend_name = "canonical"
360
584
 
361
- variations[backend_name] = (day, month, total)
585
+ # Accumulate stats for this backend
586
+ prev_day, prev_month, prev_total = backend_stats[backend_name]
587
+ backend_stats[backend_name] = (prev_day + day, prev_month + month, prev_total + total)
362
588
 
363
589
  day_total += day
364
590
  month_total += month
365
591
  total_total += total
366
592
 
593
+ # Filter to only backends with data
594
+ variations = {k: v for k, v in backend_stats.items() if v != (0, 0, 0)}
595
+
367
596
  return (day_total, month_total, total_total), variations
368
597
 
369
598
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: horde_model_reference
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: A helper library providing a way to work with the lists of generation models, utility models, and any other related files required for the AI-Horde ecosystem.
5
5
  Author-email: tazlin <tazlin.on.github@gmail.com>, db0 <mail@dbzer0.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -187,6 +187,7 @@ tests/horde_api/test_data_merger.py
187
187
  tests/horde_api/test_horde_api_integration.py
188
188
  tests/horde_api/test_horde_api_integration_live.py
189
189
  tests/horde_api/test_indexed_horde_types.py
190
+ tests/integrations/test_stats_aggregation.py
190
191
  tests/service/__init__.py
191
192
  tests/service/test_replica_backend_base.py
192
193
  tests/service/test_v1_api.py
@@ -0,0 +1,247 @@
1
+ """Tests for stats aggregation across quantization variants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from horde_model_reference.integrations.horde_api_models import (
6
+ HordeModelStatsResponse,
7
+ IndexedHordeModelStats,
8
+ )
9
+
10
+
11
+ class TestIndexedHordeModelStatsAggregation:
12
+ """Test suite for stats aggregation with quantization variants."""
13
+
14
+ def test_aggregate_stats_with_quantization_variants(self) -> None:
15
+ """Test that stats aggregate correctly across quantization variants."""
16
+ stats = HordeModelStatsResponse(
17
+ day={
18
+ "koboldcpp/Lumimaid-v0.2-8B": 4080,
19
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 1500,
20
+ "koboldcpp/Lumimaid-v0.2-8B-Q4_K_M": 800,
21
+ },
22
+ month={
23
+ "koboldcpp/Lumimaid-v0.2-8B": 40000,
24
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 15000,
25
+ "koboldcpp/Lumimaid-v0.2-8B-Q4_K_M": 8000,
26
+ },
27
+ total={
28
+ "koboldcpp/Lumimaid-v0.2-8B": 400000,
29
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 150000,
30
+ "koboldcpp/Lumimaid-v0.2-8B-Q4_K_M": 80000,
31
+ },
32
+ )
33
+
34
+ indexed = IndexedHordeModelStats(stats)
35
+ day, month, total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
36
+
37
+ # Should aggregate all quantization variants
38
+ assert day == 4080 + 1500 + 800
39
+ assert month == 40000 + 15000 + 8000
40
+ assert total == 400000 + 150000 + 80000
41
+
42
+ def test_aggregate_stats_with_org_prefix_variants(self) -> None:
43
+ """Test that stats aggregate across org-prefixed variants."""
44
+ stats = HordeModelStatsResponse(
45
+ day={
46
+ "Lumimaid-v0.2-8B": 1000,
47
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 2000,
48
+ "koboldcpp/Lumimaid-v0.2-8B": 3000,
49
+ },
50
+ month={},
51
+ total={},
52
+ )
53
+
54
+ indexed = IndexedHordeModelStats(stats)
55
+ day, _month, _total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
56
+
57
+ # Should aggregate across all prefixes
58
+ assert day == 1000 + 2000 + 3000
59
+
60
+ def test_aggregate_stats_with_all_variants(self) -> None:
61
+ """Test that stats aggregate across backend, org, and quantization variants."""
62
+ stats = HordeModelStatsResponse(
63
+ day={
64
+ "Lumimaid-v0.2-8B": 100,
65
+ "aphrodite/Lumimaid-v0.2-8B": 200,
66
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 300,
67
+ "koboldcpp/Lumimaid-v0.2-8B": 400,
68
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 500,
69
+ "koboldcpp/Lumimaid-v0.2-8B-Q4_K_M": 600,
70
+ },
71
+ month={
72
+ "Lumimaid-v0.2-8B": 1000,
73
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 3000,
74
+ },
75
+ total={},
76
+ )
77
+
78
+ indexed = IndexedHordeModelStats(stats)
79
+ day, month, _total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
80
+
81
+ assert day == 100 + 200 + 300 + 400 + 500 + 600
82
+ assert month == 1000 + 3000
83
+
84
+ def test_get_stats_with_variations_backend_breakdown(self) -> None:
85
+ """Test that variations are correctly grouped by backend."""
86
+ stats = HordeModelStatsResponse(
87
+ day={
88
+ "Lumimaid-v0.2-8B": 100,
89
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 200,
90
+ "koboldcpp/Lumimaid-v0.2-8B": 300,
91
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 400,
92
+ },
93
+ month={},
94
+ total={},
95
+ )
96
+
97
+ indexed = IndexedHordeModelStats(stats)
98
+ (day_total, _month, _total), variations = indexed.get_stats_with_variations("Lumimaid-v0.2-8B")
99
+
100
+ assert day_total == 100 + 200 + 300 + 400
101
+ assert "canonical" in variations
102
+ assert "aphrodite" in variations
103
+ assert "koboldcpp" in variations
104
+ assert variations["canonical"][0] == 100
105
+ assert variations["aphrodite"][0] == 200
106
+ assert variations["koboldcpp"][0] == 300 + 400 # Aggregates quant variants
107
+
108
+ def test_aggregate_stats_different_base_models_not_mixed(self) -> None:
109
+ """Test that different base models are not incorrectly aggregated."""
110
+ stats = HordeModelStatsResponse(
111
+ day={
112
+ "Lumimaid-v0.2-8B": 100,
113
+ "Lumimaid-v0.2-8B-Q8_0": 200,
114
+ "Llama-3-8B": 1000,
115
+ "Llama-3-8B-Q4_K_M": 2000,
116
+ },
117
+ month={},
118
+ total={},
119
+ )
120
+
121
+ indexed = IndexedHordeModelStats(stats)
122
+
123
+ lumimaid_day, _m, _t = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
124
+ llama_day, _m, _t = indexed.get_aggregated_stats("Llama-3-8B")
125
+
126
+ # Each base model should only aggregate its own variants
127
+ assert lumimaid_day == 100 + 200
128
+ assert llama_day == 1000 + 2000
129
+
130
+ def test_aggregate_stats_case_insensitive(self) -> None:
131
+ """Test that aggregation is case-insensitive."""
132
+ stats = HordeModelStatsResponse(
133
+ day={
134
+ "KOBOLDCPP/Lumimaid-v0.2-8B": 100,
135
+ "koboldcpp/lumimaid-v0.2-8b-q8_0": 200,
136
+ },
137
+ month={},
138
+ total={},
139
+ )
140
+
141
+ indexed = IndexedHordeModelStats(stats)
142
+ day, _m, _t = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
143
+
144
+ assert day == 100 + 200
145
+
146
+ def test_aggregate_stats_no_duplicates(self) -> None:
147
+ """Test that stats are not double-counted when exact match exists."""
148
+ stats = HordeModelStatsResponse(
149
+ day={
150
+ "Lumimaid-v0.2-8B": 100, # Exact match and base name match
151
+ },
152
+ month={},
153
+ total={},
154
+ )
155
+
156
+ indexed = IndexedHordeModelStats(stats)
157
+ day, _m, _t = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
158
+
159
+ # Should only count once, not twice
160
+ assert day == 100
161
+
162
+ def test_aggregate_stats_empty_response(self) -> None:
163
+ """Test aggregation with empty stats response."""
164
+ stats = HordeModelStatsResponse(day={}, month={}, total={})
165
+
166
+ indexed = IndexedHordeModelStats(stats)
167
+ day, month, total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
168
+
169
+ assert day == 0
170
+ assert month == 0
171
+ assert total == 0
172
+
173
+ def test_aggregate_stats_model_not_found(self) -> None:
174
+ """Test aggregation when model doesn't exist in stats."""
175
+ stats = HordeModelStatsResponse(
176
+ day={"SomeOtherModel": 100},
177
+ month={},
178
+ total={},
179
+ )
180
+
181
+ indexed = IndexedHordeModelStats(stats)
182
+ day, month, total = indexed.get_aggregated_stats("Lumimaid-v0.2-8B")
183
+
184
+ assert day == 0
185
+ assert month == 0
186
+ assert total == 0
187
+
188
+ def test_aggregate_stats_canonical_name_with_org_prefix(self) -> None:
189
+ """Test that canonical names with org prefix correctly match API stats.
190
+
191
+ This is a critical test case because model reference entries often have
192
+ org prefixes (e.g., "NeverSleep/Lumimaid-v0.2") but API stats may have
193
+ different prefixing patterns.
194
+ """
195
+ stats = HordeModelStatsResponse(
196
+ day={
197
+ "koboldcpp/Lumimaid-v0.2-8B": 4080,
198
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 1500,
199
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 2000,
200
+ },
201
+ month={
202
+ "koboldcpp/Lumimaid-v0.2-8B": 40000,
203
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 15000,
204
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 20000,
205
+ },
206
+ total={},
207
+ )
208
+
209
+ indexed = IndexedHordeModelStats(stats)
210
+
211
+ # Query with canonical name that HAS org prefix (like in model reference)
212
+ canonical_with_org = "NeverSleep/Lumimaid-v0.2"
213
+ day, month, _total = indexed.get_aggregated_stats(canonical_with_org)
214
+
215
+ # Should aggregate all variants even though canonical has org prefix
216
+ assert day == 4080 + 1500 + 2000
217
+ assert month == 40000 + 15000 + 20000
218
+
219
+ def test_get_stats_with_variations_canonical_with_org_prefix(self) -> None:
220
+ """Test variations breakdown with canonical name that has org prefix.
221
+
222
+ When querying for a specific model like 'NeverSleep/Lumimaid-v0.2-8B',
223
+ get_stats_with_variations should find:
224
+ - The aphrodite variant: aphrodite/NeverSleep/Lumimaid-v0.2-8B
225
+ - The koboldcpp variant: koboldcpp/Lumimaid-v0.2-8B
226
+ - Quantization variants: koboldcpp/Lumimaid-v0.2-8B-Q8_0
227
+ """
228
+ stats = HordeModelStatsResponse(
229
+ day={
230
+ "koboldcpp/Lumimaid-v0.2-8B": 300,
231
+ "koboldcpp/Lumimaid-v0.2-8B-Q8_0": 400,
232
+ "aphrodite/NeverSleep/Lumimaid-v0.2-8B": 200,
233
+ },
234
+ month={},
235
+ total={},
236
+ )
237
+
238
+ indexed = IndexedHordeModelStats(stats)
239
+
240
+ # Query with canonical name that HAS org prefix (like in model reference)
241
+ (day_total, _m, _t), variations = indexed.get_stats_with_variations("NeverSleep/Lumimaid-v0.2-8B")
242
+
243
+ assert day_total == 300 + 400 + 200
244
+ assert "aphrodite" in variations
245
+ assert "koboldcpp" in variations
246
+ assert variations["aphrodite"][0] == 200
247
+ assert variations["koboldcpp"][0] == 300 + 400