From 28aea85b1052a10e4381688dbda59849da7fd22c Mon Sep 17 00:00:00 2001 From: Josako Date: Mon, 19 May 2025 14:10:09 +0200 Subject: [PATCH] - Add functionality to add a default dictionary for configuration fields - Correct entitlement processing - Remove get_template functionality from ModelVariables, define it directly with LLM model definition in configuration file. --- config/config.py | 4 +- config/prompts/global/html_parse/1.0.0.yaml | 2 +- .../view_active_license_usage.html | 138 ++++++++++++++++++ eveai_app/templates/navbar.html | 2 +- eveai_app/views/entitlements_views.py | 58 +++++++- eveai_chat_workers/__init__.py | 4 +- eveai_entitlements/__init__.py | 2 + eveai_entitlements/tasks.py | 127 ++++++++-------- eveai_workers/__init__.py | 10 +- eveai_workers/processors/html_processor.py | 5 +- eveai_workers/processors/pdf_processor.py | 5 +- .../processors/transcription_processor.py | 5 +- eveai_workers/tasks.py | 8 +- ...license_copied_fields_in_licenseperiod_.py | 71 +++++++++ ...dd_end_date_calculated_field_to_license.py | 30 ++++ 15 files changed, 386 insertions(+), 85 deletions(-) create mode 100644 eveai_app/templates/entitlements/view_active_license_usage.html create mode 100644 migrations/public/versions/4eae969dcac2_license_copied_fields_in_licenseperiod_.py create mode 100644 migrations/public/versions/c08c3e7c3b1a_add_end_date_calculated_field_to_license.py diff --git a/config/config.py b/config/config.py index 8a6c960..8c1045a 100644 --- a/config/config.py +++ b/config/config.py @@ -75,8 +75,8 @@ class Config(object): # supported LLMs # SUPPORTED_EMBEDDINGS = ['openai.text-embedding-3-small', 'openai.text-embedding-3-large', 'mistral.mistral-embed'] SUPPORTED_EMBEDDINGS = ['mistral.mistral-embed'] - SUPPORTED_LLMS = ['openai.gpt-4o', 'anthropic.claude-3-5-sonnet', 'openai.gpt-4o-mini', - 'mistral.mistral-large-latest', 'mistral.mistral-small-latest'] + SUPPORTED_LLMS = ['openai.gpt-4o', 'openai.gpt-4o-mini', + 'mistral.mistral-large-latest', 'mistral.mistral-medium_latest', 'mistral.mistral-small-latest'] ANTHROPIC_LLM_VERSIONS = {'claude-3-5-sonnet': 'claude-3-5-sonnet-20240620', } diff --git a/config/prompts/global/html_parse/1.0.0.yaml b/config/prompts/global/html_parse/1.0.0.yaml index 4cacd7f..74a4848 100644 --- a/config/prompts/global/html_parse/1.0.0.yaml +++ b/config/prompts/global/html_parse/1.0.0.yaml @@ -13,7 +13,7 @@ content: | HTML is between triple backquotes. ```{html}``` -model: "mistral.mistral-small-latest" +llm_model: "mistral.mistral-small-latest" metadata: author: "Josako" date_added: "2024-11-10" diff --git a/eveai_app/templates/entitlements/view_active_license_usage.html b/eveai_app/templates/entitlements/view_active_license_usage.html new file mode 100644 index 0000000..4fa43b1 --- /dev/null +++ b/eveai_app/templates/entitlements/view_active_license_usage.html @@ -0,0 +1,138 @@ +{% extends 'base.html' %} +{% from "macros.html" import render_selectable_table, render_pagination %} + +{% block title %}Active License Usage{% endblock %} + +{% block content_title %}Active License Usage{% endblock %} +{% block content_description %}Overview of the current license period usage{% endblock %} + +{% block content %} +{% if active_period and active_period.license_usage %} +
+
+
+
+
License Information
+
+
+

License ID: {{ active_period.license.id }}

+

License Name: {{ active_period.license.name }}

+

Period Nr: {{ active_period.period_number }}

+

Start Date: {{ active_period.period_start }}

+

End Date: {{ active_period.period_end }}

+

Status: {{ active_period.status.value }}

+
+
+
+
+
+
+
License Details for Period
+
+
+

Max Storage: {{ active_period.max_storage_mb }} MB

+

Included Embedding: {{ active_period.included_embedding_mb }} MB

+

