발행일

메인 화면의 판독 박스는 서버 설정 셋이 그린다 — 색·임계값·켜짐을 화면 열두 곳이 각자 물었다

메인 화면의 판독 박스는 서버 설정 셋이 그린다

이 글은 2026년 9월에 당시 코드를 다시 보며 정리한 것입니다. 저장소는 퇴사 전에 스냅샷으로 보관한 것이라 커밋 이력이 없고, 경력기술서와 당시 쓴 지원서·이력서에 적은 담당 범위와 코드를 대조해 썼습니다. 날짜는 코드에 남은 시기 단서 기준입니다.

공항 보안 검색 판독 시스템의 메인 화면은 엑스레이 영상 위에 상자를 그리는 화면입니다. AI가 "이 자리에 이 클래스가 이 점수로 있다"고 보내면, 화면이 그 자리에 다각형을 그리고 색을 입히고, 점수가 높으면 소리를 냅니다. 이 글은 그 상자 하나가 그려지기까지 화면이 무엇을 묻는지를 코드로 따라간 기록이에요. 경력기술서 2.3절에 "옵션을 설정으로 뺐다"고 한 줄로 적은 것의 실제 모양이고, 이력서에 제 담당으로 적은 "로그인, 설정, 웹소켓 아이콘" 중 설정이 바로 이 자리입니다.

옵션은 어디서 왔나

2022년 중반에 쓴 지원서의 5월 항목은 이렇게 적고 있습니다. 발주처 이름은 뺐습니다.

추가된 요구사항을 만족을 시킬 수 있도록 설계 (공항 납품처 한 곳)

  • 화살표 방향을 기계에 추가 입력
  • 옵션을 추가 (레이블 표시, 레이블 위/아래, bbox 추가 등)
  • 설정의 thres 값을 직접 작성함으로써 사용자의 보기 편한 시선으로 제작

이 세 줄이 코드 어디에 있는지는 바로 찾힙니다. 화살표 방향은 localStoragedirection 키와 소켓 메시지 change_rail_left·change_rail_right로, 레이블 표시와 위/아래는 label·label_top 키로, 상자 표시는 roi 키와 BboxOn prop으로, 임계값 직접 입력은 설정 화면의 input type="number"로요. 지원서는 같은 항목에 "너무나 다양한 요구사항 탓에 기본 제품의 틀이 많이 망가져 있어서 확장성은 고려하기 힘들 것으로 판단"이라고도 적었습니다. 설정 항목이 늘어난 배경이 그것이었어요.

상자 하나에 값 셋

src/view/main/mainImage/MainImage.js는 화면이 뜨면 서버 설정을 먼저 받아 둡니다.

const [settingColor, setSettingColor] = useState([]);
useEffect(() => {
    axios.get(SETTINGS_URL + '/get_settings')
        .then(res => setSettingColor(res.data))
        .catch(err => console.log(err));
}, []);

응답은 클래스별 객체의 배열이고, 각 객체에 code·color·thres·check가 있습니다. 그리고 그 배열에서 값을 꺼내는 함수가 셋 있어요.

const setIconBackgroundColor = (icon) => { /* code가 같은 항목의 color */ }
const setThresDisplay = (icon) =>        { /* code가 같은 항목의 thres */ }
const setToggleDisplay = (icon) =>       { /* code가 같은 항목의 check */ }

이름은 set…인데 하는 일은 조회입니다. 셋 다 for 문으로 배열을 훑어 code가 맞는 항목의 필드 하나를 돌려줘요. 상자를 그리는 자리에서 이 셋을 한 번에 씁니다.

<polygon key={i}
         className="box_top"
         points={BboxXY}
         data-class={setTopCls}
         data-scores={setScore}
         style={{
             display: setThresDisplay(setTopCls) < setScore && setToggleDisplay(setTopCls) === "on" ? "flex" : "none",
             stroke: setIconBackgroundColor(setTopCls)
         }}
/>

상자가 보일 조건은 두 개의 AND입니다. 그 클래스의 임계값보다 점수가 높고, 그 클래스가 켜져 있을 것. 색은 세 번째 값이에요. 즉 상자 하나는 서버 설정의 값 셋으로 결정되고, 화면은 그 셋을 조합만 합니다. 설정 화면(Setting.js)에서 색을 고르거나 임계값을 숫자로 적으면 같은 모양 {code: {check, color, thres}}modify_settings에 보내고, 저장되면 localStoragesave_setting을 써서 다른 창이 새로고침하게 합니다.

