<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>회몬 · JH Soft Labs</title>
    <link>https://jhsoftlabs.com/</link>
    <description>CSV·Excel 실무 가이드와 직접 만들며 겪은 개발 기록. 새 글은 실제 게시일 순으로 전합니다.</description>
    <language>ko-KR</language>
    <lastBuildDate>Mon, 19 Oct 2026 15:00:00 GMT</lastBuildDate>
    <atom:link href="https://jhsoftlabs.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Excel 16자리 이상 숫자 ID의 끝자리가 바뀔 때</title>
      <link>https://jhsoftlabs.com/data-guides/excel-long-identifiers</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/excel-long-identifiers</guid>
      <pubDate>Mon, 19 Oct 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header"><p class="eyebrow">EXCEL · IDENTIFIER PRECISION</p><h1>16글자인데,<br />같은 ID가 아니었습니다.</h1><p class="story-intro">긴 상품 코드가 1.23E+15처럼 보이는 일과 끝자리 자체가 달라진 일은 다릅니다. 열 너비나 표시 서식으로 해결되는지, 이미 원본으로 돌아가야 하는 상태인지 먼저 구분하세요. 식별자라면 숫자로 계산할 필요가 있는지도 다시 확인합니다.</p><div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>11년차 개발자 · Column Harbor 운영</span></div><time datetime="2026-10-20">2026. 10. 20.</time></div></header>
        <aside class="note-summary" aria-label="이 글의 기준"><span class="eyebrow">원본이 먼저입니다</span><ol><li>Excel 숫자는 유효숫자 15자리 정밀도 제한이 있습니다.</li><li>숫자로 변환한 뒤 Text 서식을 줘도 잃은 끝자리는 복원되지 않습니다.</li><li>LEN뿐 아니라 ISTEXT와 원본 문자열의 EXACT를 함께 확인합니다.</li></ol></aside>
        <div class="story-layout"><nav class="story-toc" aria-label="목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/excel-long-identifiers#display">표시와 값 구분</a><a href="https://jhsoftlabs.com/data-guides/excel-long-identifiers#sample">16자리 재현 실습</a><a href="https://jhsoftlabs.com/data-guides/excel-long-identifiers#import">원본에서 다시 가져오기</a><a href="https://jhsoftlabs.com/data-guides/excel-long-identifiers#browser">브라우저 대안</a><a href="https://jhsoftlabs.com/data-guides/excel-long-identifiers#verify">실제 결과와 완료 기준</a></nav><div class="story-content">
          <section id="display"><p class="story-section-number">01 / DISPLAY OR DAMAGE</p><h2>지수 표시만 보고<br />손상 여부를 판단하지 않습니다</h2><p>열이 좁거나 일반 형식을 사용하면 긴 숫자가 지수 표기로 보일 수 있습니다. 표시만 바뀐 경우에는 열을 넓히고 수식 입력줄을 확인하면 원래 값이 남아 있을 수 있습니다. 하지만 유효숫자가 15자리를 넘는 식별자를 숫자로 취급하면서 끝자리가 사라졌다면 표시 형식의 문제가 아닙니다.</p><p><a href="https://support.microsoft.com/en-gb/excel/keeping-leading-zeros-and-large-numbers" target="_blank" rel="noopener noreferrer">Microsoft의 큰 숫자·앞자리 0 안내<span class="sr-only"> (새 탭)</span></a>도 숫자 정밀도와 텍스트 보존을 구분합니다. 상품 ID처럼 더하거나 평균을 내지 않는 값은 Text를 먼저 검토하세요. 큰 금액·측정값처럼 계산이 필요한 수라면 이 글의 식별자 보존법으로 정밀한 계산까지 해결된다고 보면 안 됩니다.</p></section>
          <section id="sample"><p class="story-section-number">02 / REPRODUCE</p><h2>길이는 16으로 같아도<br />EXACT는 FALSE일 수 있습니다</h2><p>빈 시트 A2에 <code>=1234567890123456</code>을 입력합니다. B2는 입력 전에 셀 표시 형식을 Text로 지정하고 <code>1234567890123456</code>을 입력합니다. 이 합성 숫자는 실제 고객·계정 번호가 아닙니다. Text 대신 앞에 작은따옴표를 붙여 입력할 수도 있지만, 이미 손상된 셀 뒤에 붙이는 조치는 복구가 아닙니다.</p><p>C2에는 <code>=TEXT(A2,"0")</code>, D2에는 <code>=EXACT(C2,B2)</code>를 넣습니다. E2는 <code>=LEN(C2)</code>, F2는 <code>=LEN(B2)</code>, G2는 <code>=ISTEXT(B2)</code>입니다. TEXT는 현재 숫자를 문자열로 표시할 뿐 원본을 되살리지 않습니다.</p><figure class="sample-block"><figcaption>2026. 10. 20. 실제 Excel 계산 결과 · 합성 입력</figcaption><p class="sample-scroll-hint">표는 좌우로 스크롤할 수 있습니다. Tab으로 진입한 뒤 방향키로 확인하세요.</p><div class="sample-table-wrap" role="region" aria-label="16자리 숫자와 Text 보존 결과 비교" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">경로</th><th scope="col">비교할 문자열</th><th scope="col">LEN</th><th scope="col">판단</th></tr></thead><tbody><tr><td>숫자 → TEXT</td><td>1234567890123450</td><td>16</td><td>끝자리가 달라짐</td></tr><tr><td>처음부터 Text</td><td>1234567890123456</td><td>16</td><td>원본과 같음</td></tr><tr><td>EXACT(C2,B2)</td><td>FALSE</td><td>—</td><td>길이만으로 검증 불가</td></tr></tbody></table></div></figure><p>같은 길이라는 이유로 검증을 끝내면 이 차이를 놓칩니다. 서로 다른 두 원본이 같은 숫자로 바뀌면 조회나 중복 검사 결과도 달라질 수 있습니다. 표시된 자릿수를 맞추거나 뒤에 0을 추측해 넣는 방법은 원본을 증명하지 못합니다.</p></section>
          <section id="import"><p class="story-section-number">03 / IMPORT AGAIN</p><h2>변환되기 전 원본으로 돌아가<br />식별자 열만 Text로 지정합니다</h2><p>CSV가 이미 손상됐다면 덮어쓰지 말고 닫습니다. 수정하지 않은 원본 CSV나 시스템에서 다시 내보낸 파일을 확보하세요. 백업 없이 끝자리를 알아낼 수 없는 상태는 수식으로 복구한다고 약속할 수 없습니다.</p><figure class="sample-block"><figcaption>입력 CSV · 더블클릭 대신 Text 가져오기로 확인</figcaption><pre class="sample-code" tabindex="0" aria-label="긴 식별자 실습 CSV">item_id,quantity
1234567890123456,2
1234567890123450,1
0000123456789012,3</pre><p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/long-id-example.csv" download>합성 CSV 내려받기</a></p></figure><ol class="story-flow"><li><strong>Data → From Text/CSV로 원본을 선택합니다.</strong><span>구분자와 인코딩을 확인합니다. 이 예제는 쉼표, 두 열, 데이터 세 행입니다.</span></li><li><strong>Transform Data에서 자동 자료형 변환을 확인합니다.</strong><span>Changed Type 단계가 item_id를 숫자로 바꿨다면 그 단계를 제거한 뒤, 변환 전 값에서 item_id를 Text로 지정합니다. 숫자로 손상된 결과를 다시 Text로 바꾸는 것과 다릅니다.</span></li><li><strong>수량은 별도로 숫자로 지정합니다.</strong><span>Close &amp; Load 후 item_id 세 문자열을 원본과 대조합니다. quantity 합계는 6이어야 합니다. ID를 합산하거나 숫자로 바꿔 비교하지 않습니다.</span></li></ol><p>일부 최신 Excel에는 Automatic Data Conversions 옵션이 있지만 버전·진입 경로에 따라 다릅니다. 이 글은 해당 옵션이 항상 있거나 모든 가져오기에 적용된다고 가정하지 않습니다. 작업 절차의 기준은 <a href="https://support.microsoft.com/en-us/excel/format-numbers-as-text" target="_blank" rel="noopener noreferrer">Microsoft의 숫자를 Text로 다루는 안내<span class="sr-only"> (새 탭)</span></a>와 사용 환경을 함께 확인하세요.</p></section>
          <section id="browser"><p class="story-section-number">04 / BROWSER ALTERNATIVE</p><h2>보존용 XLSX가 필요하다면<br />모든 셀을 Text로 만드는 대안도 있습니다</h2><p><a href="https://csv.jhsoftlabs.com/workspace/csv-to-excel" target="_blank" rel="noopener noreferrer">Column Harbor CSV→Excel 도구<span class="sr-only"> (영문, 새 탭)</span></a>에 위 CSV를 붙여넣고 Comma로 해석한 뒤 미리보기 세 행과 두 열을 대조할 수 있습니다. XLSX 사본은 브라우저에서 만들어지며 원본 CSV를 덮어쓰지 않습니다. 내려받은 Data 시트에서 item_id 값과 ISTEXT를 확인하세요.</p><div class="story-callout"><strong>quantity도 Text입니다.</strong><p>이 경로는 모든 셀을 텍스트로 보존하는 대안입니다. 자동으로 계산용 파일을 완성해주지는 않습니다. 계산하려면 수량 같은 열만 별도 검토해 숫자로 바꿔야 합니다. 이미 1234567890123450으로 바뀐 입력의 끝자리를 복원하지도 않습니다.</p></div></section>
          <section id="verify"><p class="story-section-number">05 / OBSERVED RESULT</p><h2>손상된 값이 아닌<br />원본 문자열과 대조합니다</h2><p>Windows Excel 16.0 build 5574.0의 별도 통합문서에서 숫자 리터럴, 미리 Text로 정한 셀, TEXT·LEN·EXACT·ISTEXT를 계산했습니다. 숫자에서 만든 문자열은 1234567890123450, Text 셀은 1234567890123456이었고 둘의 길이는 모두 16이었습니다. EXACT는 FALSE, Text 셀의 ISTEXT는 TRUE였습니다.</p><p>실행한 것은 Excel COM의 고정 수식 계산과 Text 셀 값 지정입니다. 리본 메뉴 클릭, CSV 가져오기, 브라우저 도구의 XLSX 내보내기, 저장 후 재열기나 모든 Excel 버전을 이번에 새로 검증한 결과가 아닙니다. 위 가져오기·브라우저 경로를 사용했다면 자신의 결과 파일을 따로 대조해야 합니다.</p><ol class="story-flow"><li><strong>첫 행뿐 아니라 값의 충돌도 확인합니다.</strong><span>첫 두 ID의 끝자리가 6과 0으로 구별되고, 세 번째 ID의 앞자리 0이 남아 있어야 합니다.</span></li><li><strong>문자열과 계산 열을 나눕니다.</strong><span>ID는 정확히 보존하고 quantity만 수치로 검토합니다. LEN만 맞거나 합계만 맞는 것으로 끝내지 않습니다.</span></li><li><strong>왕복 저장은 별도 확인합니다.</strong><span>CSV에는 Excel 셀 자료형이 저장되지 않습니다. 내보낸 원문에 문자가 남는지 확인하고 다시 가져올 때도 Text 규칙을 적용합니다.</span></li></ol></section>
        </div></div>
      ]]></description>
    </item>
    <item>
      <title>Excel TRIM으로 지워지지 않는 공백 해결하기</title>
      <link>https://jhsoftlabs.com/data-guides/excel-trim-spaces</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/excel-trim-spaces</guid>
      <pubDate>Mon, 19 Oct 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header"><p class="eyebrow">EXCEL · TEXT CLEANUP</p><h1>TRIM으로 안 지워지는 공백,<br />같은 빈칸이 아닙니다.</h1><p class="story-intro">앞뒤가 비어 보이는데 조회가 실패하거나 길이가 예상보다 길다면, 공백의 종류부터 확인하세요. TRIM을 반복해도 바뀌지 않는 값에는 다른 문자가 들어 있을 수 있습니다. 원본 옆에 비교 열을 만들어 원인을 좁혀봅니다.</p><div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>11년차 개발자 · Column Harbor 운영</span></div><time datetime="2026-10-20">2026. 10. 20.</time></div></header>
        <aside class="note-summary" aria-label="이 글의 기준"><span class="eyebrow">먼저 구분할 것</span><ol><li>일반 공백 32와 NBSP 160은 같은 문자가 아닙니다.</li><li>사람이 읽는 이름과 정확히 보존해야 하는 ID는 정리 규칙이 다릅니다.</li><li>이 수식은 모든 유니코드 공백을 제거하는 만능 해결책이 아닙니다.</li></ol></aside>
        <div class="story-layout"><nav class="story-toc" aria-label="목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/excel-trim-spaces#diagnose">공백 종류 확인</a><a href="https://jhsoftlabs.com/data-guides/excel-trim-spaces#sample">4개 값으로 비교</a><a href="https://jhsoftlabs.com/data-guides/excel-trim-spaces#formula">대체 수식 적용</a><a href="https://jhsoftlabs.com/data-guides/excel-trim-spaces#boundaries">ID와 다른 공백</a><a href="https://jhsoftlabs.com/data-guides/excel-trim-spaces#observed">실제 계산과 완료 기준</a></nav><div class="story-content">
          <section id="diagnose"><p class="story-section-number">01 / DIAGNOSE</p><h2>눈으로 보기 전에<br />문자 코드를 확인합니다</h2><p>TRIM은 일반 공백인 ASCII 32를 정리합니다. 앞뒤 공백을 없애고 단어 사이의 연속 공백을 한 칸으로 줄입니다. 웹페이지에서 가져온 값에 자주 섞이는 NBSP는 U+00A0, 십진수 160입니다. Microsoft도 TRIM만으로 NBSP를 제거하지 못한다고 <a href="https://support.microsoft.com/en-us/excel/functions/trim-function" target="_blank" rel="noopener noreferrer">TRIM 문서<span class="sr-only"> (새 탭)</span></a>에서 구분합니다.</p><p>A3의 첫 문자가 의심된다면 <code>=UNICODE(LEFT(A3,1))</code>로 읽습니다. 160이면 NBSP이며 32라면 일반 공백입니다. 끝 문자라면 LEFT 대신 RIGHT를 씁니다. 빈 셀에는 이 검사가 오류를 낼 수 있으므로 비어 있지 않은 표본에서 시작하세요. 첫 문자 코드만 같다고 셀 전체가 같다는 뜻도 아닙니다.</p></section>
          <section id="sample"><p class="story-section-number">02 / REPRODUCE</p><h2>원본 옆에서<br />네 가지 값을 나란히 봅니다</h2><p>빈 시트의 A1에 <code>raw_text</code>를 쓰고 A2:A5에 아래 수식을 각각 입력합니다. 눈에 보이지 않는 문자를 직접 복사하지 않아도 같은 입력을 만들 수 있습니다. 인수 구분자가 세미콜론인 환경에서는 쉼표를 맞춰 바꾸세요.</p><figure class="sample-block"><figcaption>A2부터 한 줄씩 입력할 합성 수식</figcaption><pre class="sample-code" tabindex="0" aria-label="TRIM 실습 입력 수식">="  Seoul  Center  "
