반복 문의 분류
150분 안팎
학습 목표
원인 확인과 미확인을 구분해 빈도를 집계합니다.
개념
반복 문의를 세는 이유는 다음 결정을 돕기 위해서입니다
FAQ를 무엇부터 작성할지 정할 때 문의 건수는 도움이 됩니다. 하지만 “로그인 안 됨”이라는 제목을 전부 같은 원인으로 세면 권한 안내와 세션 안내가 섞입니다. 이 레슨에서는 확인된 분류와 미확인 분류를 유지하면서 문의별 건수를 셉니다. 숫자는 제공 가명 자료의 요약이며 전체 사용자 발생률이 아닙니다. 원인 확실성, 영향, 해결 가능성도 같이 읽어 작업 순서를 제안합니다. 가장 많이 온 문의가 항상 먼저 해결할 결함이라고 결론 내리지 않습니다.
문의 ID가 집계 단위입니다
이번 입력의 한 행은 고유 문의 하나입니다. 동일 사용자가 보낸 후속 답장은 기존 문의 ID의 기록에 붙이는 것이 입력 전제입니다. 같은 사람의 별도 문의는 다른 ID라면 별도 건입니다. 브라우저 실습에서 동일 ID가 두 번 들어오면 어느 기록이 최신인지 판단할 시각이 없으므로 ERROR를 출력합니다. 임의로 마지막 행을 채택하지 않습니다. 문의 수와 사람 수와 API 시도 수는 서로 다른 단위입니다. m08의 attempt_id를 문의 ID로 바꾸거나 기존 시도 분모와 바로 나누지 않습니다.
분류는 다섯 칸으로 고정합니다
출력 순서는 session-expired, permission-denied, input-invalid, network-unknown, unknown입니다. 앞 네 값은 확인한 응답 분류를 뜻합니다. network-unknown은 응답 미수신이라는 증상 분류이며 통신의 근본 원인이 확정되었다는 뜻은 아닙니다. 원인 근거가 없는 증상 제목이나 누락 category는 unknown에 넣습니다. 입력의 임의 분류 문자열도 unknown으로 남깁니다. 이렇게 하면 누락을 삭제해 확정된 분류만 과장하는 일을 피하면서 입력 담당자에게 분류 계약을 보완할 근거를 보여 줍니다.
빈 자료에도 다섯 줄을 출력합니다
어떤 분류가 0건이어도 그 줄을 생략하지 않습니다. 빈 배열은 모든 분류가 0인 다섯 줄입니다. 빈 표준 입력도 조회 자료가 없는 상황을 연습하기 위해 빈 배열로 처리합니다. 잘못된 JSON은 빈 자료가 아니라 ERROR입니다. 두 경우를 구분하지 않으면 수집 오류를 문의 없음으로 보고할 수 있습니다. 브라우저 답안은 설명 문장을 덧붙이지 않고 분류 문자열과 정수 건수만 출력합니다. 고정 출력 순서는 검토자가 매번 같은 위치에서 미확인 수를 확인하도록 돕습니다.
입력 계약을 눈으로 읽습니다
표준 입력은 JSON 배열이고 각 원소는 id 문자열을 가진 객체입니다. category는 문자열이나 null 또는 누락일 수 있습니다. id는 공백만 있는 값이나 중복 값을 허용하지 않습니다. category가 숫자·배열·객체·불리언이면 잘못된 입력으로 ERROR를 출력합니다. 예를 들어 category가 null인 문의는 미확인으로 남길 수 있지만 숫자 401은 응답 상태와 분류 이름을 혼동한 값입니다. 이때 자동 추정을 하기보다 자료를 만든 담당자에게 필드 계약을 확인하도록 오류로 구분합니다.
사전은 분류별 누적값을 보관합니다
Python의 dict에 다섯 분류를 키로 만들고 각각 0을 넣습니다. 행을 하나씩 읽어 알려진 category인지 확인한 뒤 해당 값에 1을 더합니다. 알려지지 않은 문자열은 unknown 키를 선택합니다. set에는 이미 읽은 id를 넣고 다음 행이 같은 ID인지 검사합니다. 리스트의 순서대로 분류를 출력하면 사전 삽입 순서를 우연히 의존하는 코드를 줄일 수 있습니다. 이번 문제는 반복문과 조건문을 사용해 계약을 그대로 구현하는 연습이며 복잡한 통계 모델은 필요하지 않습니다.
원인과 해결 상태는 섞지 않습니다
session-expired가 확인되어도 사용자가 재로그인 후 내역을 확인했는지는 별도입니다. permission-denied 역시 분류는 확인되었지만 본인 경로 안내 결과가 pending일 수 있습니다. 해결된 문의만 세면 반복 문의의 실제 부담과 미해결 현상을 놓칩니다. 이번 분류 집계에는 사용자 결과와 무관하게 제공 범위의 모든 문의를 포함합니다. 해결률을 새로 계산하고 싶다면 해결 기준, 확인 시점, 미응답 처리, 대상 분모를 따로 정의합니다. 분류별 건수 표에 resolved라는 상태를 원인처럼 추가하지 않습니다.
기간 경계는 보고서의 계약입니다
미션 문의는 한국 시각 10월 9일 0시 이상 다음 날 0시 미만에 발생한 SUP01~SUP03입니다. 브라우저 함수에는 시각 필드가 없으므로 입력 전에 이미 범위를 고른 자료를 받는다고 설명합니다. 코드가 기간 필터를 수행했다고 주장하지 않습니다. 실제 반복 문의 비교에서는 같은 길이의 기간, 운영 시간, 접수 경로, 행사 수를 확인해야 합니다. 하루 문의 세 건과 일주일 문의 다섯 건을 숫자만 비교하면 이용량 차이를 놓칠 수 있습니다. 경계와 제외 조건을 countContract에 남깁니다.
오류 출력은 계약 위반을 드러냅니다
JSONDecodeError는 따옴표나 쉼표 문제를 뜻할 수 있습니다. 이 실습은 예외를 잡아 ERROR 한 줄로 표시합니다. ERROR가 나온 경우 먼저 입력이 배열인지, 각 행이 객체인지, id가 비어 있거나 중복인지 확인합니다. 분류 이름의 오타는 unknown 증가로 나타나며 숫자 category는 ERROR입니다. 같은 증상이라도 출력 신호가 다르므로 모든 오류를 같은 방식으로 고치지 않습니다. 제출할 때 디버깅 print가 남으면 기대 출력과 달라질 수 있어 결과 줄만 남깁니다.
미확인이 많으면 조사 절차를 개선합니다
unknown이 늘었다고 고객이 모호하게 문의한다고 탓하지 않습니다. 접수 화면에서 오류 코드와 발생 시각을 쉽게 알려 줄 수 있었는지, 담당자가 필요한 질문을 했는지 살펴봅니다. 분류를 강제로 정해 unknown을 줄이는 것은 지표 개선이 아닙니다. 확인 근거가 늘어 분류가 바뀌었다면 기존 분류와 변경 시점을 기록합니다. 원인 미확인 문의도 영향이 크면 먼저 이관할 수 있습니다. 분류의 확실성은 지원 판단을 설명하는 정보이며 처리 속도 목표 때문에 없앨 빈칸이 아닙니다.
m08 지표와 함께 설명하는 방법
이전 자료는 독립 과업 성공 시도와 성공 시간의 요약입니다. 이번 자료는 별도로 만든 가상 지원 문의입니다. 두 자료의 기간·사람·수집 경로가 연결되지 않으므로 25.0퍼센트포인트 개선이 문의를 줄였다고 말하지 않습니다. 보고서에는 metrics.json을 이전 근거로 참조하고 지원 문의 표를 별도 출처로 둡니다. 실제 효과를 보려면 같은 운영 기간의 유효 사용자 노출과 문의 연결 조건을 먼저 설계해야 합니다. 숫자의 연결 가능성을 검토하는 일이 계산 자체보다 먼저입니다.
집계 결과를 다음 행동으로 바꿉니다
미션의 session-expired 1건은 재로그인 FAQ 가상 확인과 연결하고, permission-denied 1건은 본인 경로 안내 후 결과 확인을 남깁니다. network-unknown 1건은 저장 여부 조사와 중복 제출 방지 안내를 우선 검토합니다. input-invalid가 0이라고 입력 설계 문제가 없다고 결론 내리지 않습니다. 표본에 그 사례가 없다는 뜻입니다. 동료에게 각 분류 수가 어떤 문의 ID에서 나왔는지 찾아보게 합니다. 집계가 맞고 해석이 절제되어야 지원 개선 제안의 근거로 사용할 수 있습니다.
따라하기
분류 누적의 기본 동작을 확인합니다
다음 코드를 Python 3로 실행하고 입력 계약과 결과를 비교합니다.
counts = {'session-expired':0,'unknown':0}
for category in ['session-expired',None,'wifi-guess']:
key = category if category in counts else 'unknown'
counts[key] += 1
for key in ['session-expired','unknown']: print(key,counts[key])
실행 결과
session-expired 1 unknown 2
표준 입력으로 문의를 집계합니다
아래 코드를 연습용 파일 count.py에 저장하고 python3 count.py로 실행합니다. 표준 입력으로 다음 JSON 배열을 넣고 입력을 종료합니다. [{"id":"A","category":"session-expired"},{"id":"B","category":"permission-denied"},{"id":"C","category":"network-unknown"}]
import json
import sys
ORDER = ['session-expired','permission-denied','input-invalid','network-unknown','unknown']
try:
raw = sys.stdin.read()
rows = json.loads(raw) if raw.strip() else []
if not isinstance(rows,list): raise ValueError('array required')
counts = dict.fromkeys(ORDER,0)
seen = set()
for row in rows:
if not isinstance(row,dict): raise ValueError('object required')
ident = row.get('id')
if not isinstance(ident,str) or not ident.strip() or ident in seen:
raise ValueError('unique nonblank id required')
seen.add(ident)
category = row.get('category')
if category is not None and not isinstance(category,str): raise ValueError('category type')
key = category if category in ORDER else 'unknown'
counts[key] += 1
for key in ORDER: print(key,counts[key])
except ValueError:
print('ERROR')
실행 결과
session-expired 1 permission-denied 1 input-invalid 0 network-unknown 1 unknown 0
빈 배열의 출력 계약을 확인합니다
같은 count.py에 []를 입력합니다. 다른 모든 분류도 빠짐없이 0건으로 출력하는지 확인합니다.
import json
import sys
ORDER = ['session-expired','permission-denied','input-invalid','network-unknown','unknown']
try:
raw = sys.stdin.read()
rows = json.loads(raw) if raw.strip() else []
if not isinstance(rows,list): raise ValueError('array required')
counts = dict.fromkeys(ORDER,0)
seen = set()
for row in rows:
if not isinstance(row,dict): raise ValueError('object required')
ident = row.get('id')
if not isinstance(ident,str) or not ident.strip() or ident in seen:
raise ValueError('unique nonblank id required')
seen.add(ident)
category = row.get('category')
if category is not None and not isinstance(category,str): raise ValueError('category type')
key = category if category in ORDER else 'unknown'
counts[key] += 1
for key in ORDER: print(key,counts[key])
except ValueError:
print('ERROR')
실행 결과
session-expired 0 permission-denied 0 input-invalid 0 network-unknown 0 unknown 0
중복 ID는 임의로 덮어쓰지 않습니다
같은 count.py에 [{"id":"A"},{"id":"A","category":"session-expired"}]를 입력합니다. 두 행을 두 문의로 세지 않고 계약 위반 신호를 확인합니다.
import json
import sys
ORDER = ['session-expired','permission-denied','input-invalid','network-unknown','unknown']
try:
raw = sys.stdin.read()
rows = json.loads(raw) if raw.strip() else []
if not isinstance(rows,list): raise ValueError('array required')
counts = dict.fromkeys(ORDER,0)
seen = set()
for row in rows:
if not isinstance(row,dict): raise ValueError('object required')
ident = row.get('id')
if not isinstance(ident,str) or not ident.strip() or ident in seen:
raise ValueError('unique nonblank id required')
seen.add(ident)
category = row.get('category')
if category is not None and not isinstance(category,str): raise ValueError('category type')
key = category if category in ORDER else 'unknown'
counts[key] += 1
for key in ORDER: print(key,counts[key])
except ValueError:
print('ERROR')
실행 결과
ERROR
확인 문제
실습
표준 입력 JSON 배열의 고유 문의를 다섯 분류로 셉니다. 각 행은 비어 있지 않은 문자열 id를 가지며 중복 ID는 ERROR입니다. category는 문자열·null·누락만 허용합니다. 알 수 없는 문자열·null·누락은 unknown입니다. 빈 입력과 []는 다섯 분류 모두 0건입니다. 잘못된 JSON, 배열 외 입력, 객체 외 행, 공백 ID, 중복 ID, 잘못된 category 형식은 ERROR 한 줄입니다. 출력 순서는 session-expired, permission-denied, input-invalid, network-unknown, unknown이며 각 줄은 분류와 정수 건수를 공백으로 구분합니다. 행의 기간 필터는 이미 완료된 입력입니다.
모범 답안
import json
import sys
ORDER = ['session-expired','permission-denied','input-invalid','network-unknown','unknown']
try:
raw = sys.stdin.read()
rows = json.loads(raw) if raw.strip() else []
if not isinstance(rows,list): raise ValueError('array required')
counts = dict.fromkeys(ORDER,0)
seen = set()
for row in rows:
if not isinstance(row,dict): raise ValueError('object required')
ident = row.get('id')
if not isinstance(ident,str) or not ident.strip() or ident in seen:
raise ValueError('unique nonblank id required')
seen.add(ident)
category = row.get('category')
if category is not None and not isinstance(category,str): raise ValueError('category type')
key = category if category in ORDER else 'unknown'
counts[key] += 1
for key in ORDER: print(key,counts[key])
except ValueError:
print('ERROR')
더 읽기
면접 질문
- 사용자가 로그인이 안 된다고 할 때 확인할 순서를 설명해 주시면 됩니다.