공무링크
건설 노무·근태·4대보험 신고를 처리하는 회사 주력 웹 서비스. 기존 설치형 프로그램을 웹으로 전환
초기 구조부터 담당, 동료와 2인 개발
풀스택 개발자 임현진
건설 노무·4대보험 B2B SaaS를 3년간 개발하고 있습니다. 설치형 프로그램을 웹으로 전환한 공무링크를 초기 구조부터 맡았고, 현재는 동료와 2인 체제로 개발과 운영을 함께 하고 있습니다.
SvelteKit · TypeScript · MariaDB · C#
(주)비즈웍스 · 2023.06 ~ 재직 중
건설 노무·근태·4대보험 신고를 처리하는 회사 주력 웹 서비스. 기존 설치형 프로그램을 웹으로 전환
초기 구조부터 담당, 동료와 2인 개발
공무링크의 요청을 받아 정부기관 사이트 신고를 대신 처리하는 PC 에이전트
초기 버전을 넘겨받아 개발·운영 전담
건설 일용직 근로자용 모바일 앱. NFC·QR 출퇴근 기록, 급여명세서 조회, 전자 근로계약
첫 커밋부터 참여, 구글 플레이·앱스토어 등록
요금제·알림톡·전자계약 충전과 NFC 태그 판매 결제를 처리하는 사내 공용 결제 서버
키움페이 연동, 포인트 충전·차감 이력, OpenAPI 스펙
연 1회 보수총액 신고 창구. 두 달간 사업장 1,026곳의 신고 처리
동료가 만든 초기 골격을 넘겨받아 7단계 신고 화면으로 완성·출시
숨은 규칙을 찾는 두뇌 퍼즐 웹게임. 문제 357개, 테스트 271건
Claude Code로 개발, 문제 검증 장치 구축
항공사 스케줄 파일을 달력으로 보여 주는 승무원 스케줄 웹앱. 지인 승무원 5명이 사용 시작
React 학습용으로 시작, Claude Code로 개발하며 코드 검수
공무링크 · 공무헬퍼
공무링크 · 2026.08 ~ 09
현장 근로자는 NFC·QR로 출퇴근을 태그합니다. 공무링크는 이 기록을 날짜별 근무시간으로 묶어 공수(건설 현장의 하루 근무 단위)로 환산합니다. 태그 기록에는 출근·퇴근 구분 없이 시각과 태그 방식만 저장됩니다.
고객사에서 "출퇴근 기록 일부가 보이지 않는다"는 문의가 들어왔습니다. 원인은 2026년 6월 제가 추가한 판정 규칙이었습니다. 당시 자정을 넘겨 퇴근하는 야간조의 근무시간 오류를 고치면서 "새벽 6시 이전 태그는 전날 근무로 처리"하도록 했는데, 여름철 주간조는 새벽 5시대 출근이 많아 출근 기록이 전날로 넘어갔습니다. 운영 데이터를 날짜별로 대조해 아래 세 가지 증상을 확인했습니다.
계산식에는 문제가 없었고, 태그를 어느 근무일에 넣을지 정하는 단계에서 오류가 났습니다. 이 기능은 지금까지 두 가지 방식을 거쳤고, 두 방식 모두 한쪽 근무 형태에서 오류가 났습니다.
| 방식 | 오류가 나는 경우 |
|---|---|
| ① 달력 날짜 기준으로 묶기 ~2026.06 | 자정을 넘겨 퇴근하는 야간조 |
| ② 새벽 기준 시각 이전 태그를 전날로 처리 2026.06, 본인 추가 | 기준 시각보다 일찍 출근하는 주간조 |
| ③ 하루 기록 구성으로 판정 + 야간 현장 옵션 2026.09 | 없음 (야간 여부는 현장에서 설정) |
①과 ②는 시각을 기준으로 선을 긋는 방식이라 기준 시각 부근에 출퇴근하는 근무조에서 오류가 납니다. 또한 출퇴근 구분이 없어, 04:00·18:00 기록이 야간조의 퇴근·출근인지 주간조의 긴 근무인지 시각만으로는 알 수 없습니다. 그래서 ③을 선택했습니다. 주간 현장은 하루 기록의 구성으로 판정하고, 자정 이후 퇴근하는 현장은 ② 방식을 '야간 근무 현장' 옵션으로 유지했습니다.
계산 로직을 화면 코드에서 분리해 단위 테스트가 가능하게 했고, 운영에서 확인된 주간·야간 태그 패턴으로 회귀 테스트 12건을 작성했습니다. 수정한 동작 외에도 같은 야간 기록을 주간 모드로 계산하면 결과가 달라지는지(분기 동작 확인), 야간 모드가 6월 동작을 그대로 유지하는지를 테스트에 포함했습니다.
"휴게 2시간을 빼고 공수를 보고 싶다"는 요청에는 계산식을 바꾸지 않고, 현장별 '1공수 기준 재실시간' 설정을 추가했습니다. 공수는 "1공수 = 재실 N시간"으로 계산되므로, 07:00~17:00 근무에 휴게 2시간인 현장은 재실 10시간을 1공수로 설정하면 됩니다. 계산식 변경을 택하지 않은 이유는 아래와 같습니다.
오계산되던 날이 모두 정상 계산되는 것을 운영 데이터로 확인했습니다. 태그가 1건뿐인 날은 '퇴근없음'으로 표시되어 현장에서 누락을 바로 확인할 수 있습니다.
6월에 규칙을 추가할 때는 야간조 기록만 확인했고 회귀 테스트도 작성하지 않았습니다. 주간 기록까지 함께 검증했다면 이 문제를 6월에 발견할 수 있었습니다. 이번 수정에서 계산 로직을 분리하고 주간·야간 기록으로 테스트를 작성한 것도 같은 이유입니다.
야간 근무 여부는 현장에서 직접 설정해야 합니다. 기록만으로는 판정할 수 없기 때문입니다. 1공수 기준 시간은 10분 단위로 입력받습니다. 계산부가 정수로 처리해 소수점이 잘리기 때문이며, 실무에서 쓰는 정시·30분 단위에는 영향이 없습니다.
공무링크 · 2025.12 / 2026.09
공무링크는 서비스워커로 화면 파일을 캐시합니다. 또한 정부기관 신고 자동화를 위해 사용자 PC의 공무헬퍼(사례 03)를 로컬 포트로 호출합니다.
서비스워커가 모든 요청을 가로채 네트워크로 전달하고, 실패하면 빈 503 응답을 반환하는 구조였습니다. 공무헬퍼 호출과 외부 도메인(카카오 등) 요청도 여기에 포함돼, 헬퍼 호출이 실패하면 실제 오류 대신 503이 반환됐습니다.
서비스워커가 처리하는 요청을 같은 출처의 GET 요청으로 제한하고, 헬퍼 포트·외부 도메인·POST 요청은 브라우저 기본 동작으로 넘기도록 변경했습니다.
1차 수정에서 추가한 '새 버전 감지 시 새로고침'이 기존 '설치 즉시 활성화' 설정과 겹쳐 로그인 화면이 반복해서 새로고침됐습니다. 새로고침을 1회로 제한하는 표시를 페이지 변수에 저장한 것이 원인이었습니다. 새로고침하면 변수가 초기화되어 제한이 적용되지 않았습니다. 즉시 활성화를 제거하고 1회 표시를 sessionStorage에 저장하도록 바꿔 1주일 뒤 수정했습니다.
배포 다음 날 아침마다 "클릭해도 반응이 없다"는 증상이 나타났습니다. 서비스워커가 설치 시 파일 358개(71.57MB)를 한 번에 내려받고 있었고, 캐시 이름에 버전이 포함돼 배포할 때마다 전체를 다시 받았습니다. 여기에는 대부분의 회원이 열지 않는 43.6MB 소개 영상도 포함돼 있었습니다. 첫 접속이 몰리는 아침에 이 다운로드가 브라우저 연결을 점유했습니다.
| 항목 | 기존 | 변경 |
|---|---|---|
| 설치 시 받는 범위 | 빌드 파일 전체 (358개) | 앱 뼈대(진입 스크립트 2개, CSS 73개)와 오류 화면만. 나머지는 요청 시 캐시에 저장 |
| 설치 시 등록 방식 | 일괄 등록(addAll). 파일 하나만 실패해도 설치 전체 실패 | 파일별 등록. 실패한 파일만 제외하고 설치 |
해시가 붙은 정적 파일에는 이미 1년짜리 브라우저 캐시(Cache-Control: max-age=31536000, immutable)가 적용돼 있어, 설치 시 전체를 미리 받는 것은 중복이었습니다. 요청 시 캐시에 저장하는 로직도 이미 있어 추가 구현은 필요하지 않았습니다.
등록 방식은 일부 파일이 실패했을 때를 기준으로 정했습니다. 서비스워커 설치 자체가 실패하는 것보다, 받은 파일만 등록하고 나머지는 요청 시 채우는 쪽을 선택했습니다.
1차 회귀는 캐시·업데이트 방식을 바꾸면서 로그인처럼 새로고침이 걸린 흐름을 확인하지 않아 생겼습니다. 2차 대응에서는 서비스워커 캐시와 브라우저 캐시의 역할이 겹치는지부터 확인했습니다.
공무헬퍼 · 2024.12 ~ 2026.09
공무링크는 웹 서비스라 마우스·키보드 제어나 브라우저 자동화 권한이 없습니다. 그래서 외부 기관 사이트의 신고 작업은 사용자 PC에 설치된 공무헬퍼가 로컬 포트로 요청을 받아 처리합니다. 대상은 근로복지공단 토탈서비스(고용·산재보험 신고), 건강보험·국민연금 전자신고(EDI), 건설근로자 전자카드이며, 자동화 엔드포인트는 53개입니다.
외국인 근로자 정보를 공단 시스템과 대조하는 작업은 요청 1건이 끝날 때까지 브라우저가 응답을 기다리는 구조였습니다. 50~150명을 한 번에 처리하면 5분을 넘겨 연결이 끊겼습니다.
요청 1건에 작업 전체를 싣는 구조를 없애기로 했습니다. 진행 상황을 서버에서 보내는 WebSocket도 검토했지만, 에이전트가 단순한 HTTP 리스너여서 실시간 연결 계층을 추가하는 비용이 크다고 판단해 폴링을 선택했습니다.
"마지막 1~3명을 남기고 멈춘다"는 보완 요청이 들어왔습니다. 헬퍼와 화면이 처리 수를 다르게 계산한 것이 원인이었습니다. 헬퍼는 성공한 인원만 처리 수에 포함했고, 화면은 처리 수가 전체 인원과 같아질 때 결과를 가져갔습니다. 그래서 주민등록번호 오류 등으로 실패한 인원이 1명이라도 있으면 헬퍼는 작업을 마쳤지만 화면은 완료를 인식하지 못했습니다.
10~20명 단위로 나눠 보내는 방안도 검토했으나 보류했습니다. 멈춤의 원인이 아니었고, 묶음마다 토탈서비스 메뉴 이동이 반복되어 처리 시간이 늘어나기 때문입니다.
150명 이상 일괄 처리가 끊김 없이 운영되고 있습니다. 처리 중에는 진행 인원이 표시되고, 실패 인원이 있어도 작업이 끝까지 진행된 뒤 실패·미진행 인원만 다시 대사할 수 있습니다.
진행 상황을 숫자로만 주고받으면 양쪽의 해석이 다를 때 완료를 판정할 수 없습니다. 그래서 완료 여부를 상태 값으로 명시하도록 바꿨고, 사용자 PC에 이미 설치된 구버전과의 호환도 함께 처리했습니다.
개인 프로젝트
규칙을 찾는 퍼즐은 예시를 모두 설명하면서 다른 답을 내는 규칙이 하나라도 있으면 정답이 하나로 정해지지 않습니다. 검수 에이전트 도입 전에 등록된 문제 330개는 이런 반례 검사를 거치지 않았고, 8일 동안 발견된 오류 7건(사용자 제보 3건, 자체 검산 4건)이 모두 여기서 나왔습니다.
세분화한 항목의 예: 더 단순한 성질로도 설명되는지, 사전에 등재된 낱말인지, 해설이 순환논증인지
문제 181개를 재검수해 오류 20건을 찾았고, 19건은 바로 수정했습니다. 나머지 1건은 당일 출제 중이어서 다음 날 수정했습니다. 예를 들어 시소 문제는 예시 그림이 물리적으로 반대로 그려져 있었습니다. 좌표로 모멘트를 계산하면 왼쪽 24, 오른쪽 12로 왼쪽이 내려가야 하는데 그림은 오른쪽이 내려가 있었습니다. 수정할 때는 정답은 유지하고 예시·그림·해설만 고쳐 정답이 하나가 되도록 했습니다.
검사기는 반례를 찾는 도구이며, 오류가 없음을 증명하지는 못합니다. '반례 없음'은 준비한 규칙 목록으로 반례를 찾지 못했다는 의미이고, 이 점을 스크립트 상단에 명시했습니다.
개인 프로젝트
지인 승무원들은 기존 스케줄 앱을 쓰고 있었지만, 같이 일한 크루를 찾아보는 기능이 없다는 불만이 있었습니다. 이 기능을 중심으로, 항공사 스케줄 시스템에서 받은 파일을 가져와 달력으로 보여 주는 앱을 만들었습니다.
| 항목 | 결정 |
|---|---|
| 크루 기록 저장 위치 | 같이 일한 크루 기록은 서버에 올리지 않고 사용자 기기에만 저장. 서버에는 동료 이름이 남지 않음 |
| 사진 인식 | 유료 AI API 대신 브라우저 안에서 도는 무료 한국어 OCR 사용. 운영 비용 0원을 목표로 함 |
| 로그인 | 소셜 로그인은 보류하고 복구 코드·친구 코드로 대체. 계정 ID는 로그인 방식과 분리 |
| 가져오기 경로 | 승무원이 실제로 받는 세 가지 형태(월간 파일, 상세 화면 PDF, 캡처 사진)를 모두 지원 |
| 범위 | 1차(가져오기·달력·크루 기록·친구 비교)와 2차(알림·레이오버 정보 등)까지 웹으로 만들고, 스토어 앱은 그 이후로 미룸 |
기능마다 화면 시안을 먼저 만들어 확인한 뒤 구현을 맡겼습니다. 설계 문서, 작업별 구현, 리뷰 순서로 진행합니다. 구현은 Claude Code가 하고, 저는 설계와 시안을 확정하고 나온 코드를 읽으며 검수합니다. React는 이 과정에서 익히고 있습니다.
2026년 9월 14일 설계를 시작했습니다. 주요 기능은 같이 일한 크루 검색, 친구와 스케줄 비교·모두 비는 시간 찾기, 출근 시각(쇼업) 알림, 레이오버 도시 정보입니다.
개발 방식
2024년 말부터 ChatGPT를 질문 용도로 사용했고(답변 코드는 직접 옮겨 적용), 2026년 초부터 Claude Code로 개발하고 있습니다. 사례 01과 사례 02의 2차 대응은 Claude Code로 구현했으며, 사례 02의 1차 대응과 사례 03의 1·2단계는 그 이전 작업입니다.
근태 규칙 수정(사례 01)은 테스트 통과 외에, 운영 데이터에서 오계산되던 날이 모두 정상 계산되는지 대조했습니다. 공무링크의 Playwright 회귀 테스트는 2026년 9월 137건에서 225건으로 늘렸습니다.
AI 에이전트의 역할을 나눠 코드를 수정하는 역할과 완료를 판정하는 역할을 분리했습니다. 작업을 만든 쪽이 스스로 통과 판정을 내리지 못하게 하기 위해서입니다.
기능이 동작하지 않는데 테스트 7개가 모두 통과한 일이 있었습니다. 계획·코드·테스트가 같은 가정에서 나와 같은 오류를 공유하고 있었습니다. 이후 판정 기준을 통과 여부에서 실제로 실행한 내용으로 바꾸고, 변경을 되돌렸을 때 테스트가 실패하는지 확인하는 절차를 추가했습니다.
오류가 155건인데 성공으로 끝나는 타입 검사, 여러 줄 SQL을 놓치는 검색처럼 도구가 잘못된 결과를 낸 사례를 기록해 두고 검증할 때 참고합니다.