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.
- {tai_sql-0.7.8 → tai_sql-0.7.9}/PKG-INFO +1 -1
- {tai_sql-0.7.8 → tai_sql-0.7.9}/pyproject.toml +1 -1
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/auth.py +1 -1
- tai_sql-0.7.9/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +637 -0
- tai_sql-0.7.8/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +0 -318
- {tai_sql-0.7.8 → tai_sql-0.7.9}/LICENSE +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_backup/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_backup/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/config.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/local.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_deploy/workflow.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_feed/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_feed/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/funcs.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_generate/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_hash_migrate/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_hash_migrate/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_info/status.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_init/model.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_install/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_install/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_ping/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_ping/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/funcs.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_pull/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_restore/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_restore/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_rules/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_rules/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/model.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/new.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/base.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/schemas/default.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/cmd_schema/set_default.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/prompts.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/connectivity.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/create.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/auth.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/client.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/commands/utils/github/models.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/errors.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/main.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/preamble.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/services/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/cli/services/backup.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/provider.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/recipe.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/session.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/connection/sources.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/crypto.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/base.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/bigquery.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/capabilities.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/mysql.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/postgresql.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/drivers/sqlserver.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/collector.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/render.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/errors/types.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/collect.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/executor.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/feeder.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/plan.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/presenter.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/records.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/feed/transform.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/base.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/assets/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/assets/cytoscape.min.js +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/builder.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/generator.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/graphviz_render.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/html_render.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/layout.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/model.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/templates/er.html.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/diagram/themes.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/calculated.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/context.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/base.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/domain/model.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/facade.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/dataframe.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/filters.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/read.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/triggers.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/upsert.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/vector.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/macros/write.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/session.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/aggregation.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/context.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/__init__.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/encoders.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/funcs.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/hierarchy.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/utils/rls.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/templates/shared/vector.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/analyzer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/renderer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/steps.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/python_client/triggers/transformer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/rules/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/generators/rules/templates/schema.md.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/blueprint.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/build.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/defaults.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/naming.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/reader.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/templates/schema.py.jinja2 +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/types.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/introspect/writer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/calculated.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/collector.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/column_analyzer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/constraints.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/hints.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/relation_analyzer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/analysis/schema_analyzer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/annotations.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/base.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/calculated.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/columns.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/constraints.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/datasource.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/descriptors.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/encoders.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/enumerations.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/relations.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/table.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/trigger_api.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/triggers.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/declarative/view.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/mapping/sqlalchemy_mapper.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/column.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/constraints.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/entity.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/enumeration.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/relation.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/schema.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/model/trigger.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/naming.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/ordering.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/registry.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/orm/types.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/config.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/context.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/manager.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/project/paths.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/catalog.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/installer.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/meta.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/service.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/staleness.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/comandos.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/errores.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/rules/static/modelo.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/schema.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/README.md +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/constraints.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/indexes.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/tables.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/diff/views.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/executor.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/planner.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/plan/statements.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/presenter.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/report.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/safety.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/__init__.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/local.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/model.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/normalize.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/state/remote.py +0 -0
- {tai_sql-0.7.8 → tai_sql-0.7.9}/tai_sql/sync/synchronizer.py +0 -0
|
@@ -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=
|
|
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')
|