=UNICHAR(160)&amp;"Seoul"&amp;UNICHAR(160)&amp;"Center"&amp;UNICHAR(160)
="AB  12"
=UNICHAR(8239)&amp;"Seoul"&amp;UNICHAR(8239)</pre><p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/trim-example.tsv" download>같은 값이 담긴 합성 TSV 내려받기</a></p></figure><p>TSV의 첫 열에는 실제 공백 문자들이 들어 있습니다. 입력 수식으로 만들거나, TSV를 UTF-8·탭 구분·Text로 가져온 뒤 A열 값이 같은지 확인하세요. 파일 가져오기 UI는 이번 실행 검증 범위가 아닙니다. 이 예제의 값은 모두 합성 자료이며 고객 이름이나 실제 상품 코드가 아닙니다.</p></section>
          <section id="formula"><p class="story-section-number">03 / COMPARE</p><h2>NBSP를 일반 공백으로 바꾼 뒤<br />TRIM을 적용합니다</h2><p>B2에는 <code>=TRIM(A2)</code>, C2에는 <code>=TRIM(SUBSTITUTE(A2,UNICHAR(160)," "))</code>를 입력하고 5행까지 채웁니다. SUBSTITUTE의 마지막 인수는 빈 문자열이 아니라 일반 공백 한 칸입니다. 이름 사이의 NBSP를 없애 버리면 SeoulCenter처럼 두 단어가 붙기 때문입니다.</p><p>D2에는 <code>=LEN(A2)</code>, E2에는 <code>=LEN(B2)</code>, F2에는 <code>=LEN(C2)</code>를 입력해 원본·TRIM·대체 후 길이를 비교합니다. G2의 <code>=EXACT(A2,C2)</code>는 대소문자까지 같은지 확인합니다. 길이가 줄었다는 사실만으로 정리가 올바르다고 판정하지 않습니다.</p><figure class="sample-block"><figcaption>2026. 10. 20. 실제 Excel 계산 결과 · 합성 데이터</figcaption><p class="sample-scroll-hint">표는 좌우로 스크롤할 수 있습니다. Tab으로 진입한 뒤 방향키로 확인하세요.</p><div class="sample-table-wrap" role="region" aria-label="공백 정리 전후 길이 비교" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">입력</th><th scope="col">원본 길이</th><th scope="col">TRIM 길이</th><th scope="col">160 대체 후 길이</th><th scope="col">판단</th></tr></thead><tbody><tr><td>일반 공백 · A2</td><td>17</td><td>12</td><td>12</td><td>Seoul Center</td></tr><tr><td>NBSP · A3</td><td>14</td><td>14</td><td>12</td><td>160 대체가 필요</td></tr><tr><td>합성 ID · A4</td><td>6</td><td>5</td><td>5</td><td>원본과 다름</td></tr><tr><td>좁은 NBSP · A5</td><td>7</td><td>7</td><td>7</td><td>이 수식으로 안 바뀜</td></tr></tbody></table></div></figure></section>
          <section id="boundaries"><p class="story-section-number">04 / BOUNDARIES</p><h2>공백을 줄이면<br />잘못되는 값도 있습니다</h2><p>A4의 <code>AB  12</code>는 가운데 공백 두 개가 포함된 합성 ID입니다. TRIM 결과는 <code>AB 12</code>로 바뀌고 EXACT는 FALSE가 됩니다. 원본 시스템이 두 값을 다른 키로 취급한다면 정리가 아니라 손상입니다. 표시용 이름을 고치는 규칙을 계정·상품·주문 ID에 그대로 적용하지 마세요.</p><p>A5의 U+202F는 코드 8239인 좁은 NBSP입니다. 160만 대체하는 이 수식으로는 남습니다. 코드 8203의 폭 없는 공백 등 다른 문자도 별개의 문제입니다. 문자를 확인하고 업무 규칙에 맞는 대체를 하나씩 정해야 합니다. 한꺼번에 모든 공백을 지우거나 눈에 보이지 않는다는 이유만으로 삭제하면 의미 있는 구분까지 잃습니다.</p><p>CLEAN을 추가하면 모두 해결될 것이라고 가정하지도 않습니다. <a href="https://support.microsoft.com/en-us/excel/functions/clean-function" target="_blank" rel="noopener noreferrer">Microsoft CLEAN 문서<span class="sr-only"> (새 탭)</span></a>는 처리하는 비인쇄 문자 범위를 별도로 설명합니다. 원본 열을 남겨 두고, 바뀐 행 수와 새로 충돌하는 키를 확인한 다음 결과 열만 사용하세요.</p></section>
          <section id="observed"><p class="story-section-number">05 / VERIFY</p><h2>수식이 성공한 것과<br />내 데이터가 안전한 것은 다릅니다</h2><p>Windows Excel 16.0 build 5574.0의 별도 빈 통합문서에서 위 입력 수식과 TRIM·SUBSTITUTE·LEN·EXACT·UNICODE를 계산했습니다. NBSP 행은 14→14→12, 좁은 NBSP 행은 7→7→7이었고, 합성 ID는 원본과 달라졌습니다. 이 가이드의 원본 문자열과 결과를 실제 셀 값으로 대조했습니다.</p><div class="story-callout"><strong>이번 검증은 Excel COM 계산입니다.</strong><p>리본 메뉴 클릭, TSV 붙여넣기/가져오기, 저장 후 재열기나 Mac·웹 Excel을 검증한 결과는 아닙니다. 현재 설치 환경에서 수식이 지원되는지 확인하고 복사본의 표본부터 비교하세요.</p></div><ol class="story-flow"><li><strong>원본과 결과를 함께 보관합니다.</strong><span>새 열에서 정리하고 원본 열을 덮어쓰지 않습니다.</span></li><li><strong>사라진 것이 어떤 문자였는지 확인합니다.</strong><span>NBSP인지 다른 유니코드 문자인지, 정리 후 단어가 붙지 않았는지 봅니다.</span></li><li><strong>조회 키의 충돌을 검사합니다.</strong><span>기존에 다른 ID였던 두 행이 같은 값이 되면 정리 기준부터 다시 결정합니다. 병합 결과 행 수·합계도 비교합니다.</span></li></ol></section>
        </div></div>
      ]]></description>
    </item>
    <item>
      <title>만들 수 있다는 것과 팔 수 있다는 것은 달랐다</title>
      <link>https://jhsoftlabs.com/stories/deep-constellation-retrospective</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/deep-constellation-retrospective</guid>
      <pubDate>Fri, 02 Oct 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">RETROSPECTIVE · DEEP CONSTELLATION</p>
        <h1>만들 수 있다는 것과 팔 수 있다는 것은 달랐다</h1>
        <p class="story-intro">사이트와 이미지, 홍보 영상까지 만들었지만 수입원은 만들지 못했습니다. 추가 활동을 멈추며 개발로 완성한 것과 고객에게 확인하지 못한 것을 돌아봤습니다. 다음에는 무엇부터 검증할지 정리한 회고입니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-10-03">2026. 10. 03.</time></div>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">멈추며 남긴 기준</span><ol><li>수익을 만들지 못했고 수익성이 낮다고 판단해 추가 활동을 중단했습니다.</li><li>기술적 완성도와 고객의 구매는 서로 다른 증거였습니다.</li><li>다음에는 기능보다 고객 행동, 작은 거래, 중단 기준을 먼저 확인하려 합니다.</li></ol><p>기존 사이트와 제작 기록은 남깁니다. 추가 활동 중단은 사이트 삭제나 시장 전체에 수요가 없다는 판정을 뜻하지 않습니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#result">만든 것과 얻지 못한 것</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#means">문제보다 수단을 먼저 선택했다</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#polish">완성도를 수요의 증거로 착각했다</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#promotion">홍보와 할인은 답이 아니었다</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#ai">AI와 협업할 때의 맹점</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#next">다음에는 순서를 바꾸겠다</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#stop">남길 것과 멈출 것</a><a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective#questions">시작 전에 물을 다섯 가지</a></nav>
        <div class="story-content">
          <section id="result" aria-labelledby="result-title"><p class="story-section-number">01 / OUTCOME</p><h2 id="result-title">만든 것과 얻지 못한 것</h2><p>AI로 이미지를 만들고 필요한 사람에게 판매하면 작은 수입원을 만들 수 있지 않을까. Deep Constellation은 그 생각에서 출발했습니다. 미용실과 뷰티숍을 대상으로 헤어 이미지, 홍보 콘텐츠, 게시물 디자인을 제공하려 했습니다.</p><p>사이트와 도메인, 견적과 문의 흐름, 이미지 예시와 홍보 영상을 준비했습니다. 인스타그램, 유튜브, 틱톡에 콘텐츠를 게시하며 실제 운영 단계까지 갔습니다. 하지만 확인된 유료 고객이나 재구매 실적을 확보하지 못했고 수익도 만들지 못했습니다.</p><p>2026년 10월 3일, 수익성이 낮다고 판단해 추가 활동을 중단했습니다. 제가 선택한 상품과 방식으로는 지속 가능한 수입원을 검증하지 못했습니다. 정확한 사업 손익과 전체 투입 시간은 측정하지 않았으므로 손실 규모를 임의의 숫자로 제시하지 않습니다.</p><div class="story-callout"><strong>개발 경험을 얻었다는 사실로 수익화 실패를 덮지 않기.</strong><p>처음 목표는 공부만 하는 것이 아니라 수입원을 만드는 것이었습니다. 반대로 수익을 만들지 못했다고 개발 경험까지 없던 일로 만들 필요도 없습니다.</p></div></section>
          <section id="means" aria-labelledby="means-title"><p class="story-section-number">02 / CUSTOMER PROBLEM</p><h2 id="means-title">문제보다 수단을 먼저 선택했다</h2><p>처음에는 고객이 이미 겪고 있는 문제보다 내가 가진 수단을 먼저 봤습니다. AI 도구를 사용할 수 있고 웹사이트를 개발할 수 있으니 이 능력을 상품으로 만들려 했습니다. 하지만 내가 할 수 있는 일과 고객이 돈을 내고 맡기고 싶은 일은 같지 않았습니다.</p><p>미용실에 홍보가 필요하다고 해서 모든 미용실이 AI 이미지를 구매해야 하는 것은 아닙니다. 실제 시술 사진이 더 중요하거나 직접 만드는 것으로 충분할 수 있습니다. 콘텐츠보다 예약 응대, 촬영, 재방문 관리가 더 급한 문제일 수도 있습니다. 저는 이미지 제작을 먼저 정답으로 정하고 필요한 고객을 찾는 순서로 움직였습니다.</p><p>인스타그램에서 디자이너들이 직접 AI 이미지를 만드는 사례도 접했습니다. 그 관찰만으로 업계 전체의 외주 수요가 사라졌다고 말할 수는 없습니다. 다만 고객도 같은 도구를 쓸 수 있을 때 제가 대신 제작할 이유가 무엇인지 다시 묻게 됐습니다. 결과의 품질, 줄어든 시간, 수정과 검수에서 대신 지는 책임을 실제 납품과 고객의 선택으로 입증해야 했습니다.</p></section>
          <section id="polish" aria-labelledby="polish-title"><p class="story-section-number">03 / FALSE PROGRESS</p><h2 id="polish-title">완성도를 수요의 증거로 착각했다</h2><p>사이트를 개선하는 일은 명확했습니다. 깨진 이미지와 모바일 화면을 고치고 견적 경로를 정리하면 눈에 보이는 변화가 생겼습니다. 테스트를 통과하고 배포된 페이지를 확인하면 완료 여부를 판단할 수 있었습니다.</p><p>고객이 구매할 이유를 검증하는 일은 불확실했습니다. 답장이 없을 수 있고 의견을 들어도 구매로 이어지지 않을 수 있었습니다. 확신이 부족할 때마다 고객 검증을 더 깊게 하기보다 결과가 눈에 보이는 개발과 콘텐츠 제작을 선택하기 쉬웠습니다.</p><p>9월 초 분석에는 무료 제작 도구와의 경쟁, 구매 의향의 미확인, 수정 시간과 납품 원가의 미측정이 이미 기록돼 있었습니다. 위험을 몰랐던 것이 아니라 다음 단계로 넘어가기 위한 통과 조건으로 충분히 쓰지 못했습니다. 앞으로는 분석 문서의 양보다 그 분석 때문에 어떤 작업을 하지 않기로 결정했는지를 남기려 합니다.</p><div class="story-callout"><strong>사이트가 더 좋아졌다는 것과 사업이 더 좋아졌다는 것은 달랐습니다.</strong><p>기술적 완성도는 거래를 받을 준비를 설명하지만 거래가 일어날 이유까지 증명하지는 못합니다.</p></div></section>
          <section id="promotion" aria-labelledby="promotion-title"><p class="story-section-number">04 / LIMITED SIGNALS</p><h2 id="promotion-title">홍보와 할인은 답이 아니었다</h2><p>이미지 제작에서 실제 매장 공지와 문구를 정리하는 방향으로 범위를 바꿨습니다. 피드 이미지 한 장과 짧은 게시글 문구를 제공하는 19,000원 상품도 준비했습니다. 진입 부담을 줄이는 실험이었지만 가격을 낮추는 일이 필요성을 만들지는 않았습니다.</p><p>9월 30일 점검 기록에서 이전 유튜브 홍보 영상은 조회 95회, 계속 시청 비율 4.1%였습니다. 확인 범위에서 새로운 상담 답장도 보이지 않았습니다. 그래서 가격 설명보다 완성 디자인을 먼저 보여주고 영상을 짧게 만드는 개선을 진행했습니다. 이 수치는 10월 3일에 다시 조회한 최신 지표가 아닙니다.</p><p>이 관찰은 영상의 초기 이탈을 개선할 근거는 될 수 있지만 가격이 실패 원인이라거나 시장에 수요가 없다는 결론의 근거는 아닙니다. 표본이 작고 적합한 고객에게 얼마나 도달했는지, 방문이 상담으로 얼마나 이어졌는지도 충분히 확인하지 못했습니다.</p><p>피드를 잘 운영하는 디자이너는 콘텐츠의 중요성을 알지만 직접 제작할 능력과 체계를 갖춘 사람일 수도 있었습니다. 보기 좋은 잠재 고객 목록과 구매 가능성이 높은 목록은 같지 않았습니다. “우리 이미지를 좋아할까?”보다 “지금 어떤 작업을 어떻게 처리하고, 무엇이 불편한가?”라는 질문이 필요했습니다.</p></section>
          <section id="ai" aria-labelledby="ai-title"><p class="story-section-number">05 / AI COLLABORATION</p><h2 id="ai-title">AI와 협업할 때의 맹점</h2><p>AI 코딩 에이전트와 개발, 문서화, 이미지 제작, 영상 구성, 홍보 문구를 함께 작업했습니다. 혼자였다면 오래 걸렸을 일을 빠르게 진행할 수 있었습니다. 그 속도는 장점이었지만 잘못된 가정 위에서도 빠르게 움직일 수 있다는 점은 충분히 경계하지 못했습니다.</p><p>“다음에 무엇을 만들면 좋을까?”라고 물으면 다음 기능과 콘텐츠를 제안받을 수 있었습니다. 실행할 일이 계속 생기면 앞으로 나아가는 느낌을 받기 쉽습니다. 그러나 할 일이 있다는 사실은 고객이 있다는 사실이 아니었습니다. AI의 긍정적인 평가와 설득력 있는 사업 설명도 고객의 결제를 대신할 수 없습니다.</p><p>다음에는 현재 결론에 불리한 근거, 검증되지 않은 가정, 기능을 추가하지 않아도 되는 대안, 중단을 고려할 조건도 함께 묻겠습니다. 제안받은 작업도 고객 학습에 기여하지 않는다면 보류할 수 있어야 합니다.</p></section>
          <section id="next" aria-labelledby="next-title"><p class="story-section-number">06 / A DIFFERENT ORDER</p><h2 id="next-title">다음에는 순서를 바꾸겠다</h2><ol class="story-flow"><li><strong>최근의 반복 문제부터 듣기</strong><span>“있으면 쓰겠어요?” 대신 최근 한 달에 몇 번 했고 어떻게 해결했으며 시간이나 외주 비용이 얼마나 들었는지 묻겠습니다. 현재 쓰는 대안을 먼저 확인합니다.</span></li><li><strong>개발 대신 작은 거래부터 시도하기</strong><span>고객이 이미 가진 자료로 좁은 결과물을 만들고 범위·비용·일정·수정 조건을 먼저 합의합니다. 이행할 수 없는 약속으로 결제만 받아 검증하지 않습니다.</span></li><li><strong>첫 구매와 반복 가능성을 구분하기</strong><span>검수 통과, 감당할 수 있는 총 작업 시간, 다시 맡길 필요를 확인하기 전에는 월 구독이나 자동화 플랫폼부터 만들지 않습니다.</span></li><li><strong>상담부터 전달까지 원가 기록하기</strong><span>생성뿐 아니라 자료 확인, 선별, 수정, 파일 정리와 전달을 합칩니다. 공통 홍보 시간과 도구 비용은 따로 기록해 주문 효율과 사업 전체 수익성을 구분합니다.</span></li><li><strong>확장 전에 예산과 중단 기준 정하기</strong><span>예를 들어 2주·총 10시간 이내로 첫 검증을 제한할 수 있습니다. 반복 문제, 현재 대안의 불편, 자료 제공이나 좁은 유료 작업처럼 행동으로 드러난 관심이 확인되지 않으면 확장하지 않습니다.</span></li></ol><p>구매 의사결정자 3명에게 짧게 최근 경험을 듣는 것부터 시작할 수 있습니다. 인원과 기간은 시장 평균이나 통계적 검증선이 아니라 검증을 기능 개발로 대체하지 않기 위한 개인적인 시작 규칙입니다. 대화를 거의 하지 못했다면 “수요가 없다”가 아니라 “아직 검증하지 못했다”고 기록하겠습니다.</p><p>상업적 사용 범위와 자료 권한, 재현 가능한 품질도 카탈로그에 올리기 전에 확인하겠습니다. 이미 구독 중인 도구를 쓴다고 실제 비용을 0원으로 놓지 않고 상담·수정까지 측정할 방법을 먼저 정하겠습니다.</p></section>
          <section id="stop" aria-labelledby="stop-title"><p class="story-section-number">07 / KEEP AND STOP</p><h2 id="stop-title">남길 것과 멈출 것</h2><p>배포와 로컬 환경의 차이, 공개 파일과 내부 도구의 경계, 이미지 무결성 검사, 수정 가능한 디자인 원본, 영상 생성과 공개 완료의 상태 구분은 개발 경험으로 남습니다. 사이트와 제작 도구, 문서와 홍보물도 포트폴리오로 보존합니다.</p><p>하지만 이미지 생성 능력만을 앞세워 확장하지 않겠습니다. 구체적인 고객 요구가 확인되지 않은 상태에서 제작량과 기능 수를 늘리는 추가 활동은 멈춥니다. 많이 만들었다는 이유만으로 다음 시간까지 투입하지 않으려 합니다.</p><div class="story-callout"><strong>고객이 지금 돈이나 시간을 쓰는 문제부터 확인하자.</strong><p>다음 프로젝트의 첫 성과는 페이지나 기능 수가 아니라 고객의 실제 행동을 근거로 내리는 결정이었으면 합니다. 만들지 않기로 결정하는 것도 포함해서요.</p></div></section>
          <section id="questions" aria-labelledby="questions-title"><p class="story-section-number">TAKE IT WITH YOU</p><h2 id="questions-title">시작 전에 물을 다섯 가지</h2><ol class="story-flow"><li><strong>누구의 어떤 반복 문제인가?</strong><span>최근 실제 사례를 설명할 수 있는가.</span></li><li><strong>지금은 어떻게 해결하는가?</strong><span>왜 기존 방법을 바꾸려 하는가.</span></li><li><strong>기능 없이 확인할 행동은 무엇인가?</strong><span>호의적인 답변과 실제 행동을 구분할 수 있는가.</span></li><li><strong>제공 범위와 원가는 확인했는가?</strong><span>권한을 확인했고 상담·수정까지 측정할 방법이 있는가.</span></li><li><strong>언제 확장하지 않거나 멈출 것인가?</strong><span>시간을 쓰기 전에 중단 기준을 정했는가.</span></li></ol></section>
          <aside class="story-next" aria-label="함께 읽을 기록"><a href="https://jhsoftlabs.com/stories/salon-publishing">제작·게시·배포에서 배운 것 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline">영상 검수와 공개 상태를 나눈 기록 <span aria-hidden="true">→</span></a></aside>
          <footer class="story-end"><p>2026년 10월 3일 프로젝트 운영자가 작성한 회고와 추가 활동 중단 결정을 바탕으로 공개용으로 정리했습니다. 채널 수치는 9월 30일 점검 기록입니다. 내부 문서와 고객 연락처는 공개하지 않았으며, 확인하지 않은 손실·투입 시간·시장 규모를 만들지 않았습니다.</p><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/#project-image">프로젝트 소개로 돌아가기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>CSV가 Excel에서 한 열로 열릴 때</title>
      <link>https://jhsoftlabs.com/data-guides/csv-one-column</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/csv-one-column</guid>
      <pubDate>Fri, 18 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header">
          <p class="eyebrow">CSV IMPORT · DELIMITER</p>
          <h1>CSV가 Excel에서<br />한 열로 열릴 때</h1>
          <p class="story-intro">
            파일이 망가졌다고 단정하기 전에 원본 텍스트의 구분자와 따옴표를
            확인하세요. 원본 파일을 다시 가져올 수도 있고 A열에 온전한
            레코드가 남아 있다면 그 열을 나눌 수도 있습니다.
          </p>
          <div class="story-byline">
            <img
              src="https://jhsoftlabs.com/assets/hoemon-profile.jpg"
              alt="회몬 캐릭터"
              width="40"
              height="40"
            />
            <div>
              <strong>회몬</strong
              ><span>11년차 개발자 · Column Harbor 운영</span>
            </div>
            <time datetime="2026-09-19">2026. 09. 19.</time>
          </div>
          <p class="story-revision">수정: <time datetime="2026-10-19">2026. 10. 19.</time> · 미리보기에서 데이터 변환으로 넘어가는 단계와 자료형 지정 순서를 보완했습니다.</p>
        </header>
        <aside class="note-summary">
          <span class="eyebrow">먼저 기억할 것</span>
          <ol>
            <li>복사본의 원본 텍스트에서 실제 구분자를 찾습니다.</li>
            <li>식별자는 Text로, 숫자는 목적에 맞는 자료형으로 가져옵니다.</li>
            <li>레코드 수·열 수·문장 부호·앞자리 0을 함께 확인합니다.</li>
          </ol>
          <p>
            예시는 합성 데이터입니다. 메뉴 이름은 Excel 버전에 따라 다를 수
            있습니다.
          </p>
        </aside>
        <div class="story-layout">
          <nav class="story-toc" aria-label="목차">
            <span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/csv-one-column#cause">왜 한 열로 열리는가</a
            ><a href="https://jhsoftlabs.com/data-guides/csv-one-column#inspect">원본에서 구분자 확인</a
            ><a href="https://jhsoftlabs.com/data-guides/csv-one-column#import">원본 파일 다시 가져오기</a
            ><a href="https://jhsoftlabs.com/data-guides/csv-one-column#split">Text to Columns 사용 조건</a
            ><a href="https://jhsoftlabs.com/data-guides/csv-one-column#verify">결과 검증</a><a href="https://jhsoftlabs.com/data-guides/csv-one-column#native-evidence">실제 Excel에서 확인한 결과</a>
          </nav>
          <div class="story-content">
            <section id="cause">
              <p class="story-section-number">01 / CAUSE</p>
              <h2>쉼표 파일이라고<br />추측하지 않습니다</h2>
              <p>
                CSV라는 이름은 흔히 쉼표로 구분된 파일을 뜻하지만, 실제 업무
                시스템은 지역 설정이나 내보내기 규칙 때문에 세미콜론이나 탭을
                사용할 수 있습니다. Excel이 예상한 구분자와 파일의 실제 구분자가
                다르면 한 줄 전체가 셀 하나에 들어갑니다. 이때 확장자를 바꾸거나
                모든 문장 부호를 일괄 치환하면 데이터 내용까지 바뀔 수 있습니다.
              </p>
              <p>
                특히 설명 필드 안의 쉼표는 구분자가 아닐 수 있습니다. 따옴표로
                감싼 <code>"Mug, blue"</code>의 쉼표는 제품 설명의 일부입니다.
                어떤 기호가 자주 보인다는 이유만으로 구분자로 선택하면 열이
                과도하게 늘어납니다.
              </p>
            </section>
            <section id="inspect">
              <p class="story-section-number">02 / INSPECT</p>
              <h2>작은 합성 예제로<br />경계를 먼저 봅니다</h2>
              <p>
                복사본을 메모장 같은 일반 텍스트 편집기로 열고 헤더와 두세 줄을
                확인합니다. 다음 예제는 세미콜론으로 나뉜 세 열을 만듭니다.
              </p>
              <div class="story-callout">
                <strong>sku;description;price</strong>
                <p>00123;"Mug, blue";12.50<br />00456;"Plate; small";8.00</p>
              </div>
              <figure class="sample-block">
                <figcaption>원본 텍스트 · 아래 세 줄을 그대로 복사할 수 있습니다</figcaption>
                <pre class="sample-code" tabindex="0" aria-label="세미콜론 CSV 합성 입력">sku;description;price
00123;"Mug, blue";12.50
00456;"Plate; small";8.00</pre>
                <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/semicolon-example.csv" download>합성 CSV 내려받기</a></p>
              </figure>
              <p>
                헤더에서 <code>sku</code>, <code>description</code>,
                <code>price</code> 사이를 나누는 기호는 세미콜론입니다. 첫
                설명의 쉼표와 둘째 설명의 세미콜론은 각각 값 안의 문장
                부호입니다. 둘째 설명은 따옴표 안에 있으므로 세미콜론 구분자를
                써도 하나의 필드로 남아야 합니다.
              </p>
            </section>
            <section id="import">
              <p class="story-section-number">03 / IMPORT</p>
              <h2>가장 안전한 경로는<br />원본을 다시 가져오는 것입니다</h2>
              <ol class="story-flow">
                <li>
                  <strong>새 통합문서에서 Data → From Text/CSV를 엽니다.</strong
                  ><span
                    >실패한 시도가 정리한 표를 덮어쓰지 않도록 새 시트나 새
                    파일을 사용합니다.</span
                  >
                </li>
                <li>
                  <strong
                    >미리보기에서 Delimiter를 Semicolon으로 지정합니다.</strong
                  ><span
                    >세 열이 보이고 <code>Plate; small</code>이 하나의 설명에
                    남는지 확인합니다.</span
                  >
                </li>
                <li>
                  <strong>데이터 변환(Transform Data)을 눌러 편집기를 엽니다.</strong>
                  <span>미리보기에서 바로 로드하지 않습니다. Power Query 편집기가 열리면 오른쪽 적용된 단계(Applied Steps)를 확인합니다. 메뉴 이름은 설치 언어와 Excel 버전에 따라 다를 수 있습니다.</span>
                </li>
                <li>
                  <strong>sku 열은 Text로 지정합니다.</strong
                  ><span
                    >자동 변경된 유형(Changed Type) 단계가 sku를 숫자로 바꿨다면 먼저 그 단계를 삭제합니다. 원본의 00123이 다시 보이는지 확인한 뒤 sku 열의 데이터 형식을 텍스트(Text)로 지정합니다. 이미 숫자가 된 123을 Text로 바꾸기만 해서는 앞자리 0이 돌아오지 않습니다.</span
                  >
                </li>
                <li>
                  <strong
                    >price만 숫자로 변환하고 Close &amp; Load를
                    선택합니다.</strong
                  ><span
                    >숫자 표시가 12.5로 보여도 값 12.50과 수치상 같을 수
                    있으므로 서식과 값 변환을 구분합니다.</span
                  >
                </li>
              </ol>
            </section>
            <section id="split">
              <p class="story-section-number">04 / ALTERNATIVE</p>
              <h2>Text to Columns는<br />레코드가 온전할 때만</h2>
              <p>
                각 A열 셀에 한 레코드가 완전히 남아 있다면 Data → Text to
                Columns → Delimited 경로를 사용할 수 있습니다. 원본 열을
                복사하고 빈 출력 위치를 정한 뒤, 세미콜론과 double quote 텍스트
                한정자를 선택합니다. 마지막 단계에서 sku 열을 Text로 지정합니다.
              </p>
              <p>
                따옴표 안에 줄바꿈이 있어 이미 여러 worksheet 행으로 갈라졌거나,
                식별자의 앞자리 0이 사라졌거나, 날짜가 이미 변환됐다면 이 경로로
                원래 값을 복원할 수 없습니다. 그때는 수정하지 않은 원본 파일로
                돌아가 From Text/CSV를 사용해야 합니다.
              </p>
            </section>
            <section id="verify">
              <p class="story-section-number">05 / VERIFY</p>
              <h2>열이 생긴 것만으로<br />성공이라 하지 않습니다</h2>
              <p>
                예상 결과는 데이터 레코드 2개, 열 3개, sku 00123과 00456, 설명
                <code>Mug, blue</code>와 <code>Plate; small</code>, 가격 합계
                20.50입니다. 합계만 맞아도 설명이나 ID가 손상될 수 있으므로 각
                항목을 대조합니다.
              </p>
              <figure class="sample-block">
                <figcaption>예상 결과 · sku 열을 Text로 지정한 뒤</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="세미콜론 CSV 예상 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">sku</th><th scope="col">description</th><th scope="col">price</th></tr></thead><tbody><tr><td>00123</td><td>Mug, blue</td><td>12.50</td></tr><tr><td>00456</td><td>Plate; small</td><td>8.00</td></tr></tbody></table></div>
              </figure>
              <p>
                실제 파일이 크다면 첫 줄과 마지막 줄, 따옴표·문장 부호·빈 필드가
                있는 레코드를 표본으로 확인합니다. 원본을 함께 보관하면 다음
                새로고침에서 가져오기 설정이 달라졌는지 비교할 수 있습니다.
              </p>
              <div class="story-next">
                <a class="button button--outline" href="https://support.microsoft.com/en-us/excel/get-started/import-or-export-text-txt-or-csv-files" target="_blank" rel="noopener noreferrer">Microsoft 파일 가져오기 안내 <span aria-hidden="true">↗</span></a>
                <a
                  class="button button--dark"
                  href="https://csv.jhsoftlabs.com/guides/csv-opens-in-one-column"
                  target="_blank"
                  rel="noopener noreferrer"
                  >다운로드 예제가 있는 영문 가이드
                  <span aria-hidden="true">↗</span></a
                ><span>Column Harbor의 로컬 CSV 점검기와 합성 샘플</span>
              </div>
            </section>
            <section id="native-evidence">
              <p class="story-section-number">06 / OBSERVED RESULT</p>
              <h2>같은 파일, 다른 자료형으로<br />직접 가져와 비교했습니다</h2>
              <p>2026년 10월 5일, Windows Excel 16.0 build 5569.0에서 위의 <a href="https://jhsoftlabs.com/data-guides/semicolon-example.csv" download>세미콜론 CSV</a>를 실제 Excel QueryTables 텍스트 가져오기 엔진으로 읽었습니다. UTF-8, 세미콜론 구분자, 큰따옴표 한정자, 소수점 <code>.</code>을 명시하고 첫 열만 General과 Text로 바꿔 비교했습니다.</p>
              <p class="sample-scroll-hint">넓은 표는 좌우로 스크롤할 수 있습니다. 키보드는 Tab으로 표 영역에 이동한 뒤 좌우 방향키를 누르세요.</p>
              <div class="sample-table-wrap" role="region" aria-label="General과 Text 가져오기 실제 관측 비교 표" tabindex="0"><table class="sample-table"><caption>같은 합성 입력의 실제 관측값</caption><thead><tr><th scope="col">확인 항목</th><th scope="col">sku: General</th><th scope="col">sku: Text</th></tr></thead><tbody><tr><th scope="row">데이터 레코드 / 열</th><td>2개 / 3개</td><td>2개 / 3개</td></tr><tr><th scope="row">첫 번째 sku 실제 값</th><td>숫자 123</td><td>문자열 00123</td></tr><tr><th scope="row">두 번째 sku 실제 값</th><td>숫자 456</td><td>문자열 00456</td></tr><tr><th scope="row">description 두 값</th><td>Mug, blue / Plate; small</td><td>Mug, blue / Plate; small</td></tr><tr><th scope="row">price 두 값</th><td>12.5 / 8</td><td>12.5 / 8</td></tr></tbody></table></div>
              <p>두 설정 모두 세 열과 가격은 맞았습니다. 그러나 General에서는 상품 코드가 달라졌습니다. “열이 나뉘었고 합계가 맞으니 성공”으로 끝내지 않고, 식별자까지 확인해야 하는 이유입니다.</p>
              <h3>내 파일에서 확인할 수식</h3>
              <p>위 샘플이 A1:C3에 들어왔다면 빈 셀에서 다음 세 수식을 각각 실행하세요. Text로 보존된 첫 번째 코드는 <code>TRUE</code>, <code>5</code>, <code>TRUE</code>가 나와야 합니다. General로 변환된 값은 보존된 원본에서 다시 가져옵니다.</p>
              <pre class="sample-code" tabindex="0" aria-label="상품 코드 보존 확인 수식">=ISTEXT(A2)
