diff --git a/eveai_app/views/dynamic_form_base.py b/eveai_app/views/dynamic_form_base.py
index 404ef52..7d41b31 100644
--- a/eveai_app/views/dynamic_form_base.py
+++ b/eveai_app/views/dynamic_form_base.py
@@ -3,12 +3,14 @@ from datetime import date
from flask_wtf import FlaskForm
from wtforms import (IntegerField, FloatField, BooleanField, StringField, TextAreaField, FileField,
validators, ValidationError)
-from flask import current_app, request
+from flask import current_app, request, session
import json
from wtforms.fields.choices import SelectField
from wtforms.fields.datetime import DateField
from wtforms.fields.simple import ColorField
+
+from common.models.user import TenantMake
from common.utils.config_field_types import TaggingFields, json_to_patterns, patterns_to_json
@@ -300,6 +302,22 @@ class DynamicFormBase(FlaskForm):
except Exception as e:
raise ValidationError(f"Invalid ordered list: {str(e)}")
+ def _get_system_field(self, system_name):
+ """Get the field class and kwargs for a system field. Add system field cases as you need them."""
+ field_class = None
+ extra_classes = ''
+ field_kwargs = {}
+ match system_name:
+ case 'tenant_make':
+ field_class = SelectField
+ tenant_id = session.get('tenant').get('id')
+ makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
+ choices = [(make.name, make.name) for make in makes]
+ extra_classes = ''
+ field_kwargs = {'choices': choices}
+
+ return field_class, extra_classes, field_kwargs
+
def add_dynamic_fields(self, collection_name, config, initial_data=None):
"""Add dynamic fields to the form based on the configuration.
@@ -357,11 +375,12 @@ class DynamicFormBase(FlaskForm):
extra_classes = ['monospace-text', 'pattern-input']
field_kwargs = {}
elif field_type == 'ordered_list':
- current_app.logger.debug(f"Adding ordered list field for {full_field_name}")
field_class = OrderedListField
extra_classes = ''
list_type = field_def.get('list_type', '')
field_kwargs = {'list_type': list_type}
+ elif field_type == 'system':
+ field_class, extra_classes, field_kwargs = self._get_system_field(field_def.get('system_name', ''))
else:
extra_classes = ''
field_class = {
diff --git a/eveai_app/views/interaction_forms.py b/eveai_app/views/interaction_forms.py
index 8186b0b..9c0320d 100644
--- a/eveai_app/views/interaction_forms.py
+++ b/eveai_app/views/interaction_forms.py
@@ -24,6 +24,7 @@ def get_tools():
class SpecialistForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
+ description = TextAreaField('Description', validators=[Optional()])
retrievers = QuerySelectMultipleField(
'Retrievers',
@@ -34,7 +35,7 @@ class SpecialistForm(FlaskForm):
)
type = SelectField('Specialist Type', validators=[DataRequired()])
-
+ active = BooleanField('Active', validators=[Optional()], default=True)
tuning = BooleanField('Enable Specialist Tuning', default=False)
def __init__(self, *args, **kwargs):
@@ -47,6 +48,7 @@ class SpecialistForm(FlaskForm):
class EditSpecialistForm(DynamicFormBase):
name = StringField('Name', validators=[DataRequired()])
description = TextAreaField('Description', validators=[Optional()])
+ active = BooleanField('Active', validators=[Optional()], default=True)
retrievers = QuerySelectMultipleField(
'Retrievers',
diff --git a/eveai_app/views/interaction_views.py b/eveai_app/views/interaction_views.py
index dd800e4..97507b4 100644
--- a/eveai_app/views/interaction_views.py
+++ b/eveai_app/views/interaction_views.py
@@ -162,6 +162,7 @@ def specialist():
new_specialist.type = form.type.data
new_specialist.type_version = cache_manager.specialists_version_tree_cache.get_latest_version(
new_specialist.type)
+ new_specialist.active = form.active.data
new_specialist.tuning = form.tuning.data
set_logging_information(new_specialist, dt.now(tz.utc))
@@ -231,6 +232,7 @@ def edit_specialist(specialist_id):
specialist.name = form.name.data
specialist.description = form.description.data
specialist.tuning = form.tuning.data
+ specialist.active = form.active.data
# Update the configuration dynamic fields
specialist.configuration = form.get_dynamic_data("configuration")
@@ -297,7 +299,7 @@ def specialists():
# prepare table data
rows = prepare_table_for_macro(the_specialists,
- [('id', ''), ('name', ''), ('type', '')])
+ [('id', ''), ('name', ''), ('type', ''), ('type_version', ''), ('active', ''),])
# Render the catalogs in a template
return render_template('interaction/specialists.html', rows=rows, pagination=pagination)
diff --git a/eveai_app/views/user_forms.py b/eveai_app/views/user_forms.py
index d7b5548..bdcb234 100644
--- a/eveai_app/views/user_forms.py
+++ b/eveai_app/views/user_forms.py
@@ -2,10 +2,12 @@ from flask import current_app, session
from flask_wtf import FlaskForm
from wtforms import (StringField, BooleanField, SubmitField, EmailField, IntegerField, DateField,
SelectField, SelectMultipleField, FieldList, FormField, TextAreaField)
-from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional
+from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, ValidationError
import pytz
from flask_security import current_user
+from wtforms.widgets.core import HiddenInput
+from common.models.user import TenantMake
from common.services.user import UserServices
from config.type_defs.service_types import SERVICE_TYPES
from eveai_app.views.dynamic_form_base import DynamicFormBase
@@ -132,8 +134,19 @@ class EditTenantProjectForm(FlaskForm):
self.services.choices = [(key, value['description']) for key, value in SERVICE_TYPES.items()]
+def validate_make_name(form, field):
+ # Controleer of een TenantMake met deze naam al bestaat
+ existing_make = TenantMake.query.filter_by(name=field.data).first()
+
+ # Als er een bestaande make is gevonden en we zijn niet in edit mode,
+ # of als we wel in edit mode zijn maar het is een ander record (andere id)
+ if existing_make and (not hasattr(form, 'id') or form.id.data != existing_make.id):
+ raise ValidationError(f'A Make with name "{field.data}" already exists. Choose another name.')
+
+
class TenantMakeForm(DynamicFormBase):
- name = StringField('Name', validators=[DataRequired(), Length(max=50)])
+ id = IntegerField('ID', widget=HiddenInput())
+ name = StringField('Name', validators=[DataRequired(), Length(max=50), validate_make_name])
description = TextAreaField('Description', validators=[Optional()])
active = BooleanField('Active', validators=[Optional()], default=True)
website = StringField('Website', validators=[DataRequired(), Length(max=255)])
diff --git a/eveai_app/views/user_views.py b/eveai_app/views/user_views.py
index 8192d2b..3618659 100644
--- a/eveai_app/views/user_views.py
+++ b/eveai_app/views/user_views.py
@@ -1,3 +1,4 @@
+import json
import uuid
from datetime import datetime as dt, timezone as tz
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
@@ -627,15 +628,17 @@ def delete_tenant_project(tenant_project_id):
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def tenant_make():
form = TenantMakeForm()
+ customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
+ default_customisation_options = create_default_config_from_type_config(customisation_config["configuration"])
+ form.add_dynamic_fields("configuration", customisation_config, default_customisation_options)
+
if form.validate_on_submit():
tenant_id = session['tenant']['id']
new_tenant_make = TenantMake()
form.populate_obj(new_tenant_make)
new_tenant_make.tenant_id = tenant_id
- customisation_config = cache_manager.customisations_config_cache.get_config("CHAT_CLIENT_CUSTOMISATION")
- new_tenant_make.chat_customisation_options = create_default_config_from_type_config(
- customisation_config["configuration"])
- form.add_dynamic_fields("configuration", customisation_config, new_tenant_make.chat_customisation_options)
+ customisation_options = form.get_dynamic_data("configuration")
+ new_tenant_make.chat_customisation_options = json.dumps(customisation_options)
set_logging_information(new_tenant_make, dt.now(tz.utc))
try:
diff --git a/eveai_chat_workers/specialists/traicie/TRAICIE_ROLE_DEFINITION_SPECIALIST/1_3.py b/eveai_chat_workers/specialists/traicie/TRAICIE_ROLE_DEFINITION_SPECIALIST/1_3.py
new file mode 100644
index 0000000..5b14b96
--- /dev/null
+++ b/eveai_chat_workers/specialists/traicie/TRAICIE_ROLE_DEFINITION_SPECIALIST/1_3.py
@@ -0,0 +1,198 @@
+import asyncio
+import json
+from os import wait
+from typing import Optional, List
+
+from crewai.flow.flow import start, listen, and_
+from flask import current_app
+from pydantic import BaseModel, Field
+from sqlalchemy.exc import SQLAlchemyError
+
+from common.extensions import db
+from common.models.user import Tenant
+from common.models.interaction import Specialist
+from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
+from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
+from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
+from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
+from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
+from common.services.interaction.specialist_services import SpecialistServices
+
+
+class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
+ """
+ type: TRAICIE_ROLE_DEFINITION_SPECIALIST
+ type_version: 1.0
+ Traicie Role Definition Specialist Executor class
+ """
+
+ def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
+ self.role_definition_crew = None
+
+ super().__init__(tenant_id, specialist_id, session_id, task_id)
+
+ # Load the Tenant & set language
+ self.tenant = Tenant.query.get_or_404(tenant_id)
+
+ @property
+ def type(self) -> str:
+ return "TRAICIE_ROLE_DEFINITION_SPECIALIST"
+
+ @property
+ def type_version(self) -> str:
+ return "1.3"
+
+ def _config_task_agents(self):
+ self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")
+
+ def _config_pydantic_outputs(self):
+ self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")
+
+ def _instantiate_specialist(self):
+ verbose = self.tuning
+
+ role_definition_agents = [self.traicie_hr_bp_agent]
+ role_definition_tasks = [self.traicie_get_competencies_task]
+ self.role_definition_crew = EveAICrewAICrew(
+ self,
+ "Role Definition Crew",
+ agents=role_definition_agents,
+ tasks=role_definition_tasks,
+ verbose=verbose,
+ )
+
+ self.flow = RoleDefinitionFlow(
+ self,
+ self.role_definition_crew
+ )
+
+ def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
+ self.log_tuning("Traicie Role Definition Specialist execution started", {})
+
+ flow_inputs = {
+ "vacancy_text": arguments.vacancy_text,
+ "role_name": arguments.role_name,
+ 'role_reference': arguments.role_reference,
+ }
+
+ flow_results = self.flow.kickoff(inputs=flow_inputs)
+
+ flow_state = self.flow.state
+
+ results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
+ if flow_state.competencies:
+ results.competencies = flow_state.competencies
+
+ self.create_selection_specialist(arguments, flow_state.competencies)
+
+ self.log_tuning(f"Traicie Role Definition Specialist execution ended", {"Results": results.model_dump()})
+
+ return results
+
+ def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
+ """This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
+ current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
+ selection_comptencies = []
+ for competency in competencies:
+ selection_competency = {
+ "title": competency.title,
+ "description": competency.description,
+ "assess": True,
+ "is_knockout": False,
+ }
+ selection_comptencies.append(selection_competency)
+
+ selection_config = {
+ "name": arguments.specialist_name,
+ "competencies": selection_comptencies,
+ "tone_of_voice": "Professional & Neutral",
+ "language_level": "Standard",
+ "role_reference": arguments.role_reference,
+ "make": arguments.make,
+ }
+ name = arguments.role_name
+ if len(name) > 50:
+ name = name[:47] + "..."
+
+ new_specialist = Specialist(
+ name=name,
+ description=f"Specialist for {arguments.role_name} role",
+ type="TRAICIE_SELECTION_SPECIALIST",
+ type_version="1.1",
+ tuning=False,
+ configuration=selection_config,
+ )
+ try:
+ db.session.add(new_specialist)
+ db.session.commit()
+ except SQLAlchemyError as e:
+ db.session.rollback()
+ current_app.logger.error(f"Error creating selection specialist: {str(e)}")
+ raise e
+
+ SpecialistServices.initialize_specialist(new_specialist.id, "TRAICIE_SELECTION_SPECIALIST", "1.0")
+
+
+
+
+class RoleDefinitionSpecialistInput(BaseModel):
+ role_name: str = Field(..., alias="role_name")
+ role_reference: Optional[str] = Field(..., alias="role_reference")
+ vacancy_text: Optional[str] = Field(None, alias="vacancy_text")
+
+
+class RoleDefinitionSpecialistResult(SpecialistResult):
+ competencies: Optional[List[ListItem]] = None
+
+
+class RoleDefFlowState(EveAIFlowState):
+ """Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
+ input: Optional[RoleDefinitionSpecialistInput] = None
+ competencies: Optional[List[ListItem]] = None
+
+
+class RoleDefinitionFlow(EveAICrewAIFlow[RoleDefFlowState]):
+ def __init__(self,
+ specialist_executor: CrewAIBaseSpecialistExecutor,
+ role_definitiion_crew: EveAICrewAICrew,
+ **kwargs):
+ super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
+ self.specialist_executor = specialist_executor
+ self.role_definition_crew = role_definitiion_crew
+ self.exception_raised = False
+
+ @start()
+ def process_inputs(self):
+ return ""
+
+ @listen(process_inputs)
+ async def execute_role_definition (self):
+ inputs = self.state.input.model_dump()
+ try:
+ current_app.logger.debug("In execute_role_definition")
+ crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
+ # Unfortunately, crew_output will only contain the output of the latest task.
+ # As we will only take into account the flow state, we need to ensure both competencies and criteria
+ # are copies to the flow state.
+ update = {}
+ for task in self.role_definition_crew.tasks:
+ current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
+ if task.name == "traicie_get_competencies_task":
+ # update["competencies"] = task.output.pydantic.competencies
+ self.state.competencies = task.output.pydantic.competencies
+ # crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
+ current_app.logger.debug(f"State after execute_role_definition: {self.state}")
+ current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
+ return crew_output
+ except Exception as e:
+ current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
+ self.exception_raised = True
+ raise e
+
+ async def kickoff_async(self, inputs=None):
+ current_app.logger.debug(f"Async kickoff {self.name}")
+ current_app.logger.debug(f"Inputs: {inputs}")
+ self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
+ current_app.logger.debug(f"State: {self.state}")
+ result = await super().kickoff_async(inputs)
+ return self.state
diff --git a/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_1.py b/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_1.py
new file mode 100644
index 0000000..b7b8076
--- /dev/null
+++ b/eveai_chat_workers/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1_1.py
@@ -0,0 +1,197 @@
+import asyncio
+import json
+from os import wait
+from typing import Optional, List
+
+from crewai.flow.flow import start, listen, and_
+from flask import current_app
+from pydantic import BaseModel, Field
+from sqlalchemy.exc import SQLAlchemyError
+
+from common.extensions import db
+from common.models.user import Tenant
+from common.models.interaction import Specialist
+from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
+from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
+from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
+from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
+from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
+from common.services.interaction.specialist_services import SpecialistServices
+
+
+class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
+ """
+ type: TRAICIE_SELECTION_SPECIALIST
+ type_version: 1.0
+ Traicie Selection Specialist Executor class
+ """
+
+ def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
+ self.role_definition_crew = None
+
+ super().__init__(tenant_id, specialist_id, session_id, task_id)
+
+ # Load the Tenant & set language
+ self.tenant = Tenant.query.get_or_404(tenant_id)
+
+ @property
+ def type(self) -> str:
+ return "TRAICIE_SELECTION_SPECIALIST"
+
+ @property
+ def type_version(self) -> str:
+ return "1.0"
+
+ def _config_task_agents(self):
+ self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")
+
+ def _config_pydantic_outputs(self):
+ self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")
+
+ def _instantiate_specialist(self):
+ verbose = self.tuning
+
+ role_definition_agents = [self.traicie_hr_bp_agent]
+ role_definition_tasks = [self.traicie_get_competencies_task]
+ self.role_definition_crew = EveAICrewAICrew(
+ self,
+ "Role Definition Crew",
+ agents=role_definition_agents,
+ tasks=role_definition_tasks,
+ verbose=verbose,
+ )
+
+ self.flow = RoleDefinitionFlow(
+ self,
+ self.role_definition_crew
+ )
+
+ def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
+ self.log_tuning("Traicie Role Definition Specialist execution started", {})
+
+ flow_inputs = {
+ "vacancy_text": arguments.vacancy_text,
+ "role_name": arguments.role_name,
+ 'role_reference': arguments.role_reference,
+ }
+
+ flow_results = self.flow.kickoff(inputs=flow_inputs)
+
+ flow_state = self.flow.state
+
+ results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
+ if flow_state.competencies:
+ results.competencies = flow_state.competencies
+
+ self.create_selection_specialist(arguments, flow_state.competencies)
+
+ self.log_tuning(f"Traicie Role Definition Specialist execution ended", {"Results": results.model_dump()})
+
+ return results
+
+ def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
+ """This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
+ current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
+ selection_comptencies = []
+ for competency in competencies:
+ selection_competency = {
+ "title": competency.title,
+ "description": competency.description,
+ "assess": True,
+ "is_knockout": False,
+ }
+ selection_comptencies.append(selection_competency)
+
+ selection_config = {
+ "name": arguments.specialist_name,
+ "competencies": selection_comptencies,
+ "tone_of_voice": "Professional & Neutral",
+ "language_level": "Standard",
+ "role_reference": arguments.role_reference,
+ }
+ name = arguments.role_name
+ if len(name) > 50:
+ name = name[:47] + "..."
+
+ new_specialist = Specialist(
+ name=name,
+ description=f"Specialist for {arguments.role_name} role",
+ type="TRAICIE_SELECTION_SPECIALIST",
+ type_version="1.0",
+ tuning=False,
+ configuration=selection_config,
+ )
+ try:
+ db.session.add(new_specialist)
+ db.session.commit()
+ except SQLAlchemyError as e:
+ db.session.rollback()
+ current_app.logger.error(f"Error creating selection specialist: {str(e)}")
+ raise e
+
+ SpecialistServices.initialize_specialist(new_specialist.id, "TRAICIE_SELECTION_SPECIALIST", "1.0")
+
+
+
+
+class RoleDefinitionSpecialistInput(BaseModel):
+ role_name: str = Field(..., alias="role_name")
+ role_reference: Optional[str] = Field(..., alias="role_reference")
+ vacancy_text: Optional[str] = Field(None, alias="vacancy_text")
+
+
+class RoleDefinitionSpecialistResult(SpecialistResult):
+ competencies: Optional[List[ListItem]] = None
+
+
+class RoleDefFlowState(EveAIFlowState):
+ """Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
+ input: Optional[RoleDefinitionSpecialistInput] = None
+ competencies: Optional[List[ListItem]] = None
+
+
+class RoleDefinitionFlow(EveAICrewAIFlow[RoleDefFlowState]):
+ def __init__(self,
+ specialist_executor: CrewAIBaseSpecialistExecutor,
+ role_definitiion_crew: EveAICrewAICrew,
+ **kwargs):
+ super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
+ self.specialist_executor = specialist_executor
+ self.role_definition_crew = role_definitiion_crew
+ self.exception_raised = False
+
+ @start()
+ def process_inputs(self):
+ return ""
+
+ @listen(process_inputs)
+ async def execute_role_definition (self):
+ inputs = self.state.input.model_dump()
+ try:
+ current_app.logger.debug("In execute_role_definition")
+ crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
+ # Unfortunately, crew_output will only contain the output of the latest task.
+ # As we will only take into account the flow state, we need to ensure both competencies and criteria
+ # are copies to the flow state.
+ update = {}
+ for task in self.role_definition_crew.tasks:
+ current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
+ if task.name == "traicie_get_competencies_task":
+ # update["competencies"] = task.output.pydantic.competencies
+ self.state.competencies = task.output.pydantic.competencies
+ # crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
+ current_app.logger.debug(f"State after execute_role_definition: {self.state}")
+ current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
+ return crew_output
+ except Exception as e:
+ current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
+ self.exception_raised = True
+ raise e
+
+ async def kickoff_async(self, inputs=None):
+ current_app.logger.debug(f"Async kickoff {self.name}")
+ current_app.logger.debug(f"Inputs: {inputs}")
+ self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
+ current_app.logger.debug(f"State: {self.state}")
+ result = await super().kickoff_async(inputs)
+ return self.state
diff --git a/migrations/public/versions/f40d16a0965a_make_tenantmake_name_unique.py b/migrations/public/versions/f40d16a0965a_make_tenantmake_name_unique.py
new file mode 100644
index 0000000..007352a
--- /dev/null
+++ b/migrations/public/versions/f40d16a0965a_make_tenantmake_name_unique.py
@@ -0,0 +1,31 @@
+"""Make TenantMake name unique
+
+Revision ID: f40d16a0965a
+Revises: 200bda7f5251
+Create Date: 2025-06-09 06:15:56.791634
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'f40d16a0965a'
+down_revision = '200bda7f5251'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('tenant_make', schema=None) as batch_op:
+ batch_op.create_unique_constraint(None, ['name'])
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('tenant_make', schema=None) as batch_op:
+ batch_op.drop_constraint(None, type_='unique')
+
+ # ### end Alembic commands ###
diff --git a/migrations/tenant/versions/a179785e5362_add_active_field_to_specialist.py b/migrations/tenant/versions/a179785e5362_add_active_field_to_specialist.py
new file mode 100644
index 0000000..141b634
--- /dev/null
+++ b/migrations/tenant/versions/a179785e5362_add_active_field_to_specialist.py
@@ -0,0 +1,29 @@
+"""Add active field to Specialist
+
+Revision ID: a179785e5362
+Revises: c71facc0ce7e
+Create Date: 2025-06-09 08:30:18.532600
+
+"""
+from alembic import op
+import sqlalchemy as sa
+import pgvector
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = 'a179785e5362'
+down_revision = 'c71facc0ce7e'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('specialist', sa.Column('active', sa.Boolean(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('specialist', 'active')
+ # ### end Alembic commands ###