이 세 값 모델은 SPA로 오면서 만든 게 아닙니다. 옛 서버 템플릿 settings.html에도 "탐지유형색상"이라는 제목 아래 클래스마다 input type="color"와 0~100 range 슬라이더, 켜고 끄는 버튼이 한 칸씩 있었어요. 새 화면은 그 모델을 받아 React로 옮기면서, 지원서에 적은 대로 슬라이더 옆에 숫자 입력을 붙였습니다.

같은 질문을 열두 파일이 각자 한다

문제는 이 조회가 한 곳에 있지 않다는 겁니다. get_settings를 부르는 파일이 열두 개예요. 메인 화면의 위쪽 영상(MainImage.js)과 아래쪽 이력 띠(MainBottom.js)가 각각 부르고, 둘을 감싼 Main.js도 부릅니다. 검색 목록·섬네일·상세·통계·설정·양화면까지 전부 자기 useEffect에서 같은 주소를 칩니다.

그리고 MainBottom.js 517~537행에는 MainImage.js의 조회 함수 셋이 이름까지 같게 한 벌 더 있습니다. 같은 파일 48행을 보면 부모에게서 getSetting이라는 prop으로 설정을 이미 받고 있는데도요. 한 파일 안에 설정이 두 경로로 들어오고, 조회 함수는 다른 파일 것을 복사해 둔 상태였습니다.

이걸 그때 왜 그렇게 뒀는지는 커밋이 없어서 남아 있지 않습니다. 코드에서 읽히는 건 화면을 하나씩 만들 때마다 "설정을 받아서 조회 함수 셋을 만든다"를 반복했다는 것뿐이에요. 지원서 같은 항목에 "페이지마다 데이터를 호출하다 보면 로딩 시간이 길어져서 해결할 수 있는 방법을 모색"이라고 적혀 있는데, 그 로딩에 이 열두 번의 설정 조회가 한몫했을 가능성이 있습니다.

보일지와 울릴지의 경계가 달랐다

상자를 그리는 것 말고, 점수가 높으면 소리를 내는 판정이 MainBottom.js에 있습니다.

const isEmptyBox = (file) => {
    let settingCheck = []
    getSetting.map((setting) => {
        if (setting.check === "on") settingCheck.push(setting.code)
    })
    let thres;
    for (let i = 0; i < file['top'].cls_ids.length; i++) {
        const cls = file['top'].cls_ids[i]
        for (let n = 0; n < getSetting.length; n++) {
            if (getSetting[n].code === cls) thres = getSetting[n].thres;
            if (file['top'].scores[i] >= thres && settingCheck.includes(cls)) return true
        }
    }
    return false;
}

같은 값 셋으로 같은 종류의 판정을 하는데, 상자는 thres < score이고 알람은 score >= thres입니다. 점수가 임계값과 정확히 같으면 소리는 나는데 상자는 안 보이는 조건이에요. 점수가 소수라 실제로 같은 값이 나올 일은 드물었겠지만, 두 판정이 같은 규칙이라는 보장이 코드 어디에도 없었습니다. 함수가 한 곳에 있었다면 생기지 않았을 차이입니다. 임계값을 사용자가 숫자로 직접 적게 된 뒤로는 정수 경계에 정확히 걸릴 가능성도 그만큼 커졌을 텐데, 그때 이 차이를 알고 있었는지는 남은 기록이 없습니다.

안쪽 for 문도 지금 보면 이상합니다. thres를 찾는 if와 점수를 비교하는 if가 같은 반복 안에 있어서, 아직 이 클래스의 thres를 못 찾은 순번에서도 이전 클래스의 thres로 비교가 한 번씩 돕니다. 결과가 틀리는 경우가 실제로 있었는지는 확인할 방법이 없습니다.

그때의 선택을 지금 읽으면

경력기술서에 "요구가 올 때마다 분기 코드를 넣는 대신 설정 항목을 늘렸다"고 썼고, 그건 코드가 뒷받침합니다. 클래스 하나가 늘어도 화면 코드를 고칠 필요는 없었어요. 서버 설정 배열에 한 항목이 더 오면 색도 임계값도 켜짐도 따라옵니다. 지원서가 말하는 납품처 요구 셋도 새 분기가 아니라 localStorage 키 하나씩으로 받았습니다.

그런데 설정을 읽는 쪽은 설계가 없었습니다. 값이 한 벌인 건 서버 덕분이고, 화면은 열두 곳에서 각자 물었고, 조회 함수는 복사됐고, 경계 비교는 갈렸어요. 1년차의 저는 "설정으로 빼자"까지는 갔고 "설정을 읽는 자리를 하나로 두자"까지는 못 갔습니다. 지원서에 "확장성은 고려하기 힘들 것으로 판단"이라고 적은 게 그 자리의 고백이었다고 지금은 읽힙니다.