=LEN(A2)
=EXACT(A2,"00123")</pre>
              <div class="story-callout"><strong>검증 범위는 여기까지입니다.</strong><p>이 결과는 COM 자동화로 실행한 QueryTables 경로의 관측입니다. Power Query나 Text to Columns 화면을 클릭한 검증이 아니며, CSV 더블클릭의 기본 동작이나 모든 Excel 버전을 보장하지 않습니다. 앞의 메뉴 절차와 이 엔진 검증은 구분해야 합니다.</p></div>
              <p><a href="https://learn.microsoft.com/en-us/office/vba/api/excel.querytable.textfilecolumndatatypes" target="_blank" rel="noopener noreferrer">Microsoft: 텍스트 가져오기 열 자료형 <span aria-hidden="true">↗</span></a> · <a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#native-evidence">표시 서식만 바꾼 대조 실험 보기 →</a></p>
            </section>
          </div>
        </div>
      ]]></description>
    </item>
    <item>
      <title>Excel FILTER #SPILL 오류 안전하게 해결하기</title>
      <link>https://jhsoftlabs.com/data-guides/excel-filter-spill</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/excel-filter-spill</guid>
      <pubDate>Fri, 18 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header">
          <p class="eyebrow">EXCEL · DYNAMIC ARRAYS</p>
          <h1>FILTER #SPILL 오류를<br />데이터 삭제 없이 해결하기</h1>
          <p class="story-intro">
            FILTER는 셀 하나가 아니라 여러 셀의 결과 영역을 만듭니다. 수식이
            맞아도 그 영역에 값·공백·병합 셀·표 경계가 있으면 펼칠 수 없습니다.
            먼저 Excel이 알려 주는 구체적인 원인을 확인합니다.
          </p>
          <div class="story-byline">
            <img
              src="https://jhsoftlabs.com/assets/hoemon-profile.jpg"
              alt="회몬 캐릭터"
              width="40"
              height="40"
            />
            <div>
              <strong>회몬</strong
              ><span>11년차 개발자 · Column Harbor 운영</span>
            </div>
            <time datetime="2026-09-19">2026. 09. 19.</time>
          </div>
          <p class="story-revision">수정: <time datetime="2026-09-27">2026. 09. 27.</time> · 합성 예제와 예상 결과를 보강했습니다.</p>
        </header>
        <aside class="note-summary">
          <span class="eyebrow">적용 범위</span>
          <ol>
            <li>
              Microsoft 365, Excel 2024 또는 Excel 2021의 FILTER를 기준으로
              합니다.
            </li>
            <li>원본 통합문서의 복사본에서 시험합니다.</li>
            <li>막힌 셀을 옮긴 뒤 결과 레코드와 합계를 대조합니다.</li>
          </ol>
        </aside>
        <div class="story-layout">
          <nav class="story-toc" aria-label="목차">
            <span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/excel-filter-spill#meaning">#SPILL의 의미</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-filter-spill#diagnose">구체적인 오류 진단</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-filter-spill#sample">5행 재현 예제</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-filter-spill#table">Table·병합 셀</a><a href="https://jhsoftlabs.com/data-guides/excel-filter-spill#verify">결과 검증</a>
          </nav>
          <div class="story-content">
            <section id="meaning">
              <p class="story-section-number">01 / MEANING</p>
              <h2>수식의 문법과<br />출력 공간은 별개입니다</h2>
              <p>
                <code>FILTER</code>, <code>SORT</code>, <code>UNIQUE</code> 같은
                동적 배열 수식은 결과 크기에 맞춰 아래와 옆 셀로 펼쳐집니다.
                결과가 3행 3열이면 수식을 입력한 셀을 포함해 9개의 셀이
                필요합니다. 그중 하나라도 비어 있지 않으면 수식 셀에는 #SPILL이
                표시될 수 있습니다.
              </p>
              <p>
                다만 #SPILL의 원인이 항상 막힌 셀은 아닙니다. 결과가 워크시트
                끝을 넘어가거나, 크기를 결정할 수 없는 수식이거나, Excel Table
                안에서 동적 배열을 입력한 경우도 있습니다. 경고 메뉴의 설명을
                먼저 읽고 원인에 맞는 조치를 선택합니다.
              </p>
            </section>
            <section id="diagnose">
              <p class="story-section-number">02 / DIAGNOSE</p>
              <h2>보이지 않는 빈칸도<br />수식 입력줄에서 확인합니다</h2>
              <p>
                수식 셀을 선택하고 경고 아이콘을 엽니다. 막힌 범위 문제라면
                Excel 버전에 따라 Select Obstructing Cells를 제공할 수 있습니다.
                해당 명령으로 선택된 셀을 Formula Bar에서 확인합니다. 화면상
                비어 보여도 공백 한 칸이나 <code>=""</code>을 반환하는 수식이
                있을 수 있습니다.
              </p>
              <p>
                메모나 기존 수식을 안전한 위치로 옮기거나 FILTER 수식을 충분히
                빈 영역으로 옮깁니다. 주변 영역 전체를 선택해 Delete를 누르면
                보이지 않던 업무 규칙이나 기록까지 지울 수 있으므로 피합니다.
                오류 상세가 다른 원인을 말한다면 막힌 셀 찾기를 억지로 적용하지
                않습니다.
              </p>
            </section>
            <section id="sample">
              <p class="story-section-number">03 / REPRODUCE</p>
              <h2>5행 예제로<br />막힘과 정상 결과를 비교합니다</h2>
              <p>
                빈 시트 A1:C6에 헤더 <code>record_id, region, hours</code>와
                다음 다섯 행을 입력합니다.
              </p>
              <div class="story-callout">
                <strong>R1, North, 2 · R2, South, 5 · R3, North, 0</strong>
                <p>R4, North, 4<br />R5, West, 3</p>
              </div>
              <figure class="sample-block">
                <figcaption>입력값 · 탭으로 구분되어 A1 셀에 붙여넣을 수 있습니다</figcaption>
                <pre class="sample-code" tabindex="0" aria-label="FILTER 실습 TSV 입력">record_id	region	hours
R1	North	2
R2	South	5
R3	North	0
R4	North	4
R5	West	3</pre>
                <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/filter-example.tsv" download>합성 TSV 내려받기</a></p>
              </figure>
              <p>
                K2에 <code>North</code>를 입력하고 F1:H1에 헤더 세 개를
                복사합니다. F2 한 셀에만
                <code>=FILTER(A2:C6,B2:B6=K2,"No matches")</code>를 입력합니다.
                설치 환경에 따라 인수 구분자는 쉼표 대신 세미콜론일 수 있습니다.
              </p>
              <p>
                정상 결과는 F2:H4의 R1, R3, R4이며 hours는 2, 0, 4입니다. 합계는
                6이고 0시간 레코드도 남아야 합니다. 오류를 재현하려면 수식 입력
                전에 G3에 <code>note</code>를 넣습니다. FILTER 입력 후 G3의
                note를 F2:H4 밖으로 이동하면 세 행이 펼쳐져야 합니다.
              </p>
              <figure class="sample-block">
                <figcaption>F2:H4 예상 결과 · G3의 note를 옮긴 뒤</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="FILTER 예상 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">record_id</th><th scope="col">region</th><th scope="col">hours</th></tr></thead><tbody><tr><td>R1</td><td>North</td><td>2</td></tr><tr><td>R3</td><td>North</td><td>0</td></tr><tr><td>R4</td><td>North</td><td>4</td></tr></tbody></table></div>
              </figure>
            </section>
            <section id="table">
              <p class="story-section-number">04 / STRUCTURE</p>
              <h2>
                원본은 Table이어도 되지만<br />출력 수식은 일반 범위에 둡니다
              </h2>
              <p>
                FILTER의 source가 Excel Table인 것은 유용합니다. 원본 행이 늘 때
                구조화 참조가 확장됩니다. 그러나 spill 수식 자체를 Table 계산 열
                안에 넣으면 결과가 여러 행으로 펼쳐질 수 없습니다. Table 옆의
                일반 worksheet 범위에 수식을 두고 결과가 커질 공간을 확보합니다.
              </p>
              <p>
                결과 영역에 병합 셀이 있으면 다른 빈 영역을 쓰는 것이 가장
                안전합니다. 병합을 해제해야 한다면 병합 셀의 내용과 레이아웃
                목적을 먼저 검토합니다. 열 전체 참조 때문에 결과가 시트 끝을
                넘는 경우에는 실제 데이터 범위나 Table 열 참조로 범위를
                제한합니다.
              </p>
            </section>
            <section id="verify">
              <p class="story-section-number">05 / VERIFY</p>
              <h2>오류가 사라진 뒤에도<br />선택 조건을 확인합니다</h2>
              <p>
                spill 공간을 비웠다고 FILTER 조건이 맞아지는 것은 아닙니다.
                예제에서는 레코드 ID R1·R3·R4, region North, hours 합계 6을
                대조합니다. 0을 빈 값으로 오해해 R3을 제외하지 않았는지도
                확인합니다. 세 번째 인수 <code>"No matches"</code>는 일치 결과가
                없을 때 표시할 문구이며, 막힌 공간을 해결하지는 않습니다.
              </p>
              <p>
                다른 원인도 Microsoft의 #SPILL 오류 문서에서 확인할 수
                있습니다. 이 글에서는 합성 예제(fixture)로 레코드 선택 논리를 검사했습니다.
                모든 Excel 버전의 실제 UI를 실행해 검증한 것은 아닙니다.
              </p>
              <div class="story-next">
                <a
                  class="button button--dark"
                  href="https://support.microsoft.com/en-us/excel/how-to-correct-a-spill-error"
                  target="_blank"
                  rel="noopener noreferrer"
                  >Microsoft 공식 문제 해결
                  <span aria-hidden="true">↗</span></a
                ><a
                  class="button button--outline"
                  href="https://csv.jhsoftlabs.com/guides/excel-filter-spill-error"
                  target="_blank"
                  rel="noopener noreferrer"
                  >합성 파일이 있는 영문 가이드
                  <span aria-hidden="true">↗</span></a
                >
              </div>
            </section>
          </div>
        </div>
      ]]></description>
    </item>
    <item>
      <title>Excel에서 CSV 앞자리 0 지키기</title>
      <link>https://jhsoftlabs.com/data-guides/excel-leading-zeros</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/excel-leading-zeros</guid>
      <pubDate>Fri, 18 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header">
          <p class="eyebrow">EXCEL IMPORT · IDENTIFIERS</p>
          <h1>CSV의 앞자리 0을<br />Excel에서 지키기</h1>
          <p class="story-intro">
            상품 코드, 사번, 우편번호와 계정 번호는 식별자입니다.
            Excel이 00123을 123으로 바꾼 뒤에는 셀 서식만 고쳐서
            원래 자릿수를 알아낼 수 없습니다.
          </p>
          <div class="story-byline">
            <img
              src="https://jhsoftlabs.com/assets/hoemon-profile.jpg"
              alt="회몬 캐릭터"
              width="40"
              height="40"
            />
            <div>
              <strong>회몬</strong
              ><span>11년차 개발자 · Column Harbor 운영</span>
            </div>
            <time datetime="2026-09-19">2026. 09. 19.</time>
          </div>
          <p class="story-revision">수정: <time datetime="2026-10-19">2026. 10. 19.</time> · 브라우저에서 텍스트 보존용 XLSX를 만드는 대안과 결과 확인 절차를 추가했습니다.</p>
        </header>
        <aside class="note-summary">
          <span class="eyebrow">판단 기준</span>
          <ol>
            <li>덧셈·평균을 내지 않는 값은 식별자일 수 있습니다.</li>
            <li>
              손상 후 사용자 지정 서식은 표시만 복구할 뿐 원본을 증명하지
              못합니다.
            </li>
            <li>가져온 뒤 셀 값과 다시 저장한 파일을 함께 확인합니다.</li>
          </ol>
        </aside>
        <div class="story-layout">
          <nav class="story-toc" aria-label="목차">
            <span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#meaning">숫자와 식별자 구분</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#damage">서식만 바꾸면 부족한 이유</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#import">안전한 가져오기</a><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#browser-export">브라우저에서 XLSX 만들기</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#export">CSV로 다시 저장할 때</a
            ><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#check">검증 체크리스트</a><a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#native-evidence">표시와 값의 대조 실험</a>
          </nav>
          <div class="story-content">
            <section id="meaning">
              <p class="story-section-number">01 / MEANING</p>
              <h2>모양이 숫자여도<br />의미는 숫자가 아닐 수 있습니다</h2>
              <p>
                <code>00123</code>과 <code>123</code>은 계산에서는 같은 수지만
                상품 코드 체계에서는 다른 문자열일 수 있습니다. 앞자리 0은 길이
                규칙이나 지역·분류를 나타낼 수도 있습니다. 이 열을 합산할 필요가
                없다면 숫자로 자동 변환하는 이점보다 식별 정보 손실 위험이
                큽니다.
              </p>
              <p>
                먼저 데이터 사전을 확인합니다. 코드의 길이가 고정인지, 빈 값이
                가능한지, 문자와 하이픈이 섞일 수 있는지 기록합니다. 이 기준이
                있어야 123을 00123으로 만들 때 0을 두 개 붙이는 것이 맞는지
                판단할 수 있습니다.
              </p>
            </section>
            <section id="damage">
              <p class="story-section-number">02 / DAMAGE</p>
              <h2>이미 123이 됐다면<br />서식은 원본을 기억하지 못합니다</h2>
              <figure class="article-visual" id="value-visual">
                <a href="https://jhsoftlabs.com/assets/guide-display-vs-value.svg" target="_blank" rel="noopener noreferrer" aria-label="표시값과 실제 값 비교 이미지 크게 보기 (새 탭)"><img src="https://jhsoftlabs.com/assets/guide-display-vs-value.svg" alt="숫자 123에 00000 서식을 적용한 셀과 Text로 가져온 셀은 모두 00123으로 보입니다. 내부 값은 각각 숫자 123과 문자열 00123이며 ISTEXT 결과도 FALSE와 TRUE로 다릅니다." width="640" height="720" loading="lazy" decoding="async" /></a>
                <figcaption>그림 1. 화면이 같아도 셀의 값과 자료형은 다릅니다. <a href="https://jhsoftlabs.com/data-guides/excel-leading-zeros#native-evidence">아래 실제 Excel 관측값</a>을 다시 그린 설명 이미지이며 스크린샷은 아닙니다. <a href="https://jhsoftlabs.com/assets/guide-display-vs-value.svg" target="_blank" rel="noopener noreferrer">이미지 크게 보기 (새 탭)</a></figcaption>
              </figure>
              <p>
                Excel에서 사용자 지정 형식 <code>00000</code>을 적용하면
                화면에는 00123처럼 보일 수 있습니다. 하지만 셀의 실제 값은
                여전히 숫자 123입니다. 길이가 모두 5자리라는 명확한 계약이 있을
                때 표시용으로는 쓸 수 있지만, 원본이 0123인지 00123인지 알 수
                없는 상황을 복구하지는 못합니다.
              </p>
              <p>
                일반 더블클릭으로 CSV를 연 뒤 값이 변한 경우에는 파일을 저장하지
                말고 닫은 다음 수정하지 않은 원본으로 돌아가는 것이 안전합니다.
                저장해 버렸다면 버전 기록이나 원본 내보내기를 다시 확보합니다.
              </p>
            </section>
            <section id="import">
              <p class="story-section-number">03 / IMPORT</p>
              <h2>From Text/CSV에서<br />변환 전에 Text로 지정합니다</h2>
              <ol class="story-flow">
                <li>
                  <strong
                    >새 통합문서에서 Data → From Text/CSV를 선택합니다.</strong
                  ><span>원본 CSV를 직접 더블클릭하지 않습니다.</span>
                </li>
                <li>
                  <strong>구분자와 인코딩을 미리보기에서 확인합니다.</strong
                  ><span
                    >열이 어긋난 상태에서 자료형만 바꾸면 잘못된 필드를 보존하게
                    됩니다.</span
                  >
                </li>
                <li>
                  <strong
                    >Transform Data를 열고 식별자 열을 Text로
                    지정합니다.</strong
                  ><span
                    >자동 Changed Type 단계가 00123을 123으로 만든 뒤라면 그
                    단계를 제거하고 처음부터 다시 지정합니다.</span
                  >
                </li>
                <li>
                  <strong
                    >Close &amp; Load 후 수식 입력줄에서 실제 값을
                    확인합니다.</strong
                  ><span
                    >셀 폭이나 표시 형식에 의존하지 않고 00123 자체가 남았는지
                    봅니다.</span
                  >
                </li>
              </ol>
              <div class="story-callout">
                <strong>합성 확인 값</strong>
                <p>
                  00123, 00007, A-004를 가져왔을 때 세 값이 정확히 유지되어야
                  합니다. 숫자 합계를 내는 quantity 같은 열은 별도로 숫자
                  자료형을 적용합니다.
                </p>
              </div>
              <figure class="sample-block">
                <figcaption>입력 CSV · 파일을 더블클릭하지 말고 텍스트 가져오기를 사용하세요</figcaption>
                <pre class="sample-code" tabindex="0" aria-label="앞자리 0 보존 실습 CSV 입력">item_id,quantity
