11"""Template packs controller -- listing and live application."""
22
33import json
4- from typing import Any , Literal
4+ from typing import TYPE_CHECKING , Any , Literal
55
66from litestar import Controller , get , post
77from litestar .datastructures import State # noqa: TC002
2828from synthorg .templates .errors import TemplateNotFoundError
2929from synthorg .templates .pack_loader import PackInfo , list_packs , load_pack
3030
31+ if TYPE_CHECKING :
32+ from collections .abc import Sequence
33+
34+ from synthorg .templates .schema import TemplateDepartmentConfig
35+
3136logger = get_logger (__name__ )
3237
3338
@@ -98,6 +103,48 @@ async def _read_setting_list(
98103 return []
99104
100105
106+ def _deduplicate_departments (
107+ pack_name : str ,
108+ pack_depts : Sequence [TemplateDepartmentConfig ],
109+ current_depts : list [dict [str , Any ]],
110+ ) -> list [dict [str , Any ]]:
111+ """Return pack departments that don't conflict with existing ones."""
112+ existing_names = {str (d .get ("name" , "" )).lower () for d in current_depts }
113+ if not pack_depts :
114+ return []
115+ raw : list [dict [str , Any ]] = json .loads (
116+ departments_to_json (pack_depts ),
117+ )
118+ new_depts = [d for d in raw if str (d .get ("name" , "" )).lower () not in existing_names ]
119+ if len (new_depts ) < len (raw ):
120+ logger .warning (
121+ TEMPLATE_PACK_APPLY_ERROR ,
122+ pack_name = pack_name ,
123+ action = "departments_skipped" ,
124+ skipped = len (raw ) - len (new_depts ),
125+ )
126+ return new_depts
127+
128+
129+ async def _persist_merged_config (
130+ app_state : AppState ,
131+ agents : list [dict [str , Any ]],
132+ departments : list [dict [str , Any ]],
133+ ) -> None :
134+ """Write merged agents and departments to settings."""
135+ settings_svc = app_state .settings_service
136+ await settings_svc .set (
137+ "company" ,
138+ "agents" ,
139+ json .dumps (agents ),
140+ )
141+ await settings_svc .set (
142+ "company" ,
143+ "departments" ,
144+ json .dumps (departments ),
145+ )
146+
147+
101148# ---- Controller -----------------------------------------------------------
102149
103150
@@ -134,9 +181,6 @@ async def apply_template_pack(
134181 ) -> ApiResponse [ApplyTemplatePackResponse ]:
135182 """Apply a template pack to the running organization.
136183
137- Loads the pack, expands its agents and departments, and
138- appends them to the current company configuration.
139-
140184 Args:
141185 data: Pack name and optional variables.
142186 state: Application state.
@@ -150,7 +194,6 @@ async def apply_template_pack(
150194 pack_name = data .pack_name ,
151195 )
152196
153- # Load and validate the pack.
154197 try :
155198 loaded = load_pack (data .pack_name )
156199 except TemplateNotFoundError as exc :
@@ -162,52 +205,20 @@ async def apply_template_pack(
162205 msg = f"Template pack { data .pack_name !r} not found"
163206 raise NotFoundError (msg ) from exc
164207
165- # Expand pack agents into persistable dicts.
166208 pack_agents = expand_template_agents (loaded .template )
167-
168- # Read current state.
169209 current_agents = await _read_setting_list (app_state , "agents" )
170- current_depts = await _read_setting_list (
171- app_state ,
172- "departments" ,
173- )
210+ current_depts = await _read_setting_list (app_state , "departments" )
174211
175- # Merge departments (skip existing by name).
176- existing_dept_names = {str (d .get ("name" , "" )).lower () for d in current_depts }
177- if loaded .template .departments :
178- new_depts_raw : list [dict [str , Any ]] = json .loads (
179- departments_to_json (loaded .template .departments ),
180- )
181- else :
182- new_depts_raw = []
183- new_depts = [
184- d
185- for d in new_depts_raw
186- if str (d .get ("name" , "" )).lower () not in existing_dept_names
187- ]
188- if len (new_depts ) < len (new_depts_raw ):
189- skipped = len (new_depts_raw ) - len (new_depts )
190- logger .warning (
191- TEMPLATE_PACK_APPLY_ERROR ,
192- pack_name = data .pack_name ,
193- action = "departments_skipped" ,
194- skipped = skipped ,
195- )
196-
197- # Persist.
198- merged_agents = current_agents + pack_agents
199- merged_depts = current_depts + new_depts
200-
201- settings_svc = app_state .settings_service
202- await settings_svc .set (
203- "company" ,
204- "agents" ,
205- json .dumps (merged_agents ),
212+ new_depts = _deduplicate_departments (
213+ data .pack_name ,
214+ loaded .template .departments ,
215+ current_depts ,
206216 )
207- await settings_svc .set (
208- "company" ,
209- "departments" ,
210- json .dumps (merged_depts ),
217+
218+ await _persist_merged_config (
219+ app_state ,
220+ current_agents + pack_agents ,
221+ current_depts + new_depts ,
211222 )
212223
213224 logger .info (
0 commit comments