업무용 스크립트 요청문에 입력·출력·오류 원칙 담기
“자동화해 줘”를 실행 가능한 작업 설명으로 바꿉니다. 민감정보 없는 합성 샘플과 손으로 확인한 기대 결과를 붙여, 팀별 작업 기록 집계 스크립트를 요청하는 문장을 완성합니다.
AI 채팅 도우미에게 명확한 데이터 정제 규칙을 제안하게 한 뒤, 승인한 규칙만 CSV 사본에 적용하고 셀 단위 전후 변경 기록을 만듭니다. 예제는 모든 변환을 직접 확인할 수 있도록 만든 합성 데이터입니다.
이런 분께AI의 도움으로 반복 가능한 CSV 정제 규칙을 만들되, AI가 데이터를 임의로 바꾸지 못하도록 직접 검토하려는 사람을 위한 글입니다.
이 예제의 사람 이름과 이메일 주소는 모두 합성 데이터이며 실제 인물이 아닙니다. 목표는 AI에게 파일을 직접 정제해 달라고 맡기는 것이 아닙니다. 대신 AI가 규칙을 설명하게 하고, 사용자가 그 규칙을 검토하고 승인한 뒤 결정적으로 적용하는 방식입니다.
다음 내용을 sample_people.csv로 저장합니다. 앞뒤 공백, 이메일 대소문자, 부서명 대소문자, 결측값 표기, 숫자 표기가 서로 일관되지 않습니다.
row_id,name,email,department,hours
1, alice kim ,ALICE.KIM@example.com, sales ,8
2,Bob Lee,bob.lee@example.com,Engineering,7.5
3,Carol Park, carol.park@example.com ,SALES, 8.0
4,Dan Choi,,engineering,NA
5,Eve Han,eve.han@example.com, Sales,9
AI에게 각 열의 의미와 대표 값을 알려주고, 의미를 바꾸지 않는 안전한 형식 정제와 해석이 필요한 변환을 구분해 달라고 요청합니다.
그럴듯해 보인다는 이유만으로 규칙을 승인하면 안 됩니다. 예를 들어 모든 이름을 Title Case로 바꾸면 원래 의도한 대소문자가 훼손될 수 있습니다. hours의 빈 값을 모두 0으로 바꾸는 규칙 역시 원본에 없던 정보를 새로 추가하는 것입니다.
이 예제에서 보수적인 AI 답변은 다음과 같은 규칙을 제안할 수 있습니다. 코드를 작성하기 전에 각 항목을 먼저 검토합니다.
| 열 | 승인한 규칙 | 이유 |
|---|---|---|
| name | 앞뒤 공백만 제거 | 대소문자는 바꾸지 않고 불필요한 공백만 제거 |
| 공백 제거 후 비어 있지 않은 값만 소문자로 변환 | 결측값을 새로 만들지 않으면서 예제 이메일 형식을 통일 | |
| department | 공백 제거 후 대소문자를 무시하고 sales는 Sales, engineering은 Engineering으로 매핑 | 예제에서 확인 가능한 제한된 목록만 사용 |
| hours | 공백 제거, NA는 빈 값으로 변경, 유효한 소수 표기를 정규화 | 선택한 결측값 표기와 숫자 표현을 일관되게 만듦 |
| row_id | 변경하지 않음 | 레코드 식별자 유지 |
이 규칙들은 의도적으로 범위를 좁게 잡았습니다. 알 수 없는 부서 값은 추측해서 바꾸지 않고 그대로 둡니다. 빈 이메일은 빈 상태로 유지합니다. 행 삭제나 중복 제거도 수행하지 않습니다.
작은 예제라면 실행 전에 예상 결과를 직접 계산할 수 있습니다. 이렇게 만든 기준표를 나중에 스크립트 결과와 비교합니다.
| row_id | 열 | 변경 전 | 변경 후 |
|---|---|---|---|
| 1 | name | alice kim | alice kim |
| 1 | ALICE.KIM@example.com | alice.kim@example.com | |
| 1 | department | sales | Sales |
| 3 | carol.park@example.com | carol.park@example.com | |
| 3 | department | SALES | Sales |
| 3 | hours | 8.0 | 8 |
| 4 | department | engineering | Engineering |
| 4 | hours | NA | |
| 5 | department | Sales | Sales |
따라서 변경되는 셀은 총 9개여야 합니다. row 2는 아무 값도 바뀌지 않아야 하며, 출력 결과에는 원래 순서 그대로 5개 행이 모두 남아 있어야 합니다.
다음 표준 라이브러리 기반 스크립트는 예상 열을 먼저 검증하고, 각 행을 정제하면서 값이 실제로 바뀐 모든 셀을 기록합니다. outputs 폴더가 이미 존재하면 실행을 중단하므로 이전 검토 결과를 실수로 덮어쓸 가능성을 줄입니다.
import csv
import sys
from decimal import Decimal, InvalidOperation
from pathlib import Path
INPUT = Path("sample_people.csv")
OUTPUT_DIR = Path("outputs")
CLEANED = OUTPUT_DIR / "sample_people_cleaned.csv"
CHANGES = OUTPUT_DIR / "changes.csv"
EXPECTED_FIELDS = ["row_id", "name", "email", "department", "hours"]
DEPARTMENT_MAP = {
"sales": "Sales",
"engineering": "Engineering",
}
def clean_hours(value):
trimmed = value.strip()
if trimmed == "NA":
return "", "NA converted to blank"
if trimmed == "":
return "", "trim whitespace"
try:
number = Decimal(trimmed)
except InvalidOperation:
return trimmed, "trim whitespace"
if number == number.to_integral():
normalized = str(number.quantize(Decimal("1")))
else:
normalized = format(number.normalize(), "f")
return normalized, "normalize numeric notation"
def clean_cell(column, value):
if column == "row_id":
return value, "preserve row_id"
if column == "name":
return value.strip(), "trim name whitespace"
if column == "email":
trimmed = value.strip()
return trimmed.lower() if trimmed else "", "trim and lowercase email"
if column == "department":
trimmed = value.strip()
normalized = DEPARTMENT_MAP.get(trimmed.lower(), trimmed)
return normalized, "trim and normalize known department"
if column == "hours":
return clean_hours(value)
return value, "no rule"
def main():
if not INPUT.is_file():
sys.exit(f"Input file not found: {INPUT}")
if OUTPUT_DIR.exists():
sys.exit(f"Stop: {OUTPUT_DIR} already exists. Review or rename it first.")
OUTPUT_DIR.mkdir()
cleaned_rows = []
changes = []
with INPUT.open("r", encoding="utf-8", newline="") as source:
reader = csv.DictReader(source)
if reader.fieldnames != EXPECTED_FIELDS:
sys.exit(
f"Unexpected columns. Expected {EXPECTED_FIELDS}, got {reader.fieldnames}"
)
for row in reader:
cleaned = {}
for column in EXPECTED_FIELDS:
before = row[column]
after, rule = clean_cell(column, before)
cleaned[column] = after
if before != after:
changes.append(
{
"row_id": row["row_id"],
"column": column,
"before": before,
"after": after,
"rule": rule,
}
)
cleaned_rows.append(cleaned)
with CLEANED.open("x", encoding="utf-8", newline="") as target:
writer = csv.DictWriter(target, fieldnames=EXPECTED_FIELDS)
writer.writeheader()
writer.writerows(cleaned_rows)
with CHANGES.open("x", encoding="utf-8", newline="") as target:
writer = csv.DictWriter(
target,
fieldnames=["row_id", "column", "before", "after", "rule"],
)
writer.writeheader()
writer.writerows(changes)
print(f"Rows written: {len(cleaned_rows)}")
print(f"Changed cells: {len(changes)}")
print(f"Cleaned copy: {CLEANED}")
print(f"Change log: {CHANGES}")
if __name__ == "__main__":
main()
clean_csv.py와 sample_people.csv를 같은 폴더에 둔 뒤 python clean_csv.py로 실행합니다. 이 예제에서 예상되는 요약은 5 rows written과 9 changed cells입니다.
정제된 파일을 사용하기 전에 outputs/changes.csv부터 확인합니다. 각 행에는 row_id, 열 이름, 원래 값, 변경 값, 적용된 규칙이 기록되어 있어 한 번의 변환을 설명할 수 있어야 합니다.
여기서 AI는 데이터 의미를 최종적으로 판단하는 주체가 아니라 정제 규칙 초안을 만드는 보조 수단입니다. 열 정의, 유효한 범주, 결측값 규칙, 허용 가능한 변환은 데이터 소유자나 다른 신뢰할 수 있는 기준에서 확인해야 합니다.
2026-09-21 · hand-checked example · Python 3.12
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.