콘텐츠로 이동

Zio.Code()

네 진입점 중 유일하게 표에 잠금이 걸립니다. 넷을 견주어 보려면 Zio.Database (개요) 의 표를 보십시오.

기초코드 화면에서 사람이 관리하는 표를 스킬에서 읽고 씁니다. 프롬프트에 목록을 실어 주는 {{$테이블.필드}} 가 보는 것과 같은 데이터입니다.

왜 이 진입점이 필요한가. 기초코드는 code_rows 테이블의 data 컬럼에 JSONB 로 들어갑니다. 이 진입점이 없으면 스킬이 그 저장 구조를 알고 Zio.raw() 로 SQL 을 짜야 합니다 — 엔진 내부 구조에 대한 raw 접근이고, 저장 방식이 바뀌면 모든 인스턴스의 스킬이 함께 깨집니다.

엔진은 어떤 표가 있는지 모릅니다. 건설업은 공종, 패션업은 브랜드, 요식업은 점포 를 만듭니다. 표 이름도 칼럼 이름도 전부 인자로만 오갑니다. 특정 표를 전제한 코드를 SDK 나 엔진에 넣지 마십시오.

Method Description
Zio.code(table_name) 기초코드 표 하나를 지정합니다. 기초코드 화면의 테이블 ID(영문명)를 씁니다. 없는 표를 지정하면 LookupError 가 납니다 — 오타를 조용히 빈 목록으로 넘기지 않습니다.
.where(**kwargs) 행을 걸러냅니다. 비교 접미사를 그대로 씁니다. (예: use_yn="Y", client_name__startswith="GS")
.where_or(**kwargs) 같은 표의 여러 조건을 OR 로 묶습니다(다른 .where() 와는 AND). 키워드 검색처럼 여러 칸 중 하나라도 맞으면 될 때 씁니다 — 예전엔 질의를 여러 번 나눠 파이썬에서 합쳐야 했습니다. 접미사(__contains 등)는 .where() 와 같습니다. 여러 번 부르면 각 호출이 별개의 OR 덩어리가 되어 서로 AND 로 이어집니다. fetch()·count()(읽기)에만 걸립니다 — update()·delete() 에 OR 을 걸면 의도보다 많은 행을 건드릴 수 있어 받지 않습니다. (예: .where_or(client_name__contains=kw, aliases__contains=kw))
Zio.code.list() 어떤 표가 있는지 먼저 봅니다. [{table_name, title, description, field_count}] — 행은 안 옵니다. 이름을 모르는 채 Zio.code("추측") 를 되풀이하지 말고 여기서 고르십시오.
.fetch() 행 목록을 GraphResult 로 가져옵니다. 각 행은 칼럼명이 그대로 키가 된 평평한 dict 입니다. 행 식별자는 _id 로 함께 들어갑니다.
.info() 자체의 설명을 dict 로 가져옵니다. {table_name, title, description, fields:[{name, label, type}]}. 제목을 안 붙인 표는 title 이 표 이름 그대로입니다.
.create(props) 행 하나를 넣습니다. 중복은 보지 않습니다 — 무엇이 중복인지는 표마다 다르므로, 넣기 전에 .where(...).fetch() 로 확인하십시오.
.update(props) 조건에 맞는 행을 고칩니다. 준 칼럼만 덮어쓰고 나머지는 그대로 둡니다. 조건이 없으면 ValueError 입니다.
.delete() 조건에 맞는 행을 지웁니다. 지워진 행을 그대로 돌려줍니다 — 무엇이 사라졌는지 남기라는 뜻입니다. 조건이 없으면 ValueError 입니다.
.create_table(columns, title, description) 표를 만듭니다. columns=[{name, label, type}]. 만들어진 표는 사람이 화면에서 이어서 관리합니다 — 스킬이 세웠다고 잠기지 않습니다. 태어날 때는 잠금이 하나도 없습니다(화면에서 만든 표와 같습니다).
.add_column(column) 칸 하나를 뒤에 더합니다. {name, label, type}. 이미 든 행은 그 칸이 빈 채로 보입니다 — 값을 지어 넣지 않습니다.
.drop_column(column_name) 칸 하나를 선언에서 뺍니다. 행에 든 값은 안 지웁니다 — 같은 이름으로 다시 더하면 돌아옵니다. 남은 값은 화면에서는 사라지지만 .fetch() 에는 그대로 실립니다 (읽기까지 거르면 그 값을 볼 자리가 없어지므로). 다시 쓰려 하면 선언에 없는 칼럼이라 ValueError 입니다. 마지막 한 칸은 못 뺍니다.
.rename_column(old_name, new_name) 칸 이름을 바꾸고 행에 든 값도 같이 옮깁니다. 선언만 바꾸면 값이 옛 키에 남아, 지워지지도 않고 보이지도 않는 상태가 됩니다.
.drop_table(force=False) 표와 그 행을 없앱니다. 다른 곳이 이 표를 물고 있으면 거부하고 어디가 물고 있는지 알려줍니다. 알고도 없애려면 force=True 입니다. 돌려주는 것은 {table_name, deleted_rows, dependencies}.
.resolve(value, name, alias=None) 이 표가 아는 표기입니까. 알면 정식명을, 모르면 None 을 돌려줍니다. 100% 일치만 봅니다 — 대소문자·공백·기호는 무시합니다(GS 25 = GS25).
.suggest(value, name, alias=None, limit=3) 닮은 정식명을 골라 줍니다. .resolve() 가 못 찾았을 때 사람에게 되물을 후보입니다. 재료는 별칭까지, 내놓는 것은 정식명뿐입니다.
.name_conflicts(name, alias=None) 같은 별칭이 두 정식명에 걸린 곳을 알려줍니다. [{"alias": "편의점", "claimed_by": ["GS25", "CVSNET"]}]. 빈 목록이면 깨끗한 표입니다.

