From 1917d76304bcffd8e03b17903b7c4e68482d98b8 Mon Sep 17 00:00:00 2001 From: hbyang Date: Tue, 7 Jul 2026 15:23:23 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor(db):=20postgres-init=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=ED=8C=8C=EC=9D=BC=EC=9D=84=20init-data/?= =?UTF-8?q?=20=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 00-init.sql → init-data/init.sql (스키마 DDL), temp-data.sql → init-data/init-data.sql (로컬 시드). Co-Authored-By: Claude Fable 5 --- postgres-init/{temp-data.sql => init-data/init-data.sql} | 0 postgres-init/{00-init.sql => init-data/init.sql} | 9 --------- 2 files changed, 9 deletions(-) rename postgres-init/{temp-data.sql => init-data/init-data.sql} (100%) rename postgres-init/{00-init.sql => init-data/init.sql} (98%) diff --git a/postgres-init/temp-data.sql b/postgres-init/init-data/init-data.sql similarity index 100% rename from postgres-init/temp-data.sql rename to postgres-init/init-data/init-data.sql diff --git a/postgres-init/00-init.sql b/postgres-init/init-data/init.sql similarity index 98% rename from postgres-init/00-init.sql rename to postgres-init/init-data/init.sql index 1cee41b..1b130cc 100644 --- a/postgres-init/00-init.sql +++ b/postgres-init/init-data/init.sql @@ -657,12 +657,3 @@ ORDER BY company_id, supplier_type, price_range_index, adjustment_id DESC; -- 새 스키마 변경 시 위 테이블 정의와 이 섹션을 동시에 갱신한다 (구 04-alter*.sql 의 역할). -- 기준선: 2026-07-07 main 스키마. 그보다 오래된 DB 는 git 이력의 04-alter*.sql 을 먼저 적용. --- [2026-07-07] 협상 카드: script 길이 제한 해제(TEXT) + 톤·전략 분류 컬럼 -ALTER TABLE card.nego_cards ALTER COLUMN script TYPE TEXT; -ALTER TABLE card.wild_cards ALTER COLUMN script TYPE TEXT; -ALTER TABLE card.nego_cards - ADD COLUMN IF NOT EXISTS tone SMALLINT NULL, -- 카드 톤(CardTone): 1=강경, 2=정중, 3=우호, 4=중립, 5=단호 - ADD COLUMN IF NOT EXISTS strategy_type SMALLINT NULL; -- 전략 유형(CardStrategyType): 1=경쟁, 2=수용, 3=고수, 4=협력, 5=선점, 6=종결 -ALTER TABLE card.wild_cards - ADD COLUMN IF NOT EXISTS tone SMALLINT NULL, -- 카드 톤(CardTone): 1=강경, 2=정중, 3=우호, 4=중립, 5=단호 - ADD COLUMN IF NOT EXISTS strategy_type SMALLINT NULL; -- 전략 유형(CardStrategyType): 1=경쟁, 2=수용, 3=고수, 4=협력, 5=선점, 6=종결 From 8c700b1e080a29a02375664e856bc6e5b757c7c3 Mon Sep 17 00:00:00 2001 From: hbyang Date: Tue, 7 Jul 2026 15:53:48 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[fix]=20agent:=20format=5Fscript=20?= =?UTF-8?q?=EC=83=89=20=EB=A7=88=EC=BB=A4=20{{=EA=B0=95=EC=A1=B0|..}}=20?= =?UTF-8?q?=EB=B3=B4=EC=A1=B4=20=E2=80=94=20format=5Fmap=20=E2=86=92=20?= =?UTF-8?q?=EC=A0=95=EA=B7=9C=EC=8B=9D=20=EC=B9=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit str.format_map 이 {{·}} 를 이스케이프로 해석해 색 마커 {{강조|텍스트}} 를 {강조|텍스트} 로 붕괴시키던 버그 수정. 정규식 \{(\w+)\} 으로 {변수} 단일 토큰만 치환 — 색 마커는 내부 |(비-\w)로 매칭되지 않아 그대로 보존된다. {변수} 치환·미등록 토큰 원형 유지는 동일. 엔드투엔드 검증(backoffice_db + 실 DB 카드)에서 발견. 회귀 테스트 추가, agent 87/87. Co-Authored-By: Claude Fable 5 --- .../chat/service/script_repository.py | 21 ++++++++++++------- agent/tests/test_scripts_resources.py | 8 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/agent/negotiation/chat/service/script_repository.py b/agent/negotiation/chat/service/script_repository.py index c0e4a57..61a1d5d 100644 --- a/agent/negotiation/chat/service/script_repository.py +++ b/agent/negotiation/chat/service/script_repository.py @@ -13,6 +13,7 @@ Chat_server 구조 참고, 동적 import 해킹/특정사 표현은 제거(CLEAN import json import os +import re from typing import Any, Dict, Optional from common.database.db_session_manager import DB_SESSION_MNG @@ -24,6 +25,8 @@ from tenancy.config import TenantConfig _RQ_FILES = {"재협상": "scripts_renegotiation.json", "재견적": "scripts_requote.json"} _CARD_SOURCE_DB = "backoffice_db" +# `{name}` 단일 토큰만 매칭(색 마커 `{{강조|...}}` 는 내부 `|` 로 인해 비매칭 → 보존). +_VAR_TOKEN_RE = re.compile(r"\{(\w+)\}") class ScriptRepository: @@ -125,21 +128,23 @@ class ScriptRepository: } def format_script(self, text: str, variables: Optional[Dict[str, Any]] = None) -> str: - """{company_name}/{service_name} + 협상 변수 치환. 누락 변수는 원형 유지(KeyError 방지).""" + """{company_name}/{service_name} + 협상 변수 치환. 누락 변수는 원형 유지. + + 정규식으로 `{name}` 단일 토큰만 치환한다 — str.format_map 은 `{{`·`}}` 를 이스케이프로 + 해석해 색 마커 `{{강조|...}}` 를 `{강조|...}` 로 붕괴시키므로 쓰지 않는다. + 색 마커는 내부에 `|`(비-\\w)가 있어 `\\{(\\w+)\\}` 에 매칭되지 않아 그대로 보존된다. + """ if not text: return text ctx = self._brand_vars() if variables: ctx.update({k: v for k, v in variables.items() if v is not None}) - class _Safe(dict): - def __missing__(self, key): - return "{" + key + "}" + def _repl(m): + key = m.group(1) + return str(ctx[key]) if key in ctx else m.group(0) # 미등록 토큰은 원형 - try: - return text.format_map(_Safe(ctx)) - except (ValueError, IndexError): - return text # 형식 토큰 충돌 시 원형 + return _VAR_TOKEN_RE.sub(_repl, text) def get_step(self, step: str, rq_type: str = "재협상", variables: Optional[Dict[str, Any]] = None) -> Optional[dict]: """step 정의를 반환하되 script 를 치환해서 돌려준다.""" diff --git a/agent/tests/test_scripts_resources.py b/agent/tests/test_scripts_resources.py index 461ca24..d977b32 100644 --- a/agent/tests/test_scripts_resources.py +++ b/agent/tests/test_scripts_resources.py @@ -89,6 +89,14 @@ def test_client_step_and_variable_mapping_load(): assert vm["인터넷 최저가"] == "internet_min_price" +def test_format_script_preserves_color_markers(): + """변수 치환이 색 마커 {{강조|...}} 를 보존해야 한다(회귀: format_map 이 {{}} 를 {} 로 붕괴시킴).""" + repo = _repo() + out = repo.format_script("**{input_price}원**·{{안내|{target}원}}·{unknown}", + {"input_price": 9800, "target": 10000}) + assert out == "**9800원**·{{안내|10000원}}·{unknown}" # 마커 보존 + 변수 치환 + 미등록 원형 + + @pytest.mark.asyncio async def test_resolve_card_script_file_mode_default(): """기본(source_type='file'): resolve_card_script 가 파일 카드 멘트를 반환(하위호환)."""