Included Interaction Tokens: {{ active_period.included_interaction_tokens }}

+

Overage Storage Allowed: {{ active_period.additional_storage_allowed }}

+

Overage Embedding Allowed: {{ active_period.additional_embedding_allowed }}

+

Overage Interaction Allowed: {{ active_period.additional_interaction_allowed }}

+
+
+
+
+ +
+
+
+
+
Storage Usage
+
+
+

+ {{ usage_data.storage_percent }}% +

+
+
+
+
+

{{ usage_data.storage_used_rounded or 0.0 }} / {{ active_period.max_storage_mb or 0 }} MB

+
+
+
+ +
+
+
+
Embedding Usage
+
+
+

+ {{ usage_data.embedding_percent }}% +

+
+
+
+
+

{{ usage_data.embedding_used_rounded or 0.0 }} / {{ active_period.included_embedding_mb or 0 }} MB

+
+
+
+ +
+
+
+
Interaction Usage
+
+
+

+ {{ usage_data.interaction_percent }}% +

+
+
+
+
+

{{ usage_data.interaction_used_rounded or 0.0 }} / {{ active_period.included_interaction_tokens or 0 }} M tokens

+
+
+
+
+ +{% else %} +
+ There's no active license period for this tenant, or no usage data has been recorded yet. +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/eveai_app/templates/navbar.html b/eveai_app/templates/navbar.html index 6393024..493224f 100644 --- a/eveai_app/templates/navbar.html +++ b/eveai_app/templates/navbar.html @@ -114,7 +114,7 @@ {'name': 'License Tiers', 'url': '/entitlements/view_license_tiers', 'roles': ['Super User', 'Partner Admin']}, {'name': 'Trigger Actions', 'url': '/administration/trigger_actions', 'roles': ['Super User']}, {'name': 'Licenses', 'url': '/entitlements/view_licenses', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']}, - {'name': 'Usage', 'url': '/entitlements/view_usages', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']}, + {'name': 'Active Usage', 'url': '/entitlements/active_usage', 'roles': ['Super User', 'Tenant Admin', 'Partner Admin']}, ]) }} {% endif %} {% if current_user.is_authenticated %} diff --git a/eveai_app/views/entitlements_views.py b/eveai_app/views/entitlements_views.py index 36ac6d8..41dd7f3 100644 --- a/eveai_app/views/entitlements_views.py +++ b/eveai_app/views/entitlements_views.py @@ -1,5 +1,5 @@ from datetime import datetime as dt, timezone as tz, timedelta -from flask import request, redirect, flash, render_template, Blueprint, session, current_app +from flask import request, redirect, flash, render_template, Blueprint, session, current_app, jsonify from flask_security import roles_accepted, current_user from sqlalchemy.exc import SQLAlchemyError from sqlalchemy import or_, desc @@ -395,4 +395,58 @@ def transition_period_status(license_id, period_id): db.session.rollback() flash(f'Error updating status: {str(e)}', 'danger') - return redirect(prefixed_url_for('entitlements_bp.view_license_periods', license_id=license_id)) \ No newline at end of file + return redirect(prefixed_url_for('entitlements_bp.view_license_periods', license_id=license_id)) + + +@entitlements_bp.route('/active_usage') +@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin') +def active_license_usage(): + # Retrieve the active license period for the current tenant + tenant_id = session.get('tenant', {}).get('id') + if not tenant_id: + flash('No active or pending license period found for this tenant', 'warning') + return redirect(prefixed_url_for('user_bp.select_tenant')) + + active_period = LicensePeriod.query \ + .join(License) \ + .filter( + License.tenant_id == tenant_id, + LicensePeriod.status.in_([PeriodStatus.ACTIVE, PeriodStatus.PENDING]) + ).first() + + if not active_period: + flash('Geen actieve of pending licentieperiode gevonden voor deze tenant', 'warning') + return render_template('entitlements/view_active_license_usage.html', active_period=None) + + # Bereken de percentages voor gebruik + usage_data = {} + if active_period.license_usage: + # Storage percentage + if active_period.max_storage_mb > 0: + storage_used = active_period.license_usage.storage_mb_used or 0.0 + usage_data['storage_used_rounded'] = round(storage_used, 2) + usage_data['storage_percent'] = round(storage_used / active_period.max_storage_mb * 100, 2) + else: + usage_data['storage_percent'] = 0.0 + + # Embedding percentage + if active_period.included_embedding_mb > 0: + embedding_used = active_period.license_usage.embedding_mb_used or 0.0 + usage_data['embedding_used_rounded'] = round(embedding_used, 2) + usage_data['embedding_percent'] = round(embedding_used / active_period.included_embedding_mb * 100, 2) + else: + usage_data['embedding_percent'] = 0.0 + + # Interaction tokens percentage + if active_period.included_interaction_tokens > 0: + interaction_used = active_period.license_usage.interaction_total_tokens_used / 1_000_000 or 0.0 + usage_data['interaction_used_rounded'] = round(interaction_used, 2) + usage_data['interaction_percent'] = ( + round(interaction_used / active_period.included_interaction_tokens * 100, 2)) + else: + usage_data['interaction_percent'] = 0 + + return render_template('entitlements/view_active_license_usage.html', + active_period=active_period, + usage_data=usage_data) + diff --git a/eveai_chat_workers/__init__.py b/eveai_chat_workers/__init__.py index 35ea409..ecf77d3 100644 --- a/eveai_chat_workers/__init__.py +++ b/eveai_chat_workers/__init__.py @@ -3,9 +3,8 @@ import logging.config from flask import Flask import os -from common.langchain.templates.template_manager import TemplateManager from common.utils.celery_utils import make_celery, init_celery -from common.extensions import db, template_manager, cache_manager +from common.extensions import db, cache_manager from config.logging_config import LOGGING from config.config import get_config @@ -44,7 +43,6 @@ def create_app(config_file=None): def register_extensions(app): db.init_app(app) cache_manager.init_app(app) - template_manager.init_app(app) def register_cache_handlers(app): diff --git a/eveai_entitlements/__init__.py b/eveai_entitlements/__init__.py index ad20310..c9c9f32 100644 --- a/eveai_entitlements/__init__.py +++ b/eveai_entitlements/__init__.py @@ -47,6 +47,8 @@ def register_extensions(app): def register_cache_handlers(app): from common.utils.cache.license_cache import register_license_cache_handlers register_license_cache_handlers(cache_manager) + from common.utils.cache.config_cache import register_config_cache_handlers + register_config_cache_handlers(cache_manager) app, celery = create_app() diff --git a/eveai_entitlements/tasks.py b/eveai_entitlements/tasks.py index 42fef20..598bd94 100644 --- a/eveai_entitlements/tasks.py +++ b/eveai_entitlements/tasks.py @@ -28,69 +28,65 @@ def persist_business_events(log_entries): Args: log_entries: List of log event dictionaries to persist """ + event_logs = [] + for entry in log_entries: + event_log = BusinessEventLog( + timestamp=entry.pop('timestamp'), + event_type=entry.pop('event_type'), + tenant_id=entry.pop('tenant_id'), + trace_id=entry.pop('trace_id'), + span_id=entry.pop('span_id', None), + span_name=entry.pop('span_name', None), + parent_span_id=entry.pop('parent_span_id', None), + document_version_id=entry.pop('document_version_id', None), + document_version_file_size=entry.pop('document_version_file_size', None), + specialist_id=entry.pop('specialist_id', None), + specialist_type=entry.pop('specialist_type', None), + specialist_type_version=entry.pop('specialist_type_version', None), + chat_session_id=entry.pop('chat_session_id', None), + interaction_id=entry.pop('interaction_id', None), + environment=entry.pop('environment', None), + llm_metrics_total_tokens=entry.pop('llm_metrics_total_tokens', None), + llm_metrics_prompt_tokens=entry.pop('llm_metrics_prompt_tokens', None), + llm_metrics_completion_tokens=entry.pop('llm_metrics_completion_tokens', None), + llm_metrics_nr_of_pages=entry.pop('llm_metrics_nr_of_pages', None), + llm_metrics_total_time=entry.pop('llm_metrics_total_time', None), + llm_metrics_call_count=entry.pop('llm_metrics_call_count', None), + llm_interaction_type=entry.pop('llm_interaction_type', None), + message=entry.pop('message', None) + ) + db.session.add(event_log) + event_logs.append(event_log) + + # Perform a bulk insert of all entries + db.session.commit() + current_app.logger.info(f"Successfully persisted {len(event_logs)} business event logs") + + tenant_id = event_logs[0].tenant_id try: - event_logs = [] - for entry in log_entries: - event_log = BusinessEventLog( - timestamp=entry.pop('timestamp'), - event_type=entry.pop('event_type'), - tenant_id=entry.pop('tenant_id'), - trace_id=entry.pop('trace_id'), - span_id=entry.pop('span_id', None), - span_name=entry.pop('span_name', None), - parent_span_id=entry.pop('parent_span_id', None), - document_version_id=entry.pop('document_version_id', None), - document_version_file_size=entry.pop('document_version_file_size', None), - specialist_id=entry.pop('specialist_id', None), - specialist_type=entry.pop('specialist_type', None), - specialist_type_version=entry.pop('specialist_type_version', None), - chat_session_id=entry.pop('chat_session_id', None), - interaction_id=entry.pop('interaction_id', None), - environment=entry.pop('environment', None), - llm_metrics_total_tokens=entry.pop('llm_metrics_total_tokens', None), - llm_metrics_prompt_tokens=entry.pop('llm_metrics_prompt_tokens', None), - llm_metrics_completion_tokens=entry.pop('llm_metrics_completion_tokens', None), - llm_metrics_nr_of_pages=entry.pop('llm_metrics_nr_of_pages', None), - llm_metrics_total_time=entry.pop('llm_metrics_total_time', None), - llm_metrics_call_count=entry.pop('llm_metrics_call_count', None), - llm_interaction_type=entry.pop('llm_interaction_type', None), - message=entry.pop('message', None) - ) - event_logs.append(event_log) + license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) + except EveAIException as e: + current_app.logger.error(f"Failed to find license period for tenant {tenant_id}: {str(e)}") + raise e - # Perform a bulk insert of all entries - db.session.bulk_save_objects(event_logs) - db.session.commit() - current_app.logger.info(f"Successfully persisted {len(event_logs)} business event logs") - - tenant_id = event_logs[0].tenant_id + lic_usage = None + if not license_period.license_usage: + lic_usage = LicenseUsage( + tenant_id=tenant_id, + license_period_id=license_period.id, + ) try: - license_period = LicensePeriodServices.find_current_license_period_for_usage(tenant_id) - except EveAIException as e: - current_app.logger.error(f"Failed to find license period for tenant {tenant_id}: {str(e)}") - return - lic_usage = None - if not license_period.license_usage: - lic_usage = LicenseUsage( - tenant_id=tenant_id, - license_period_id=license_period.id, - ) - try: - db.session.add(lic_usage) - db.session.commit() - current_app.logger.info(f"Created new license usage for tenant {tenant_id}") - except SQLAlchemyError as e: - db.session.rollback() - current_app.logger.error(f"Error trying to create license usage for tenant {tenant_id}: {str(e)}") - return - else: - lic_usage = license_period.license_usage + db.session.add(lic_usage) + db.session.commit() + current_app.logger.info(f"Created new license usage for tenant {tenant_id}") + except SQLAlchemyError as e: + db.session.rollback() + current_app.logger.error(f"Error trying to create license usage for tenant {tenant_id}: {str(e)}") + raise e + else: + lic_usage = license_period.license_usage - process_logs_for_license_usage(tenant_id, lic_usage, event_logs) - - except Exception as e: - current_app.logger.error(f"Failed to persist business event logs: {e}") - db.session.rollback() + process_logs_for_license_usage(tenant_id, lic_usage, event_logs) def process_logs_for_license_usage(tenant_id, license_usage, logs): @@ -103,12 +99,16 @@ def process_logs_for_license_usage(tenant_id, license_usage, logs): interaction_completion_tokens_used = 0 interaction_total_tokens_used = 0 + current_app.logger.info(f"Processing {len(logs)} logs for tenant {tenant_id}") + recalculate_storage = False # Process each log for log in logs: # Case for 'Create Embeddings' event + current_app.logger.debug(f"Processing log for tenant {tenant_id}: {log.id} - {log.event_type} - {log.message}") if log.event_type == 'Create Embeddings': + current_app.logger.debug(f"In Create Embeddings") recalculate_storage = True if log.message == 'Starting Trace for Create Embeddings': embedding_mb_used += log.document_version_file_size @@ -138,6 +138,13 @@ def process_logs_for_license_usage(tenant_id, license_usage, logs): # Mark the log as processed by setting the license_usage_id log.license_usage_id = license_usage.id + db.session.add(log) + + current_app.logger.debug(f"Finished processing {len(logs)} logs for tenant {tenant_id}") + current_app.logger.debug(f"Embedding MB Used: {embedding_mb_used}") + current_app.logger.debug(f"Embedding Prompt Tokens Used: {embedding_prompt_tokens_used}") + current_app.logger.debug(f"Embedding Completion Tokens Used: {embedding_completion_tokens_used}") + current_app.logger.debug(f"Embedding Total Tokens Used: {embedding_total_tokens_used}") # Update the LicenseUsage record with the accumulated values license_usage.embedding_mb_used += embedding_mb_used @@ -154,8 +161,6 @@ def process_logs_for_license_usage(tenant_id, license_usage, logs): # Commit the updates to the LicenseUsage and log records try: db.session.add(license_usage) - for log in logs: - db.session.add(log) db.session.commit() except SQLAlchemyError as e: db.session.rollback() diff --git a/eveai_workers/__init__.py b/eveai_workers/__init__.py index 49ced80..8d22e67 100644 --- a/eveai_workers/__init__.py +++ b/eveai_workers/__init__.py @@ -4,7 +4,7 @@ from flask import Flask import os from common.utils.celery_utils import make_celery, init_celery -from common.extensions import db, minio_client, template_manager, cache_manager +from common.extensions import db, minio_client, cache_manager import config.logging_config as logging_config from config.config import get_config @@ -26,6 +26,8 @@ def create_app(config_file=None): register_extensions(app) + register_cache_handlers(app) + from . import processors celery = make_celery(app.name, app.config) @@ -43,7 +45,11 @@ def register_extensions(app): db.init_app(app) minio_client.init_app(app) cache_manager.init_app(app) - template_manager.init_app(app) + + +def register_cache_handlers(app): + from common.utils.cache.config_cache import register_config_cache_handlers + register_config_cache_handlers(cache_manager) app, celery = create_app() diff --git a/eveai_workers/processors/html_processor.py b/eveai_workers/processors/html_processor.py index 4c71fad..711cca6 100644 --- a/eveai_workers/processors/html_processor.py +++ b/eveai_workers/processors/html_processor.py @@ -3,7 +3,7 @@ from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from common.extensions import db, minio_client -from common.utils.model_utils import create_language_template, get_embedding_llm +from common.utils.model_utils import create_language_template, get_embedding_llm, get_template from .base_processor import BaseProcessor from common.utils.business_event_context import current_event from .processor_registry import ProcessorRegistry @@ -81,8 +81,7 @@ class HTMLProcessor(BaseProcessor): def _generate_markdown_from_html(self, html_content): self._log(f'Generating markdown from HTML for tenant {self.tenant.id}') - llm = get_embedding_llm() - template = self.model_variables.get_template("html_parse") + template, llm = get_template("html_parse") parse_prompt = ChatPromptTemplate.from_template(template) setup = RunnablePassthrough() output_parser = StrOutputParser() diff --git a/eveai_workers/processors/pdf_processor.py b/eveai_workers/processors/pdf_processor.py index 00d22ed..328497e 100644 --- a/eveai_workers/processors/pdf_processor.py +++ b/eveai_workers/processors/pdf_processor.py @@ -9,7 +9,7 @@ from langchain_core.runnables import RunnablePassthrough from common.eveai_model.tracked_mistral_ocr_client import TrackedMistralOcrClient from common.extensions import minio_client -from common.utils.model_utils import create_language_template, get_embedding_llm +from common.utils.model_utils import create_language_template, get_embedding_llm, get_template from .base_processor import BaseProcessor from common.utils.business_event_context import current_event from .processor_registry import ProcessorRegistry @@ -208,8 +208,7 @@ class PDFProcessor(BaseProcessor): return text_splitter.split_text(content) def _process_chunks_with_llm(self, chunks): - llm = get_embedding_llm() - template = self.model_variables.get_template('pdf_parse') + template, llm = get_template('pdf_parse') pdf_prompt = ChatPromptTemplate.from_template(template) setup = RunnablePassthrough() output_parser = StrOutputParser() diff --git a/eveai_workers/processors/transcription_processor.py b/eveai_workers/processors/transcription_processor.py index 5f8d461..7ba494e 100644 --- a/eveai_workers/processors/transcription_processor.py +++ b/eveai_workers/processors/transcription_processor.py @@ -4,7 +4,7 @@ from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough -from common.utils.model_utils import create_language_template, get_embedding_llm +from common.utils.model_utils import create_language_template, get_embedding_llm, get_template from .base_processor import BaseProcessor from common.utils.business_event_context import current_event @@ -46,8 +46,7 @@ class TranscriptionBaseProcessor(BaseProcessor): def _process_chunks(self, chunks): self.log_tuning("_process_chunks", {"Nr of Chunks": len(chunks)}) - llm = get_embedding_llm() - template = self.model_variables.get_template('transcript') + template, llm = get_template('transcript') language_template = create_language_template(template, self.document_version.language) transcript_prompt = ChatPromptTemplate.from_template(language_template) setup = RunnablePassthrough() diff --git a/eveai_workers/tasks.py b/eveai_workers/tasks.py index d382b22..dd35b82 100644 --- a/eveai_workers/tasks.py +++ b/eveai_workers/tasks.py @@ -12,13 +12,13 @@ from langchain_core.runnables import RunnablePassthrough from sqlalchemy import or_ from sqlalchemy.exc import SQLAlchemyError -from common.extensions import db +from common.extensions import db, cache_manager from common.models.document import DocumentVersion, Embedding, Document, Processor, Catalog from common.models.user import Tenant from common.utils.celery_utils import current_celery from common.utils.database import Database from common.utils.model_utils import create_language_template, get_model_variables, get_embedding_model_and_class, \ - get_embedding_llm + get_embedding_llm, get_template from common.utils.business_event import BusinessEvent from common.utils.business_event_context import current_event @@ -211,8 +211,8 @@ def enrich_chunks(tenant, model_variables, document_version, title, chunks): def summarize_chunk(tenant, model_variables, document_version, chunk): current_event.log("Starting Summarizing Chunk") - llm = get_embedding_llm() - template = model_variables.get_template("summary") + + template, llm = get_template("summary") language_template = create_language_template(template, document_version.language) summary_prompt = ChatPromptTemplate.from_template(language_template) setup = RunnablePassthrough() diff --git a/migrations/public/versions/4eae969dcac2_license_copied_fields_in_licenseperiod_.py b/migrations/public/versions/4eae969dcac2_license_copied_fields_in_licenseperiod_.py new file mode 100644 index 0000000..b9d7743 --- /dev/null +++ b/migrations/public/versions/4eae969dcac2_license_copied_fields_in_licenseperiod_.py @@ -0,0 +1,71 @@ +"""license-copied fields in LicensePeriod should be Nullable + +Revision ID: 4eae969dcac2 +Revises: c08c3e7c3b1a +Create Date: 2025-05-18 20:13:11.555330 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4eae969dcac2' +down_revision = 'c08c3e7c3b1a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('license_period', schema=None) as batch_op: + batch_op.alter_column('currency', + existing_type=sa.VARCHAR(length=20), + nullable=True) + batch_op.alter_column('basic_fee', + existing_type=sa.DOUBLE_PRECISION(precision=53), + nullable=True) + batch_op.alter_column('max_storage_mb', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('additional_storage_price', + existing_type=sa.DOUBLE_PRECISION(precision=53), + nullable=True) + batch_op.alter_column('additional_storage_bucket', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('included_embedding_mb', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('additional_embedding_price', + existing_type=sa.NUMERIC(precision=10, scale=4), + nullable=True) + batch_op.alter_column('additional_embedding_bucket', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('included_interaction_tokens', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('additional_interaction_token_price', + existing_type=sa.NUMERIC(precision=10, scale=4), + nullable=True) + batch_op.alter_column('additional_interaction_bucket', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('additional_storage_allowed', + existing_type=sa.BOOLEAN(), + nullable=True) + batch_op.alter_column('additional_embedding_allowed', + existing_type=sa.BOOLEAN(), + nullable=True) + batch_op.alter_column('additional_interaction_allowed', + existing_type=sa.BOOLEAN(), + nullable=True) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/migrations/public/versions/c08c3e7c3b1a_add_end_date_calculated_field_to_license.py b/migrations/public/versions/c08c3e7c3b1a_add_end_date_calculated_field_to_license.py new file mode 100644 index 0000000..34537d6 --- /dev/null +++ b/migrations/public/versions/c08c3e7c3b1a_add_end_date_calculated_field_to_license.py @@ -0,0 +1,30 @@ +"""Add End Date (calculated field) to License + +Revision ID: c08c3e7c3b1a +Revises: 845d0428c5fe +Create Date: 2025-05-18 19:55:32.702250 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c08c3e7c3b1a' +down_revision = '845d0428c5fe' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('license', schema=None) as batch_op: + batch_op.add_column(sa.Column('end_date', sa.Date(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ###