tai-sql 0.8.0__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tai_sql-0.8.0 → tai_sql-0.8.2}/PKG-INFO +1 -1
- {tai_sql-0.8.0 → tai_sql-0.8.2}/pyproject.toml +1 -1
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/schemas/chatbot.py +247 -133
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2 +19 -1
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/write.jinja2 +23 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/LICENSE +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_backup/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_backup/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_deploy/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_deploy/config.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_deploy/local.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_deploy/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_deploy/workflow.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_feed/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_feed/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_generate/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_generate/funcs.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_generate/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_hash_migrate/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_hash_migrate/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_info/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_info/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_info/status.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_init/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_init/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_init/model.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_install/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_install/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_ping/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_ping/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_pull/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_pull/funcs.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_pull/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_restore/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_restore/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_rules/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_rules/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/model.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/new.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/schemas/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/schemas/auth.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/schemas/base.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/schemas/default.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/cmd_schema/set_default.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/prompts.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/connectivity.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/create.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/github/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/github/auth.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/github/client.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/commands/utils/github/models.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/errors.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/main.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/preamble.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/services/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/cli/services/backup.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/provider.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/recipe.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/session.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/connection/sources.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/crypto.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/base.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/bigquery.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/capabilities.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/mysql.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/postgresql.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/drivers/sqlserver.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/errors/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/errors/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/errors/collector.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/errors/render.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/errors/types.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/collect.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/executor.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/feeder.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/plan.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/presenter.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/records.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/feed/transform.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/base.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/assets/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/assets/cytoscape.min.js +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/builder.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/generator.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/graphviz_render.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/html_render.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/layout.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/model.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/templates/er.html.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/diagram/themes.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/calculated.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/context.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/base.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/domain/__init__.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/domain/dao.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/domain/model.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/facade.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/aggregation.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/dataframe.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/filters.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/hierarchy.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/read.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/triggers.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/upsert.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/vector.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/session.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/__init__.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/aggregation.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/binary.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/context.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/utils/__init__.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/utils/encoders.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/utils/funcs.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/utils/hierarchy.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/utils/rls.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/shared/vector.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/analyzer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/renderer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/scopes.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/steps.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/triggers/transformer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/rules/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/rules/templates/schema.md.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/blueprint.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/build.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/defaults.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/naming.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/reader.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/templates/schema.py.jinja2 +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/types.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/introspect/writer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/calculated.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/collector.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/column_analyzer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/constraints.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/hints.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/relation_analyzer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/analysis/schema_analyzer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/annotations.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/base.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/calculated.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/columns.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/constraints.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/datasource.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/descriptors.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/encoders.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/enumerations.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/relations.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/table.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/trigger_api.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/triggers.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/declarative/view.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/mapping/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/mapping/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/mapping/sqlalchemy_mapper.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/column.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/constraints.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/entity.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/enumeration.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/relation.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/schema.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/model/trigger.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/naming.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/ordering.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/registry.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/orm/types.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/config.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/context.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/manager.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/project/paths.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/catalog.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/installer.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/meta.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/service.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/staleness.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/static/comandos.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/static/errores.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/rules/static/modelo.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/schema.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/README.md +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/diff/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/diff/constraints.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/diff/indexes.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/diff/tables.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/diff/views.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/executor.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/ownership.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/plan/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/plan/planner.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/plan/statements.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/presenter.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/report.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/safety.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/state/__init__.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/state/local.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/state/model.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/state/normalize.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/state/remote.py +0 -0
- {tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/sync/synchronizer.py +0 -0
|
@@ -6,7 +6,7 @@ from .base import SchemaTemplate
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ChatbotSchema(SchemaTemplate):
|
|
9
|
-
"""Plantilla chatbot: Chat, Mensaje, TokenUsage, ToolCall, Modelo, Tarifa, Bolsa,
|
|
9
|
+
"""Plantilla chatbot: Chat, Mensaje, TokenUsage, TokenUsageParte, ToolCall, Sugerencia, Modelo, Tarifa, Bolsa,
|
|
10
10
|
LimitePresupuesto y vistas"""
|
|
11
11
|
|
|
12
12
|
def get_content(self) -> str:
|
|
@@ -105,6 +105,7 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
105
105
|
|
|
106
106
|
token_usage: onetomany[TokenUsage] # Relación one-to-many con la tabla TokenUsage
|
|
107
107
|
tool_calls: onetomany[ToolCall] # Relación one-to-many con la tabla ToolCall
|
|
108
|
+
sugerencias: onetomany[Sugerencia] # Lo que se sugiere preguntar tras esta respuesta
|
|
108
109
|
|
|
109
110
|
chat: manytoone[Chat] = relation(fields=['chat_id'], references=['id'], backref='messages') # Relación many-to-one con Chat
|
|
110
111
|
|
|
@@ -131,8 +132,19 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
131
132
|
tarifa_id: col[bigint | None] = column(index=True, description='Tarifa con la que se valora')
|
|
132
133
|
bolsa_id: col[bigint | None] = column(index=True, description='Bolsa a la que se carga; vacío si no hay bolsas')
|
|
133
134
|
|
|
135
|
+
# De dónde sale y lo exacto que dio el proveedor; de qué se compone va en sus partes
|
|
136
|
+
# (token_usage_parte). Lo que se cobra sigue siendo prompt_tokens y completion_tokens:
|
|
137
|
+
# las partes los reparten, y suman lo mismo. Vacíos en los apuntes anteriores
|
|
138
|
+
origen: col[str | None] = column(index=True, description='De dónde sale: respuesta, subagente, titulo, sugerencias o resumen')
|
|
139
|
+
llamadas: col[int | None] = column(description='Cuántas llamadas al modelo hizo')
|
|
140
|
+
cache_read: col[int | None] = column(description='Tokens de la entrada leídos de la caché del proveedor, ya incluidos en prompt_tokens')
|
|
141
|
+
cache_write: col[int | None] = column(description='Tokens de la entrada escritos en la caché del proveedor, ya incluidos en prompt_tokens')
|
|
142
|
+
estimado: col[bool | None] = column(description='Si los tokens se estimaron por el texto porque el proveedor no dio el uso: entonces no tiene partes')
|
|
143
|
+
herramienta: col[str | None] = column(description='La herramienta que creó el sub-agente, si el origen es subagente')
|
|
144
|
+
|
|
134
145
|
message: manytoone[Mensaje] = relation(fields=['message_id'], references=['id'], backref='token_usage', onDelete='set null')
|
|
135
146
|
chat: manytoone[Chat] = relation(fields=['chat_id'], references=['id'], backref='token_usage', onDelete='set null')
|
|
147
|
+
partes: onetomany[TokenUsageParte] # De qué se compone: las partes de su entrada y su salida
|
|
136
148
|
|
|
137
149
|
|
|
138
150
|
class ToolCall(Table):
|
|
@@ -151,6 +163,34 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
151
163
|
message: manytoone[Mensaje] = relation(fields=['message_id'], references=['id'], backref='tool_calls') # Relación many-to-one con Mensaje
|
|
152
164
|
|
|
153
165
|
|
|
166
|
+
class TokenUsageParte(Table):
|
|
167
|
+
"""Tabla con las partes de un apunte de consumo: de qué se compone su entrada y su salida"""
|
|
168
|
+
__tablename__ = "token_usage_parte"
|
|
169
|
+
|
|
170
|
+
id: col[bigint] = column(primary_key=True, autoincrement=True)
|
|
171
|
+
tipo: col[str] = column(description="'entrada' o 'salida'")
|
|
172
|
+
parte: col[str] = column(description='sistema, definiciones, contexto, contexto_herramientas, mensaje, ronda o peticion en la entrada; texto, llamadas_herramienta o razonamiento en la salida')
|
|
173
|
+
tokens: col[int] = column(description='Los tokens de esta parte: las de un tipo suman los del apunte')
|
|
174
|
+
token_usage_id: col[bigint] = column(index=True, description='El apunte del que es parte')
|
|
175
|
+
|
|
176
|
+
# `apunte` y no `token_usage`: los triggers en cascada comparan las relaciones por su
|
|
177
|
+
# nombre, sin la ruta, y con el mismo nombre que Mensaje.token_usage, crear un mensaje
|
|
178
|
+
# con sus apuntes y sus partes subía y bajaba entre apunte y parte sin fin
|
|
179
|
+
apunte: manytoone[TokenUsage] = relation(fields=['token_usage_id'], references=['id'], backref='partes', onDelete='cascade')
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Sugerencia(Table):
|
|
183
|
+
"""Tabla con lo que se sugiere preguntar tras una respuesta del asistente"""
|
|
184
|
+
__tablename__ = "sugerencia"
|
|
185
|
+
|
|
186
|
+
id: col[bigint] = column(primary_key=True, autoincrement=True)
|
|
187
|
+
texto: col[str] = column(description='La sugerencia, escrita como la diría el usuario')
|
|
188
|
+
posicion: col[int] = column(description='Su orden entre las de la misma respuesta, desde 0')
|
|
189
|
+
message_id: col[bigint] = column(index=True, description='La respuesta tras la que se sugiere')
|
|
190
|
+
|
|
191
|
+
message: manytoone[Mensaje] = relation(fields=['message_id'], references=['id'], backref='sugerencias', onDelete='cascade')
|
|
192
|
+
|
|
193
|
+
|
|
154
194
|
class Modelo(Table):
|
|
155
195
|
"""Tabla que almacena los modelos con los que puede responder el agente"""
|
|
156
196
|
__tablename__ = "modelo"
|
|
@@ -183,6 +223,11 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
183
223
|
modelo_id: col[bigint] = column(description='Modelo al que pone precio')
|
|
184
224
|
precio_input_eur_millon: col[float] = column(default=0.0, description='Euros por millón de tokens de entrada, al precio del proveedor')
|
|
185
225
|
precio_output_eur_millon: col[float] = column(default=0.0, description='Euros por millón de tokens de salida, al precio del proveedor')
|
|
226
|
+
# La entrada que sale de la caché del proveedor («cached input») y la que se escribe en ella
|
|
227
|
+
# («cache writes»: solo OpenAI y Azure desde GPT-5.6) tienen su precio. Vacío es al precio
|
|
228
|
+
# de entrada, que es lo de una tarifa sin él
|
|
229
|
+
precio_cache_input_eur_millon: col[float | None] = column(description='Cache input: euros por millón de tokens de entrada leídos de la caché, al precio del proveedor; vacío, al de entrada')
|
|
230
|
+
precio_cache_write_eur_millon: col[float | None] = column(description='Cache write: euros por millón de tokens de entrada escritos en la caché, al precio del proveedor; vacío, al de entrada')
|
|
186
231
|
margen: col[float] = column(default=1.0, description='Multiplicador comercial sobre el precio del proveedor')
|
|
187
232
|
desde: col[date] = column(description='Primer día en que rige')
|
|
188
233
|
# Una tarifa no se edita ni se borra: se da de alta la siguiente con su `desde` y la
|
|
@@ -220,22 +265,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
220
265
|
|
|
221
266
|
# Definición de vistas
|
|
222
267
|
|
|
223
|
-
class UserStats(View):
|
|
224
|
-
"""Vista que muestra estadísticas de usuarios y sus chats"""
|
|
225
|
-
__tablename__ = "user_stats"
|
|
226
|
-
__query__ = query('user_stats.sql') # Esto es necesario para usar tai-sql push
|
|
227
|
-
|
|
228
|
-
username: col[str]
|
|
229
|
-
total_chats: col[int]
|
|
230
|
-
active_chats: col[int]
|
|
231
|
-
total_messages: col[int]
|
|
232
|
-
last_activity: col[datetime | None]
|
|
233
|
-
|
|
234
|
-
most_used_model: col[str | None]
|
|
235
|
-
most_used_model_total_tokens: col[int]
|
|
236
|
-
most_used_model_active_days: col[int]
|
|
237
|
-
|
|
238
|
-
|
|
239
268
|
class ConsumoDiario(View):
|
|
240
269
|
"""Vista que muestra el consumo por usuario, día y modelo"""
|
|
241
270
|
__tablename__ = "consumo_diario"
|
|
@@ -253,20 +282,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
253
282
|
message_count: col[int]
|
|
254
283
|
|
|
255
284
|
|
|
256
|
-
class ChatActivity(View):
|
|
257
|
-
"""Vista que muestra la actividad reciente de chats"""
|
|
258
|
-
__tablename__ = "chat_activity"
|
|
259
|
-
__query__ = query('chat_activity.sql')
|
|
260
|
-
|
|
261
|
-
chat_id: col[str]
|
|
262
|
-
chat_title: col[str]
|
|
263
|
-
username: col[str]
|
|
264
|
-
message_count: col[int]
|
|
265
|
-
last_message_timestamp: col[datetime]
|
|
266
|
-
total_tokens_consumed: col[int]
|
|
267
|
-
is_active: col[bool]
|
|
268
|
-
|
|
269
|
-
|
|
270
285
|
class ConsumoMensual(View):
|
|
271
286
|
"""Vista que muestra el consumo y el coste por usuario, mes y modelo"""
|
|
272
287
|
__tablename__ = "consumo_mensual"
|
|
@@ -300,6 +315,80 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
300
315
|
total_tokens: col[int]
|
|
301
316
|
|
|
302
317
|
|
|
318
|
+
class ConsumoApunte(View):
|
|
319
|
+
"""Vista que muestra cada apunte de consumo con su modelo y su coste"""
|
|
320
|
+
__tablename__ = "consumo_apunte"
|
|
321
|
+
__query__ = query('consumo_apunte.sql')
|
|
322
|
+
|
|
323
|
+
token_usage_id: col[bigint]
|
|
324
|
+
timestamp: col[datetime]
|
|
325
|
+
username: col[str]
|
|
326
|
+
message_id: col[bigint | None]
|
|
327
|
+
chat_id: col[bigint | None]
|
|
328
|
+
origen: col[str]
|
|
329
|
+
llamadas: col[int | None]
|
|
330
|
+
cache_read: col[int | None]
|
|
331
|
+
cache_write: col[int | None]
|
|
332
|
+
estimado: col[bool | None]
|
|
333
|
+
herramienta: col[str | None]
|
|
334
|
+
# Las vueltas del agente en el mensaje del que cuelga
|
|
335
|
+
rondas: col[int | None]
|
|
336
|
+
tarifa_id: col[bigint | None]
|
|
337
|
+
# Del modelo de la tarifa: vacíos si el apunte no tiene
|
|
338
|
+
modelo: col[str | None]
|
|
339
|
+
provider: col[str | None]
|
|
340
|
+
model_name: col[str | None]
|
|
341
|
+
prompt_tokens: col[int]
|
|
342
|
+
completion_tokens: col[int]
|
|
343
|
+
total_tokens: col[int]
|
|
344
|
+
# Como en consumo_mensual: por fila, con el margen y redondeado. Vacíos sin tarifa
|
|
345
|
+
coste_entrada_eur: col[float | None]
|
|
346
|
+
coste_salida_eur: col[float | None]
|
|
347
|
+
coste_eur: col[float | None]
|
|
348
|
+
# Lo que la caché ahorró frente al precio de entrada (negativo si escribir costó más), y
|
|
349
|
+
# si la tarifa tiene precio para lo leído
|
|
350
|
+
ahorro_cache_eur: col[float | None]
|
|
351
|
+
precio_cache: col[bool]
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class ConsumoParte(View):
|
|
355
|
+
"""Vista que muestra cada parte de un apunte de consumo con su coste"""
|
|
356
|
+
__tablename__ = "consumo_parte"
|
|
357
|
+
__query__ = query('consumo_parte.sql')
|
|
358
|
+
|
|
359
|
+
parte_id: col[bigint]
|
|
360
|
+
token_usage_id: col[bigint]
|
|
361
|
+
# Del apunte, para filtrar las partes igual que los apuntes
|
|
362
|
+
timestamp: col[datetime]
|
|
363
|
+
username: col[str]
|
|
364
|
+
modelo: col[str | None]
|
|
365
|
+
# 'entrada' o 'salida'
|
|
366
|
+
tipo: col[str]
|
|
367
|
+
parte: col[str]
|
|
368
|
+
tokens: col[int]
|
|
369
|
+
coste_eur: col[float | None]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class Valoracion(View):
|
|
373
|
+
"""Vista que muestra las respuestas valoradas con un comentario"""
|
|
374
|
+
__tablename__ = "valoracion"
|
|
375
|
+
__query__ = query('valoracion.sql')
|
|
376
|
+
|
|
377
|
+
message_id: col[bigint]
|
|
378
|
+
timestamp: col[datetime]
|
|
379
|
+
content: col[str]
|
|
380
|
+
# 'good' o 'bad'
|
|
381
|
+
feedback: col[str]
|
|
382
|
+
feedback_comment: col[str]
|
|
383
|
+
total_duration_ms: col[int | None]
|
|
384
|
+
model_name: col[str | None]
|
|
385
|
+
chat_id: col[bigint]
|
|
386
|
+
chat_title: col[str | None]
|
|
387
|
+
username: col[str]
|
|
388
|
+
# Todo lo que se apuntó con la respuesta: la suya y la de sus sub-agentes
|
|
389
|
+
total_tokens: col[int | None]
|
|
390
|
+
|
|
391
|
+
|
|
303
392
|
class BolsaEstado(View):
|
|
304
393
|
"""Vista que muestra cuánto lleva gastado cada bolsa de consumo"""
|
|
305
394
|
__tablename__ = "bolsa_estado"
|
|
@@ -316,22 +405,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
316
405
|
estado: col[str]
|
|
317
406
|
|
|
318
407
|
|
|
319
|
-
class TarifaVigente(View):
|
|
320
|
-
"""Vista que muestra la tarifa que rige hoy para cada modelo, a precio de venta"""
|
|
321
|
-
__tablename__ = "tarifa_vigente"
|
|
322
|
-
__query__ = query('tarifa_vigente.sql')
|
|
323
|
-
|
|
324
|
-
tarifa_id: col[bigint]
|
|
325
|
-
modelo_id: col[bigint]
|
|
326
|
-
alias: col[str]
|
|
327
|
-
provider: col[str]
|
|
328
|
-
model_name: col[str]
|
|
329
|
-
precio_input_eur_millon: col[float]
|
|
330
|
-
precio_output_eur_millon: col[float]
|
|
331
|
-
desde: col[date]
|
|
332
|
-
hasta: col[date | None]
|
|
333
|
-
|
|
334
|
-
|
|
335
408
|
class TarifaHistorico(View):
|
|
336
409
|
"""Vista que muestra todas las tarifas de cada modelo, a precio de venta"""
|
|
337
410
|
__tablename__ = "tarifa_historico"
|
|
@@ -344,8 +417,12 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
344
417
|
model_name: col[str]
|
|
345
418
|
precio_input_eur_millon: col[float]
|
|
346
419
|
precio_output_eur_millon: col[float]
|
|
420
|
+
# Vacíos si la tarifa no los tiene: lo de la caché va al precio de entrada
|
|
421
|
+
precio_cache_input_eur_millon: col[float | None]
|
|
422
|
+
precio_cache_write_eur_millon: col[float | None]
|
|
347
423
|
desde: col[date]
|
|
348
424
|
hasta: col[date | None]
|
|
425
|
+
# La que rige hoy: una por modelo como mucho
|
|
349
426
|
vigente: col[bool]
|
|
350
427
|
|
|
351
428
|
|
|
@@ -389,7 +466,11 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
389
466
|
b.nombre,
|
|
390
467
|
b.importe_eur,
|
|
391
468
|
b.created_at,
|
|
469
|
+
-- La entrada, a precio de entrada, más lo que cambia la caché: lo leído de ella (cache input) y lo
|
|
470
|
+
-- escrito en ella (cache write), cada uno a su precio y, sin él, al de entrada (la diferencia es 0)
|
|
392
471
|
COALESCE(SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
472
|
+
+ COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
473
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
393
474
|
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
394
475
|
* t.margen / 1000000)::numeric, 8)), 0)::double precision as consumido_eur,
|
|
395
476
|
MIN(tu.timestamp) as primer_consumo,
|
|
@@ -417,23 +498,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
417
498
|
ORDER BY g.id;
|
|
418
499
|
''').rstrip()
|
|
419
500
|
|
|
420
|
-
chat_activity_sql = dedent('''\
|
|
421
|
-
-- Vista para actividad de chats
|
|
422
|
-
SELECT
|
|
423
|
-
c.id as chat_id,
|
|
424
|
-
c.title as chat_title,
|
|
425
|
-
c.username,
|
|
426
|
-
COUNT(m.id) as message_count,
|
|
427
|
-
MAX(m.timestamp) as last_message_timestamp,
|
|
428
|
-
COALESCE(SUM(tu.total_tokens), 0) as total_tokens_consumed,
|
|
429
|
-
c.is_active
|
|
430
|
-
FROM chat c
|
|
431
|
-
LEFT JOIN mensaje m ON c.id = m.chat_id
|
|
432
|
-
LEFT JOIN token_usage tu ON m.id = tu.message_id
|
|
433
|
-
GROUP BY c.id, c.title, c.username, c.is_active
|
|
434
|
-
ORDER BY last_message_timestamp DESC NULLS LAST;
|
|
435
|
-
''').rstrip()
|
|
436
|
-
|
|
437
501
|
consumo_diario_sql = dedent('''\
|
|
438
502
|
-- Vista para el consumo por usuario, día y modelo. Los tokens y el importe son todos los
|
|
439
503
|
-- apuntes, tengan mensaje o no; los mensajes, los que envió el usuario
|
|
@@ -446,7 +510,11 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
446
510
|
SUM(tu.prompt_tokens) as total_prompt_tokens,
|
|
447
511
|
SUM(tu.completion_tokens) as total_completion_tokens,
|
|
448
512
|
SUM(tu.total_tokens) as total_tokens,
|
|
513
|
+
-- La entrada, a precio de entrada, más lo que cambia la caché: lo leído de ella (cache input) y lo
|
|
514
|
+
-- escrito en ella (cache write), cada uno a su precio y, sin él, al de entrada (la diferencia es 0)
|
|
449
515
|
SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
516
|
+
+ COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
517
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
450
518
|
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
451
519
|
* t.margen / 1000000)::numeric, 8))::double precision as total_cost_eur,
|
|
452
520
|
COUNT(DISTINCT COALESCE(tu.chat_id, me.chat_id)) as chat_count
|
|
@@ -512,7 +580,11 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
512
580
|
SUM(tu.prompt_tokens) as total_prompt_tokens,
|
|
513
581
|
SUM(tu.completion_tokens) as total_completion_tokens,
|
|
514
582
|
SUM(tu.total_tokens) as total_tokens,
|
|
583
|
+
-- La entrada, a precio de entrada, más lo que cambia la caché: lo leído de ella (cache input) y lo
|
|
584
|
+
-- escrito en ella (cache write), cada uno a su precio y, sin él, al de entrada (la diferencia es 0)
|
|
515
585
|
SUM(ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
586
|
+
+ COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
587
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
516
588
|
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
517
589
|
* t.margen / 1000000)::numeric, 8))::double precision as coste_eur
|
|
518
590
|
FROM token_usage tu
|
|
@@ -561,6 +633,112 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
561
633
|
ORDER BY mes DESC, username, total_tokens DESC;
|
|
562
634
|
''').rstrip()
|
|
563
635
|
|
|
636
|
+
consumo_apunte_sql = dedent('''\
|
|
637
|
+
-- Vista con cada apunte de consumo: de dónde sale, con qué modelo y lo exacto que dio el
|
|
638
|
+
-- proveedor y las vueltas del agente, con su coste calculado como en consumo_mensual (por fila, con el margen y
|
|
639
|
+
-- redondeado a 8 decimales). Un apunte sin tarifa sale con el modelo y el coste vacíos.
|
|
640
|
+
-- Un apunte sin origen es 'respuesta' si cuelga de un mensaje, y si no 'sin_clasificar'
|
|
641
|
+
SELECT
|
|
642
|
+
tu.id as token_usage_id,
|
|
643
|
+
tu.timestamp,
|
|
644
|
+
tu.username,
|
|
645
|
+
tu.message_id,
|
|
646
|
+
tu.chat_id,
|
|
647
|
+
COALESCE(tu.origen, CASE WHEN tu.message_id IS NOT NULL THEN 'respuesta' ELSE 'sin_clasificar' END) as origen,
|
|
648
|
+
tu.llamadas,
|
|
649
|
+
tu.cache_read,
|
|
650
|
+
tu.cache_write,
|
|
651
|
+
tu.estimado,
|
|
652
|
+
tu.herramienta,
|
|
653
|
+
ms.agent_iterations as rondas,
|
|
654
|
+
tu.tarifa_id,
|
|
655
|
+
m.alias as modelo,
|
|
656
|
+
m.provider,
|
|
657
|
+
m.model_name,
|
|
658
|
+
tu.prompt_tokens,
|
|
659
|
+
tu.completion_tokens,
|
|
660
|
+
tu.total_tokens,
|
|
661
|
+
-- La entrada, a precio de entrada, más lo que cambia la caché: lo leído de ella (cache input) y lo
|
|
662
|
+
-- escrito en ella (cache write), cada uno a su precio y, sin él, al de entrada (la diferencia es 0)
|
|
663
|
+
ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
664
|
+
+ COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
665
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon))
|
|
666
|
+
* t.margen / 1000000)::numeric, 8)::double precision as coste_entrada_eur,
|
|
667
|
+
ROUND((tu.completion_tokens * t.precio_output_eur_millon
|
|
668
|
+
* t.margen / 1000000)::numeric, 8)::double precision as coste_salida_eur,
|
|
669
|
+
ROUND(((tu.prompt_tokens * t.precio_input_eur_millon
|
|
670
|
+
+ COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
671
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
672
|
+
+ tu.completion_tokens * t.precio_output_eur_millon)
|
|
673
|
+
* t.margen / 1000000)::numeric, 8)::double precision as coste_eur,
|
|
674
|
+
-- lo que la caché ahorró frente a cobrar la entrada entera a precio de entrada (negativo
|
|
675
|
+
-- si escribir en ella costó más), y si la tarifa tiene precio de cache input
|
|
676
|
+
ROUND((-(COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
677
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon))
|
|
678
|
+
* t.margen / 1000000)::numeric, 8)::double precision as ahorro_cache_eur,
|
|
679
|
+
(t.precio_cache_input_eur_millon IS NOT NULL) as precio_cache
|
|
680
|
+
FROM token_usage tu
|
|
681
|
+
LEFT JOIN tarifa t ON t.id = tu.tarifa_id
|
|
682
|
+
LEFT JOIN modelo m ON m.id = t.modelo_id
|
|
683
|
+
LEFT JOIN mensaje ms ON ms.id = tu.message_id
|
|
684
|
+
ORDER BY tu.timestamp, tu.id;
|
|
685
|
+
''').rstrip()
|
|
686
|
+
|
|
687
|
+
consumo_parte_sql = dedent('''\
|
|
688
|
+
-- Vista con cada parte de un apunte de consumo (token_usage_parte) y su coste, como en
|
|
689
|
+
-- consumo_apunte: la salida a su precio, y la entrada al precio medio de la de su apunte, que
|
|
690
|
+
-- reparte lo que cambia la caché (no se sabe qué parte salió de ella). Lleva la fecha, el
|
|
691
|
+
-- usuario y el modelo del apunte, para filtrar las
|
|
692
|
+
-- partes igual que los apuntes. Un apunte sin partes no sale: las suyas son el apunte
|
|
693
|
+
-- entero, que está en consumo_apunte
|
|
694
|
+
SELECT
|
|
695
|
+
p.id as parte_id,
|
|
696
|
+
p.token_usage_id,
|
|
697
|
+
tu.timestamp,
|
|
698
|
+
tu.username,
|
|
699
|
+
m.alias as modelo,
|
|
700
|
+
p.tipo,
|
|
701
|
+
p.parte,
|
|
702
|
+
p.tokens,
|
|
703
|
+
ROUND((p.tokens * CASE WHEN p.tipo = 'entrada'
|
|
704
|
+
THEN t.precio_input_eur_millon
|
|
705
|
+
+ COALESCE((COALESCE(tu.cache_read, 0) * (COALESCE(t.precio_cache_input_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon)
|
|
706
|
+
+ COALESCE(tu.cache_write, 0) * (COALESCE(t.precio_cache_write_eur_millon, t.precio_input_eur_millon) - t.precio_input_eur_millon))
|
|
707
|
+
/ NULLIF(tu.prompt_tokens, 0), 0)
|
|
708
|
+
ELSE t.precio_output_eur_millon END
|
|
709
|
+
* t.margen / 1000000)::numeric, 8)::double precision as coste_eur
|
|
710
|
+
FROM token_usage_parte p
|
|
711
|
+
JOIN token_usage tu ON tu.id = p.token_usage_id
|
|
712
|
+
LEFT JOIN tarifa t ON t.id = tu.tarifa_id
|
|
713
|
+
LEFT JOIN modelo m ON m.id = t.modelo_id
|
|
714
|
+
WHERE p.tokens <> 0
|
|
715
|
+
ORDER BY p.id;
|
|
716
|
+
''').rstrip()
|
|
717
|
+
|
|
718
|
+
valoracion_sql = dedent('''\
|
|
719
|
+
-- Vista con las respuestas que el usuario valoró con un comentario, con su conversación
|
|
720
|
+
-- y lo que se apuntó con ellas. Solo las que tienen comentario: la columna feedback nace
|
|
721
|
+
-- 'good', así que una valoración sin texto no se distingue de un mensaje sin valorar
|
|
722
|
+
SELECT
|
|
723
|
+
m.id as message_id,
|
|
724
|
+
m.timestamp,
|
|
725
|
+
m.content,
|
|
726
|
+
m.feedback::text as feedback,
|
|
727
|
+
m.feedback_comment,
|
|
728
|
+
m.total_duration_ms,
|
|
729
|
+
m.model_name,
|
|
730
|
+
c.id as chat_id,
|
|
731
|
+
c.title as chat_title,
|
|
732
|
+
c.username,
|
|
733
|
+
(SELECT SUM(tu.total_tokens) FROM token_usage tu WHERE tu.message_id = m.id) as total_tokens
|
|
734
|
+
FROM mensaje m
|
|
735
|
+
JOIN chat c ON c.id = m.chat_id
|
|
736
|
+
WHERE m.role = 'asistente'
|
|
737
|
+
AND m.feedback_comment IS NOT NULL
|
|
738
|
+
AND trim(m.feedback_comment) <> ''
|
|
739
|
+
ORDER BY m.timestamp DESC;
|
|
740
|
+
''').rstrip()
|
|
741
|
+
|
|
564
742
|
modelo_disponible_sql = dedent('''\
|
|
565
743
|
-- Vista con el catálogo de modelos; el endpoint y la variable de la clave no salen de aquí
|
|
566
744
|
WITH ventana AS (
|
|
@@ -613,6 +791,8 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
613
791
|
m.model_name,
|
|
614
792
|
(v.precio_input_eur_millon * v.margen)::double precision as precio_input_eur_millon,
|
|
615
793
|
(v.precio_output_eur_millon * v.margen)::double precision as precio_output_eur_millon,
|
|
794
|
+
(v.precio_cache_input_eur_millon * v.margen)::double precision as precio_cache_input_eur_millon,
|
|
795
|
+
(v.precio_cache_write_eur_millon * v.margen)::double precision as precio_cache_write_eur_millon,
|
|
616
796
|
v.desde,
|
|
617
797
|
v.hasta_efectivo as hasta,
|
|
618
798
|
(v.desde <= CURRENT_DATE
|
|
@@ -622,36 +802,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
622
802
|
ORDER BY m.alias, v.desde DESC;
|
|
623
803
|
''').rstrip()
|
|
624
804
|
|
|
625
|
-
tarifa_vigente_sql = dedent('''\
|
|
626
|
-
-- Vista con la tarifa que rige hoy para cada modelo, ya a precio de venta: el precio del
|
|
627
|
-
-- proveedor y el margen no salen de la tabla. La ventana se deriva de la tarifa siguiente,
|
|
628
|
-
-- así que dar de alta una tarifa nueva no obliga a cerrar la anterior
|
|
629
|
-
WITH ventana AS (
|
|
630
|
-
SELECT
|
|
631
|
-
t.*,
|
|
632
|
-
COALESCE(
|
|
633
|
-
t.hasta,
|
|
634
|
-
LEAD(t.desde) OVER (PARTITION BY t.modelo_id ORDER BY t.desde) - 1
|
|
635
|
-
) as hasta_efectivo
|
|
636
|
-
FROM tarifa t
|
|
637
|
-
)
|
|
638
|
-
SELECT
|
|
639
|
-
v.id as tarifa_id,
|
|
640
|
-
v.modelo_id,
|
|
641
|
-
m.alias,
|
|
642
|
-
m.provider,
|
|
643
|
-
m.model_name,
|
|
644
|
-
(v.precio_input_eur_millon * v.margen)::double precision as precio_input_eur_millon,
|
|
645
|
-
(v.precio_output_eur_millon * v.margen)::double precision as precio_output_eur_millon,
|
|
646
|
-
v.desde,
|
|
647
|
-
v.hasta_efectivo as hasta
|
|
648
|
-
FROM ventana v
|
|
649
|
-
JOIN modelo m ON m.id = v.modelo_id
|
|
650
|
-
WHERE v.desde <= CURRENT_DATE
|
|
651
|
-
AND (v.hasta_efectivo IS NULL OR v.hasta_efectivo >= CURRENT_DATE)
|
|
652
|
-
ORDER BY m.alias;
|
|
653
|
-
''').rstrip()
|
|
654
|
-
|
|
655
805
|
tokens_mensaje_sql = dedent('''\
|
|
656
806
|
-- Vista con los tokens de cada mensaje; no lleva importes a propósito: es la única que
|
|
657
807
|
-- puede leer cualquier usuario
|
|
@@ -670,42 +820,6 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
670
820
|
GROUP BY tu.message_id, m.chat_id, c.username, m.model_name, m.provider;
|
|
671
821
|
''').rstrip()
|
|
672
822
|
|
|
673
|
-
user_stats_sql = dedent('''\
|
|
674
|
-
-- Vista para estadísticas de usuarios
|
|
675
|
-
WITH top_model AS (
|
|
676
|
-
SELECT DISTINCT ON (c.username)
|
|
677
|
-
c.username,
|
|
678
|
-
m.model_name,
|
|
679
|
-
SUM(tu.total_tokens) AS total_tokens,
|
|
680
|
-
COUNT(DISTINCT DATE(tu.timestamp)) AS active_days
|
|
681
|
-
FROM chat c
|
|
682
|
-
JOIN mensaje m ON c.id = m.chat_id
|
|
683
|
-
JOIN token_usage tu ON m.id = tu.message_id
|
|
684
|
-
GROUP BY c.username, m.model_name
|
|
685
|
-
ORDER BY c.username, SUM(tu.total_tokens) DESC
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
SELECT
|
|
689
|
-
c.username,
|
|
690
|
-
COUNT(DISTINCT c.id) AS total_chats,
|
|
691
|
-
COUNT(DISTINCT CASE WHEN c.is_active THEN c.id END) AS active_chats,
|
|
692
|
-
COUNT(DISTINCT m.id) AS total_messages,
|
|
693
|
-
MAX(m.timestamp) AS last_activity,
|
|
694
|
-
|
|
695
|
-
tm.model_name AS most_used_model,
|
|
696
|
-
tm.total_tokens AS most_used_model_total_tokens,
|
|
697
|
-
tm.active_days AS most_used_model_active_days
|
|
698
|
-
|
|
699
|
-
FROM chat c
|
|
700
|
-
LEFT JOIN mensaje m ON c.id = m.chat_id
|
|
701
|
-
LEFT JOIN top_model tm ON c.username = tm.username
|
|
702
|
-
GROUP BY
|
|
703
|
-
c.username,
|
|
704
|
-
tm.model_name,
|
|
705
|
-
tm.total_tokens,
|
|
706
|
-
tm.active_days;
|
|
707
|
-
''').rstrip()
|
|
708
|
-
|
|
709
823
|
usuario_consumo_sql = dedent('''\
|
|
710
824
|
-- Vista con los usuarios a los que se mide el consumo. Es la que leen sus pantallas:
|
|
711
825
|
-- /rbac/{realm}/user pide administración del realm y la contraseña no sale de aquí
|
|
@@ -723,12 +837,12 @@ class ChatbotSchema(SchemaTemplate):
|
|
|
723
837
|
''').rstrip()
|
|
724
838
|
|
|
725
839
|
(views_dir / 'bolsa_estado.sql').write_text(bolsa_estado_sql, encoding='utf-8')
|
|
726
|
-
(views_dir / '
|
|
840
|
+
(views_dir / 'consumo_apunte.sql').write_text(consumo_apunte_sql, encoding='utf-8')
|
|
841
|
+
(views_dir / 'consumo_parte.sql').write_text(consumo_parte_sql, encoding='utf-8')
|
|
727
842
|
(views_dir / 'consumo_diario.sql').write_text(consumo_diario_sql, encoding='utf-8')
|
|
728
843
|
(views_dir / 'consumo_mensual.sql').write_text(consumo_mensual_sql, encoding='utf-8')
|
|
729
844
|
(views_dir / 'modelo_disponible.sql').write_text(modelo_disponible_sql, encoding='utf-8')
|
|
730
845
|
(views_dir / 'tarifa_historico.sql').write_text(tarifa_historico_sql, encoding='utf-8')
|
|
731
|
-
(views_dir / 'tarifa_vigente.sql').write_text(tarifa_vigente_sql, encoding='utf-8')
|
|
732
846
|
(views_dir / 'tokens_mensaje.sql').write_text(tokens_mensaje_sql, encoding='utf-8')
|
|
733
|
-
(views_dir / 'user_stats.sql').write_text(user_stats_sql, encoding='utf-8')
|
|
734
847
|
(views_dir / 'usuario_consumo.sql').write_text(usuario_consumo_sql, encoding='utf-8')
|
|
848
|
+
(views_dir / 'valoracion.sql').write_text(valoracion_sql, encoding='utf-8')
|
{tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/domain/dtos.py.jinja2
RENAMED
|
@@ -221,10 +221,28 @@ class {{ model.name }}Read(PrettyModel):
|
|
|
221
221
|
|
|
222
222
|
{% if not model.is_view %}
|
|
223
223
|
@classmethod
|
|
224
|
+
{% if _all_vector_cols %}
|
|
225
|
+
def from_created_instance(
|
|
226
|
+
cls,
|
|
227
|
+
instance: {{ model.name }},
|
|
228
|
+
included: set[str],
|
|
229
|
+
excluded: str=None,
|
|
230
|
+
include_vectors: bool = False,
|
|
231
|
+
) -> {{ model.name }}Read:
|
|
232
|
+
{% else %}
|
|
224
233
|
def from_created_instance(cls, instance: {{ model.name }}, included: set[str], excluded: str=None) -> {{ model.name }}Read:
|
|
234
|
+
{% endif %}
|
|
235
|
+
{% if _all_vector_cols %}
|
|
236
|
+
# Como `from_instance`: los vectores solo si se piden. Son miles de números por fila, y
|
|
237
|
+
# lo que se acaba de crear se devuelve entero (`create_many` de mil filas eran millones).
|
|
238
|
+
# Las filas anidadas los dejan fuera siempre, como las relaciones de `from_instance`.
|
|
239
|
+
{% endif %}
|
|
225
240
|
dto_data = {
|
|
226
241
|
{% for column in model.columns %}
|
|
227
|
-
{% if
|
|
242
|
+
{% if column.hash %}
|
|
243
|
+
{% elif column.type == 'vector' %}
|
|
244
|
+
'{{ column.name }}': instance.{{ column.name }} if include_vectors else None,
|
|
245
|
+
{% else %}
|
|
228
246
|
'{{ column.name }}': instance.{{ column.name }},
|
|
229
247
|
{% endif %}
|
|
230
248
|
{% endfor %}
|
{tai_sql-0.8.0 → tai_sql-0.8.2}/tai_sql/generators/python_client/templates/macros/write.jinja2
RENAMED
|
@@ -139,11 +139,15 @@
|
|
|
139
139
|
return len({{ await_keyword -}} cls.create_many([{{ model.name }}Create.from_dict(record) for record in records_data]))
|
|
140
140
|
{% endif %}
|
|
141
141
|
|
|
142
|
+
{% set _created_vector_cols = model.columns | selectattr('type', 'equalto', 'vector') | list %}
|
|
142
143
|
@classmethod
|
|
143
144
|
@{{ error_handler_name }}
|
|
144
145
|
{{ def_keyword }} create(
|
|
145
146
|
cls,
|
|
146
147
|
{{ model.tablename }}: {{ model.name }}Create,
|
|
148
|
+
{% if _created_vector_cols %}
|
|
149
|
+
include_vectors: bool = False,
|
|
150
|
+
{% endif %}
|
|
147
151
|
session: Optional[{{ session_type }}] = None
|
|
148
152
|
) -> {{ model.name }}Read:
|
|
149
153
|
"""
|
|
@@ -151,6 +155,10 @@
|
|
|
151
155
|
|
|
152
156
|
Args:
|
|
153
157
|
{{ model.tablename }}: Datos del {{ model.tablename }} a crear
|
|
158
|
+
{% if _created_vector_cols %}
|
|
159
|
+
include_vectors: Si True, el registro devuelto lleva sus columnas vectoriales. Por
|
|
160
|
+
defecto no, como en `find`: se guardan igual, solo no se devuelven.
|
|
161
|
+
{% endif %}
|
|
154
162
|
session: Sesión existente (opcional)
|
|
155
163
|
|
|
156
164
|
Returns:
|
|
@@ -173,7 +181,11 @@
|
|
|
173
181
|
{% endif %}
|
|
174
182
|
{{ await_keyword -}} session.flush()
|
|
175
183
|
{{ await_keyword -}} cls._on_create_after(new_instance, included, session)
|
|
184
|
+
{% if _created_vector_cols %}
|
|
185
|
+
return {{ model.name }}Read.from_created_instance(new_instance, included, include_vectors=include_vectors)
|
|
186
|
+
{% else %}
|
|
176
187
|
return {{ model.name }}Read.from_created_instance(new_instance, included)
|
|
188
|
+
{% endif %}
|
|
177
189
|
|
|
178
190
|
if session is not None:
|
|
179
191
|
data = {{ await_keyword -}} _execute_create(session)
|
|
@@ -199,6 +211,9 @@
|
|
|
199
211
|
{{ def_keyword }} create_many(
|
|
200
212
|
cls,
|
|
201
213
|
records: List[{{ model.name }}Create],
|
|
214
|
+
{% if _created_vector_cols %}
|
|
215
|
+
include_vectors: bool = False,
|
|
216
|
+
{% endif %}
|
|
202
217
|
session: Optional[{{ session_type }}] = None
|
|
203
218
|
) -> List[{{ model.name }}Read]:
|
|
204
219
|
"""
|
|
@@ -206,6 +221,10 @@
|
|
|
206
221
|
|
|
207
222
|
Args:
|
|
208
223
|
records: Lista de {{ model.name }}Create con los datos de los registros
|
|
224
|
+
{% if _created_vector_cols %}
|
|
225
|
+
include_vectors: Si True, los registros devueltos llevan sus columnas vectoriales. Por
|
|
226
|
+
defecto no, como en `find_many`: con mil filas serían millones de números.
|
|
227
|
+
{% endif %}
|
|
209
228
|
session: Sesión existente (opcional)
|
|
210
229
|
|
|
211
230
|
Returns:
|
|
@@ -240,7 +259,11 @@
|
|
|
240
259
|
# Los DTO se construyen después de los triggers `after`, como en `create`: lo que
|
|
241
260
|
# escriban en la fila tiene que salir en lo que se devuelve
|
|
242
261
|
return [
|
|
262
|
+
{% if _created_vector_cols %}
|
|
263
|
+
{{ model.name }}Read.from_created_instance(instance, included, include_vectors=include_vectors)
|
|
264
|
+
{% else %}
|
|
243
265
|
{{ model.name }}Read.from_created_instance(instance, included)
|
|
266
|
+
{% endif %}
|
|
244
267
|
for instance, included in creadas
|
|
245
268
|
]
|
|
246
269
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|