tai-sql 0.7.8__tar.gz → 0.7.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 (227) hide show
  1. {tai_sql-0.7.8 → tai_sql-0.7.9}/PKG-INFO +1 -1
  2. {tai_sql-0.7.8 → tai_sql-0.7.9}/pyproject.toml +1 -1
  3. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/auth.py +1 -1
  4. tai_sql-0.7.9/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +637 -0
  5. tai_sql-0.7.8/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +0 -318
  6. {tai_sql-0.7.8 → tai_sql-0.7.9}/LICENSE +0 -0
  7. {tai_sql-0.7.8 → tai_sql-0.7.9}/README.md +0 -0
  8. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/__init__.py +0 -0
  9. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/__init__.py +0 -0
  10. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_backup/__init__.py +0 -0
  11. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_backup/main.py +0 -0
  12. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/__init__.py +0 -0
  13. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/config.py +0 -0
  14. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/local.py +0 -0
  15. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/main.py +0 -0
  16. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/workflow.py +0 -0
  17. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_feed/__init__.py +0 -0
  18. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_feed/main.py +0 -0
  19. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/__init__.py +0 -0
  20. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/funcs.py +0 -0
  21. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/main.py +0 -0
  22. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_hash_migrate/__init__.py +0 -0
  23. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_hash_migrate/main.py +0 -0
  24. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/__init__.py +0 -0
  25. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/main.py +0 -0
  26. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/status.py +0 -0
  27. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/__init__.py +0 -0
  28. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/main.py +0 -0
  29. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/model.py +0 -0
  30. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_install/__init__.py +0 -0
  31. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_install/main.py +0 -0
  32. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_ping/__init__.py +0 -0
  33. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_ping/main.py +0 -0
  34. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/__init__.py +0 -0
  35. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/funcs.py +0 -0
  36. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/main.py +0 -0
  37. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_restore/__init__.py +0 -0
  38. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_restore/main.py +0 -0
  39. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_rules/__init__.py +0 -0
  40. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_rules/main.py +0 -0
  41. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/__init__.py +0 -0
  42. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/model.py +0 -0
  43. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/new.py +0 -0
  44. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/__init__.py +0 -0
  45. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/base.py +0 -0
  46. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/default.py +0 -0
  47. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/set_default.py +0 -0
  48. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/prompts.py +0 -0
  49. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/__init__.py +0 -0
  50. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/connectivity.py +0 -0
  51. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/create.py +0 -0
  52. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/__init__.py +0 -0
  53. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/auth.py +0 -0
  54. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/client.py +0 -0
  55. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/models.py +0 -0
  56. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/errors.py +0 -0
  57. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/main.py +0 -0
  58. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/preamble.py +0 -0
  59. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/services/__init__.py +0 -0
  60. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/services/backup.py +0 -0
  61. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/README.md +0 -0
  62. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/__init__.py +0 -0
  63. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/provider.py +0 -0
  64. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/recipe.py +0 -0
  65. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/session.py +0 -0
  66. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/sources.py +0 -0
  67. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/crypto.py +0 -0
  68. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/__init__.py +0 -0
  69. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/base.py +0 -0
  70. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/bigquery.py +0 -0
  71. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/capabilities.py +0 -0
  72. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/mysql.py +0 -0
  73. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/postgresql.py +0 -0
  74. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/sqlserver.py +0 -0
  75. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/README.md +0 -0
  76. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/__init__.py +0 -0
  77. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/collector.py +0 -0
  78. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/render.py +0 -0
  79. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/types.py +0 -0
  80. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/README.md +0 -0
  81. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/__init__.py +0 -0
  82. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/collect.py +0 -0
  83. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/executor.py +0 -0
  84. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/feeder.py +0 -0
  85. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/plan.py +0 -0
  86. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/presenter.py +0 -0
  87. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/records.py +0 -0
  88. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/transform.py +0 -0
  89. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/__init__.py +0 -0
  90. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/base.py +0 -0
  91. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/README.md +0 -0
  92. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/__init__.py +0 -0
  93. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/assets/README.md +0 -0
  94. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/assets/cytoscape.min.js +0 -0
  95. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/builder.py +0 -0
  96. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/generator.py +0 -0
  97. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/graphviz_render.py +0 -0
  98. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/html_render.py +0 -0
  99. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/layout.py +0 -0
  100. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/model.py +0 -0
  101. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/templates/er.html.jinja2 +0 -0
  102. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/themes.py +0 -0
  103. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/__init__.py +0 -0
  104. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/calculated.py +0 -0
  105. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/context.py +0 -0
  106. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/base.py.jinja2 +0 -0
  107. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/__init__.py.jinja2 +0 -0
  108. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2 +0 -0
  109. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2 +0 -0
  110. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/model.py.jinja2 +0 -0
  111. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/facade.py.jinja2 +0 -0
  112. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +0 -0
  113. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/dataframe.jinja2 +0 -0
  114. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/filters.jinja2 +0 -0
  115. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2 +0 -0
  116. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/read.jinja2 +0 -0
  117. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/triggers.jinja2 +0 -0
  118. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/upsert.jinja2 +0 -0
  119. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/vector.jinja2 +0 -0
  120. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/write.jinja2 +0 -0
  121. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/session.py.jinja2 +0 -0
  122. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/__init__.py.jinja2 +0 -0
  123. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/aggregation.py.jinja2 +0 -0
  124. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/context.py.jinja2 +0 -0
  125. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/__init__.py.jinja2 +0 -0
  126. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/encoders.py.jinja2 +0 -0
  127. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/funcs.py.jinja2 +0 -0
  128. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/hierarchy.py.jinja2 +0 -0
  129. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/rls.py.jinja2 +0 -0
  130. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/vector.py.jinja2 +0 -0
  131. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/__init__.py +0 -0
  132. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/analyzer.py +0 -0
  133. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/renderer.py +0 -0
  134. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/steps.py +0 -0
  135. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/transformer.py +0 -0
  136. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/rules/__init__.py +0 -0
  137. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/rules/templates/schema.md.jinja2 +0 -0
  138. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/README.md +0 -0
  139. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/__init__.py +0 -0
  140. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/blueprint.py +0 -0
  141. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/build.py +0 -0
  142. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/defaults.py +0 -0
  143. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/naming.py +0 -0
  144. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/reader.py +0 -0
  145. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/templates/schema.py.jinja2 +0 -0
  146. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/types.py +0 -0
  147. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/writer.py +0 -0
  148. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/README.md +0 -0
  149. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/__init__.py +0 -0
  150. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/README.md +0 -0
  151. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/__init__.py +0 -0
  152. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/calculated.py +0 -0
  153. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/collector.py +0 -0
  154. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/column_analyzer.py +0 -0
  155. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/constraints.py +0 -0
  156. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/hints.py +0 -0
  157. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/relation_analyzer.py +0 -0
  158. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/schema_analyzer.py +0 -0
  159. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/README.md +0 -0
  160. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/__init__.py +0 -0
  161. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/annotations.py +0 -0
  162. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/base.py +0 -0
  163. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/calculated.py +0 -0
  164. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/columns.py +0 -0
  165. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/constraints.py +0 -0
  166. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/datasource.py +0 -0
  167. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/descriptors.py +0 -0
  168. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/encoders.py +0 -0
  169. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/enumerations.py +0 -0
  170. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/relations.py +0 -0
  171. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/table.py +0 -0
  172. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/trigger_api.py +0 -0
  173. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/triggers.py +0 -0
  174. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/view.py +0 -0
  175. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/README.md +0 -0
  176. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/__init__.py +0 -0
  177. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/sqlalchemy_mapper.py +0 -0
  178. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/README.md +0 -0
  179. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/__init__.py +0 -0
  180. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/column.py +0 -0
  181. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/constraints.py +0 -0
  182. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/entity.py +0 -0
  183. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/enumeration.py +0 -0
  184. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/relation.py +0 -0
  185. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/schema.py +0 -0
  186. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/trigger.py +0 -0
  187. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/naming.py +0 -0
  188. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/ordering.py +0 -0
  189. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/registry.py +0 -0
  190. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/types.py +0 -0
  191. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/README.md +0 -0
  192. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/__init__.py +0 -0
  193. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/config.py +0 -0
  194. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/context.py +0 -0
  195. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/manager.py +0 -0
  196. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/paths.py +0 -0
  197. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/README.md +0 -0
  198. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/__init__.py +0 -0
  199. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/catalog.py +0 -0
  200. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/installer.py +0 -0
  201. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/meta.py +0 -0
  202. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/service.py +0 -0
  203. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/staleness.py +0 -0
  204. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/comandos.md +0 -0
  205. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/errores.md +0 -0
  206. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/modelo.md +0 -0
  207. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/schema.py +0 -0
  208. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/README.md +0 -0
  209. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/__init__.py +0 -0
  210. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/__init__.py +0 -0
  211. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/constraints.py +0 -0
  212. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/indexes.py +0 -0
  213. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/tables.py +0 -0
  214. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/views.py +0 -0
  215. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/executor.py +0 -0
  216. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/__init__.py +0 -0
  217. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/planner.py +0 -0
  218. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/statements.py +0 -0
  219. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/presenter.py +0 -0
  220. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/report.py +0 -0
  221. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/safety.py +0 -0
  222. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/__init__.py +0 -0
  223. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/local.py +0 -0
  224. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/model.py +0 -0
  225. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/normalize.py +0 -0
  226. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/remote.py +0 -0
  227. {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/synchronizer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tai-sql
3
- Version: 0.7.8
3
+ Version: 0.7.9
4
4
  Summary: SQL database management and code generation tool
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tai-sql"
3
- version = "0.7.8"
3
+ version = "0.7.9"
4
4
  description = "SQL database management and code generation tool"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -35,7 +35,7 @@ class AuthSchema(SchemaTemplate):
35
35
  provider=env('MAIN_DATABASE_URL'),
36
36
  schema='auth',
37
37
  syntax='v2',
38
- operative_fields=False,
38
+ operative_fields=True, # Añade created_at, updated_at, created_by, updated_by a todas las tablas
39
39
  )