00123,2
00007,1
A-004,3</pre>
                <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/identifier-example.csv" download>합성 CSV 내려받기</a></p>
              </figure>
            </section>
            <section id="browser-export" aria-labelledby="browser-export-title">
              <p class="story-section-number">BROWSER ALTERNATIVE</p>
              <h2 id="browser-export-title">설치 없이 보존용 <br />XLSX 사본을 만들 수도 있습니다</h2>
              <p>CSV를 먼저 Excel로 여는 대신 Column Harbor의 <a href="https://csv.jhsoftlabs.com/workspace/csv-to-excel" target="_blank" rel="noopener noreferrer">CSV→Excel 도구 (영문, 새 탭)</a>로 텍스트 셀로 된 XLSX를 만들 수 있습니다. 가입이나 파일 업로드 없이 브라우저에서 처리합니다. 원본 CSV는 덮어쓰지 않습니다.</p>
              <ol class="story-flow">
                <li><strong>바로 위 CSV 예제를 붙여넣습니다.</strong><span>CSV to convert 입력란에 헤더와 세 행을 넣고 구분자는 Comma로 둡니다. 원본 파일을 쓸 때도 식별자가 이미 손상되지 않았는지 먼저 확인하세요.</span></li>
                <li><strong>Create Excel workbook을 누르고 미리보기를 대조합니다.</strong><span>데이터 3행, 열 2개인지 확인합니다. item_id는 00123, 00007, A-004이며 quantity는 2, 1, 3이어야 합니다.</span></li>
                <li><strong>Download XLSX로 사본을 내려받습니다.</strong><span>Data 시트의 A2에서 =ISTEXT(A2), =LEN(A2), =EXACT(A2,"00123")을 빈 셀에 각각 입력해 TRUE, 5, TRUE인지 확인합니다. 파일 생성 성공과 내 Excel 환경에서의 결과 확인은 별개입니다.</span></li>
              </ol>
              <div class="story-callout"><strong>quantity도 텍스트로 저장됩니다.</strong><p>이 경로는 모든 셀을 텍스트로 보존하므로 계산용 통합문서를 자동 완성하는 기능이 아닙니다. 합계가 필요하면 Excel에서 quantity 같은 계산 대상 열만 검토한 뒤 숫자로 변환하세요. 이미 123으로 바뀐 원본에서 00123을 복원해 주지는 않습니다. 앞의 Power Query 경로는 식별자만 Text로, 수량은 숫자로 지정한다는 차이가 있습니다.</p></div>
            </section>
            <section id="export">
              <p class="story-section-number">04 / EXPORT</p>
              <h2>XLSX와 CSV의<br />보존 범위가 다릅니다</h2>
              <p>
                XLSX는 셀의 자료형과 표시 서식을 함께 저장하지만 CSV는 텍스트
                값만 저장합니다. Excel 안에서 Text였던 00123을 올바르게 CSV로
                내보내면 문자 0이 파일에 남습니다. 그러나 그 CSV를 다시
                더블클릭하면 Excel이 또 숫자로 추론할 수 있습니다.
              </p>
              <p>
                따라서 저장 성공 여부는 Excel 화면만으로 확인하지 않습니다.
                내보낸 CSV 복사본을 텍스트 편집기로 열어 00123이 실제로 쓰였는지
                확인하고, 다시 가져올 때도 Text 규칙을 적용합니다. CSV를 전달할
                때는 식별자 열이라는 설명이나 가져오기 규칙을 함께 전달하는 편이
                안전합니다.
              </p>
            </section>
            <section id="check">
              <p class="story-section-number">05 / VERIFY</p>
              <h2>길이와 정확한 값,<br />왕복 저장을 확인합니다</h2>
              <p>
                표본 ID의 문자 길이, 원본과의 정확 일치, 빈 값 개수, 중복 개수를
                확인합니다. 아주 긴 숫자형 ID라면 Excel의 15자리 정밀도 한계도
                별도 문제이므로 반드시 Text로 다룹니다. 이미 16자리 뒤가 0으로
                바뀐 값은 표시 형식으로 복구할 수 없습니다.
              </p>
              <p>
                화면에 0이 보이더라도 파일과 셀의 실제 값에 0이 남아 있는지
                확인하세요. 이렇게 대조하면 가져오기와 내보내기 중
                어느 단계에서 값이 달라졌는지 구분할 수 있습니다.
              </p>
              <figure class="sample-block">
                <figcaption>Power Query 가져오기 예상 결과 · item_id는 Text, quantity는 숫자</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="Text 가져오기 예상 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">item_id</th><th scope="col">quantity</th><th scope="col">검증</th></tr></thead><tbody><tr><td>00123</td><td>2</td><td>5글자 보존</td></tr><tr><td>00007</td><td>1</td><td>5글자 보존</td></tr><tr><td>A-004</td><td>3</td><td>문자와 하이픈 보존</td></tr></tbody></table></div>
              </figure>
              <div class="story-next">
                <a class="button button--outline" href="https://support.microsoft.com/en-us/excel/text-import-wizard" target="_blank" rel="noopener noreferrer">Microsoft 자료형 지정 안내 <span aria-hidden="true">↗</span></a>
                <a
                  class="button button--dark"
                  href="https://csv.jhsoftlabs.com/guides/excel-leading-zeros"
                  target="_blank"
                  rel="noopener noreferrer"
                  >영문 가이드와 샘플 파일 <span aria-hidden="true">↗</span></a
                >
              </div>
            </section>
            <section id="native-evidence">
              <p class="story-section-number">06 / OBSERVED RESULT</p>
              <h2>같은 00123으로 보여도<br />셀 안의 값은 달랐습니다</h2>
              <p>2026년 10월 5일, Excel 16.0 build 5569.0에서 <a href="https://jhsoftlabs.com/data-guides/semicolon-example.csv" download>상품 코드가 들어 있는 합성 CSV</a>를 QueryTables 텍스트 가져오기로 읽었습니다. General로 변환한 셀과 Text로 보존한 셀을 비교한 뒤, 숫자 셀에만 사용자 지정 표시 형식 <code>00000</code>을 적용했습니다.</p>
              <p class="sample-scroll-hint">넓은 표는 좌우로 스크롤할 수 있습니다. 키보드는 Tab으로 표 영역에 이동한 뒤 좌우 방향키를 누르세요.</p>
              <div class="sample-table-wrap" role="region" aria-label="표시 문자열과 실제 값의 차이 비교 표" tabindex="0"><table class="sample-table"><caption>직접 읽은 표시 문자열과 셀 내부 값</caption><thead><tr><th scope="col">설정</th><th scope="col">표시 또는 문자열</th><th scope="col">내부 값</th><th scope="col">자료형</th></tr></thead><tbody><tr><th scope="row">General 가져오기</th><td>123</td><td>123</td><td>숫자</td></tr><tr><th scope="row">숫자에 00000 서식</th><td>00123</td><td>123</td><td>숫자</td></tr><tr><th scope="row">Text 가져오기</th><td>00123</td><td>00123</td><td>문자열</td></tr></tbody></table></div>
              <p>두 번째 셀의 표시 문자열은 <code>00123</code>이었지만, 실제 값은 여전히 숫자 123이었습니다. 다섯 자리가 업무 규칙으로 확정된 표시용 코드라면 이 서식이 유용할 수 있습니다. 다만 원래 값이 123, 0123, 00123 중 무엇이었는지를 복구했다는 증거는 아닙니다.</p>
              <h3>따라 해 볼 때의 완료 기준</h3>
              <ol class="story-flow"><li><strong>새 파일 두 개에 동일한 원본을 가져옵니다.</strong><span>한쪽은 식별자 열을 General, 다른 쪽은 Text로 지정합니다. 원본은 덮어쓰지 않습니다.</span></li><li><strong>General 쪽에만 00000 표시 서식을 줍니다.</strong><span>화면이 비슷해져도 수식 입력줄과 <code>=ISTEXT(A2)</code> 결과를 비교합니다. Text 쪽은 TRUE, 숫자 쪽은 FALSE여야 합니다.</span></li><li><strong>정확한 문자열과 길이를 대조합니다.</strong><span><code>=LEN(A2)</code>와 <code>=EXACT(A2,"00123")</code>를 함께 봅니다. 표시 형식이 아니라 원본 식별자 보존이 목표입니다.</span></li></ol>
              <div class="story-callout"><strong>관측과 미확인을 나눕니다.</strong><p>별도의 빈 통합문서에서 COM으로 가져오기와 표시 형식을 실행하고 값을 읽었습니다. 리본 메뉴 클릭, Power Query, 저장 후 재가져오기까지 검증한 결과는 아닙니다. CSV 왕복 저장은 앞 절의 절차대로 자신의 환경에서 별도로 확인하세요.</p></div>
              <p><a href="https://jhsoftlabs.com/data-guides/csv-one-column#native-evidence">같은 입력의 구분자·자료형 검증 전체 보기 →</a></p>
            </section>
          </div>
        </div>
      ]]></description>
    </item>
    <item>
      <title>Power Query 병합 후 행이 늘어날 때</title>
      <link>https://jhsoftlabs.com/data-guides/power-query-merge-rows</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/data-guides/power-query-merge-rows</guid>
      <pubDate>Fri, 18 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header">
          <p class="eyebrow">POWER QUERY · MERGE</p>
          <h1>병합했더니 행과 합계가<br />늘어났을 때</h1>
          <p class="story-intro">
            Left Outer 병합은 왼쪽 행마다 오른쪽의 모든 일치 레코드를
            연결합니다. 행 증가가 오류인지 정상적인 one-to-many 관계인지는
            보고서의 한 행이 무엇을 표현해야 하는지에 따라 달라집니다.
          </p>
          <div class="story-byline">
            <img
              src="https://jhsoftlabs.com/assets/hoemon-profile.jpg"
              alt="회몬 캐릭터"
              width="40"
              height="40"
            />
            <div>
              <strong>회몬</strong
              ><span>11년차 개발자 · Column Harbor 운영</span>
            </div>
            <time datetime="2026-09-19">2026. 09. 19.</time>
          </div>
          <p class="story-revision">수정: <time datetime="2026-10-05">2026. 10. 05.</time> · 파일 준비 없이 실행할 전체 쿼리와 실제 Excel 엔진 검증 결과를 추가했습니다.</p>
        </header>
        <aside class="note-summary">
          <span class="eyebrow">삭제 전에 확인</span>
          <ol>
            <li>중첩 테이블을 펼치기 전에 키별 매치 수를 셉니다.</li>
            <li>
              여러 주소·주문처럼 유효한 관계를 중복으로 오해하지 않습니다.
            </li>
            <li>한 행이 필요하면 명시적인 업무 규칙으로 한 건을 선택합니다.</li>
          </ol>
        </aside>
        <div class="story-layout">
          <nav class="story-toc" aria-label="목차">
            <span>이 글에서</span><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#why">행이 늘어나는 원리</a
            ><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#audit">펼치기 전 감사</a><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#choice">관계별 선택</a
            ><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#distinct">Remove Duplicates 주의</a
            ><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#verify">검증 항목</a><a href="https://jhsoftlabs.com/data-guides/power-query-merge-rows#runnable-query">직접 실행하는 병합 실험</a>
          </nav>
          <div class="story-content">
            <section id="why">
              <p class="story-section-number">01 / WHY</p>
              <h2>병합은 한 건만<br />골라 주지 않습니다</h2>
              <p>
                판매 테이블에 고객 C10의 판매가 두 건 있고 주소 테이블에 C10의
                유효한 주소가 두 건 있다면, 고객 ID로 병합한 뒤 주소를 펼칠 때
                각 판매가 주소 두 건과 결합됩니다. 판매 두 행이 네 행으로
                늘어나는 것은 병합 엔진이 중복을 만든 것이 아니라 요청한 키로
                가능한 모든 조합을 보여준 것입니다.
              </p>
              <p>
                합성 예제에서 판매는 네 건이고 금액 합계는 42입니다. 주소 매치
                수가 판매 순서대로 2, 2, 1, 0이라면 펼친 결과는 여섯 행이
                됩니다. 반복된 판매 금액을 다시 더하면 72로 보입니다. 이때 72는
                새 매출이 아니라 주소를 펼치면서 같은 판매가 반복된 결과입니다.
              </p>
              <figure class="article-visual" id="merge-visual">
                <a href="https://jhsoftlabs.com/assets/guide-merge-multiplication.svg" target="_blank" rel="noopener noreferrer" aria-label="병합 행 증가 설명 이미지 크게 보기 (새 탭)"><img src="https://jhsoftlabs.com/assets/guide-merge-multiplication.svg" alt="고객 C10의 S1과 S2가 각각 주소 A1·A2에 연결되어 두 판매의 금액이 30에서 60으로 반복됩니다. 나머지 판매를 포함한 전체 합계는 42에서 72로 보입니다." width="640" height="820" loading="lazy" decoding="async" /></a>
                <figcaption>그림 1. 같은 판매가 여러 주소와 연결되는 과정입니다. 본문의 합성 데이터를 그린 설명 이미지이며 Excel 스크린샷이 아닙니다. <a href="https://jhsoftlabs.com/assets/guide-merge-multiplication.svg" target="_blank" rel="noopener noreferrer">이미지 크게 보기 (새 탭)</a></figcaption>
              </figure>
              <figure class="sample-block">
                <figcaption>왼쪽 입력 · Sales (4행, 합계 42)</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="Sales 판매 입력 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">sale_id</th><th scope="col">customer_id</th><th scope="col">amount</th></tr></thead><tbody><tr><td>S1</td><td>C10</td><td>10</td></tr><tr><td>S2</td><td>C10</td><td>20</td></tr><tr><td>S3</td><td>C20</td><td>5</td></tr><tr><td>S4</td><td>C30</td><td>7</td></tr></tbody></table></div>
                <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/sales-example.csv" download>판매 CSV 내려받기</a></p>
              </figure>
              <figure class="sample-block">
                <figcaption>오른쪽 입력 · Addresses (C10에 두 주소)</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="Addresses 주소 입력 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">address_id</th><th scope="col">customer_id</th><th scope="col">city</th></tr></thead><tbody><tr><td>A1</td><td>C10</td><td>Seoul</td></tr><tr><td>A2</td><td>C10</td><td>Busan</td></tr><tr><td>A3</td><td>C20</td><td>Incheon</td></tr></tbody></table></div>
                <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/addresses-example.csv" download>주소 CSV 내려받기</a></p>
              </figure>
              <ol class="story-flow"><li><strong>두 CSV를 각각 Power Query로 가져옵니다.</strong><span>Excel의 Data → From Text/CSV에서 파일별로 Transform Data를 열고 customer_id를 두 쿼리에서 같은 Text 자료형으로 확인합니다.</span></li><li><strong>Sales를 왼쪽, Addresses를 오른쪽에 놓고 병합합니다.</strong><span>Home → Merge Queries에서 양쪽 customer_id를 선택하고 Join Kind는 Left Outer를 사용합니다.</span></li><li><strong>먼저 매치 수를 기록한 다음 Addresses를 펼칩니다.</strong><span>펼칠 때 address_id만 선택해 판매 ID별 행 수를 비교합니다. 일치하지 않는 C30 판매는 null 주소를 가진 채 남습니다.</span></li></ol>
            </section>
            <section id="audit">
              <p class="story-section-number">02 / AUDIT</p>
              <h2>중첩 테이블 상태에서<br />매치 수를 확인합니다</h2>
              <p>
                병합 직후에는 오른쪽 결과가 중첩 테이블 열에 들어 있습니다. 주소
                열 이름이 <code>Addresses</code>라면 Custom Column에
                <code>Table.RowCount([Addresses])</code>를 추가해 각 왼쪽 행의
                매치 수를 셉니다. 0은 미일치, 1은 단일 일치, 2 이상은 다중
                일치입니다.
              </p>
              <p>
                원래 판매 ID와 매치 수를 함께 남겨 두면 어떤 행이 늘어날지
                펼치기 전에 알 수 있습니다. 진단용으로 Keep Duplicates를
                사용한다면 원본 조회의 별도 Reference나 복사본에서만 사용합니다.
                Keep Duplicates 결과에는 유일한 키가 남지 않습니다. 이 결과를
                실제 업무의 병합에 쓰는 lookup으로 돌려 쓰면 정상 고객이 사라집니다.
              </p>
            </section>
            <section id="choice">
              <p class="story-section-number">03 / DECISION</p>
              <h2>원하는 출력의<br />행 의미를 결정합니다</h2>
              <p>
                모든 주소를 보고 싶은 보고서라면 one-to-many 관계를 유지하고
                판매 금액을 주소별 새 매출처럼 합산하지 않습니다. 주소 존재
                여부만 필요하다면 중첩 테이블을 펼치지 않고 매치 수가 0보다 큰지
                계산할 수 있습니다.
              </p>
              <p>
                판매당 주소 하나가 필요하다면 선택 규칙이 있어야 합니다. 판매
                레코드가 address_id를 갖고 있다면 고객 ID 대신 그 키로 연결하는
                것이 가장 명확합니다. “현재 주소”를 선택한다면 고객마다
                current=true가 정확히 하나인지, 동률과 누락을 어떻게 처리할지
                먼저 정의합니다.
              </p>
              <div class="story-callout">
                <strong
                  >행 수를 원래대로 만드는 것은 목표가 아니라
                  결과입니다.</strong
                >
                <p>
                  잘못된 주소 하나를 남겨도 행 수는 맞을 수 있습니다. ID, 미일치
                  고객, 선택된 주소와 금액을 같이 확인해야 합니다.
                </p>
              </div>
              <p>판매 시점에 선택한 주소 ID가 기록돼 있다면 그 값을 사용할 수 있습니다. 다음 파일은 설명을 위해 주소 ID를 함께 넣은 별도 합성 데이터입니다. 실제 데이터에 주소 ID가 없다면 임의로 이 값을 채우지 말고, 담당자와 주소 선택 규칙부터 정하세요.</p>
              <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/sales-address-example.csv" download>주소 ID가 포함된 판매 CSV 내려받기</a></p>
              <ol class="story-flow"><li><strong>새 판매 파일과 기존 주소 파일을 가져옵니다.</strong><span>두 쿼리의 address_id 열을 Text로 맞춥니다. 주소 쿼리에서 address_id가 중복되거나 비어 있는지 먼저 확인합니다.</span></li><li><strong>customer_id와 address_id 두 열로 Left Outer 병합합니다.</strong><span>양쪽에서 Ctrl 키를 누른 채 customer_id, address_id 순서로 선택합니다. 고객과 주소가 모두 같은 행만 연결해 다른 고객의 주소 연결도 피합니다.</span></li><li><strong>city를 펼치고 원본 판매 ID와 금액을 대조합니다.</strong><span>S1부터 S4까지 한 행씩, 합계 42가 남아야 합니다. 주소가 없는 S4는 null이며 판매 자체는 유지합니다.</span></li></ol>
              <figure class="sample-block">
                <figcaption>주소 선택이 명시된 병합 결과 · 판매 4행, 합계 42</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="고객과 주소 키를 함께 쓴 병합 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">sale_id</th><th scope="col">address_id</th><th scope="col">city</th><th scope="col">amount</th></tr></thead><tbody><tr><td>S1</td><td>A1</td><td>Seoul</td><td>10</td></tr><tr><td>S2</td><td>A2</td><td>Busan</td><td>20</td></tr><tr><td>S3</td><td>A3</td><td>Incheon</td><td>5</td></tr><tr><td>S4</td><td>빈 값</td><td>null</td><td>7</td></tr></tbody></table></div>
              </figure>
            </section>
            <section id="distinct">
              <p class="story-section-number">04 / CAUTION</p>
              <h2>
                정렬 후 Remove Duplicates가<br />최신 행을 보장한다고 가정하지
                않습니다
              </h2>
              <p>
                Power Query의 <code>Table.Distinct</code>는 중복 레코드 중 어느
                행이 보존될지 일반적으로 보장하지 않습니다. 단순히 날짜 내림차순
                정렬 후 고객 ID 중복을 제거해 최신 주소가 남을 것이라고 믿으면
                새로고침이나 쿼리 최적화에서 다른 결과가 생길 수 있습니다.
              </p>
              <p>
                최신 레코드가 필요하다면 고객별 최대 날짜를 구한 뒤 원본과 다시
                매치하고, 같은 최대 날짜가 여러 건인 상황에 tie-break 규칙을
                둡니다. 주소 두 개가 모두 실제로 유효하다면 하나를 삭제하지 말고
                보고서의 목적에 맞춰 관계를 보존합니다.
              </p>
            </section>
            <section id="verify">
              <p class="story-section-number">05 / VERIFY</p>
              <h2>판매 4건·합계 42를<br />여러 각도에서 대조합니다</h2>
              <p>
                원본 판매 ID 네 개가 모두 남았는지, 매치 수가 2·2·1·0인지, 펼친
                결과가 여섯 행인지 확인합니다. 한 주소 선택 규칙을 적용했다면
                선택된 주소 값과 미일치 고객의 null 처리도 봅니다. 마지막으로
                보고서가 판매 단위라면 합계가 42인지 대조합니다.
              </p>
              <figure class="sample-block">
                <figcaption>customer_id 기준 Left Outer 병합 후 주소를 펼친 결과</figcaption>
                <div class="sample-table-wrap" role="region" aria-label="고객 키만 쓴 중복 병합 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">sale_id</th><th scope="col">amount</th><th scope="col">address_id</th></tr></thead><tbody><tr><td>S1</td><td>10</td><td>A1</td></tr><tr><td>S1</td><td>10</td><td>A2</td></tr><tr><td>S2</td><td>20</td><td>A1</td></tr><tr><td>S2</td><td>20</td><td>A2</td></tr><tr><td>S3</td><td>5</td><td>A3</td></tr><tr><td>S4</td><td>7</td><td>null</td></tr></tbody></table></div>
              </figure>
              <p>왼쪽 판매 행 단위의 금액 합계는 42입니다. 펼친 표의 amount를 단순 합산하면 C10의 판매 10과 20이 두 번씩 포함되어 72가 됩니다. 주소를 하나로 줄여야 한다면 먼저 어떤 주소를 남길지 정하고, 그 규칙을 적용한 결과를 원본 판매 ID와 대조하세요.</p>
              <p>
                아래 실습에서는 같은 합성 데이터를 쿼리 안에 넣어 실제 Excel
                엔진에서 확인했습니다. 외부 파일이나 DB를 연결할 때의 folding,
                privacy level, connector 동작까지 검증한 것은 아닙니다.
              </p>
              <div class="story-next">
                <a class="button button--outline" href="https://learn.microsoft.com/en-us/powerquery-m/table-nestedjoin" target="_blank" rel="noopener noreferrer">Microsoft 병합 함수 설명 <span aria-hidden="true">↗</span></a>
                <a
                  class="button button--dark"
                  href="https://csv.jhsoftlabs.com/guides/power-query-merge-duplicates-extra-rows"
                  target="_blank"
                  rel="noopener noreferrer"
                  >입력·예상 CSV가 있는 영문 가이드
                  <span aria-hidden="true">↗</span></a
                >
              </div>
            </section>
            <section id="runnable-query" aria-labelledby="runnable-query-title">
              <p class="story-section-number">06 / RUN THE EXPERIMENT</p>
              <h2 id="runnable-query-title">파일 없이 실행하고,<br />병합 키 하나의 차이를 확인하세요</h2>
              <p>실무 파일의 경로나 고객 정보를 바꾸기 전에 작은 데이터로 원리를 확인할 수 있습니다. 아래 쿼리에는 판매 네 건과 주소 세 건이 모두 들어 있습니다. 앞의 CSV를 읽지 않는 독립 실습이며, 주소 ID가 포함된 판매 예제를 기준으로 합니다. 로그인이나 외부 서비스 연결은 필요하지 않습니다.</p>
              <ol class="story-flow">
                <li><strong>새 통합 문서에서 빈 쿼리를 만듭니다.</strong><span>Windows Excel의 데이터 → 데이터 가져오기 → 기타 원본에서 → 빈 쿼리로 들어갑니다. Power Query 편집기의 홈 → 고급 편집기에서 기존 내용을 지우고 아래 전체 코드를 넣습니다. 메뉴 이름은 Excel 버전과 언어에 따라 다를 수 있습니다.</span></li>
                <li><strong>먼저 audit 모드로 미리 보기를 확인합니다.</strong><span>Mode = "audit"를 유지합니다. sale_id 순서대로 match_count가 2, 2, 1, 0이면 고객 ID만으로 연결할 때 어떤 판매가 반복될지 확인한 것입니다.</span></li>
                <li><strong>첫 번째 Mode 대입문만 바꿔 비교합니다.</strong><span>Mode = "expanded"로 바꾸면 잘못된 금액 합계를, Mode = "corrected"로 바꾸면 고객 ID와 주소 ID를 함께 쓰는 결과를 봅니다. 아래 조건식의 문자열까지 일괄 치환하지 마세요. 닫기 및 로드로 표를 시트에 놓아 amount를 합산해도 됩니다.</span></li>
              </ol>
              <p class="sample-links"><a href="https://jhsoftlabs.com/data-guides/merge-lab.pq" download>전체 M 쿼리 내려받기 (.pq)</a></p>
              <details class="template-preview">
                <summary>전체 M 코드 보기 · 다운로드와 동일한 내용 <span aria-hidden="true">+</span></summary>
                <pre class="sample-code" tabindex="0" aria-label="전체 Power Query M 실습 코드"><code id="merge-lab-source">// Synthetic learning data only. Change Mode to audit, expanded, or corrected.
let
    Mode = "audit",
    Sales = #table(
        type table [sale_id=text, customer_id=text, address_id=nullable text, amount=number],
        {{"S1","C10","A1",10}, {"S2","C10","A2",20},
         {"S3","C20","A3",5}, {"S4","C30",null,7}}
    ),
    Addresses = #table(
        type table [address_id=text, customer_id=text, city=text],
        {{"A1","C10","Seoul"}, {"A2","C10","Busan"}, {"A3","C20","Incheon"}}
    ),
    ByCustomer = Table.NestedJoin(Sales, {"customer_id"}, Addresses, {"customer_id"}, "matches", JoinKind.LeftOuter),
    Audit = Table.AddColumn(ByCustomer, "match_count", each Table.RowCount([matches]), Int64.Type),
    Expanded = Table.ExpandTableColumn(Audit, "matches", {"city"}, {"city"}),
    KeyCounts = Table.Group(Addresses, {"customer_id", "address_id"}, {{"count", each Table.RowCount(_), Int64.Type}}),
    BadKeys = Table.SelectRows(KeyCounts, each [count] &lt;&gt; 1 or [customer_id] = "" or [address_id] = ""),
    CheckedAddresses = if Table.RowCount(BadKeys) &gt; 0 then error "Address lookup keys must be nonempty and unique" else Addresses,
    ByAddress = Table.NestedJoin(Sales, {"customer_id", "address_id"}, CheckedAddresses, {"customer_id", "address_id"}, "matches", JoinKind.LeftOuter),
    Corrected = Table.ExpandTableColumn(ByAddress, "matches", {"city"}, {"city"}),
    Selected = if Mode = "audit" then Table.RemoveColumns(Audit, {"matches"})
        else if Mode = "expanded" then Expanded
        else if Mode = "corrected" then Corrected
        else error "Mode must be audit, expanded, or corrected",
    Result = Table.Sort(Selected, {{"sale_id", Order.Ascending}})
in
    Result