막히는 표가 있습니다 — PermissionError

섹션 제목: “막히는 표가 있습니다 — PermissionError”

잠금은 표에 적혀 있습니다. 화면(기초코드 관리)이 보는 것과 같은 값을 SDK 도 봅니다 — 판정이 두 벌이면 「화면에서는 못 하는데 스킬로는 되는」 구멍이 생깁니다.

무엇을 잠그나
is_data_user_editable 행 전체. 꺼져 있으면 읽기(fetch·count·info)만 됩니다
allowed_actions 그중 어느 작업까지인가. ["update"] 면 값은 고치되 줄은 못 늘리고 못 지웁니다. 비어 있으면 전부 허용입니다
is_schema_user_editable 칸 구성. add_column·drop_column·rename_column·drop_table 이 걸립니다

막히면 왜 막혔고 무엇은 되는지가 문장으로 옵니다. 그 문장을 읽고 멈추십시오 — 이름만 다른 표를 새로 만들거나 같은 호출을 되풀이하는 것은 해결이 아닙니다. 잠금은 사람이 화면에서 풉니다.

PermissionError: 'llm_callers' 표는 삭제를 허용하지 않습니다. 이 표에 허용된 것: 수정.
PermissionError: 'llms' 표는 칸 구성이 잠겨 있습니다(칸 추가 거부).
시스템이 칸 이름으로 읽는 표라, 바뀌면 조회가 조용히 어긋납니다.
행은 잠금과 별개입니다 — 값만 고치려면 create·update·delete 를 쓰십시오.

잠긴 것은 엔진이 칸 이름으로 읽는 시스템 표들입니다 (llms·llm_callers·system_configs·collectors·common_code). 담당자가 만든 표는 잠겨 있지 않습니다 — 화면에서 만들었든 스킬로 세웠든 같습니다.

어느 칸이 정식명이고 어느 칸이 별칭인지는 표를 만든 사람만 압니다. 건설업은 공종명, 패션업은 브랜드명 이고, 별칭 칸을 아예 안 만든 표도 있습니다. SDK 가 aliases 같은 이름을 정해두면 그 순간 한 산업 전용이 되고, 별칭 칸이 없는 표에서는 조용히 빈손이 됩니다.

Zio.code("client").resolve("GS편의점", name="client_name", alias="aliases")
# → 'GS25' 별칭에 100% 일치
Zio.code("client").resolve("하나비전", name="client_name", alias="aliases")
# → None 모르는 표기다. 되물을 차례
Zio.code("client").suggest("하나비전", name="client_name", alias="aliases")
# → ['한화비전', '하나시스'] 사람에게 보여줄 후보
# 별칭 칸이 없는 표는 alias 를 안 주면 됩니다 — 정식명만 재료가 됩니다
Zio.code("product").suggest("삼성전자", name="product_name")

.resolve().suggest()한 함수로 합치지 않은 이유가 있습니다. “아는 것” 과 “닮은 것” 이 섞이면, 안 물어도 될 것을 묻거나 물어야 할 것을 그냥 넘깁니다. .resolve() 가 답을 주면 통과, 못 주면 .suggest() 로 후보를 받아 되묻기 — 이 두 줄이 표준 흐름입니다.

사전이 틀렸을 때는 손대지 않는 것이 낫습니다. 같은 별칭이 두 정식명에 걸려 있으면(편의점 이 두 편의점 회사에) 어느 쪽으로 붙을지가 표에 적힌 순서로 정해지고, 나중에 왜 그렇게 붙었는지 아무도 못 찾습니다. .resolve()·.suggest() 는 그런 별칭을 아예 쓰지 않으므로 틀린 곳에 붙지는 않지만, 표가 틀렸다는 것을 알려면 .name_conflicts() 를 한 번 부르십시오. 무엇을 할지는 알려주지 않습니다 — 그 표를 통째로 건너뛸지는 도메인 판단입니다.