검증

  • 날짜 근거: MainBottom.js 86행에 이력 조회의 시작일로 2022-04-01 / 00:00이 박혀 있고 주석에 "임의의 날짜"라고 적혀 있습니다. 코드에 남은 시기 단서 중 가장 늦은 것이라 이 글의 날짜로 썼습니다. 스냅샷 커밋은 2022-07-24이고, 지원서의 납품처 요구 항목은 2022년 5월 이후로 적혀 있어 스냅샷이 그 반영 뒤의 상태라는 것과 맞습니다.
  • get_settings 호출 파일 12개는 grep -rl로 셌습니다. MainImage.js의 조회 함수 셋(2040행)과 MainBottom.js의 복제(517537행), MainBottom.js 48행의 getSetting prop은 파일에서 직접 확인했습니다.
  • 상자 조건 thres < score(MainImage.js 114행)와 알람 조건 score >= thres(MainBottom.js 258행)는 원문 그대로입니다.
  • Setting.js 56~74행에서 {code: {check, color, thres}}modify_settings에 보내고 save_setting을 쓰는 것, 247행의 input type="number", settings.htmlinput type="color"·range 구조는 확인했습니다.
  • 지원서 옵션 셋과 코드 키의 대응(direction·change_rail_*, label·label_top, roi·BboxOn)은 index.js·MainHeader.js·MainImage.js에서 grep으로 확인했습니다. 지원서·이력서는 원문 PDF를 읽고 옮겼고 회사·발주처 이름만 뺐습니다.
항목경력기술서 2.3절지원서·이력서코드판정
옵션을 설정으로통계 설정·옵션설정이 본인 담당(이력서)클래스별 color·thres·check, 화면은 조합만일치
임계값 직접 입력언급 없음"thres 값을 직접 작성"(2022.05, 납품처 요구)Setting.js input type="number"일치
레이블 표시·위/아래, bbox언급 없음옵션 추가(2022.05, 납품처 요구)label·label_top·roi 키, BboxOn일치
화살표 방향언급 없음"화살표 방향을 기계에 추가 입력"direction 키, change_rail_* 소켓일치
카메라 위치 설정적음언급 없음해당 코드 없음이 저장소에서 확인 불가
2D/3D 비디오 판독 설정적음언급 없음해당 코드 없음이 저장소에서 확인 불가

남은 것 · 한계

  • 커밋 이력이 없습니다. 열두 곳의 호출과 함수 복제 중 어디까지가 제 손인지 이 저장소로는 알 수 없습니다. 이력서가 본인 담당으로 적은 건 로그인·설정·웹소켓 아이콘이고, 지원서가 본인 작업으로 적은 건 납품처 요구 옵션과 임계값 입력입니다. 이 글은 그 범위에서만 "했다"고 썼습니다.
  • 경계 비교 차이가 실제 오판을 냈는지 모릅니다. 점수는 소수라 같은 값이 드물었을 것이라는 건 추정이고, 로그도 재현 환경도 없습니다.
  • 카메라 위치·2D/3D 설정은 이 코드에도 지원서에도 없습니다. 경력기술서에 적은 그 항목이 다른 저장소에 있었는지, 서버 쪽 설정이었는지는 확인하지 못했습니다.
  • 로딩 시간 문제의 원인이 설정 조회였다는 건 추정입니다. 지원서는 "방법을 모색"이라고만 적었고, 무엇을 했는지는 스냅샷에서 구분되지 않습니다.
  • 4년 뒤 팔레트에 없는 색은 조용히 안 칠해진다에서는 색이 한 팔레트에서 오지 않으면 화면이 조용히 틀린다는 걸 142곳을 고치며 배웠는데, 여기서는 색이 서버 한 벌에서 오는 대신 읽는 쪽이 열두 벌이었습니다. 같은 문제의 반대편이에요.

관련 글: 산업 AI 솔루션 기업 프런트엔드 2021–2022 · 팔레트에 없는 색은 조용히 안 칠해진다 · 관계도 11개 화면을 렌더러 한 곳에서 바꿨다 · 설정을 localStorage에 두고 소켓 메시지 16갈래로 창끼리 맞췄다 · 클래스 팔레트 20종이 다섯 파일에 복사돼 있고 Redux는 import만 남았다