</code></pre>
              </details>
              <figure class="sample-block">
                <figcaption>2026. 10. 05. 실제 Excel 엔진 실행 결과 · 합성 데이터</figcaption>
                <p class="sample-scroll-hint">넓은 표는 좌우로 스크롤할 수 있습니다. 키보드는 Tab으로 표 영역에 이동한 뒤 좌우 방향키를 누르세요.</p>
                <div class="sample-table-wrap" role="region" aria-label="Power Query 모드별 실제 실행 결과 표" tabindex="0"><table class="sample-table"><thead><tr><th scope="col">Mode</th><th scope="col">출력 행 수</th><th scope="col">amount 합계</th><th scope="col">확인할 값</th></tr></thead><tbody><tr><td>audit</td><td>4</td><td>42</td><td>match_count: 2, 2, 1, 0</td></tr><tr><td>expanded</td><td>6</td><td>72</td><td>S1·S2 각각 두 행</td></tr><tr><td>corrected</td><td>4</td><td>42</td><td>S1 Seoul, S2 Busan, S3 Incheon, S4 null</td></tr></tbody></table></div>
              </figure>
              <p><strong>왜 corrected는 결과를 임의로 한 건씩 지우지 않을까요?</strong> 판매가 가리키는 주소 ID와 고객 ID를 함께 비교하므로 S1은 A1, S2는 A2와 연결됩니다. S4는 주소가 없지만 Left Outer 덕분에 판매 금액 7이 남습니다. 중복 삭제로 합계만 맞추는 것과 달리 어떤 주소를 선택했는지도 설명할 수 있습니다.</p>
              <p><strong>잘못된 조회 테이블도 넣어 보세요.</strong> Addresses의 첫 번째 행 바로 뒤에 <code>{"A1","C10","Duplicate"},</code>를 추가하고 corrected 모드를 실행합니다. 같은 고객·주소 키가 두 건이므로 <code>Address lookup keys must be nonempty and unique</code> 오류로 멈춥니다. audit와 expanded는 문제를 관찰하는 모드여서 이 검사를 적용하지 않습니다. 실무에서는 오류를 숨기기보다 원본 주소의 중복 사유와 담당자를 확인해야 합니다.</p>
              <div class="story-callout"><strong>실행한 범위와 아직 확인하지 않은 범위</strong><p>Windows Excel 16.0, build 5569.0의 Microsoft Mashup OLE DB 엔진에서 이 전체 쿼리를 실행했습니다. 세 모드의 행 수·합계, 매치 수, 수정 후 전체 레코드, 중복 키 오류까지 9개 검사를 통과했습니다. 메뉴를 마우스로 따라 누른 UI 테스트나 Mac·웹 Excel 호환성, 외부 DB 연결 검증은 아닙니다. 예제의 키는 짧은 합성 문자열입니다. 실데이터에서는 공백·null·자료형·대소문자 규칙도 따로 정해야 합니다.</p></div>
              <p>미리 보기가 예상과 다르면 첫 줄의 Mode, 입력 행, 마지막 <code>in Result</code>를 먼저 비교하세요. 주소 순서는 보장하지 않으므로 expanded의 같은 판매 안에서 Seoul과 Busan의 표시 순서가 다른 것은 오류가 아닙니다. 반대로 corrected에서 S4가 없어지거나 합계가 달라졌다면 조인 종류와 원본 입력부터 다시 확인해야 합니다.</p>
              <p>함수 동작의 기준은 Microsoft의 <a href="https://learn.microsoft.com/en-us/powerquery-m/table-nestedjoin" target="_blank" rel="noopener noreferrer">Table.NestedJoin</a>과 <a href="https://learn.microsoft.com/en-us/powerquery-m/table-expandtablecolumn" target="_blank" rel="noopener noreferrer">Table.ExpandTableColumn</a> 문서입니다. 이 실험은 해당 동작을 작은 판매 데이터로 재현한 것으로, 모든 보고서에서 행 수를 줄여야 한다는 규칙은 아닙니다.</p>
            </section>
          </div>
        </div>
      ]]></description>
    </item>
    <item>
      <title>AI가 내 경험을 대신 지어내지 않도록</title>
      <link>https://jhsoftlabs.com/stories/ai-answer-fidelity</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/ai-answer-fidelity</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 05 · INTERVIEWER LENS</p>
        <h1>AI가 내 경험을 대신 지어내지 않도록</h1>
        <p class="story-intro">AI가 다듬은 답변에 사용자가 말하지 않은 경험이 들어갈 수 있었습니다. 형식을 검사하는 것과 사실을 지키는 것은 달랐습니다. 새 초안부터 저장된 결과의 표시와 복사까지, 같은 기준을 적용한 과정을 적었습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
        <p class="story-revision">수정: <time datetime="2026-09-30">2026. 09. 30.</time> · 학습용 코드와 탐지 범위 예시를 추가했습니다.</p>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">WHAT CHANGED, AND WHY</span><ol><li>정해진 JSON 형식과 사실에 맞는 내용은 다른 검사입니다.</li><li>원문 밖의 수치·기술이 추가되는 신호에는 보수적인 대안을 둡니다.</li><li>새 결과뿐 아니라 과거 결과의 표시와 복사도 같은 기준으로 다룹니다.</li></ol><p>2026년 9월까지의 개발·QA 기록을 편집했습니다. 사실 추가를 완전히 막았다는 품질 보증은 아닙니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#plausible">더 구체적인 문장이 더 나쁜 답변일 수 있었습니다</a><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#two-checks">필드가 존재하는 것과 사실인 것은 다릅니다</a><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#number-check">작은 검사로 확인할 수 있는 것</a><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#boundaries">화면은 안전한데 복사는 원본이면 안 됩니다</a><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#limits">통과한 테스트 수로 답변 품질을 말하지 않기</a><a href="https://jhsoftlabs.com/stories/ai-answer-fidelity#next">내 작업에 적용하기</a></nav>
        <div class="story-content">
          <section id="plausible" aria-labelledby="plausible-title"><p class="story-section-number">01 / DEVELOPMENT NOTES</p><h2 id="plausible-title">더 구체적인 문장이 더 나쁜 답변일 수 있었습니다</h2><p>면접 답변 진단에서 AI가 해야 할 일은 지원자의 경험을 더 잘 설명하도록 돕는 것입니다. 그런데 짧은 답변을 긴 성공담으로 바꾸다 보면 지원자가 말하지 않은 성과까지 채워 넣기 쉽습니다. 개발 기록에서는 짧은 원문의 과도한 확장과, QA 초안에 원문 밖 측정 시간·구현 세부사항이 추가된 사례를 확인했습니다.</p><p>수치와 기술 이름이 들어가면 글은 전문적으로 보입니다. 하지만 면접관이 “어떻게 측정했나요?”라고 물었을 때 본인의 경험으로 설명할 수 없다면 개선이 아닙니다. 이 제품에서는 문장의 화려함보다 사용자가 책임지고 말할 수 있는 범위를 먼저 두기로 했습니다.</p><div class="story-callout"><strong>설명용 합성 예시</strong><p>원문: “느린 조회 쿼리를 수정했습니다.”<br />확인되지 않은 확장: “Redis를 도입해 응답 시간을 40% 줄였습니다.”<br />기술 선택과 개선 수치는 원문만으로 확인할 수 없습니다. 실제 사용자 답변이나 모델 재실행 결과가 아닙니다.</p></div></section>
          <section id="two-checks" aria-labelledby="two-checks-title"><p class="story-section-number">02 / DEVELOPMENT NOTES</p><h2 id="two-checks-title">필드가 존재하는 것과 사실인 것은 다릅니다</h2><p>점수, 요약, 약점, 개선 초안, 꼬리질문은 앱이 일정한 형태로 읽을 수 있어야 합니다. JSON Schema 기반 구조화 출력과 앱의 범위 검사는 그 계약을 위한 것이었습니다. 그러나 형태를 통과한 문자열에도 원문 밖 주장이 들어갈 수 있습니다.</p><p>그래서 “처리할 수 있는 형식인가”와 “원문보다 더 큰 주장을 만드는가”를 별도의 질문으로 나눴습니다. 프롬프트에는 확인한 사실과 보완 제안을 구분하도록 넣고 출력 뒤에는 숫자·단위와 선정한 주요 기술 표현을 비교하는 보호 로직을 두었습니다.</p><ol class="story-flow"><li><strong>원문과 초안을 비교합니다</strong><span>숫자·단위·일부 기술 표현을 정규화해 원문 밖 추가 신호를 살핍니다.</span></li><li><strong>의심되면 더 보수적으로 돌려줍니다</strong><span>검증되지 않은 구체성을 그대로 주는 대신, 원문과 실제 경험으로 보완하라는 안내를 제공합니다.</span></li><li><strong>한계를 남깁니다</strong><span>의미 전체를 이해하는 사실 확인기가 아닙니다. 목록 밖 주장과 같은 숫자의 다른 의미는 놓칠 수 있고 안전한 설명을 막을 수도 있습니다.</span></li></ol></section>
          <section id="number-check" aria-labelledby="number-check-title">
            <p class="story-section-number">EXAMPLE / NUMBER TOKENS</p><h2 id="number-check-title">작은 검사로 확인할 수 있는 것</h2>
            <p>아래 JavaScript는 원문에 없던 숫자 표기를 찾는 학습용 축약 예시입니다. 서비스의 실제 보호 함수 전체를 옮긴 코드는 아닙니다. 브라우저 개발자 도구나 Node.js에서 합성 문장으로 확인할 수 있습니다.</p>
            <pre class="sample-code sample-code--wrap" id="number-check-example"><code>function findAddedNumbers(source, draft) {
  const numbers = (text) =&gt; new Set(
    text.normalize("NFKC").match(/\d+(?:[.,]\d+)?%?/g) ?? []
  );
  const known = numbers(source);
  return [...numbers(draft)].filter((value) =&gt; !known.has(value));
}

findAddedNumbers("조회 쿼리를 수정했습니다.", "응답 시간을 40% 줄였습니다.");
// ["40%"]</code></pre>
            <figure class="sample-block"><figcaption>합성 문장으로 확인하는 탐지 범위</figcaption><div class="sample-table-wrap"><table class="sample-table"><thead><tr><th scope="col">원문</th><th scope="col">초안</th><th scope="col">추가 숫자</th></tr></thead><tbody><tr><td>조회 쿼리를 수정했습니다.</td><td>응답 시간을 40% 줄였습니다.</td><td>["40%"]</td></tr><tr><td>응답 시간을 40% 줄였습니다.</td><td>응답 시간을 40% 줄였습니다.</td><td>[]</td></tr><tr><td>작업 2개를 처리했습니다.</td><td>서버 2대를 운영했습니다.</td><td>[]</td></tr><tr><td>조회 쿼리를 수정했습니다.</td><td>Redis를 도입했습니다.</td><td>[]</td></tr></tbody></table></div></figure>
            <p>빈 배열은 이 함수가 새 숫자 표기를 찾지 못했다는 뜻입니다. 같은 숫자를 다른 의미로 쓰거나, 기술 이름만 추가하거나, 수치를 한글로 풀어 쓰는 경우는 잡지 못합니다. 탐지 결과가 비어 있어도 사실 확인을 통과했다고 처리하지 마세요. 단위·기술 표현 비교와 사람의 원문 대조가 따로 필요합니다.</p>
          </section>
          <section id="boundaries" aria-labelledby="boundaries-title"><p class="story-section-number">03 / DEVELOPMENT NOTES</p><h2 id="boundaries-title">화면은 안전한데 복사는 원본이면 안 됩니다</h2><p>새 생성 경로만 바꾸면 예전에 저장한 결과는 예전 방식으로 읽힙니다. 화면에 보이는 초안을 보수적으로 바꿨어도 복사 버튼이 가공 전 값을 가져가면 사용자가 실제로 가져가는 문장은 달라집니다.</p><p>같은 보호 함수를 새 평가의 후처리, 저장된 결과의 표시, 결과 복사에 적용했습니다. 과거 데이터를 모두 다시 생성하거나 삭제한 것은 아닙니다. 사용자에게 전달하는 세 경로의 기준을 맞췄습니다.</p><p>회귀 검사에는 빈 초안, 원문 밖 숫자, 단위 표기, 기술 동의어, 이미 보호된 문구를 넣었습니다. 평가 함수가 실제로 이 경로를 거치는지도 확인했습니다. 보호를 위해 AI를 한 번 더 호출하는 방식은 쓰지 않았습니다.</p></section>
          <section id="limits" aria-labelledby="limits-title"><p class="story-section-number">04 / DEVELOPMENT NOTES</p><h2 id="limits-title">통과한 테스트 수로 답변 품질을 말하지 않기</h2><p>이 검사는 정해진 사례에서 보호 정책이 유지되는지 확인합니다. 모든 문장에서 환각이 사라졌다는 뜻은 아닙니다. 스키마 검사와 휴리스틱만으로 사용자 경험의 진위를 판정할 수도 없습니다.</p><p>결과 출처도 구분했습니다. 규칙 기반 대체 결과가 표시됐다는 사실을 기대한 AI 평가의 성공으로 세지 않았습니다. 유효한 본 평가 뒤의 추가 다듬기만 실패한 경우와, 평가 자체가 실패한 경우 역시 같은 상태로 묶지 않았습니다.</p><p>다음으로 사실 추가·누락·과장·구체성을 나눠 보는 사람의 평가와 오탐·미탐 측정이 필요합니다. 이 글에서 그러한 대규모 검증을 완료했다고 말하지 않습니다. 점수 상승 역시 실제 면접 실력이나 합격률 상승과는 별개입니다.</p></section>
          <section id="next" class="note-template" aria-labelledby="next-title"><p class="story-section-number">KEEP EXPLORING</p><h2 id="next-title">읽은 뒤, 내 작업에서 확인할 것</h2><p>내 서비스의 원문과 개선 초안, 화면 표시와 복사 결과를 각각 비교해보세요. 같은 보호 기준이 모든 전달 경로에 적용되는지부터 확인할 수 있습니다.</p><a class="button button--dark" href="https://jhsoftlabs.com/tools/release-check/?kind=ai">내 AI 서비스 점검하기<span aria-hidden="true">→</span></a></section>
          <footer class="story-end"><p>초안 품질 개선 문서와 검증대장의 생성·표시·복사 경계 기록을 바탕으로 작성했습니다. 고객 원문, 내부 운영 상한과 소표본 통계는 싣지 않았습니다. 이번 글 작성 과정에서 실제 AI나 운영 DB를 새로 실행하지 않았습니다.</p><a href="https://jhsoftlabs.com/stories/guest-trial-funnel">이어서 읽기 · 로그인 다음에 남은 장벽 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>AI가 답한 뒤에 생기는 문제들</title>
      <link>https://jhsoftlabs.com/stories/ai-retry-safety</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/ai-retry-safety</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 03 · INTERVIEWER LENS</p>
        <h1>AI가 답한 뒤에 생기는 문제들</h1>
        <p class="story-intro">AI가 답변을 만들어도 저장이나 응답이 실패할 수 있었습니다. 같은 요청이 다시 들어오면 결과와 사용량은 어떻게 처리해야 할까요? 중간 실패와 재시도, 멈춘 화면을 점검한 기록입니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
        <p class="story-revision">수정: <time datetime="2026-10-05">2026. 10. 05.</time> · 저장 전 실패와 응답 유실을 직접 실행하는 학습 예제를 추가했습니다.</p>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">이번 작업에서 남긴 것</span><ol><li>중간 실패의 가능성을 실제 장애와 구분했습니다.</li><li>같은 요청의 재시도와 새 작업을 구분했습니다.</li><li>서버 상태뿐 아니라 사용자가 보는 끝맺음도 확인했습니다.</li></ol><p>작업 기록 기반의 회고입니다. 확인한 일과 아직 모르는 결과를 나눠 적었습니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/ai-retry-safety#risk">결과와 사용량이 따로 움직일 수 있었습니다</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#retry">응답이 없다는 것이 실패했다는 뜻은 아니었습니다</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#screen">서버에서는 만료됐는데 화면은 계속 기다렸습니다</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#verification">코드 준비와 운영 적용을 다른 칸에 적었습니다</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#scenarios">실패 조건별 기대 결과</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#retry-lab">실패를 직접 주입하는 실행 예제</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#takeaway">다음 기능보다 먼저 적을 실패 시나리오</a><a href="https://jhsoftlabs.com/stories/ai-retry-safety#template">함께 쓰는 무료 템플릿</a></nav>
        <div class="story-content">
          <section id="risk" aria-labelledby="risk-title"><p class="story-section-number">01 / WORK NOTES</p><h2 id="risk-title">결과와 사용량이 따로 움직일 수 있었습니다</h2><p>Interviewer Lens는 개발자의 면접 답변을 AI로 진단하는 서비스입니다. 진단을 실행하면 답변을 만들고 결과를 저장하고 사용한 횟수도 기록해야 합니다. 눈에 보이는 답변 화면 뒤에 여러 단계가 이어집니다.</p><p>코드를 점검했을 때 사용량을 차감한 다음 결과를 따로 저장하는 흐름이 있었습니다. 중간에 실패하면 결과는 없는데 횟수만 줄어드는 상황이 생길 수 있는 구조였습니다. 이것은 점검에서 발견한 실패 가능성이지, 실제 사용자에게 같은 사고가 발생했다고 확인한 기록은 아닙니다.</p><p>수정의 기준은 결과 저장과 사용량 반영을 함께 확정하거나 함께 되돌리는 것이었습니다. 둘 중 하나만 성공한 상태를 남기지 않도록 만들고 중간 저장 지점에 의도적으로 오류를 넣어 확인했습니다.</p></section>
          <section id="retry" aria-labelledby="retry-title"><p class="story-section-number">02 / WORK NOTES</p><h2 id="retry-title">응답이 없다는 것이 실패했다는 뜻은 아니었습니다</h2><p>데이터베이스에서는 저장이 끝났지만 브라우저로 돌아오는 응답만 끊길 수 있습니다. 이때 사용자가 다시 누른 요청을 전부 새 작업으로 처리하면 AI 실행과 사용량 기록이 중복될 수 있습니다.</p><p>그래서 같은 요청의 재시도인지 확인하고 이미 확정된 결과가 있으면 그 기록을 다시 확인하는 경로를 연결했습니다. 완료된 결과를 뒤늦은 오류 때문에 실패로 덮어쓰지 않는지도 함께 점검했습니다. 접수 단계에서도 중복 요청이 새 AI 작업을 반복해서 시작하지 않도록 처리했습니다.</p><ol class="story-flow"><li><strong>접수</strong><span>같은 작업을 다시 요청한 것인지 확인합니다.</span></li><li><strong>확정</strong><span>결과 저장과 사용량 반영을 하나의 처리 단위로 묶습니다.</span></li><li><strong>재확인</strong><span>응답이 끊겨도 이미 완료된 상태를 확인할 수 있게 합니다.</span></li></ol><p>하나의 테스트만으로 충분하지는 않았습니다. 같은 요청이 동시에 들어오는 경우와 마지막 남은 사용 횟수를 여러 요청이 함께 쓰려는 경우도 별도로 검증했습니다.</p></section>
          <section id="screen" aria-labelledby="screen-title"><p class="story-section-number">03 / WORK NOTES</p><h2 id="screen-title">서버에서는 만료됐는데 화면은 계속 기다렸습니다</h2><p>마지막 화면 흐름 점검에서는 다른 빈틈을 발견했습니다. 서버가 작업 도중 종료되면 작업 예약은 만료돼도 사용자의 화면은 계속 ‘처리 중’으로 남을 수 있었습니다. 내부 상태가 정리되는 것과 사용자에게 다음 행동이 보이는 것은 별개의 문제였습니다.</p><p>상태를 조회할 때 만료된 작업을 정리하고 다시 제출할 수 있도록 보완했습니다. 이후 재배포하고 운영 화면에서 제출 버튼과 무료 한도, 최근 기록이 표시되는 흐름을 확인했습니다.</p><div class="story-callout"><strong>실패도 사용자에게 끝맺음이 있어야 합니다.</strong><p>끝나지 않는 로딩 대신, 지금 어떤 상태인지와 다시 시도할 수 있는지를 알려주는 것까지가 서비스의 처리 흐름입니다.</p></div></section>
          <section id="verification" aria-labelledby="verification-title"><p class="story-section-number">04 / WORK NOTES</p><h2 id="verification-title">코드 준비와 운영 적용을 다른 칸에 적었습니다</h2><p>작업은 DB 처리 기반을 먼저 만들고 이를 서비스 흐름에 연결한 뒤 운영에 적용하는 순서로 진행했습니다. 앞 단계가 끝났을 때는 준비된 코드를 운영 적용 완료라고 기록하지 않았습니다.</p><p>검증도 층을 나눴습니다. 중간 실패 시 되돌아오는지 확인하는 테스트, 실제 데이터베이스 연결 간 동시성 테스트, 서버 요청 흐름과 운영 화면 확인을 구분했습니다. 검증용 입력은 합성 데이터로 준비하고 기존 사용자 기록을 보존하는 조건을 확인했습니다.</p><p>조회한 완료 기록에는 안정화 적용과 검증이 남아 있습니다. 다만 이 작업으로 모든 장애를 없앴다고 말할 수는 없습니다. 이 글은 운영 환경의 구성값이나 사용자 답변을 공개하지 않으며 현재 보안 상태 전체를 인증하는 문서도 아닙니다.</p></section>
          <section id="scenarios" aria-labelledby="scenarios-title"><p class="story-section-number">05 / REPRODUCE THE DECISION</p><h2 id="scenarios-title">같은 요청을 다시 보냈을 때 무엇을 확인할까</h2><p>아래 표는 작업 기록의 검증 기준을 독자가 자신의 서비스에 적용할 수 있게 다시 쓴 공개용 예시입니다. 내부 테스트 코드나 실제 고객 사고 기록은 아닙니다. 합성 요청 ID <code>req-42</code>와 사용 가능 횟수 1회를 가정합니다.</p><figure class="sample-block"><figcaption>실패 조건과 확인할 결과</figcaption><div class="sample-table-wrap"><table class="sample-table"><thead><tr><th scope="col">조건</th><th scope="col">저장 결과</th><th scope="col">사용 횟수</th><th scope="col">화면 상태</th></tr></thead><tbody><tr><td>결과 저장 중 오류</td><td>없음</td><td>1회 유지</td><td>재시도 가능</td></tr><tr><td>저장 성공, 응답만 유실</td><td>req-42 결과 1건</td><td>0회</td><td>재조회로 결과 표시</td></tr><tr><td>같은 ID 동시 재시도</td><td>req-42 결과 1건</td><td>0회</td><td>같은 결과 표시</td></tr><tr><td>다른 ID로 동시 요청</td><td>허용된 1건</td><td>음수 아님</td><td>초과 요청은 안내</td></tr><tr><td>예약 만료</td><td>완료 결과 없음</td><td>확정 차감 없음</td><td>대기 종료·재시도 안내</td></tr></tbody></table></div></figure><p>자신의 서비스에서 따라 할 때는 요청 식별자, 결과 저장, 사용량 반영을 한 행씩 기록해 보세요. 실패 주입 뒤에는 DB의 결과 수와 남은 횟수를 함께 조회하고 화면에서 로딩이 끝나는지도 확인합니다. 위 표의 숫자는 설명을 위한 기대값이며 현재 서비스의 실제 이용 한도나 운영 결과를 뜻하지 않습니다.</p></section>
          <section id="retry-lab" aria-labelledby="retry-lab-title">
            <p class="story-section-number">06 / RUN THE EXPERIMENT</p>
            <h2 id="retry-lab-title">저장 전 실패와 응답 유실은<br />같은 재시도로 풀리지 않습니다</h2>
            <figure class="article-visual" id="retry-visual">
              <a href="https://jhsoftlabs.com/assets/story-retry-boundary.svg" target="_blank" rel="noopener noreferrer" aria-label="확정 전후 재시도 흐름 이미지 크게 보기 (새 탭)"><img src="https://jhsoftlabs.com/assets/story-retry-boundary.svg" alt="확정 전 실패는 결과 0건과 남은 횟수 1회를 유지하고 재시도에서 처음 확정합니다. 확정 후 응답 유실은 이미 결과 1건이 있으므로 같은 키의 재시도에 기존 결과를 반환하며 추가 차감하지 않습니다." width="640" height="860" loading="lazy" decoding="async" /></a>
              <figcaption>그림 1. 아래 실행 예제의 두 경로를 비교했습니다. 단일 프로세스 학습 모델의 설명이며 실제 운영 화면이나 DB 동시성 검증 결과는 아닙니다. <a href="https://jhsoftlabs.com/assets/story-retry-boundary.svg" target="_blank" rel="noopener noreferrer">이미지 크게 보기 (새 탭)</a></figcaption>
            </figure>
            <p>표만 읽으면 둘 다 “오류가 났으니 다시 실행”으로 보이기 쉽습니다. 아래 작은 프로그램은 확정 전과 확정 후에 각각 오류를 던져 결과 수와 남은 횟수가 어떻게 다른지 보여줍니다. 이 글을 위해 새로 만든 공개 학습 모델입니다. 실제 서비스 코드, AI 응답, 고객 데이터는 사용하지 않습니다.</p>
            <p><strong>준비물은 Node.js뿐입니다.</strong> 의존성 설치, API 키, DB 연결이 없습니다. 실행해도 네트워크 요청이나 파일 저장을 하지 않습니다. 내려받은 파일이 있는 폴더에서 다음 명령을 실행하면 검증을 통과한 줄이 순서대로 출력됩니다. Node.js가 설치돼 있지 않으면 아래 전체 코드와 출력 비교만으로도 상태 변화를 따라갈 수 있습니다.</p>
            <p class="sample-links"><a href="https://jhsoftlabs.com/stories/retry-lab.mjs" download>재시도 실험 내려받기 (.mjs)</a></p>
            <pre class="sample-code" tabindex="0" aria-label="재시도 실습 실행 명령"><code>node retry-lab.mjs</code></pre>
            <details class="template-preview">
              <summary>전체 JavaScript 코드 보기 · 다운로드와 동일한 내용 <span aria-hidden="true">+</span></summary>
              <pre class="sample-code" tabindex="0" aria-label="전체 재시도 실습 코드"><code id="retry-lab-source">// Learning model only: one process, in-memory state, no real AI/DB/payment calls.