사람에게 보일 문장에는 .info() 의 제목을 쓰십시오

섹션 제목: “사람에게 보일 문장에는 .info() 의 제목을 쓰십시오”

표를 읽는 것과 표에 대해 읽는 것은 다릅니다. .fetch() 는 거래처 목록을 주고, .info() 는 그 표가 “주요거래처” 라고 알려줍니다.

# 나쁨 — 사람은 'client' 가 뭔지 모른다
질문 = f"기초코드에 없는 값입니다: '{}'"
# 나쁨 — 담당자가 화면에서 표 이름을 바꿔도 문장은 안 따라온다
질문 = f"주요거래처에 없는 값입니다: '{}'"
# 좋음
title = Zio.code(code_table).info()["title"]
질문 = f"{title}에 없는 값입니다: '{}'. 어느 쪽입니까?"

표의 한글 이름은 도메인 담당자가 화면에서 붙인 것입니다. 스킬에 적어두면 그 순간부터 두 곳에 같은 이름이 살게 되고, 담당자는 자기가 바꾼 이름이 왜 화면에 안 나오는지 알 수 없습니다.

조건 없는 update·delete 를 거부합니다. 기준 데이터가 한 번에 날아가면 되돌릴 방법이 없습니다. .where() 를 반드시 거십시오.

선언에 없는 칼럼을 거부합니다. 화면은 선언된 칼럼만 그리므로, 받아주면 아무 데도 안 보이는 값이 저장되고 나중에 아무도 못 찾습니다. 오류 문장에 그 표에서 쓸 수 있는 칼럼이 함께 옵니다.

기초코드의 주인은 도메인 담당자입니다. 사람이 화면에서 세운 표에 스킬이 한 줄 더하는 것이지, 스킬이 자기 저장소로 쓰는 곳이 아닙니다. 스킬이 만들고 지우는 표가 필요하면 Zio.entity() 를 쓰십시오 — 상태가 바뀌고 사라지는 성격의 데이터(작업 큐, 처리 이력)는 그쪽입니다.

기초코드 활용 실전 예제 — 표기 정규화

섹션 제목: “기초코드 활용 실전 예제 — 표기 정규화”

모델이 GS THE FRESH 라고 뽑아도 그래프에는 GS더프레쉬 로 넣고 싶을 때. 어떤 표기를 어디로 모을지는 기초코드에만 적혀 있고, 엔진은 그 규칙을 모릅니다.

from zio_ontology import Zio, zio_skill
@zio_skill
def normalize_names(code_table: str, code_field: str,
alias_field: str, node_label: str,
dry_run: bool = True):
"""기초코드의 별칭 칼럼을 읽어 갈라진 노드 표기를 정식 표기로 모은다.
표 이름·칼럼 이름을 전부 인자로 받으므로 어느 산업에서든 그대로 쓴다.
"""
table = Zio.code(code_table)
# 1) 그래프의 실제 표기를 하나씩 표에 물어본다.
# 사전을 손으로 짜지 않는다 — 별칭 충돌(같은 별칭이 두 정식명에 걸린 것)을
# 거르는 것도 표기 열쇠를 만드는 것도 .resolve() 안에 들어 있다.
planned, unknown = [], []
for node in Zio.node(node_label).fetch().data:
name = node.get(code_field)
if not name:
continue
canon = table.resolve(name, name=code_field, alias=alias_field)
if canon is None:
# 표에 없는 새 값 — 임의로 가까운 이름에 붙이지 않는다.
# 닮은 후보는 사람에게 보여줄 것이지, 코드가 고를 것이 아니다.
unknown.append({
"value": name,
"options": table.suggest(name, name=code_field, alias=alias_field),
})
elif canon != name:
planned.append((name, canon))
# 2) 확인하고 실행한다
if dry_run:
return {"planned": planned, "unknown": unknown, "applied": False}
for name, canon in planned:
Zio.node(node_label).where(**{code_field: name}).consolidate_into(**{code_field: canon})
return {"planned": planned, "unknown": unknown, "applied": True}

dry_run 이 기본값 True 인 것에 주의하십시오. .consolidate_into() 는 되돌릴 수 없으므로, 무엇이 합쳐질지 먼저 보고 실행하는 두 단계를 스킬 쪽에서 만들어 두는 것이 안전합니다.

unknown 에 담긴 options되묻기 화면의 버튼이 됩니다. 후보가 비어 있어도 괜찮습니다 — 그때는 사람이 직접 적고, 그 답을 별칭 칸에 넣으면 다음부터 .resolve() 가 바로 찾습니다. 되묻기는 한 번이면 끝납니다.