40
40
 
41
41
  # Configurar los generadores
@@ -0,0 +1,637 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+ from textwrap import dedent
4
+
5
+ from .base import SchemaTemplate
6
+
7
+
8
+ class ChatbotSchema(SchemaTemplate):
9
+ """Plantilla chatbot: Chat, Mensaje, TokenUsage, ToolCall, Modelo, Tarifa, Bolsa,
10
+ LimitePresupuesto y vistas"""
11
+
12
+ def get_content(self) -> str:
13
+ return dedent(f'''
14
+ # -*- coding: utf-8 -*-
15
+ """
16
+ Esquema de base de datos para un chatbot con soporte de mensajes, tokens y herramientas.
17
+ Usa tai_sql para definir tablas, relaciones, vistas y generar automáticamente modelos y CRUDs.
18
+ Ejecuta por consola tai-sql generate para generar los recursos definidos en este esquema.
19
+ """
20
+ from __future__ import annotations
21
+ import os
22
+ from tai_sql import *
23
+ from tai_sql.generators import *
24
+
25
+
26
+ # Configurar el datasource
27
+ datasource(
28
+ provider=env('MAIN_DATABASE_URL'), # Además de env, también puedes usar (para testing) connection_string y params
29
+ schema='{self.schema_name}', # Esquema del datasource
30
+ syntax='v2', # Sintaxis V2: col[type], onetomany[Model], manytoone[Model]
31
+ operative_fields=True, # Añade created_at, updated_at, created_by, updated_by a todas las tablas
32
+ )
33
+
34
+ # Configurar los generadores
35
+ generate(
36
+ PythonClientGenerator(
37
+ output_dir=[
38
+ '{self.namespace}/{self.subnamespace}', # Directorio donde se generará el cliente python
39
+ # Añade más directorios aquí
40
+ ]
41
+ ),
42
+ ERDiagramGenerator(
43
+ output_dir=[
44
+ '{self.namespace}/diagrams', # Directorio donde se generará el diagrama
45
+ # Añade más directorios aquí
46
+ ],
47
+ theme='classic',
48
+ format='html'
49
+ )
50
+ )
51
+
52
+ # Definición de tablas y relaciones
53
+
54
+ class Chat(Table):
55
+ """Tabla que almacena las conversaciones del chatbot"""
56
+ __tablename__ = "chat"
57
+
58
+ id: col[bigint] = column(primary_key=True, autoincrement=True, description='UUID del chat')
59
+ title: col[str] = column(description='Título de la conversación')
60
+ username: col[str] = column(description='ID del usuario propietario del chat')
61
+ is_active: col[bool] = column(default=True, description='Estado activo del chat')
62
+ last_message_timestamp: col[datetime | None] = column(description='Timestamp del último mensaje del chat')
63
+
64
+ messages: onetomany[Mensaje] # Relación one-to-many con la tabla Mensaje
65
+
66
+
67
+ class Feedback(Enum):
68
+ """Enum para el feedback de los mensajes"""
69
+ GOOD = 'good'
70
+ BAD = 'bad'
71
+
72
+
73
+ class MessageRole(Enum):
74
+ """Roles de los mensajes en el chat"""
75
+ USER = 'usuario'
76
+ ASSISTANT = 'asistente'
77
+ SYSTEM = 'sistema'
78
+
79
+
80
+ class Mensaje(Table):
81
+ """Tabla que almacena los mensajes individuales de cada chat"""
82
+ __tablename__ = "mensaje"
83
+
84
+ id: col[bigint] = column(primary_key=True, autoincrement=True, description='UUID del mensaje')
85
+ content: col[str] = column(description='Contenido del mensaje')
86
+ role: col[MessageRole] = column(description='Rol del mensaje (user, assistant, system)')
87
+ timestamp: col[datetime] = column(default=datetime.now, description='Timestamp del mensaje')
88
+ feedback: col[Feedback] = column(default=Feedback.GOOD, description='Feedback del mensaje')
89
+ feedback_comment: col[str | None] = column(description='Comentario adicional del feedback')
90
+ reasoning: col[str | None] = column(description='Texto de razonamiento del agente antes de usar herramientas')
91
+ agent_iterations: col[int] = column(default=0, description='Número de iteraciones del agente')
92
+ total_duration_ms: col[int | None] = column(description='Duración total de la respuesta en ms')
93
+ model_name: col[str | None] = column(description='Modelo con el que se respondió')
94
+ provider: col[str | None] = column(description='Proveedor del modelo con el que se respondió')
95
+
96
+ chat_id: col[bigint] = column(description='ID del chat al que pertenece el mensaje')
97
+
98
+ token_usage: onetomany[TokenUsage] # Relación one-to-many con la tabla TokenUsage
99
+ tool_calls: onetomany[ToolCall] # Relación one-to-many con la tabla ToolCall
100
+
101
+ chat: manytoone[Chat] = relation(fields=['chat_id'], references=['id'], backref='messages') # Relación many-to-one con Chat
102
+
103
+ @on_create(timing='after')
104
+ def update_last_message_timestamp(self, t: TriggerAPI):
105
+ """Actualiza el timestamp del último mensaje en el chat"""
106
+ t.update(Chat, self.chat_id, last_message_timestamp=self.timestamp)
107
+
108
+
109
+ class TokenUsage(Table):
110
+ """Tabla que almacena el consumo de tokens de cada respuesta"""
111
+ __tablename__ = "token_usage"
112
+
113
+ id: col[bigint] = column(primary_key=True, autoincrement=True)
114
+ prompt_tokens: col[int] = column(default=0, description='Tokens consumidos en el prompt')
115
+ completion_tokens: col[int] = column(default=0, description='Tokens consumidos en la respuesta')
116
+ total_tokens: col[int] = column(default=0, description='Total de tokens consumidos')
117
+ timestamp: col[datetime] = column(default=datetime.now, description='Timestamp del consumo')
118
+
119
+ username: col[str] = column(index=True, description='Usuario que consumió')
120
+ message_id: col[bigint | None] = column(description='Mensaje que lo generó; vacío si se borró la conversación')
121
+
122
+ tarifa_id: col[bigint | None] = column(index=True, description='Tarifa con la que se valora')
123
+ bolsa_id: col[bigint | None] = column(index=True, description='Bolsa a la que se carga; vacío si no hay bolsas')
124
+
125
+ message: manytoone[Mensaje] = relation(fields=['message_id'], references=['id'], backref='token_usage', onDelete='set null')
126
+
127
+
128
+ class ToolCall(Table):
129
+ """Tabla que almacena las llamadas a herramientas realizadas por el agente durante la generación de respuestas"""
130
+ __tablename__ = "tool_call"
131
+
132
+ id: col[bigint] = column(primary_key=True, autoincrement=True, description='ID de la llamada a herramienta')
133
+ tool_name: col[str] = column(description='Nombre de la herramienta invocada')
134
+ tool_args: col[dict | None] = column(description='Argumentos de entrada en formato JSON')
135
+ tool_result: col[dict | None] = column(description='Resultado de la herramienta en formato JSON')
136
+ duration_ms: col[int | None] = column(description='Duración de la ejecución en milisegundos')
137
+ success: col[bool] = column(default=True, description='Si la llamada fue exitosa')
138
+ timestamp: col[datetime] = column(default=datetime.now, description='Timestamp de la llamada')
139
+ message_id: col[bigint] = column(description='ID del mensaje asociado')
140
+
141
+ message: manytoone[Mensaje] = relation(fields=['message_id'], references=['id'], backref='tool_calls') # Relación many-to-one con Mensaje
142
+
143
+
144
+ class Modelo(Table):
145
+ """Tabla que almacena los modelos con los que puede responder el agente"""
146
+ __tablename__ = "modelo"
147
+
148
+ id: col[bigint] = column(primary_key=True, autoincrement=True)
149
+ alias: col[str] = column(unique=True, description='Nombre corto con el que se pide el modelo')
150
+ etiqueta: col[str | None] = column(description='Nombre con el que se ve en el chat; sin él se usa el del modelo')
151
+ provider: col[str] = column(description='Proveedor: openai, azure-openai, anthropic o google')
152
+ model_name: col[str] = column(description='Nombre del modelo o del despliegue')
153
+ endpoint: col[str | None] = column(description='Endpoint del proveedor cloud')
154
+ # Solo el nombre de la variable: la clave no se guarda en la base de datos
155
+ api_key_env: col[str | None] = column(description='Variable de entorno que guarda la clave')
156
+ credentials_path: col[str | None] = column(description='Ruta del fichero de credenciales de Google')
157
+ google_cloud_project: col[str | None] = column(description='Proyecto de Google Cloud')
158
+ location: col[str | None] = column(description='Región del modelo en Google Cloud')
159
+ max_tokens: col[int] = column(default=2048, description='Máximo de tokens de la respuesta')
160
+ temperature: col[float] = column(default=0.7, description='Temperatura de generación')
161
+ thinking_budget: col[int | None] = column(description='Presupuesto de razonamiento')
162
+ activo: col[bool] = column(default=True, description='Si no está activo, no se puede pedir')
163
+ por_defecto: col[bool] = column(default=False, description='Con el que se responde si no se pide otro')
164
+
165
+ tarifas: onetomany[Tarifa] # Relación one-to-many con la tabla Tarifa
166
+
167
+
168
+ class Tarifa(Table):
169
+ """Tabla que almacena el precio y el margen de un modelo a partir de una fecha"""
170
+ __tablename__ = "tarifa"
171
+
172
+ id: col[bigint] = column(primary_key=True, autoincrement=True)
173
+ modelo_id: col[bigint] = column(description='Modelo al que pone precio')
174
+ precio_input_eur_millon: col[float] = column(default=0.0, description='Euros por millón de tokens de entrada, al precio del proveedor')
175
+ precio_output_eur_millon: col[float] = column(default=0.0, description='Euros por millón de tokens de salida, al precio del proveedor')
176
+ margen: col[float] = column(default=1.0, description='Multiplicador comercial sobre el precio del proveedor')
177
+ desde: col[date] = column(description='Primer día en que rige')
178
+ # Una tarifa no se edita ni se borra: se da de alta la siguiente con su `desde` y la
179
+ # vista cierra la anterior. Es lo que permite saber con qué precio y con qué margen se
180
+ # valoró un consumo de hace meses. `hasta` solo se rellena para retirar un modelo
181
+ hasta: col[date | None] = column(description='Cierre explícito, para dejar de poder usar el modelo')
182
+
183
+ modelo: manytoone[Modelo] = relation(fields=['modelo_id'], references=['id'], backref='tarifas', onDelete='restrict')
184
+
185
+
186
+ class Bolsa(Table):
187
+ """Tabla que almacena las bolsas de consumo contratadas"""
188
+ __tablename__ = "bolsa"
189
+
190
+ # El id marca el orden de la cadena: se gasta la más antigua que quede con saldo.
191
+ # La fecha de alta es `created_at`, que ya ponen los campos operativos
192
+ id: col[bigint] = column(primary_key=True, autoincrement=True)
193
+ nombre: col[str] = column(description='Nombre con el que se ve en facturación')
194
+ importe_eur: col[float] = column(default=0.0, description='Euros contratados')
195
+
196
+
197
+ class LimitePresupuesto(Table):
198
+ """Tabla que almacena el límite de gasto de cada usuario en cada mes"""
199
+ __tablename__ = "limite_presupuesto"
200
+
201
+ # Una fila por mes: al cerrarse el mes su límite queda congelado y las facturas
202
+ # antiguas no cambian aunque se reparta otra bolsa
203
+ mes: col[str] = column(primary_key=True, description="Mes al que se aplica, como 'YYYY-MM'")
204
+ # La fila con username '*' es el límite por defecto del mes, el que se le pone a quien
205
+ # llega nuevo; el límite general es la suma de los individuales
206
+ username: col[str] = column(primary_key=True, description="Usuario al que aplica, o '*' para el límite por defecto")
207
+ limite_eur: col[float] = column(default=10.0, description='Límite de gasto mensual en euros')
208
+ personalizado: col[bool] = column(default=False, description='True si lo fijó el administrador a mano, False si sale del reparto')
209
+
210
+
211
+ # Definición de vistas
212
+
213
+ class UserStats(View):
214
+ """Vista que muestra estadísticas de usuarios y sus chats"""
215
+ __tablename__ = "user_stats"
216
+ __query__ = query('user_stats.sql') # Esto es necesario para usar tai-sql push
217
+
218
+ username: col[str]
219
+ total_chats: col[int]
220
+ active_chats: col[int]
221
+ total_messages: col[int]
222
+ last_activity: col[datetime | None]
223
+
224
+ most_used_model: col[str | None]
225
+ most_used_model_total_tokens: col[int]
226
+ most_used_model_active_days: col[int]
227
+
228
+
229
+ class ConsumoDiario(View):
230
+ """Vista que muestra el consumo por usuario, día y modelo"""
231
+ __tablename__ = "consumo_diario"
232
+ __query__ = query('consumo_diario.sql')
233
+
234
+ username: col[str]
235
+ date: col[datetime]
236
+ provider: col[str]
237
+ model_name: col[str]
238
+ total_prompt_tokens: col[int]
239
+ total_completion_tokens: col[int]
240
+ total_tokens: col[int]
241
+ total_cost_eur: col[float]
242
+ chat_count: col[int]
243
+ message_count: col[int]
244
+
245
+
246
+ class ChatActivity(View):
247
+ """Vista que muestra la actividad reciente de chats"""
248
+ __tablename__ = "chat_activity"
249
+ __query__ = query('chat_activity.sql')
250
+
251
+ chat_id: col[str]
252
+ chat_title: col[str]
253
+ username: col[str]
254
+ message_count: col[int]
255
+ last_message_timestamp: col[datetime]
256
+ total_tokens_consumed: col[int]
257
+ is_active: col[bool]
258
+
259
+
260
+ class ConsumoMensual(View):
261
+ """Vista que muestra el consumo y el coste por usuario, mes y modelo"""
262
+ __tablename__ = "consumo_mensual"
263
+ __query__ = query('consumo_mensual.sql')
264
+
265
+ username: col[str]
266
+ mes: col[datetime]
267
+ provider: col[str]
268
+ model_name: col[str]
269
+ total_prompt_tokens: col[int]
270
+ total_completion_tokens: col[int]
271
+ total_tokens: col[int]
272
+ coste_eur: col[float]
273
+ message_count: col[int]
274
+
275
+
276
+ class TokensMensaje(View):
277
+ """Vista que muestra los tokens de cada mensaje, sin importes"""
278
+ __tablename__ = "tokens_mensaje"
279
+ __query__ = query('tokens_mensaje.sql')
280
+
281
+ message_id: col[bigint]
282
+ chat_id: col[bigint]
283
+ model_name: col[str | None]
284
+ provider: col[str | None]
285
+ prompt_tokens: col[int]
286
+ completion_tokens: col[int]
287
+ total_tokens: col[int]
288
+
289
+
290
+ class BolsaEstado(View):
291
+ """Vista que muestra cuánto lleva gastado cada bolsa de consumo"""
292
+ __tablename__ = "bolsa_estado"
293
+ __query__ = query('bolsa_estado.sql')
294
+
295
+ bolsa_id: col[bigint]
296
+ nombre: col[str]
297
+ importe_eur: col[float]
298
+ alta: col[datetime]
299
+ consumido_eur: col[float]
300
+ restante_eur: col[float]
301
+ primer_consumo: col[datetime | None]
302
+ ultimo_consumo: col[datetime | None]
303
+ estado: col[str]
304
+
305
+
306
+ class TarifaVigente(View):
307
+ """Vista que muestra la tarifa que rige hoy para cada modelo, a precio de venta"""
308
+ __tablename__ = "tarifa_vigente"
309
+ __query__ = query('tarifa_vigente.sql')
310
+
311
+ tarifa_id: col[bigint]
312
+ modelo_id: col[bigint]
313
+ alias: col[str]
314
+ provider: col[str]
315
+ model_name: col[str]
316
+ precio_input_eur_millon: col[float]
317
+ precio_output_eur_millon: col[float]
318
+ desde: col[date]
319
+ hasta: col[date | None]
320
+
321
+
322
+ class TarifaHistorico(View):
323
+ """Vista que muestra todas las tarifas de cada modelo, a precio de venta"""
324
+ __tablename__ = "tarifa_historico"
325
+ __query__ = query('tarifa_historico.sql')
326
+
327
+ tarifa_id: col[bigint]
328
+ modelo_id: col[bigint]
329
+ alias: col[str]
330
+ provider: col[str]
331
+ model_name: col[str]
332
+ precio_input_eur_millon: col[float]
333
+ precio_output_eur_millon: col[float]
334
+ desde: col[date]
335
+ hasta: col[date | None]
336
+ vigente: col[bool]
337
+
338
+
339
+ class ModeloDisponible(View):
340
+ """Vista que muestra el catálogo de modelos, sin sus datos de conexión"""
341
+ __tablename__ = "modelo_disponible"
342
+ __query__ = query('modelo_disponible.sql')
343
+
344
+ modelo_id: col[bigint]
345
+ alias: col[str]
346
+ etiqueta: col[str]
347
+ provider: col[str]
348
+ model_name: col[str]
349
+ activo: col[bool]
350
+ por_defecto: col[bool]
351
+ con_tarifa: col[bool]
352
+
353
+
354
+ class UsuarioFacturacion(View):
355
+ """Vista que muestra los usuarios a los que se factura"""
356
+ __tablename__ = "usuario_facturacion"
357
+ __query__ = query('usuario_facturacion.sql')
358
+
359
+ username: col[str]
360
+ realm_name: col[str]
361
+ first_name: col[str | None]
362
+ last_name: col[str | None]
363
+ full_name: col[str | None]
364
+ email: col[str | None]
365
+ is_active: col[bool]
366
+ alta: col[datetime]
367
+ ''').strip()
368
+
369
+ def create_views(self, views_dir: Path) -> None:
370
+ bolsa_estado_sql = dedent('''\
371
+ -- Vista con lo que lleva gastado cada bolsa de consumo. Las bolsas se encadenan por orden
372
+ -- de alta: se gasta la más antigua hasta agotarla y entonces entra la siguiente
373
+ WITH gasto AS (
374
+ SELECT
375
+ b.id,
376
+ b.nombre,
377
+ b.importe_eur,
378
+ b.created_at,
379
+ COALESCE(SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
380
+ + tu.completion_tokens * t.precio_output_eur_millon)
381
+ * t.margen / 1000000)::numeric, 8)), 0)::double precision as consumido_eur,
382
+ MIN(tu.timestamp) as primer_consumo,
383
+ MAX(tu.timestamp) as ultimo_consumo
384
+ FROM bolsa b
385
+ LEFT JOIN token_usage tu ON tu.bolsa_id = b.id
386
+ LEFT JOIN tarifa t ON t.id = tu.tarifa_id
387
+ GROUP BY b.id, b.nombre, b.importe_eur, b.created_at
388
+ )
389
+ SELECT
390
+ g.id as bolsa_id,
391
+ g.nombre,
392
+ g.importe_eur,
393
+ g.created_at as alta,
394
+ g.consumido_eur,
395
+ GREATEST(g.importe_eur - g.consumido_eur, 0) as restante_eur,
396
+ g.primer_consumo,
397
+ g.ultimo_consumo,
398
+ CASE
399
+ WHEN g.consumido_eur >= g.importe_eur THEN 'agotada'
400
+ WHEN g.id = (SELECT MIN(h.id) FROM gasto h WHERE h.consumido_eur < h.importe_eur) THEN 'en uso'
401
+ ELSE 'en espera'
402
+ END as estado
403
+ FROM gasto g
404
+ ORDER BY g.id;
405
+ ''').rstrip()
406
+
407
+ chat_activity_sql = dedent('''\
408
+ -- Vista para actividad de chats
409
+ SELECT
410
+ c.id as chat_id,
411
+ c.title as chat_title,
412
+ c.username,
413
+ COUNT(m.id) as message_count,
414
+ MAX(m.timestamp) as last_message_timestamp,
415
+ COALESCE(SUM(tu.total_tokens), 0) as total_tokens_consumed,
416
+ c.is_active
417
+ FROM chat c
418
+ LEFT JOIN mensaje m ON c.id = m.chat_id
419
+ LEFT JOIN token_usage tu ON m.id = tu.message_id
420
+ GROUP BY c.id, c.title, c.username, c.is_active
421
+ ORDER BY last_message_timestamp DESC NULLS LAST;
422
+ ''').rstrip()
423
+
424
+ consumo_diario_sql = dedent('''\
425
+ -- Vista para el consumo por usuario, día y modelo
426
+ SELECT
427
+ tu.username,
428
+ DATE(tu.timestamp) as date,
429
+ m.provider,
430
+ m.model_name,
431
+ SUM(tu.prompt_tokens) as total_prompt_tokens,
432
+ SUM(tu.completion_tokens) as total_completion_tokens,
433
+ SUM(tu.total_tokens) as total_tokens,
434
+ SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
435
+ + tu.completion_tokens * t.precio_output_eur_millon)
436
+ * t.margen / 1000000)::numeric, 8))::double precision as total_cost_eur,
437
+ COUNT(DISTINCT me.chat_id) as chat_count,
438
+ COUNT(*) as message_count
439
+ FROM token_usage tu
440
+ JOIN tarifa t ON t.id = tu.tarifa_id
441
+ JOIN modelo m ON m.id = t.modelo_id
442
+ LEFT JOIN mensaje me ON me.id = tu.message_id
443
+ GROUP BY tu.username, DATE(tu.timestamp), m.provider, m.model_name
444
+ ORDER BY date DESC, total_tokens DESC;
445
+ ''').rstrip()
446
+
447
+ consumo_mensual_sql = dedent('''\
448
+ -- Vista para el consumo y el coste por usuario, mes y modelo. El importe se calcula aquí:
449
+ -- el apunte guarda con qué tarifa se valora y el margen no sale de la tabla `tarifa`
450
+ SELECT
451
+ tu.username,
452
+ DATE_TRUNC('month', tu.timestamp)::date as mes,
453
+ m.provider,
454
+ m.model_name,
455
+ SUM(tu.prompt_tokens) as total_prompt_tokens,
456
+ SUM(tu.completion_tokens) as total_completion_tokens,
457
+ SUM(tu.total_tokens) as total_tokens,
458
+ SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
459
+ + tu.completion_tokens * t.precio_output_eur_millon)
460
+ * t.margen / 1000000)::numeric, 8))::double precision as coste_eur,
461
+ COUNT(*) as message_count
462
+ FROM token_usage tu
463
+ JOIN tarifa t ON t.id = tu.tarifa_id
464
+ JOIN modelo m ON m.id = t.modelo_id
465
+ GROUP BY tu.username, DATE_TRUNC('month', tu.timestamp), m.provider, m.model_name
466
+ ORDER BY mes DESC, tu.username, total_tokens DESC;
467
+ ''').rstrip()
468
+
469
+ modelo_disponible_sql = dedent('''\
470
+ -- Vista con el catálogo de modelos; el endpoint y la variable de la clave no salen de aquí
471
+ WITH ventana AS (
472
+ SELECT
473
+ t.modelo_id,
474
+ t.id,
475
+ t.desde,
476
+ COALESCE(
477
+ t.hasta,
478
+ LEAD(t.desde) OVER (PARTITION BY t.modelo_id ORDER BY t.desde) - 1
479
+ ) as hasta_efectivo
480
+ FROM tarifa t
481
+ ),
482
+ vigente AS (
483
+ SELECT v.modelo_id, v.id
484
+ FROM ventana v
485
+ WHERE v.desde <= CURRENT_DATE
486
+ AND (v.hasta_efectivo IS NULL OR v.hasta_efectivo >= CURRENT_DATE)
487
+ )
488
+ SELECT
489
+ m.id as modelo_id,
490
+ m.alias,
491
+ COALESCE(m.etiqueta, m.model_name) as etiqueta,
492
+ m.provider,
493
+ m.model_name,
494
+ m.activo,
495
+ m.por_defecto,
496
+ (vi.id IS NOT NULL) as con_tarifa
497
+ FROM modelo m
498
+ LEFT JOIN vigente vi ON vi.modelo_id = m.id
499
+ ORDER BY m.alias;
500
+ ''').rstrip()
501
+
502
+ tarifa_historico_sql = dedent('''\
503
+ -- Vista con todas las tarifas de cada modelo y el periodo en que rigieron, a precio de venta
504
+ WITH ventana AS (
505
+ SELECT
506
+ t.*,
507
+ COALESCE(
508
+ t.hasta,
509
+ LEAD(t.desde) OVER (PARTITION BY t.modelo_id ORDER BY t.desde) - 1
510
+ ) as hasta_efectivo
511
+ FROM tarifa t
512
+ )
513
+ SELECT
514
+ v.id as tarifa_id,
515
+ v.modelo_id,
516
+ m.alias,
517
+ m.provider,
518
+ m.model_name,
519
+ (v.precio_input_eur_millon * v.margen)::double precision as precio_input_eur_millon,
520
+ (v.precio_output_eur_millon * v.margen)::double precision as precio_output_eur_millon,
521
+ v.desde,
522
+ v.hasta_efectivo as hasta,
523
+ (v.desde <= CURRENT_DATE
524
+ AND (v.hasta_efectivo IS NULL OR v.hasta_efectivo >= CURRENT_DATE)) as vigente
525
+ FROM ventana v
526
+ JOIN modelo m ON m.id = v.modelo_id
527
+ ORDER BY m.alias, v.desde DESC;
528
+ ''').rstrip()
529
+
530
+ tarifa_vigente_sql = dedent('''\
531
+ -- Vista con la tarifa que rige hoy para cada modelo, ya a precio de venta: el precio del
532
+ -- proveedor y el margen no salen de la tabla. La ventana se deriva de la tarifa siguiente,
533
+ -- así que dar de alta una tarifa nueva no obliga a cerrar la anterior
534
+ WITH ventana AS (
535
+ SELECT
536
+ t.*,
537
+ COALESCE(
538
+ t.hasta,
539
+ LEAD(t.desde) OVER (PARTITION BY t.modelo_id ORDER BY t.desde) - 1
540
+ ) as hasta_efectivo
541
+ FROM tarifa t
542
+ )
543
+ SELECT
544
+ v.id as tarifa_id,
545
+ v.modelo_id,
546
+ m.alias,
547
+ m.provider,
548
+ m.model_name,
549
+ (v.precio_input_eur_millon * v.margen)::double precision as precio_input_eur_millon,
550
+ (v.precio_output_eur_millon * v.margen)::double precision as precio_output_eur_millon,
551
+ v.desde,
552
+ v.hasta_efectivo as hasta
553
+ FROM ventana v
554
+ JOIN modelo m ON m.id = v.modelo_id
555
+ WHERE v.desde <= CURRENT_DATE
556
+ AND (v.hasta_efectivo IS NULL OR v.hasta_efectivo >= CURRENT_DATE)
557
+ ORDER BY m.alias;
558
+ ''').rstrip()
559
+
560
+ tokens_mensaje_sql = dedent('''\
561
+ -- Vista con los tokens de cada mensaje; no lleva importes a propósito: es la única que
562
+ -- puede leer cualquier usuario
563
+ SELECT
564
+ tu.message_id,
565
+ m.chat_id,
566
+ m.model_name,
567
+ m.provider,
568
+ SUM(tu.prompt_tokens) as prompt_tokens,
569
+ SUM(tu.completion_tokens) as completion_tokens,
570
+ SUM(tu.total_tokens) as total_tokens
571
+ FROM token_usage tu
572
+ JOIN mensaje m ON m.id = tu.message_id
573
+ GROUP BY tu.message_id, m.chat_id, m.model_name, m.provider;
574
+ ''').rstrip()
575
+
576
+ user_stats_sql = dedent('''\
577
+ -- Vista para estadísticas de usuarios
578
+ WITH top_model AS (
579
+ SELECT DISTINCT ON (c.username)
580
+ c.username,
581
+ m.model_name,
582
+ SUM(tu.total_tokens) AS total_tokens,
583
+ COUNT(DISTINCT DATE(tu.timestamp)) AS active_days
584
+ FROM chat c
585
+ JOIN mensaje m ON c.id = m.chat_id
586
+ JOIN token_usage tu ON m.id = tu.message_id
587
+ GROUP BY c.username, m.model_name
588
+ ORDER BY c.username, SUM(tu.total_tokens) DESC
589
+ )
590
+
591
+ SELECT
592
+ c.username,
593
+ COUNT(DISTINCT c.id) AS total_chats,
594
+ COUNT(DISTINCT CASE WHEN c.is_active THEN c.id END) AS active_chats,
595
+ COUNT(DISTINCT m.id) AS total_messages,
596
+ MAX(m.timestamp) AS last_activity,
597
+
598
+ tm.model_name AS most_used_model,
599
+ tm.total_tokens AS most_used_model_total_tokens,
600
+ tm.active_days AS most_used_model_active_days
601
+
602
+ FROM chat c
603
+ LEFT JOIN mensaje m ON c.id = m.chat_id
604
+ LEFT JOIN top_model tm ON c.username = tm.username
605
+ GROUP BY
606
+ c.username,
607
+ tm.model_name,
608
+ tm.total_tokens,
609
+ tm.active_days;
610
+ ''').rstrip()
611
+
612
+ usuario_facturacion_sql = dedent('''\
613
+ -- Vista con los usuarios a los que se factura. Es la que leen las pantallas de facturación:
614
+ -- /rbac/{realm}/user pide administración del realm y la contraseña no sale de aquí
615
+ SELECT
616
+ u.username,
617
+ u.realm_name,
618
+ u.first_name,
619
+ u.last_name,
620
+ u.full_name,
621
+ u.email,
622
+ u.is_active,
623
+ u.created_at as alta
624
+ FROM auth."user" u
625
+ ORDER BY u.username;
626
+ ''').rstrip()
627
+
628
+ (views_dir / 'bolsa_estado.sql').write_text(bolsa_estado_sql, encoding='utf-8')
629
+ (views_dir / 'chat_activity.sql').write_text(chat_activity_sql, encoding='utf-8')
630
+ (views_dir / 'consumo_diario.sql').write_text(consumo_diario_sql, encoding='utf-8')
631
+ (views_dir / 'consumo_mensual.sql').write_text(consumo_mensual_sql, encoding='utf-8')
632
+ (views_dir / 'modelo_disponible.sql').write_text(modelo_disponible_sql, encoding='utf-8')
633
+ (views_dir / 'tarifa_historico.sql').write_text(tarifa_historico_sql, encoding='utf-8')
634
+ (views_dir / 'tarifa_vigente.sql').write_text(tarifa_vigente_sql, encoding='utf-8')
635
+ (views_dir / 'tokens_mensaje.sql').write_text(tokens_mensaje_sql, encoding='utf-8')
636
+ (views_dir / 'user_stats.sql').write_text(user_stats_sql, encoding='utf-8')
637
+ (views_dir / 'usuario_facturacion.sql').write_text(usuario_facturacion_sql, encoding='utf-8')