import assert from "node:assert/strict";

function createLab() {
  let state = { remaining: 1, results: new Map() };
  function submit(requestId, answer, failure = "none") {
    const prior = state.results.get(requestId);
    if (prior) {
      if (prior.answer !== answer) throw new Error("KEY_REUSED_WITH_DIFFERENT_INPUT");
      return prior;
    }
    if (state.remaining &lt; 1) throw new Error("LIMIT_REACHED");
    const result = Object.freeze({ id: requestId, answer, feedback: "synthetic result" });
    const next = { remaining: state.remaining - 1, results: new Map(state.results) };
    next.results.set(requestId, result);
    if (failure === "before-commit") throw new Error("SAVE_FAILED");
    // One synchronous assignment models a commit; this is NOT a DB transaction.
    state = next;
    if (failure === "after-commit") throw new Error("RESPONSE_LOST");
    return result;
  }
  const snapshot = () =&gt; ({ results: state.results.size, remaining: state.remaining });
  return { submit, snapshot };
}

const before = createLab();
assert.throws(() =&gt; before.submit("req-42", "sample", "before-commit"), /SAVE_FAILED/);
assert.deepEqual(before.snapshot(), { results: 0, remaining: 1 });
console.log("before-commit: results=0 remaining=1");
before.submit("req-42", "sample");
assert.deepEqual(before.snapshot(), { results: 1, remaining: 0 });
console.log("retry-after-failure: results=1 remaining=0");

const after = createLab();
assert.throws(() =&gt; after.submit("req-42", "sample", "after-commit"), /RESPONSE_LOST/);
assert.deepEqual(after.snapshot(), { results: 1, remaining: 0 });
console.log("response-lost: results=1 remaining=0");
const original = after.submit("req-42", "sample");
assert.strictEqual(after.submit("req-42", "sample"), original);
assert.deepEqual(after.snapshot(), { results: 1, remaining: 0 });
console.log("same-key-retry: same-result remaining=0");
assert.throws(() =&gt; after.submit("req-42", "changed"), /KEY_REUSED_WITH_DIFFERENT_INPUT/);
assert.throws(() =&gt; after.submit("req-43", "sample"), /LIMIT_REACHED/);
assert.deepEqual(after.snapshot(), { results: 1, remaining: 0 });
console.log("changed-input: rejected; new-key: limit-reached");
</code></pre>
            </details>
            <figure class="sample-block"><figcaption>2026. 10. 05. 이 파일을 Node.js로 실행해 확인한 출력</figcaption><pre class="sample-code sample-code--wrap" tabindex="0" aria-label="재시도 실습의 정상 출력"><code id="retry-lab-output">before-commit: results=0 remaining=1
retry-after-failure: results=1 remaining=0
response-lost: results=1 remaining=0
same-key-retry: same-result remaining=0
changed-input: rejected; new-key: limit-reached</code></pre></figure>
            <p>첫 실험은 <code>state = next</code> 이전에 실패하므로 임시로 만든 결과와 차감 값이 확정되지 않습니다. 다시 실행하면 결과 한 건과 차감 한 번이 함께 남습니다. 두 번째 실험은 같은 대입문 이후에 실패합니다. 호출자는 오류를 받지만 이미 상태가 바뀌었으므로 재시도는 기존 결과를 돌려줘야 합니다.</p>
            <p><strong>한도 확인보다 기존 요청 조회를 먼저 하는 이유가 있습니다.</strong> 이미 성공한 사용자는 남은 횟수가 0이어도 자신의 완료 결과를 다시 받아야 합니다. 반대로 같은 키로 답변을 바꿔 보내는 것은 재시도가 아닙니다. 예제는 이를 거부하고 새 키로 요청하면 새 작업으로 판단해 한도 검사를 적용합니다.</p>
            <h3>코드를 일부러 바꾸면 어떤 검사가 실패할까요?</h3>
            <ol class="story-flow"><li><strong>한도 검사를 기존 결과 조회보다 앞으로 옮겨 보세요.</strong><span>응답이 유실된 뒤 같은 req-42를 다시 조회하는 줄에서 LIMIT_REACHED가 납니다. 이미 확정된 결과를 되찾을 수 없어진 회귀입니다.</span></li><li><strong>state = next를 저장 전 오류 검사 위로 옮겨 보세요.</strong><span>첫 실험의 results=0, remaining=1 검사가 실패합니다. 실패했다고 알려 놓고 이미 결과와 횟수를 바꾼 상태가 됩니다.</span></li><li><strong>입력 비교 조건을 지워 보세요.</strong><span>changed 입력을 거부해야 하는 assert.throws 검사가 실패합니다. 같은 요청 키가 다른 작업에 재사용돼도 예전 결과를 돌려주는 문제가 드러납니다.</span></li></ol>
            <div class="story-callout"><strong>이 모델이 통과했다고 운영 DB가 안전해지는 것은 아닙니다.</strong><p>단일 프로세스의 동기 메모리 모델이라 재시작하면 상태가 사라집니다. 동시 요청, 다중 서버, 외부 AI 호출의 중복 비용, 실제 DB 트랜잭션·복구·예약 만료는 구현하지 않았습니다. 앞의 운영 작업 회고와 이 공개 예제의 검증 범위를 혼동하지 마세요.</p></div>
            <h3>내 서비스에 옮길 때 따로 정할 것</h3>
            <p>요청 키는 인증된 사용자와 작업 종류까지 포함해 범위를 정해야 다른 사용자의 결과를 돌려주지 않습니다. 확정 결과와 사용량은 실제 저장소의 원자적 처리로 묶고 동시에 들어오는 같은 키는 고유 제약 등으로 조정해야 합니다. 결과가 아직 없는 처리 중 상태도 따로 필요합니다. 프런트엔드 버튼 비활성화만으로는 이 조건들을 보장하지 못합니다.</p>
            <p>최소한 서로 다른 DB 연결에서 같은 키를 동시에 보내는 경우, 남은 한 번에 서로 다른 키 두 개가 경쟁하는 경우, 확정 직후 연결이 끊기는 경우를 별도로 시험하세요. 확인할 것은 단순한 HTTP 성공 여부가 아니라 결과 ID, 결과 건수, 남은 횟수입니다. 원문 답변이나 토큰을 로그에 남기는 대신 검증용 합성 입력과 요청 식별자를 사용하는 편이 안전합니다.</p>
          </section>
          <section id="takeaway" aria-labelledby="takeaway-title"><p class="story-section-number">07 / WORK NOTES</p><h2 id="takeaway-title">다음 기능보다 먼저 적을 실패 시나리오</h2><p>다음 AI 기능을 만들 때도 정상 응답만 확인하고 끝내지 않으려 합니다. 두 번 눌렀을 때, 저장 뒤 연결이 끊겼을 때, 기다리던 작업이 만료됐을 때 사용자가 무엇을 보게 될지 먼저 적을 수 있습니다.</p><p>확인할 대상은 모델의 문장만이 아닙니다. 요청의 정체성, 결과의 보존, 사용량의 일관성, 다시 시작하는 경로까지입니다. 아래 템플릿은 이 기록을 다른 서비스에서도 사용할 수 있도록 새로 정리한 점검 양식입니다. 위 학습 모델과 템플릿은 그대로 붙여 넣는 운영 코드나 SQL을 대신하지 않습니다.</p></section>
          <section id="template" class="note-template" aria-labelledby="template-title"><p class="story-section-number">TAKE IT WITH YOU</p><h2 id="template-title">이 과정을 내 작업에 적용하기</h2><p>이번 시행착오에서 점검 항목을 추려 빈 양식으로 다시 만들었습니다. 내부 문서 원본이 아닌 공개용 템플릿이며 예시는 합성한 상황입니다.</p><a class="button button--dark" href="https://jhsoftlabs.com/templates/#ai-release">무료 템플릿 미리보기 <span aria-hidden="true">→</span></a></section>
          <footer class="story-end"><p>2026년 9월의 코드 점검·장애 시나리오 검증·운영 적용 대화를 바탕으로 작성했습니다. 가능성으로 발견한 문제와 확인한 개선을 구분했으며 실제 고객 사고를 주장하거나 장애 방지를 보장하지 않습니다.</p><a href="https://interview.jhsoftlabs.com/" target="_blank" rel="noopener noreferrer">면접 답변 진단 살펴보기 <span aria-hidden="true">↗</span><span class="sr-only"> (새 탭)</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>숫자로 바꾸기 전에, 원본을 한 번 더</title>
      <link>https://jhsoftlabs.com/stories/column-harbor</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/column-harbor</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
        <header class="story-header">
          <p class="eyebrow">BUILD NOTE 01 · COLUMN HARBOR</p>
          <h1>숫자로 바꾸기 전에,<br /><span>원본을 한 번 더.</span></h1>
          <p class="story-intro">CSV를 가져오는 과정에서 원래 값이 달라질 수 있습니다. Column Harbor는 데이터를 자동으로 고치기보다 어떤 값이 위험한지 먼저 보여주도록 만들었습니다. 원본을 다루는 방식과 도구의 한계를 소개합니다.</p>
          <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
        </header>

        <figure class="story-comparison">
          <div><span>원본 상품 코드</span><code>00123</code><small>식별자로 남아야 하는 값</small></div>
          <span class="comparison-arrow" aria-hidden="true">→</span>
          <div><span>숫자로 해석한 예시</span><code>123</code><small>같은 숫자, 다른 상품 코드</small></div>
          <figcaption>설명용 합성 예시입니다. 프로그램의 설정과 가져오기 방식에 따라 실제 결과는 달라집니다.</figcaption>
        </figure>

        <div class="story-layout">
          <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/column-harbor#problem">작은 차이의 의미</a><a href="https://jhsoftlabs.com/stories/column-harbor#local">브라우저 안의 흐름</a><a href="https://jhsoftlabs.com/stories/column-harbor#boundaries">자동으로 고치지 않는 것</a><a href="https://jhsoftlabs.com/stories/column-harbor#next">점검 다음의 한 걸음</a></nav>
          <div class="story-content">
            <section id="problem" aria-labelledby="problem-title">
              <p class="story-section-number">01 / THE PROBLEM</p>
              <h2 id="problem-title">숫자처럼 보여도<br />계산할 값은 아닐 수 있습니다.</h2>
              <p>상품 코드 <code>00123</code>은 숫자 123과 다른 식별자일 수 있습니다. CSV를 열면서 숫자로 해석하면 앞자리 0이 사라질 수 있고 긴 ID는 숫자 정밀도의 영향을 받을 수 있습니다. Microsoft도 이런 값을 보존하기 위한 텍스트 가져오기 방식을 안내합니다. <a href="https://support.microsoft.com/en-us/excel/keeping-leading-zeros-and-large-numbers" target="_blank" rel="noopener noreferrer">공식 안내 읽기 <span aria-hidden="true">↗</span><span class="sr-only"> (영문, 새 탭)</span></a></p>
              <p>Column Harbor는 이 지점에서 출발하는 도구입니다. 파일이 이미 깨졌다고 단정하는 대신, 가져오기 전에 확인할 값과 위치를 보여줍니다. 경고의 의미는 ‘오류 확정’이 아니라 ‘이 값은 한 번 더 살펴보세요’에 가깝습니다.</p>
            </section>

            <section id="local" aria-labelledby="local-title">
              <p class="story-section-number">02 / THE FLOW</p>
              <h2 id="local-title">파일을 선택해도<br />서버로 올리지는 않습니다.</h2>
              <p>점검기는 선택한 파일이나 붙여 넣은 텍스트를 현재 브라우저에서 읽습니다. 내용을 서버에 보내 AI로 분석하는 방식이 아니라, 원본 문자열을 해석하고 정해진 규칙으로 위험 신호를 찾습니다.</p>
              <ol class="story-flow">
                <li><strong>원본 읽기</strong><span>UTF-8 텍스트를 읽고 선택한 구분자로 레코드와 필드를 나눕니다.</span></li>
                <li><strong>값은 문자열로 유지</strong><span>상품 코드나 날짜처럼 보이는 값을 임의로 숫자·날짜로 바꾸지 않습니다.</span></li>
                <li><strong>위험과 위치 보여주기</strong><span>앞자리 0, 긴 숫자, 날짜 패턴, 열 개수 등 확인할 부분과 원본 값을 표시합니다.</span></li>
              </ol>
              <p>점검 입력과 결과는 현재 페이지의 메모리에 머무릅니다. 다만 파일이 로컬에서 처리된다는 말이 웹사이트 접속 자체의 완전한 익명성을 뜻하지는 않습니다. 내려받은 점검 보고서에도 원본 값 일부가 들어갈 수 있으므로 공유 전에 확인해야 합니다. <a href="https://csv.jhsoftlabs.com/privacy" target="_blank" rel="noopener noreferrer">파일 처리 안내 <span aria-hidden="true">↗</span><span class="sr-only"> (영문, 새 탭)</span></a></p>
            </section>

            <section id="boundaries" aria-labelledby="boundaries-title">
              <p class="story-section-number">03 / THE BOUNDARY</p>
              <h2 id="boundaries-title">친절한 자동 수정이<br />항상 정답은 아니니까요.</h2>
              <p><code>03/04/2026</code>이 3월 4일인지, 4월 3일인지는 모양만 보고 확정할 수 없습니다. <code>123</code>의 원래 값이 <code>00123</code>이었는지도 이미 손실된 파일만으로는 알 수 없습니다. 그래서 점검기는 원본을 덮어쓰거나 복구를 약속하지 않습니다.</p>
              <div class="story-callout"><strong>찾는 것과 고치는 것은 다른 책임입니다.</strong><p>경고가 없다고 모든 가져오기가 안전한 것은 아닙니다. 목적지 프로그램의 규칙과 데이터의 의미는 별도로 확인해야 합니다.</p></div>
              <p>브라우저에서 다루는 범위에도 제한을 둡니다. 확인 당시 점검기는 최대 5 MiB의 UTF-8 텍스트, 20,000개 데이터 레코드, 250,000개 셀을 다룹니다. 대용량 처리나 모든 업무 규칙의 검증을 대신하는 도구는 아닙니다.</p>
            </section>

            <section id="next" aria-labelledby="next-title">
              <p class="story-section-number">04 / WHAT COMES NEXT</p>
              <h2 id="next-title">경고 다음에는,<br />다음 행동이 있어야 합니다.</h2>
              <p>위험 신호만 보여주고 끝나지 않도록 점검 결과에서 관련 가이드로 이동할 수 있습니다. 앞자리 0은 텍스트로 가져오는 방법으로, 한 열에 몰린 값은 구분자와 가져오기 설정으로 이어집니다.</p>
              <p>이 프로젝트가 맡는 역할은 작습니다. 원본이 어떻게 생겼는지 확인하고 달라질 수 있는 부분을 찾고 다음 행동을 고르는 것. 파일을 바꾸기 전에 한 번 멈춰 볼 수 있는 도구입니다.</p>
              <div class="story-next"><a class="button button--dark" href="https://csv.jhsoftlabs.com/#checker" target="_blank" rel="noopener noreferrer">내 CSV 점검하기 <span aria-hidden="true">↗</span><span class="sr-only"> (영문 사이트, 새 탭)</span></a><span>무료 · 가입 불필요 · 영문 서비스</span></div>
            </section>
            <footer class="story-end"><p>2026년 9월 13일의 구현과 공개 안내를 확인해 작성한 설계 기록입니다. 기능과 제한은 이후 달라질 수 있습니다. 예시는 실제 고객 데이터가 아닙니다.</p><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
          </div>
        </div>
      ]]></description>
    </item>
    <item>
      <title>글자 수는 같았는데, ID는 달라졌다</title>
      <link>https://jhsoftlabs.com/stories/excel-native-checks</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/excel-native-checks</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header"><p class="eyebrow">FIELD NOTE 04 · COLUMN HARBOR</p><h1>글자 수는 같았는데, ID는 달라졌다</h1><p class="story-intro">계산한 예상값과 실제 Excel의 결과가 달랐습니다. 긴 ID를 가져오고 저장해 다시 열어보며 글자 수만으로 원본 보존을 판단할 수 없다는 걸 확인했습니다. 가이드의 검증 기준을 바꾼 과정입니다.</p><div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div></header>
      <figure class="verification-board">
        <div class="verification-lane"><span class="eyebrow">원본 · 서로 다른 두 ID</span><code>123456789012345<span>6</span></code><code>123456789012345<span>7</span></code><p>마지막 한 자리가 다른 식별자입니다.</p></div>
        <div class="verification-lane verification-lane--changed"><span class="eyebrow">General 가져오기 · 관측한 값</span><code>123456789012345<span>0</span></code><code>123456789012345<span>0</span></code><p>길이는 16자리 그대로. 값은 같아졌습니다.</p></div>
        <figcaption>합성 데이터로 재현한 가져오기 결과를 다시 그린 도해입니다. 고객 데이터나 실제 Excel 화면이 아닙니다.</figcaption>
      </figure>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">먼저 가져갈 세 가지</span><ol><li>길이가 아니라 원래 문자와 자료형까지 확인합니다.</li><li>필터·숨김·작업 순서도 실험의 입력으로 기록합니다.</li><li>대체 경로가 성공해도 원인을 해결했다고 쓰지 않습니다.</li></ol><p>Windows Excel Version 16.0 / Build 5569.0, 별도 합성 워크북에서 확인한 범위입니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/excel-native-checks#engine">기대값과 실제 엔진</a><a href="https://jhsoftlabs.com/stories/excel-native-checks#identifier">길이가 같은 두 ID</a><a href="https://jhsoftlabs.com/stories/excel-native-checks#state">수식 밖에 있던 입력</a><a href="https://jhsoftlabs.com/stories/excel-native-checks#unresolved">해결하지 못한 경로</a><a href="https://jhsoftlabs.com/stories/excel-native-checks#next-check">내 작업에 적용하기</a></nav>
        <div class="story-content">
          <section id="engine" aria-labelledby="engine-title"><p class="story-section-number">01 / EXPECTED VS OBSERVED</p><h2 id="engine-title">계산이 맞아도 검증이 끝난 것은 아니었습니다</h2><p><a href="https://jhsoftlabs.com/stories/guides-that-work">앞선 제작 기록</a>에서는 독자가 따라 할 수 있는 예제를 만드는 일을 다뤘습니다. 그 다음에는 한 걸음 더 들어갔습니다. 독립 스크립트의 기대값이 맞더라도 Excel의 가져오기와 자료형 처리까지 확인한 것은 아니기 때문입니다.</p><p>실제 Excel을 별도 합성 워크북으로 실행해 값, 자료형, 표시 형식, 조작 순서를 함께 기록했습니다. 신규 원고 검사 32개와 기존 원고 검사 62개를 남겼습니다. 합계 94개는 특정 환경의 검사 행 수이지, 서로 다른 Excel 기능 94개나 모든 버전의 검증을 뜻하지 않습니다.</p></section>
          <section id="identifier" aria-labelledby="identifier-title"><p class="story-section-number">02 / SAME LENGTH, DIFFERENT VALUE</p><h2 id="identifier-title">16자리라는 사실만으로는 충분하지 않았습니다</h2><p>같은 합성 파일에서 ID 열을 General로 가져온 대조군과 Text로 가져온 경우를 비교했습니다. General 대조군에서는 서로 다른 두 ID가 같은 숫자로 합쳐졌습니다. 두 값 모두 길이는 여전히 16자리였습니다.</p><p>원문을 보존한 경로는 <code>QueryTables</code>에서 해당 열을 Text로 지정한 가져오기였습니다. 성공 여부는 원래 문자열과 <code>Value2</code>의 직접 비교, <code>ISTEXT</code>, <code>LEN</code>, <code>EXACT</code>를 함께 사용해 확인했습니다.</p><p>Excel의 숫자 정밀도는 유효 숫자 15자리로 제한됩니다. 계산할 숫자가 아닌 긴 식별자는 숫자로 바꾸기 전에 텍스트로 다루는 것이 중요합니다. <a href="https://support.microsoft.com/en-US/Excel/keeping-leading-zeros-and-large-numbers" target="_blank" rel="noopener noreferrer">Microsoft의 앞자리 0·긴 숫자 안내 <span aria-hidden="true">↗</span><span class="sr-only"> (영문, 새 탭)</span></a></p><div class="story-callout"><strong>‘보기 좋음’과 ‘원본 보존’은 다른 확인입니다.</strong><p>지수 표기로 보인다고 반드시 손상된 것은 아닙니다. 반대로 길게 펼쳐 보이거나 글자 수가 같다고 안전한 것도 아닙니다. 어떤 문자가 실제로 남았는지가 기준입니다.</p></div></section>
          <section id="state" aria-labelledby="state-title"><p class="story-section-number">03 / THE HIDDEN INPUT</p><h2 id="state-title">수식 밖에도 입력이 있었습니다</h2><p><code>SUBTOTAL</code> 실습에서는 필터를 적용한 뒤 행을 수동으로 숨긴 혼합 상태가 예상과 달랐습니다. 이 빌드에서 관측한 결과를 모든 Excel 버전의 일반 규칙으로 바꾸지는 않았습니다.</p><p>원고를 ‘필터만 적용하는 실습’과 ‘필터를 완전히 끈 뒤 수동으로 숨기는 실습’으로 나눴습니다. 실습 전에 이전 필터와 숨김 상태를 초기화하고 혼합 상태의 결과는 별도 관측으로 남겼습니다.</p><ol class="story-flow"><li><strong>초기 상태</strong><span>필터와 숨김을 초기화하고 어떤 행이 보이는지 확인합니다.</span></li><li><strong>한 가지 변화</strong><span>필터 또는 수동 숨김 중 하나를 먼저 적용합니다.</span></li><li><strong>관측과 범위</strong><span>예상값·실제값·실행 순서를 남기고 다른 환경까지 일반화하지 않습니다.</span></li></ol><p>공식과 셀 값만으로 실험을 설명할 수 없었습니다. 작업 순서와 이전 상태도 입력의 일부였습니다.</p></section>
          <section id="unresolved" aria-labelledby="unresolved-title"><p class="story-section-number">04 / WHAT IS STILL UNKNOWN</p><h2 id="unresolved-title">우회에 성공해도 원인은 미해결이었습니다</h2><p><code>OpenText</code>에 Text 설정을 전달했는데도 긴 ID가 숫자로 읽히는 현상을 별도로 재현했습니다. 파일 확장자와 인수 전달을 달리하는 시도도 했지만 근본 원인을 확정하지 못했습니다.</p><p>따라서 결론은 두 줄입니다. QueryTables의 Text 가져오기에서는 원문 보존을 확인했습니다. OpenText 경로의 원인은 아직 미해결입니다. 성공한 대체 방법을 찾았다는 사실과 원인을 해결했다는 주장을 섞지 않았습니다.</p><p>최신 동적 배열 함수, Power Query 편집기·M 실행, 모든 언어와 인코딩 조합은 이번 검증 범위가 아닙니다. 사용한 원고나 샘플이 바뀌면 과거의 PASS도 그 전 버전에 대한 근거로 남습니다.</p></section>
          <section id="next-check" class="note-template" aria-labelledby="next-check-title"><p class="story-section-number">TAKE IT WITH YOU</p><h2 id="next-check-title">다음 가이드에는, 예상과 실제를 나란히</h2><p>샘플, 예상값, 자료형, 실제 엔진, 저장 후 재확인, 미검증 범위를 여섯 개의 질문으로 정리했습니다. 내 원고의 상태를 골라 적고 Markdown으로 가져갈 수 있습니다. 자동 검사는 아니며 입력한 상태는 서버에 보내지 않습니다.</p><div class="story-next"><a class="button button--dark" href="https://jhsoftlabs.com/tools/release-check/?kind=guide">내 가이드 점검하기 <span aria-hidden="true">→</span></a><a class="template-context" href="https://jhsoftlabs.com/templates/#guide-quality">전체 검수 양식 보기</a></div></section>
          <footer class="story-end"><p>2026년 9월의 실제 Excel 실행 보고서와 개발 기록을 바탕으로 작성했습니다. 합성 샘플과 특정 빌드의 관측이며 고객 파일·모든 버전·검색·매출 성과에 대한 검증이 아닙니다.</p><a href="https://csv.jhsoftlabs.com/" target="_blank" rel="noopener noreferrer">Column Harbor에서 파일 위험 점검하기 <span aria-hidden="true">↗</span><span class="sr-only"> (영문 사이트, 새 탭)</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>로그인을 없앤 다음에도, 전환율을 말하지 않은 이유</title>
      <link>https://jhsoftlabs.com/stories/guest-trial-funnel</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/guest-trial-funnel</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 06 · INTERVIEWER LENS</p>
        <h1>로그인을 없앤 다음에도, 전환율을 말하지 않은 이유</h1>
        <p class="story-intro">로그인 없이 첫 진단을 할 수 있게 바꿨는데 결과 복사가 막혀 있었습니다. 긴 답변을 처음부터 쓰는 부담도 남았습니다. 이 문제들을 고치며 무엇을 관찰했는지, 왜 전환율 상승은 말하지 않았는지 적었습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">WHAT CHANGED, AND WHY</span><ol><li>체험 진입뿐 아니라 결과를 가져가는 마지막 행동까지 확인합니다.</li><li>답변을 쓰기 어려운 사람에게는 설명용 예시를 먼저 제공합니다.</li><li>분모·중복·QA를 구분하기 전에는 전환율을 만들지 않습니다.</li></ol><p>기능 구현과 당시 계측 기록에 대한 회고입니다. 로그인 제거의 전환율 상승이나 인과 효과는 확인하지 않았습니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/guest-trial-funnel#guest">로그인은 늦추되 결과는 이어지게</a><a href="https://jhsoftlabs.com/stories/guest-trial-funnel#last-action">결과는 보이는데 복사가 막혀 있었습니다</a><a href="https://jhsoftlabs.com/stories/guest-trial-funnel#example">로그인 다음에는 빈 답변 칸이 있었습니다</a><a href="https://jhsoftlabs.com/stories/guest-trial-funnel#denominator">숫자가 있다는 이유만으로 비율을 만들지 않기</a><a href="https://jhsoftlabs.com/stories/guest-trial-funnel#next">내 작업에 적용하기</a></nav>
        <div class="story-content">
          <section id="guest" aria-labelledby="guest-title"><p class="story-section-number">01 / DEVELOPMENT NOTES</p><h2 id="guest-title">로그인은 늦추되 결과는 이어지게</h2><p>서비스에 관심을 가진 사람이 곧바로 계정을 만들지는 않았습니다. 첫 가치를 경험하기 전에 면접 답변도 쓰고 로그인도 해야 하는 흐름이 부담일 수 있다고 봤습니다. 그래서 첫 진단은 로그인 없이 입력하고 전체 결과를 보고 복사할 수 있도록 열었습니다.</p><p>로그인은 결과를 계정 기록으로 가져오거나 다음 연습으로 이어질 때의 행동으로 옮겼습니다. 단순히 로그인 조건을 지운 것은 아닙니다. 서명된 게스트 쿠키로 주체를 확인하고 봇 검증과 별도 제한을 유지했습니다. 계정 이전에서는 중복 이전과 사용량 반영을 함께 다뤘습니다.</p><p>게스트 쿠키나 IP가 한 사람을 완벽하게 식별하는 것은 아닙니다. 로그인 없는 체험과 악용 방지는 함께 설계해야 하지만 그 설계를 사람당 완전한 제한으로 설명하지는 않습니다.</p></section>
          <section id="last-action" aria-labelledby="last-action-title"><p class="story-section-number">02 / DEVELOPMENT NOTES</p><h2 id="last-action-title">결과는 보이는데 복사가 막혀 있었습니다</h2><p>수정 과정에서 비로그인 상호작용을 한꺼번에 막는 조건이 남았습니다. 결과를 보여주면서 복사 버튼까지 비활성화한 회귀였습니다. 입구는 열었지만 결과를 가져가는 마지막 행동에 장벽을 남긴 셈입니다.</p><p>전체 처리 중의 잠금과 로그인해야 가능한 후속 동작의 잠금을 분리했습니다. 이후 확인 단위도 “진단 화면이 열린다”에서 “입력하고, 결과를 보고, 복사하거나 계정에 저장할 수 있다”로 바꿨습니다.</p><div class="story-callout"><strong>화면 단위의 성공과 작업 단위의 성공은 다릅니다.</strong><p>버튼 하나가 활성화됐는지보다, 사용자가 기대한 결과를 가져갈 수 있는지까지 이어서 확인해야 했습니다.</p></div></section>
          <section id="example" aria-labelledby="example-title"><p class="story-section-number">03 / DEVELOPMENT NOTES</p><h2 id="example-title">로그인 다음에는 빈 답변 칸이 있었습니다</h2><p>계정을 만들지 않아도 긴 답변을 처음부터 쓰는 일은 쉽지 않습니다. 무엇을 얻는지 충분히 이해하기 전에 노력을 요구하는 문제는 남았습니다. 그래서 질문과 주어진 사실, 보완 전후, 바꾼 이유, 예상 꼬리질문을 읽을 수 있는 공개 예시를 만들었습니다.</p><p>예시는 로그인이나 AI 실행 없이 열람하고 같은 질문으로 연습하는 흐름에 연결했습니다. 설명용 답변을 사용자의 입력에 몰래 채우거나 자동 제출하지 않았습니다. 자체 작성 예시는 제품의 사용법을 설명하는 자료이지, 실제 후기나 AI 품질을 증명하는 결과가 아닙니다.</p><p>이 경로를 관찰하기 위해 예시 열람, 연습 클릭, 입력 화면 표시, 입력 시작, 서버 접수·완료를 구분했습니다. 이벤트에는 답변이나 이력서 원문을 넣지 않고 필요한 단계와 허용된 식별자만 남겼습니다.</p></section>
          <section id="denominator" aria-labelledby="denominator-title"><p class="story-section-number">04 / DEVELOPMENT NOTES</p><h2 id="denominator-title">숫자가 있다는 이유만으로 비율을 만들지 않기</h2><p>진단 행에는 게스트 결과를 계정으로 옮긴 복사본과 운영자 QA가 섞일 수 있습니다. 브라우저의 결과 확인 이벤트와 서버의 완료 이벤트를 더하면 같은 작업을 중복으로 셀 수도 있습니다. 계측을 시작하기 전 방문에는 새 식별자가 없으므로 과거 행동을 소급해 복원할 수도 없습니다.</p><ol class="story-flow"><li><strong>무엇을 한 번으로 셀지</strong><span>사람, 탭 세션, 요청, 저장 행을 서로 바꿔 쓰지 않습니다. 결과 이전 복사본은 새로운 진단과 구분합니다.</span></li><li><strong>어떤 관찰을 제외할지</strong><span>QA 유입과 지정 운영자 활동을 성장 집계에서 분리하고 제외 범위를 기록합니다.</span></li><li><strong>어디까지 연결되는지</strong><span>같은 기간의 같은 탭 관찰도 순서나 인과의 증거는 아닙니다. 기기 이동·추적 차단·미수집 구간은 한계로 남깁니다.</span></li></ol><p>분모가 없으면 전환율은 0%가 아니라 판단 불가입니다. 당시에는 게스트 체험과 예시, 단계별 계측을 구현하고 확인했다는 사실을 말할 수 있었습니다. 반복 사용, 전환 상승, 시장 적합성은 별도로 검증해야 할 질문으로 남았습니다.</p></section>
          <section id="next" class="note-template" aria-labelledby="next-title"><p class="story-section-number">KEEP EXPLORING</p><h2 id="next-title">읽은 뒤, 내 작업에서 확인할 것</h2><p>입구의 로그인만이 아니라 결과를 가져가는 마지막 행동까지 살펴보세요. Interviewer Lens의 현재 사용 흐름은 서비스 페이지에서 확인할 수 있습니다.</p><a class="button button--dark" href="https://interview.jhsoftlabs.com/" target="_blank" rel="noopener noreferrer">Interviewer Lens 살펴보기<span aria-hidden="true">↗</span><span class="sr-only"> (새 탭)</span></a></section>
          <footer class="story-end"><p>게스트 체험·결과 복사 회귀와 유입 계측 개발기록을 바탕으로 작성했습니다. 문서에 따로 제안된 후속 화면 개편은 완료 성과에 포함하지 않았습니다. 현재 서비스 정책과 화면은 이후 변경될 수 있으며 이번 편집에서 전환율이나 고객 행동을 새로 조회하지 않았습니다.</p><a href="https://jhsoftlabs.com/stories/ai-retry-safety">이어서 읽기 · 결과 저장과 재시도의 경계 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>24편을 쓴 뒤에도, 끝났다고 할 수 없었다</title>
      <link>https://jhsoftlabs.com/stories/guides-that-work</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/guides-that-work</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 02 · COLUMN HARBOR</p>
        <h1>24편을 쓴 뒤에도, 끝났다고 할 수 없었다</h1>
        <p class="story-intro">CSV 가이드 24편을 만들고도 예제를 다시 고쳐야 했습니다. 글을 공개한 뒤에는 검색에서 발견되는지도 따로 확인했습니다. 독자가 따라 할 수 있는 예제와 완료 기준을 어떻게 점검했는지 적었습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">이번 작업에서 남긴 것</span><ol><li>키워드보다 독자가 해야 할 행동을 구분했습니다.</li><li>문제가 드러나지 않는 예제를 수정했습니다.</li><li>공개 완료와 검색 성과를 따로 기록했습니다.</li></ol><p>작업 기록 기반의 회고입니다. 확인한 일과 아직 모르는 결과를 나눠 적었습니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/guides-that-work#intent">비슷한 검색어여도 할 일은 반대였습니다</a><a href="https://jhsoftlabs.com/stories/guides-that-work#example">설명은 있었지만 문제가 안 보이는 예제가 있었습니다</a><a href="https://jhsoftlabs.com/stories/guides-that-work#delivery">이미지는 장식보다 확인 도구로</a><a href="https://jhsoftlabs.com/stories/guides-that-work#discovery">공개한 글 수를 검색 성과로 쓰지 않았습니다</a><a href="https://jhsoftlabs.com/stories/guides-that-work#takeaway">다음 원고에는 ‘예상 결과’부터 적기</a><a href="https://jhsoftlabs.com/stories/guides-that-work#template">함께 쓰는 무료 템플릿</a></nav>
        <div class="story-content">
          <section id="intent" aria-labelledby="intent-title"><p class="story-section-number">01 / WORK NOTES</p><h2 id="intent-title">비슷한 검색어여도 할 일은 반대였습니다</h2><p>Column Harbor의 영어 가이드는 기존 4편을 보강하고 20편을 추가해 24편으로 확장했습니다. CSV 오류, 숫자와 ID 보존, 날짜, 텍스트 정리, 파일 비교와 결합, 형식 변환의 여섯 주제로 묶었습니다.</p><p>분류에서 중요했던 것은 같은 단어가 들어간 글을 모으는 것만이 아니었습니다. ‘숫자를 텍스트로 보존해야 하는 상황’과 ‘숫자가 텍스트라서 합계가 계산되지 않는 상황’은 비슷해 보여도 독자가 해야 할 행동은 반대입니다. 전자는 식별자를 지키는 문제이고 후자는 계산할 값을 다루는 문제입니다.</p><p>그래서 제목과 첫 설명에 증상뿐 아니라 어떤 상황을 해결하는 글인지 드러내도록 정리했습니다. 한 글에서 모든 경우를 해결하려 하기보다 독자가 지금 겪는 문제 하나에 맞춰 예제와 절차를 연결했습니다.</p></section>
          <section id="example" aria-labelledby="example-title"><p class="story-section-number">02 / WORK NOTES</p><h2 id="example-title">설명은 있었지만 문제가 안 보이는 예제가 있었습니다</h2><p>최종 검토 중 날짜 정렬 글에서 문제 상황이 눈에 드러나지 않는 예제를 발견해 수정했습니다. 문장으로 ‘정렬이 잘못된다’고 말하더라도 독자가 같은 샘플로 차이를 확인할 수 없다면 예제는 설명의 근거가 되기 어렵습니다.</p><p>변환 글에서는 실행 코드가 내는 결과를 예상 결과와 대조했습니다. 특히 빈 값과 0을 같은 것으로 읽지 않도록 설명을 다듬었고 TSV 변환에서는 저장한 파일을 다시 읽어 원래 값이 보존되는지 확인하도록 절차를 보강했습니다.</p><div class="story-callout"><strong>작성자의 화면에서 맞아 보이는 것만으로는 부족했습니다.</strong><p>문제가 드러나는 입력, 실행할 절차, 확인 가능한 결과를 한 세트로 준비해야 독자가 직접 판단할 수 있습니다.</p></div><p>여러 파일을 비교하거나 합치는 글은 파일별 샘플을 내려받게 했습니다. 독자가 예제를 다시 만드는 데 시간을 쓰기보다 설명한 차이를 바로 확인하도록 하기 위해서입니다.</p></section>
          <section id="delivery" aria-labelledby="delivery-title"><p class="story-section-number">03 / WORK NOTES</p><h2 id="delivery-title">이미지는 장식보다 확인 도구로</h2><p>본문에는 원본과 처리 결과를 비교하는 도해 48개를 준비했습니다. 실제 Excel 화면인 것처럼 꾸미기보다 무엇이 달라지는지를 설명하는 도해로 구성했습니다. 큰 화면에서는 좌우로, 좁은 화면에서는 위아래로 읽히도록 점검했습니다.</p><p>글마다 이미지, 샘플 다운로드, 관련 글 연결을 확인했습니다. 원고가 완성돼도 내려받을 파일이 없거나 다음 설명으로 가는 링크가 끊겨 있다면 독자에게는 미완성된 안내이기 때문입니다. <a href="https://csv.jhsoftlabs.com/guides/" target="_blank" rel="noopener noreferrer">공개한 영어 가이드 둘러보기 ↗<span class="sr-only"> (새 탭)</span></a></p></section>
          <section id="discovery" aria-labelledby="discovery-title"><p class="story-section-number">04 / WORK NOTES</p><h2 id="discovery-title">공개한 글 수를 검색 성과로 쓰지 않았습니다</h2><p>후속 작업에서는 브랜드명 검색과 개별 문제 해결 검색어를 나눠 보았습니다. 조회한 작업 기록에서는 홈페이지의 색인을 확인했지만 신규 대표 글은 발견된 뒤 아직 색인되지 않은 상태였습니다. 따라서 홈페이지가 검색에 들어갔다는 사실을 모든 글의 검색 노출로 확대하지 않았습니다.</p><p>사이트 이름을 설명하는 정보와 가이드 목록 제목을 보완하고 갱신된 사이트맵을 제출하는 작업이 이어졌습니다. 다만 이 글은 그 작업으로 순위나 방문자가 늘었다고 주장하지 않습니다. 재수집 요청이나 색인 여부는 성과 확인의 일부일 뿐입니다.</p><p>Google도 수집, 색인, 검색 결과 제공을 서로 다른 단계로 설명하고 사이트가 지침을 따르더라도 모든 단계의 처리를 보장하지 않는다고 안내합니다. <a href="https://developers.google.com/search/docs/fundamentals/how-search-works" target="_blank" rel="noopener noreferrer">Google 검색의 처리 단계 ↗<span class="sr-only"> (새 탭)</span></a></p></section>
          <section id="takeaway" aria-labelledby="takeaway-title"><p class="story-section-number">05 / WORK NOTES</p><h2 id="takeaway-title">다음 원고에는 ‘예상 결과’부터 적기</h2><p>이번 기록에서 가져갈 수 있는 방법은 간단합니다. 원고 분량을 늘리기 전에 ‘독자가 이 샘플을 사용하면 무엇을 확인하게 되는가’를 한 줄로 적습니다. 그 다음에 입력 파일, 절차, 결과, 잘못 적용하면 안 되는 조건을 맞춥니다.</p><p>제작 기록에는 공개 여부를, 검색 기록에는 색인·노출·클릭을 따로 남깁니다. 아직 측정하지 않은 칸은 0으로 채우지 않고 ‘미확인’으로 둡니다. 그래야 다음에 더 쓸지, 기존 글을 고칠지 판단할 근거가 생깁니다.</p></section>
          <section id="template" class="note-template" aria-labelledby="template-title"><p class="story-section-number">TAKE IT WITH YOU</p><h2 id="template-title">이 과정을 내 작업에 적용하기</h2><p>이번 시행착오에서 점검 항목을 추려 빈 양식으로 다시 만들었습니다. 내부 문서 원본이 아닌 공개용 템플릿이며 예시는 합성한 상황입니다.</p><a class="button button--dark" href="https://jhsoftlabs.com/templates/#guide-quality">무료 템플릿 미리보기 <span aria-hidden="true">→</span></a></section>
          <footer class="story-end"><p>2026년 9월의 원고 제작 및 검색 점검 대화를 바탕으로 정리했습니다. 24편과 도해 48개는 당시 제작 범위이며, 현재 누적 수치나 검색·매출 성과를 뜻하지 않습니다.</p><a href="https://csv.jhsoftlabs.com/guides/" target="_blank" rel="noopener noreferrer">영어 가이드 읽기 <span aria-hidden="true">↗</span><span class="sr-only"> (새 탭)</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>색을 바꿔도 같아 보이던 홈페이지를 고친 방법</title>
      <link>https://jhsoftlabs.com/stories/homepage-information-order</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/homepage-information-order</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 08 · JH SOFT LABS</p>
        <h1>색을 바꿔도 같아 보이던 홈페이지를 고친 방법</h1>
        <p class="story-intro">첫 홈페이지는 큰 소개문과 카드가 먼저 눈에 들어왔습니다. 실제 프로젝트를 찾기 쉽도록 화면 순서를 바꾸고 글·자료에 분류와 검색을 붙였습니다. 작업 기록과 점검 도구를 연결한 과정도 함께 적었습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">WHAT CHANGED, AND WHY</span><ol><li>프로젝트, 글, 채널을 같은 종류의 링크로 다루지 않았습니다.</li><li>프로젝트 이름과 용도를 시각 예시보다 먼저 읽도록 바꿨습니다.</li><li>작동하는 기본 HTML 위에 검색과 작은 도구를 얹었습니다.</li></ol><p>홈페이지를 만들며 받은 피드백과 점검 내용을 정리했습니다. 레이아웃을 바꾼 뒤 클릭률이 올랐는지는 아직 확인하지 못했습니다.</p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/homepage-information-order#same-shape">문제는 색상이 아니라 읽는 순서였습니다</a><a href="https://jhsoftlabs.com/stories/homepage-information-order#reading-order">그림보다 먼저, 무엇을 하는지</a><a href="https://jhsoftlabs.com/stories/homepage-information-order#simple">정적 홈페이지에 필요한 상태만 두기</a><a href="https://jhsoftlabs.com/stories/homepage-information-order#verification">잘 보인다는 판단과, 확인 가능한 검사를 나누기</a><a href="https://jhsoftlabs.com/stories/homepage-information-order#next">내 작업에 적용하기</a></nav>
        <div class="story-content">
          <section id="same-shape" aria-labelledby="same-shape-title"><p class="story-section-number">01 / DEVELOPMENT NOTES</p><h2 id="same-shape-title">문제는 색상이 아니라 읽는 순서였습니다</h2><p>처음 홈페이지는 큰 제목, 넓은 소개 영역, 반복된 카드로 구성했습니다. 색과 문구를 바꿔도 같은 화면처럼 느껴지는 문제가 남았습니다. 실제 서비스보다 “어떤 사람의 공간인가”를 설명하는 문장이 더 먼저, 더 크게 읽혔습니다.</p><p>소개 내용도 다시 확인해야 했습니다. 면접 답변 진단 도구를 준비 글처럼 다루거나 AI 홍보 콘텐츠 제작 서비스를 사이드 프로젝트 아카이브로 설명하면 카드가 아무리 깔끔해도 목적지를 잘못 안내합니다.</p><p>먼저 서비스의 성격을 확인하고 프로젝트를 글·SNS와 분리했습니다. 이 기록을 작성할 당시 홈페이지에서는 대표 프로젝트 세 개의 이름과 용도를 먼저 보여줬습니다. 캐릭터와 소개는 신뢰를 보태되 실제로 써 볼 대상을 가리지 않도록 배치했습니다.</p></section>
          <section id="reading-order" aria-labelledby="reading-order-title"><p class="story-section-number">02 / DEVELOPMENT NOTES</p><h2 id="reading-order-title">그림보다 먼저, 무엇을 하는지</h2><p>프로젝트 그림은 기능을 짐작하게 해 주지만 그것만으로 용도가 확정되지는 않습니다. 면접 진단 예시, AI 홍보물, CSV 값 비교 앞에 이름과 짧은 목적 설명을 배치했습니다. 코드의 읽기 순서도 시각 순서와 맞췄습니다.</p><p>그 다음에는 방문 목적에 따라 이동할 수 있게 했습니다. 프로젝트 바로가기, 글·자료 분류, 검색, 입문 경로, 실험 기록을 연결하되 대표 서비스 카드를 계속 늘리는 방식은 피했습니다. 모든 링크를 똑같이 크게 만드는 대신 어떤 행동을 먼저 할지 정했습니다.</p><div class="story-callout"><strong>먼저 써 보기 → 만든 과정 읽기 → 내 작업에 적용하기</strong><p>프로젝트, 개발기, 무료 템플릿과 작은 도구가 각각 다른 역할을 갖도록 만들었습니다. 이 순서는 제품 가설이지, 측정으로 입증한 최적 전환 경로는 아닙니다.</p></div></section>
          <section id="simple" aria-labelledby="simple-title"><p class="story-section-number">03 / DEVELOPMENT NOTES</p><h2 id="simple-title">정적 홈페이지에 필요한 상태만 두기</h2><p>HTML·CSS·vanilla JavaScript를 유지했습니다. 링크와 본문은 HTML에 있고 검색·분류·더 보기는 기존 스크립트가 담당합니다. 입문 경로와 템플릿 미리보기는 네이티브 펼침으로 구성해 별도의 앱 상태 없이도 읽을 수 있습니다.</p><p>CSV 비교는 합성 상품 코드 하나만 사용하는 설명용 체험입니다. 실제 파일을 받거나 원본을 수정하지 않습니다. 작업 점검 도구도 자동 진단을 붙이는 대신 방문자가 상태를 직접 고르고 기록을 가져가도록 만들었습니다.</p><p>점검 상태는 현재 탭의 메모리에만 두고 URL에는 작업 유형만 남겼습니다. 분류한 항목 수를 배포 안전 점수로 부르지 않았습니다. 기능이 많아 보이는 것보다 실제로 제공하는 범위와 저장하지 않는 것을 분명히 하는 쪽을 선택했습니다.</p></section>
          <section id="verification" aria-labelledby="verification-title"><p class="story-section-number">04 / DEVELOPMENT NOTES</p><h2 id="verification-title">잘 보인다는 판단과, 확인 가능한 검사를 나누기</h2><p>화면에서는 여러 폭의 가로 넘침과 글자 잘림, 키보드 조작을 확인했습니다. 자동 검사에서는 기존 외부 링크, 프로젝트 수, 검색·URL 복원, 내부 경로와 메타데이터, 다운로드 원문과 미리보기의 일치를 다뤘습니다.</p><p>작업 점검 도구의 Blob 생성과 오류 대체 경로는 자동 검사했지만 당시 인앱 브라우저에서는 실제 파일 저장과 OS 클립보드 전달을 확정하지 못했습니다. 성공 안내만으로 검증 완료라고 하지 않고 API 없이 텍스트를 직접 선택하는 경로를 추가해 실제 화면에서 확인했습니다.</p><p>비공개 출처는 공개 원고와 분리하고 Git·웹 배포에서 제외했습니다. 배포 후에는 필요한 페이지의 응답뿐 아니라 내부 문서가 공개되지 않는지도 확인했습니다. 이런 점검도 완전한 보안 감사나 모든 보조기기의 접근성 검증과는 다릅니다.</p><p>정보 순서와 행동 경로, 실패 시 대안이 바뀌었습니다. 클릭률과 전환율의 개선은 별도 관찰이 있어야 합니다. 다음 기능도 공간이 비어 보인다는 이유보다 방문자가 막히는 지점이 확인됐을 때 추가하려고 합니다.</p></section>
          <section id="next" class="note-template" aria-labelledby="next-title"><p class="story-section-number">KEEP EXPLORING</p><h2 id="next-title">읽은 뒤, 내 작업에서 확인할 것</h2><p>내 홈페이지에서도 제목보다 먼저 보여야 할 서비스가 있는지, 스크립트나 외부 기능이 실패해도 내용을 읽고 가져갈 수 있는지 확인해보세요.</p><a class="button button--dark" href="https://jhsoftlabs.com/tools/release-check/?kind=site">내 웹사이트 점검하기<span aria-hidden="true">→</span></a></section>
          <footer class="story-end"><p>홈페이지 개선계획·검증 기록과 프로젝트 포트폴리오의 정보 구조 사례를 바탕으로 작성했습니다. 과거 검증과 이번 콘텐츠 편집을 구분하며, 방문·매출 효과나 전면적인 접근성 검증 완료를 주장하지 않습니다.</p><a href="https://jhsoftlabs.com/stories/excel-native-checks">이어서 읽기 · 예상과 실제 결과를 나란히 보기 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>이미지를 만들었는데, 일은 끝나지 않았다</title>
      <link>https://jhsoftlabs.com/stories/salon-publishing</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/salon-publishing</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 01 · DEEP CONSTELLATION</p>
        <h1>이미지를 만들었는데, 일은 끝나지 않았다</h1>
        <p class="story-intro">AI 이미지를 만든 뒤에도 할 일이 남았습니다. 게시물로 내보내고 웹사이트를 배포한 다음 상대에게 소개하기까지, 단계마다 어디까지 마쳤는지 확인해야 했습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">이번 작업에서 남긴 것</span><ol><li>이미지 제작과 실제 게시를 분리했습니다.</li><li>공개 파일과 내부 점검의 경계를 다시 맞췄습니다.</li><li>소개를 보낸 사실을 고객 반응으로 계산하지 않았습니다.</li></ol><p>작업 기록 기반의 회고입니다. 확인한 일과 아직 모르는 결과를 나눠 적었습니다.</p></aside>
      <aside class="story-callout" aria-label="프로젝트 상태 업데이트"><strong>2026. 10. 03. · 추가 활동을 중단했습니다.</strong><p>이 글은 당시 제작·배포 기록으로 보존합니다. 이후 수익을 만들지 못했고 수익성이 낮다고 판단해 추가 활동을 멈췄습니다. <a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective">중단 배경과 다음의 기준을 담은 회고 읽기 →</a></p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/salon-publishing#output">완성 파일 다음의 일</a><a href="https://jhsoftlabs.com/stories/salon-publishing#deployment">로컬 검사는 통과했는데 배포는 실패했습니다</a><a href="https://jhsoftlabs.com/stories/salon-publishing#outreach">소개를 보냈다는 것과, 관심을 얻었다는 것</a><a href="https://jhsoftlabs.com/stories/salon-publishing#takeaway">다음부터 남길 세 칸</a><a href="https://jhsoftlabs.com/stories/salon-publishing#template">함께 쓰는 무료 템플릿</a></nav>
        <div class="story-content">
          <section id="output" aria-labelledby="output-title"><p class="story-section-number">01 / WORK NOTES</p><h2 id="output-title">완성 파일 다음의 일</h2><p>Deep Constellation에서는 미용실 홍보에 쓸 AI 이미지를 만들고 게시글과 서비스 페이지로 연결하는 작업을 진행했습니다. 이번 기록의 출발점은 단발 C컬과 아웃컬을 비교하는 게시물입니다. 완성된 이미지가 있어도 그것만으로 게시 준비가 끝난 것은 아니었습니다.</p><p>게시할 때는 AI로 만든 예시라는 표시와 실제 시술 결과가 아니라는 설명을 함께 넣었습니다. 이미지의 분위기를 보여주는 일과 특정 매장의 실적을 증명하는 일은 다르기 때문입니다. 제작 완료와 게시 완료도 따로 기록했습니다. 게시하지 않은 파일을 이미 공개한 결과물처럼 세지 않기 위해서입니다.</p><div class="story-callout"><strong>결과물은 파일 하나가 아니라, 전달 가능한 한 묶음입니다.</strong><p>이미지, 설명, 예시 표기, 연결할 페이지. 이 네 가지를 함께 확인해야 방문자가 무엇을 보고 있는지 이해할 수 있습니다.</p></div></section>
          <section id="deployment" aria-labelledby="deployment-title"><p class="story-section-number">02 / WORK NOTES</p><h2 id="deployment-title">로컬 검사는 통과했는데 배포는 실패했습니다</h2><p>같은 작업 중 홈페이지의 자동 배포가 실패했습니다. 로컬에서 빌드와 테스트를 확인했는데도 배포 환경에서는 내부 운영 자료를 참조하는 테스트가 실행되지 않았습니다. 공개 배포에서 제외한 문서를 테스트가 여전히 필요로 하는 상태였습니다.</p><p>이때 검사를 전부 끌 필요는 없었습니다. 공개 사이트를 검사하는 범위와 내부 운영 키트를 검사하는 범위를 분리했습니다. 내부 전용 테스트는 공개 배포 입력에서 제외하고 로컬에서는 전체 검사를 유지했습니다.</p><p>수정 후에는 실제 도메인에서 홈페이지와 소개 페이지, 이미지가 열리는지 확인했습니다. 반대로 내부 운영 자료는 공개 주소로 열리지 않는지도 확인했습니다. 배포 성공 표시만 보는 것과, 공개되어야 할 것과 없어야 할 것을 모두 확인하는 것은 다른 점검이었습니다.</p></section>
          <section id="outreach" aria-labelledby="outreach-title"><p class="story-section-number">03 / WORK NOTES</p><h2 id="outreach-title">소개를 보냈다는 것과, 관심을 얻었다는 것</h2><p>게시물은 관련성이 있는 소수의 매장에 소개했습니다. 후보를 볼 때 팔로워 수만 기준으로 삼지 않고 최근 활동과 헤어 콘텐츠의 관련성을 함께 살폈습니다. 이미 연락한 대상과 겹치거나 거절 의사를 보인 곳을 다시 접촉하지 않도록 확인하는 절차도 두었습니다.</p><p>하지만 메시지 전송과 링크 미리보기를 확인했다고 해서 문의나 주문이 생긴 것은 아닙니다. 이번에 조회한 기록에는 소개를 보낸 사실은 있지만 그에 대한 답변과 구매 전환은 확인되지 않았습니다. 그래서 이 글을 ‘고객 확보 성공기’로 쓰지 않았습니다.</p><p>상대의 계정명, 대화 내용, 연락처는 여기에 공개하지 않습니다. 소개 전에 어떤 기준을 확인했는지와 결과를 어디까지 확인했는지만 공개할 수 있습니다.</p></section>
          <section id="takeaway" aria-labelledby="takeaway-title"><p class="story-section-number">04 / WORK NOTES</p><h2 id="takeaway-title">다음부터 남길 세 칸</h2><ol class="story-flow"><li><strong>만든 것</strong><span>최종 파일, 사용할 채널, 게시글, AI 예시 표기까지 준비했는가.</span></li><li><strong>내보낸 것</strong><span>게시물과 서비스 링크가 실제로 열리는가. 내부 자료는 공개되지 않는가.</span></li><li><strong>돌아온 반응</strong><span>답변·문의·주문 중 무엇을 확인했는가. 아직 확인하지 못한 것은 무엇인가.</span></li></ol><p>이 세 칸을 나누면 제작량을 성과로 착각하는 일을 줄일 수 있습니다. 예쁜 샘플을 만드는 일도 중요하지만 전달한 뒤 무엇을 배웠는지 남기는 일까지가 하나의 실험입니다.</p><p>공개 페이지를 운영할 때는 호스팅의 상업적 이용 조건도 별도로 확인해야 합니다. 배포가 기술적으로 성공했다는 사실만으로 운영 조건까지 해결된 것으로 기록하지 않는 것도 같은 원칙입니다.</p></section>
          <section id="template" class="note-template" aria-labelledby="template-title"><p class="story-section-number">TAKE IT WITH YOU</p><h2 id="template-title">이 과정을 내 작업에 적용하기</h2><p>이번 시행착오에서 점검 항목을 추려 빈 양식으로 다시 만들었습니다. 내부 문서 원본이 아닌 공개용 템플릿이며 예시는 합성한 상황입니다.</p><a class="button button--dark" href="https://jhsoftlabs.com/templates/#content-release">무료 템플릿 미리보기 <span aria-hidden="true">→</span></a></section>
          <footer class="story-end"><p>본문은 2026년 9월의 제작·게시·배포 대화를 바탕으로 정리한 당시 기록이며, 고객 확보나 매출 성과를 검증한 글이 아닙니다. 2026년 10월 3일의 추가 활동 중단 안내를 별도로 덧붙였고, 이후의 판단은 연결된 회고에 정리했습니다.</p><a href="https://image.jhsoftlabs.com/hair-salon.html#cases" target="_blank" rel="noopener noreferrer">홍보물 제작 사례 보기 <span aria-hidden="true">↗</span><span class="sr-only"> (새 탭)</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
    <item>
      <title>24초 영상에도, 배포 파이프라인이 필요했습니다</title>
      <link>https://jhsoftlabs.com/stories/shorts-release-pipeline</link>
      <guid isPermaLink="true">https://jhsoftlabs.com/stories/shorts-release-pipeline</guid>
      <pubDate>Sat, 12 Sep 2026 15:00:00 GMT</pubDate>
      <description><![CDATA[
      <header class="story-header">
        <p class="eyebrow">FIELD NOTE 07 · DEEP CONSTELLATION</p>
        <h1>24초 영상에도, 배포 파이프라인이 필요했습니다</h1>
        <p class="story-intro">AI 이미지 세 장으로 짧은 홍보영상을 만들었습니다. 키프레임과 문자 경계를 살피고 완성 파일을 끝까지 디코딩했습니다. 제작·게시 여부와 성과 측정 상태도 따로 기록했습니다.</p>
        <div class="story-byline"><img src="https://jhsoftlabs.com/assets/hoemon-profile.jpg" alt="회몬 캐릭터" width="40" height="40" /><div><strong>회몬</strong><span>개발자 · 메이커</span></div><time datetime="2026-09-13">2026. 09. 13.</time></div>
        <p class="story-revision">수정: <time datetime="2026-10-05">2026. 10. 05.</time> · 당시의 무음 완성본과 장면 설명을 본문에 추가했습니다.</p>
      </header>
      <aside class="note-summary" aria-label="이 기록의 핵심"><span class="eyebrow">WHAT CHANGED, AND WHY</span><ol><li>기존 AI 이미지는 원본 그대로 두고 문구와 모션을 별도로 합성했습니다.</li><li>예쁜 키프레임과 끝까지 재생되는 출력 파일은 별도로 검사했습니다.</li><li>제작 완료, 게시 요청, 공개 확인, 성과 측정을 나눠 기록했습니다.</li></ol><p>2026년 9월 13일의 첫 홍보영상 제작·QA·후속 게시 기록입니다. 다른 캠페인 영상의 제작 상태와 섞지 않았습니다.</p></aside>
      <aside class="story-callout" aria-label="프로젝트 상태 업데이트"><strong>2026. 10. 03. · 추가 활동을 중단했습니다.</strong><p>영상 제작과 검수 기록은 남기되, 수익화 성공으로 소개하지 않습니다. <a href="https://jhsoftlabs.com/stories/deep-constellation-retrospective">중단 배경과 다음의 기준을 담은 회고 읽기 →</a></p></aside>
      <div class="story-layout">
        <nav class="story-toc" aria-label="이 글의 목차"><span>이 글에서</span><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline#package">영상 한 개보다, 다시 만들 수 있는 묶음</a><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline#preview">전체 렌더 전에, 읽히는지 먼저</a><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline#decode">인코딩이 끝난 것과 재생이 되는 것은 달랐습니다</a><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline#publication">제작 시점의 미게시와, 이후 공개 완료는 모순이 아닙니다</a><a href="https://jhsoftlabs.com/stories/shorts-release-pipeline#next">내 작업에 적용하기</a></nav>
        <div class="story-content">
          <figure class="article-video" id="video-example">
            <video controls playsinline preload="none" width="1080" height="1920" poster="../assets/salon-season.webp" aria-label="Deep Constellation 24초 무음 제작 예시" aria-describedby="video-caption video-status">
              <source src="https://jhsoftlabs.com/assets/deep-constellation-promo-muted.mp4" type="video/mp4" />
              이 브라우저에서는 동영상을 재생할 수 없습니다. 아래 MP4 링크나 장면 설명을 이용하세요.
            </video>
            <figcaption id="video-caption">2026년 9월 13일 제작한 24초 무음 버전입니다. AI 가상 매장·인물 예시이며 실제 고객 납품이나 시술 결과가 아닙니다. 재생 전 대표 이미지는 영상에 사용한 시즌 포스터입니다. 자동재생하지 않으며 외부 영상 플레이어에 연결하지 않습니다.</figcaption>
          </figure>
          <p id="video-status">이 영상은 <strong>활동 중단 전의 제작 기록</strong>입니다. 끝부분의 상담 안내도 당시 영상에 포함된 문구이며, 현재 서비스 운영이나 수익 성과를 뜻하지 않습니다. 약 6.9MB의 MP4는 재생할 때 불러옵니다.</p>
          <p class="sample-links"><a href="https://jhsoftlabs.com/assets/deep-constellation-promo-muted.mp4" download>무음 MP4 내려받기</a></p>
          <details class="template-preview video-transcript">
            <summary>영상 대신 읽는 장면 설명 <span aria-hidden="true">+</span></summary>
            <ol class="story-flow">
              <li><strong>0:00–0:03 · 질문</strong><span>시즌·오픈·예약용 포스터가 모인 화면에 “홍보할 때마다, 새 사진이 필요할까요?”를 표시합니다.</span></li>
              <li><strong>0:03–0:05.5 · 주제 전환</strong><span>“사진이 없어도, 소식은 있어요.”라는 문구로 매장의 소식을 콘텐츠로 만드는 주제를 소개합니다.</span></li>
              <li><strong>0:05.5–0:08.5 · 시즌 예시</strong><span>가을 헤어스타일 포스터와 “계절에 맞는 스타일 제안.”을 보여줍니다. 인물은 AI 가상 예시입니다.</span></li>
              <li><strong>0:08.5–0:11.5 · 오픈 예시</strong><span>가상 매장의 오픈 포스터에 “새로운 시작을 알리는 한 장.”을 겹쳐 보여줍니다.</span></li>
              <li><strong>0:11.5–0:14.5 · 예약 예시</strong><span>예약 안내 포스터에 “반복하는 설명도 한눈에.”를 표시합니다.</span></li>
              <li><strong>0:14.5–0:18.5 · 제작 묶음</strong><span>“알릴 소식 하나를, 게시 준비 한 묶음으로.”와 피드 1종·같은 디자인 스토리·게시글 구성을 설명합니다.</span></li>
              <li><strong>0:18.5–0:24 · 당시의 마무리 문구</strong><span>“원장님은 소식만. 우리는 콘텐츠로.”와 당시 홈페이지 상담 안내를 표시합니다. 음성 내레이션은 없습니다.</span></li>
            </ol>
          </details>
          <section id="package" aria-labelledby="package-title"><p class="story-section-number">01 / DEVELOPMENT NOTES</p><h2 id="package-title">영상 한 개보다, 다시 만들 수 있는 묶음</h2><p>Deep Constellation을 소개하는 짧은 영상은 기존 시즌·오픈·예약용 AI 이미지를 재료로 삼았습니다. 새로운 고객 사례를 만드는 작업이 아니라, 매장 소식이 어떻게 홍보 콘텐츠가 되는지 보여주는 자체 시연이었습니다.</p><p>원본 래스터 이미지는 수정하지 않고 문구와 모션을 코드로 합성했습니다. 출력은 1080×1920, 30fps, 24초의 세로 영상이었습니다. 실사 인물이 움직이는 AI 생성 영상으로 소개하지 않았습니다.</p><p>제작 산출물도 MP4 하나로 두지 않았습니다. 음악 포함본과 무음본, 커버, 화면 문구의 타이밍 파일, 검수용 프레임, 출력 규격과 해시를 담은 manifest를 함께 남겼습니다. 화면 문구는 이미 영상에 들어 있으므로 타이밍 파일을 음성 내레이션 자막처럼 중복 표시하지 않도록 구분했습니다.</p></section>
          <section id="preview" aria-labelledby="preview-title"><p class="story-section-number">02 / DEVELOPMENT NOTES</p><h2 id="preview-title">전체 렌더 전에, 읽히는지 먼저</h2><p>작은 포스터 안의 글자를 세로 영상에 그대로 넣으면 화면에는 있지만 읽기 어려울 수 있습니다. 포스터는 시각 예시로 두고 핵심 설명은 별도의 큰 문구로 배치했습니다. 먼저 미리보기 키프레임을 만들고 문자 경계와 최소 크기를 확인했습니다.</p><p>당시 검사에서는 일곱 키프레임을 직접 보고 핵심 문구가 설정한 안전 영역 안에 있는지 확인했습니다. 이 검사는 프로젝트에서 정한 레이아웃 기준입니다. 모든 플랫폼과 기기의 버튼·자막 위치를 보장하는 공식 규격이라고 설명하지 않습니다.</p><div class="story-callout"><strong>원본, 설명, 공개 표기를 분리했습니다.</strong><p>자체 AI 가상 예시와 실제 고객·시술 결과는 다릅니다. 그 구분을 각 장면에서도 읽을 수 있게 두고 이후 게시 단계의 표기와 따로 확인했습니다.</p></div></section>
          <section id="decode" aria-labelledby="decode-title"><p class="story-section-number">03 / DEVELOPMENT NOTES</p><h2 id="decode-title">인코딩이 끝난 것과 재생이 되는 것은 달랐습니다</h2><p>키프레임이 잘 나왔다고 최종 동영상이 온전하다고 볼 수는 없었습니다. 출력된 두 MP4를 FFmpeg로 끝까지 디코딩하고 브라우저에서 해상도와 24초 재생시간을 확인했습니다. 제작 QA에는 음악 포함본 720프레임과 디코딩 오류 없음이 기록돼 있습니다.</p><p>검은 화면 구간과 오디오 클리핑도 설정한 검사 조건으로 살폈습니다. 여기서 “보고 없음”은 그 검사 조건의 결과입니다. 실제 기기의 청취 평가나 모든 재생 환경을 대체하지 않습니다.</p><p>배경음은 코드로 새로 합성했고 기존 곡이나 외부 오디오 샘플은 쓰지 않았습니다. 폰트 라이선스 사본도 제작 자료에 보존했습니다. 그렇다고 이후 플랫폼의 자동 권리 판정까지 보장한다고 쓰지는 않았습니다.</p></section>
          <section id="publication" aria-labelledby="publication-title"><p class="story-section-number">04 / DEVELOPMENT NOTES</p><h2 id="publication-title">제작 시점의 미게시와, 이후 공개 완료는 모순이 아닙니다</h2><p>제작 패키지에는 당시의 <code>published: false</code>가 남아 있습니다. 후속 게시 로그에는 같은 첫 홍보영상의 Instagram·YouTube 공개 확인과, TikTok의 검토 중 상태가 전체 공개로 바뀐 확인이 별도로 기록돼 있습니다.</p><p>제작 기록을 덮어써 처음부터 공개됐던 것처럼 만들지 않았습니다. 원본 파일의 식별 정보와 게시 URL·확인 시점을 연결했습니다. 별도로 만든 캠페인 영상의 미게시 상태도 이 영상의 후속 상태와 합치지 않았습니다.</p><ol class="story-flow"><li><strong>제작 완료</strong><span>파일·규격·디코딩·키프레임을 검사한 상태입니다.</span></li><li><strong>게시 요청과 공개 확인</strong><span>버튼 실행과 처리 완료, 실제 공개 범위를 따로 기록합니다. 처리 중에는 곧바로 중복 업로드하지 않습니다.</span></li><li><strong>반응 확인</strong><span>도달·유지율·문의는 실제 측정 자료가 있어야 합니다. 당시 기록에는 성과 측정이 없었습니다.</span></li></ol><p>이 흐름은 영상도 소프트웨어처럼 복잡하게 만들자는 뜻이 아닙니다. 나중에 다른 파일을 보며 결과를 착각하지 않도록 무엇을 만들었고 어디까지 확인했는지 연결해 두자는 기준입니다.</p></section>
          <section id="next" class="note-template" aria-labelledby="next-title"><p class="story-section-number">KEEP EXPLORING</p><h2 id="next-title">읽은 뒤, 내 작업에서 확인할 것</h2><p>완성된 홍보영상과 제작 의도를 함께 보세요. 내 콘텐츠에서도 파일 검수, 게시 상태, 반응 측정을 따로 기록하는 것부터 시작할 수 있습니다.</p><a class="button button--dark" href="https://www.youtube.com/shorts/xRBCWjGjQZc" target="_blank" rel="noopener noreferrer">완성된 홍보영상 보기<span aria-hidden="true">↗</span><span class="sr-only"> (새 탭)</span></a></section>
          <footer class="story-end"><p>2026년 9월 13일 첫 홍보영상의 제작 QA와 후속 게시 기록을 대조한 회고입니다. 자체 AI 가상 예시이며 실제 고객 자료가 아닙니다. 당시 기록 이후의 플랫폼 공개 상태와 조회·문의 성과는 이 글의 확인 범위에 포함하지 않습니다.</p><a href="https://jhsoftlabs.com/stories/salon-publishing">이어서 읽기 · 이미지 다음에 남은 게시와 전달 <span aria-hidden="true">→</span></a><a href="https://jhsoftlabs.com/stories/">개발기록 전체 보기 <span aria-hidden="true">→</span></a></footer>
        </div>
      </div>
    ]]></description>
    </item>
  </channel>
</rss>
