<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>ETYMOLOGY</title><description>지구과학 교사가 수업·학교 업무용 프로그램을 만들고 배운 것을 기록합니다.</description><link>https://blog.etymology.me</link><item><title>블로그를 시작하며: 서버 대신 정적 사이트를 고른 이유</title><link>https://blog.etymology.me/blog/why-static-blog</link><guid isPermaLink="true">https://blog.etymology.me/blog/why-static-blog</guid><description>워드프레스, Ghost, 라즈베리파이 서버까지 고민한 끝에 글쓰기에 집중하려고 정적 사이트를 고른 과정을 적었다.</description><pubDate>Tue, 10 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;만들어 둔 앱과 도구가 쌓이면서, 어떻게 만들었고 어디서 막혔는지 적어 둘 곳이 필요해졌다. 위키에 메모로만 남기던 것을 글로 풀어 보려고 블로그를 시작한다.&lt;/p&gt;
&lt;h2&gt;고민한 선택지&lt;/h2&gt;
&lt;p&gt;| 방식 | 장점 | 부담 |
|---|---|---|
| 가입형(티스토리·네이버 등) | 가입만 하면 끝 | 디자인·수식·코드 삽입에 제약 |
| 설치형(워드프레스·Ghost) | 웹에서 글쓰기, 플러그인 풍부 | 서버, 보안 업데이트, 백업을 직접 관리 |
| 정적 사이트(Astro) | 무료, 빠르고, 서버 관리 거의 없음 | 글을 마크다운 파일로 쓰고 배포해야 함 |&lt;/p&gt;
&lt;h2&gt;서버를 직접 운영해 보고 싶다는 마음&lt;/h2&gt;
&lt;p&gt;솔직히 가장 끌린 건 설치형이었다. 집에 라즈베리파이를 두고 워드프레스나 Ghost를 올려 보는 일은 그 자체로 재미있다. 이미 집 서브 PC에 Cloudflare Tunnel로 서비스를 하나 돌리고 있어서, 같은 방식으로 하위 주소를 하나 더 붙이면 된다는 것도 알았다.&lt;/p&gt;
&lt;p&gt;그런데 이렇게 생각했다. &lt;strong&gt;블로그를 만드는 일에 힘을 쏟다가 정작 글을 못 쓰게 되지 않을까?&lt;/strong&gt; 서버를 만지는 건 취미로 따로 하면 되고, 블로그는 꾸준히 쓰는 것이 먼저라고 판단했다.&lt;/p&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;블로그는 &lt;strong&gt;정적 사이트&lt;/strong&gt;로 간다. 글은 마크다운 파일 하나로 쓰고, 빌드해서 올리면 끝난다.&lt;/li&gt;
&lt;li&gt;서버 실험(라즈베리파이)은 블로그와 분리해서 따로 즐긴다.&lt;/li&gt;
&lt;li&gt;테마는 Astro 기반의 Astro Theme Pure를 골랐다. 수식(KaTeX)과 검색, 다크 모드가 기본으로 들어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;앞으로 쓸 글&lt;/h2&gt;
&lt;p&gt;직접 만든 웹앱과 게임의 개발기, 학교 업무를 자동화하면서 부딪힌 문제의 해결 기록, 집 서버와 도구에 관한 메모를 남길 생각이다. 완성된 결과보다 &lt;strong&gt;막혔던 지점과 해결 과정&lt;/strong&gt;을 적는 쪽에 무게를 두려고 한다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>마이크로 불어서 끄는 생일 케이크 페이지</title><link>https://blog.etymology.me/blog/birthday-candle-mic</link><guid isPermaLink="true">https://blog.etymology.me/blog/birthday-candle-mic</guid><description>촛불에 입김을 불면 꺼지고 폭죽이 터지는 모바일 웹페이지. 바람 소리를 감지하는 방법과 마이크 권한 문제를 정리했다.</description><pubDate>Tue, 10 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;생일 축하 메시지를 링크로 보내고 싶었다. 받는 사람이 케이크 위 촛불 다섯 개를 &lt;strong&gt;휴대폰에 후 불어서&lt;/strong&gt; 끄면 폭죽이 터지는 페이지다.&lt;/p&gt;
&lt;h2&gt;바람을 어떻게 감지하나&lt;/h2&gt;
&lt;p&gt;Web Audio의 &lt;code&gt;AnalyserNode&lt;/code&gt;로 마이크 입력의 소리 크기(RMS)를 계속 잰다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;접속 후 처음 0.7초 동안의 주변 소음으로 기준값을 잡는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max(0.06, 기준×4)&lt;/code&gt;를 넘는 소리가 0.2초 이상 이어지면 &quot;분다&quot;고 판단한다.&lt;/li&gt;
&lt;li&gt;촛불을 앞에서부터 0.17초 간격으로 하나씩 끈다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;에코 제거, 소음 억제, 자동 이득 조절은 모두 끈다. 켜 두면 입김처럼 낮고 거센 소리를 브라우저가 소음으로 지워 버린다.&lt;/p&gt;
&lt;p&gt;폭죽은 캔버스에 입자(중력, 회전, 페이드)로 직접 그렸다.&lt;/p&gt;
&lt;h2&gt;한계&lt;/h2&gt;
&lt;p&gt;소리 크기만 보기 때문에 큰 말소리에도 꺼질 수 있다. 입김은 저음이 강하다는 특징이 있어서 주파수 분포로 걸러 내면 더 나아질 것 같고, 실제 기기에서 감도를 조정해 볼 계획이다.&lt;/p&gt;
&lt;h2&gt;알게 된 것들&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;마이크는 https에서만 켜진다.&lt;/strong&gt; 처음에는 Claude 아티팩트 화면으로 만들려 했는데, 아티팩트는 마이크와 카메라를 막는다. 마이크가 필요한 페이지는 Firebase Hosting 같은 https 호스팅에 올려야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;카카오톡 인앱 브라우저는 마이크를 막는다.&lt;/strong&gt; 링크를 카톡으로 보내면 받는 사람이 인앱 브라우저로 열기 때문에, 접속 시 기본 브라우저로 넘기는 한 줄을 넣었다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;if (/KAKAOTALK/i.test(navigator.userAgent)) {
  location.href = &apos;kakaotalk://web/openExternal?url=&apos; + encodeURIComponent(location.href)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;아이폰과 일부 기기는 화면을 한 번 만져야 소리가 시작된다.&lt;/strong&gt; 브라우저가 자동 재생을 막기 때문에 &lt;code&gt;AudioContext.resume()&lt;/code&gt;을 터치 시점에 호출해야 한다. 마이크 허용을 거부하는 사람을 위해, 촛불을 눌러서도 끌 수 있게 했다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>학교망에서 개발하다 만난 SSL 검사 오류 모음</title><link>https://blog.etymology.me/blog/school-network-ssl-tips</link><guid isPermaLink="true">https://blog.etymology.me/blog/school-network-ssl-tips</guid><description>npm, Firebase, gcloud, curl이 학교 네트워크에서만 실패하는 이유와, 도구별로 확인한 해결 방법을 정리했다.</description><pubDate>Mon, 09 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;학교 네트워크에서 개발 도구를 쓰다 보면, 집에서는 되던 명령이 이상한 오류를 내며 실패한다. 원인은 대부분 같다. &lt;strong&gt;학교망이 HTTPS 통신을 중간에서 검사&lt;/strong&gt;하면서 자기 인증서로 바꿔 끼우기 때문에, 도구들이 &quot;신뢰할 수 없는 인증서&quot;라고 거부하는 것이다. 겪은 순서대로 정리한다.&lt;/p&gt;
&lt;h2&gt;Node 기반 도구(npm, firebase-tools 등)&lt;/h2&gt;
&lt;p&gt;증상은 &lt;code&gt;SELF_SIGNED_CERT_IN_CHAIN&lt;/code&gt;이나 &lt;code&gt;Failed to make request&lt;/code&gt;다. 해당 명령 한 번에만 시스템 인증서를 쓰게 하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NODE_OPTIONS=--use-system-ca npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PowerShell에서는 &lt;code&gt;$env:NODE_OPTIONS=&apos;--use-system-ca&apos;&lt;/code&gt;를 먼저 설정한다. 상시 환경변수로 등록하지 않고 필요할 때만 붙이는 쪽이 안전하다고 판단했다. &lt;code&gt;firebase deploy&lt;/code&gt;가 &quot;Assertion failed: resolving hosting target&quot; 같은 모호한 메시지로 실패하는 것도 같은 원인이었다.&lt;/p&gt;
&lt;h2&gt;Git Bash의 curl&lt;/h2&gt;
&lt;p&gt;HTTPS 요청이 상태 코드 &lt;code&gt;000&lt;/code&gt;으로 실패한다. 이럴 때는 윈도우 인증서 저장소를 쓰는 PowerShell의 &lt;code&gt;Invoke-WebRequest&lt;/code&gt;로 같은 주소를 불러 보면 된다. 사이트가 죽은 건지 검사 때문인지 구분할 수 있다.&lt;/p&gt;
&lt;h2&gt;Python 라이브러리&lt;/h2&gt;
&lt;p&gt;Hugging Face에서 모델을 내려받을 때 같은 문제가 난다. 스크립트 앞부분에서 &lt;code&gt;truststore&lt;/code&gt;로 시스템 인증서를 쓰게 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import truststore
truststore.inject_into_ssl()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;gcloud는 우회가 안 된다&lt;/h2&gt;
&lt;p&gt;구글 클라우드 CLI는 자체 파이썬을 쓰는데, 이 파이썬이 프록시 인증서를 거부한다(&lt;code&gt;Missing Authority Key Identifier&lt;/code&gt;). 사용자 CA 파일을 지정해도 소용이 없었다. 이 경우는 &lt;strong&gt;학교망 밖(집이나 다른 Wi-Fi)에서 실행&lt;/strong&gt;하거나 웹 콘솔로 하는 수밖에 없었다.&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;| 도구 | 해결 |
|---|---|
| Node, npm, firebase | 해당 명령에만 &lt;code&gt;--use-system-ca&lt;/code&gt; |
| curl(Git Bash) | PowerShell &lt;code&gt;Invoke-WebRequest&lt;/code&gt;로 대체 |
| Python(Hugging Face 등) | &lt;code&gt;truststore.inject_into_ssl()&lt;/code&gt; |
| gcloud | 학교망 밖에서 실행 |&lt;/p&gt;
&lt;p&gt;오류 문구가 도구마다 달라서 처음에는 서로 다른 문제로 보인다. &lt;strong&gt;&quot;집에서는 되는데 학교에서만 안 된다&quot;면 SSL 검사를 먼저 의심하면&lt;/strong&gt; 시간을 아낄 수 있다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>학교 도서관 장서로 만든 진로 도서 추천 웹앱</title><link>https://blog.etymology.me/blog/library-book-recommender</link><guid isPermaLink="true">https://blog.etymology.me/blog/library-book-recommender</guid><description>장서 1만 4천 권을 AI로 정리하고 의미 검색으로 책을 추천하기까지의 설계와, API 이용 약관 때문에 방향을 바꾼 이야기.</description><pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;학생에게 진로에 맞는 책을 권할 때 서가를 한참 뒤지게 된다. 도서관 장서 안에서 상담 메모에 맞는 책을 골라 주는 웹앱을 만들었다.&lt;/p&gt;
&lt;h2&gt;데이터 만들기&lt;/h2&gt;
&lt;p&gt;장서 목록만으로는 &quot;이 책이 무슨 내용인지&quot;를 알 수 없다. 그래서 다음 순서로 데이터를 쌓았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;국립중앙도서관 자료로 ISBN, 분류, 주제명을 채운다.&lt;/li&gt;
&lt;li&gt;도서관 정보나루에서 책 소개와 키워드를 모은다.&lt;/li&gt;
&lt;li&gt;Claude Haiku로 책마다 소개 2~3문장, 계열, 키워드, 난이도, 생각해 볼 질문을 태깅한다. 1만 4천 권을 배치로 처리하는 데 약 3.7달러가 들었다.&lt;/li&gt;
&lt;li&gt;책 하나하나를 벡터로 바꿔서 &lt;strong&gt;의미로 검색&lt;/strong&gt;할 수 있게 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;추천 방식&lt;/h2&gt;
&lt;p&gt;상담 메모를 검색 문장 몇 개로 바꾸고, 벡터로 후보 60권을 뽑은 뒤, 언어 모델이 &quot;진로에 직결되는 책 3권, 다른 분야 1권, 입문 1권&quot;을 골라 이유를 쓴다. 학습서와 이미 읽은 책은 제외하고, 시리즈는 1권만 보여 준다.&lt;/p&gt;
&lt;h2&gt;시험해 보니&lt;/h2&gt;
&lt;p&gt;가상 학생 12명에 대해 추천된 60권을 서점 정보와 하나씩 대조했다. 처음에는 &lt;strong&gt;추천 이유를 지어낸 경우&lt;/strong&gt;가 4건, 학생에게 부적절한 책이 6건이었다. 번호와 제목을 함께 대조하도록 프롬프트를 고치고 후보 필터를 손봤더니 각각 1건, 2건으로 줄었다. 소개 내용이 사실과 다른 경우 2건은 태깅 데이터 자체의 오류라서 남았다.&lt;/p&gt;
&lt;h2&gt;약관이 설계를 바꿨다&lt;/h2&gt;
&lt;p&gt;개발 중반에 자료 출처의 이용 약관을 읽어 보고 문제를 발견했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;카카오 책 검색 응답은 &lt;strong&gt;AI 입력을 금지&lt;/strong&gt;하고 있었다.&lt;/li&gt;
&lt;li&gt;정보나루의 표지 이미지 주소는 비영리 화면 표시만 허용하고 저장과 AI 입력은 허용하지 않았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이미 태깅과 임베딩에 카카오 소개가 섞여 있었기 때문에, 교내에 공개하는 2판은 &lt;strong&gt;카카오 자료를 빼고&lt;/strong&gt; 다시 만들었다. 데이터를 일괄 수집하거나 AI에 넣기 전에 약관의 저장·AI 입력·출처 표시 조항을 먼저 확인하는 것을 이때부터 규칙으로 삼았다.&lt;/p&gt;
&lt;h2&gt;2판의 구조&lt;/h2&gt;
&lt;p&gt;질문을 벡터로 바꾸는 부분은 집에 있는 서브 PC가 맡고(다음 글 참고), 최종 큐레이션은 언어 모델이 한다. 서버가 꺼져 있을 때는 미리 만들어 둔 목록으로 대신 보여 준다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>집 노트북을 서버로: ONNX 양자화와 Cloudflare Tunnel</title><link>https://blog.etymology.me/blog/home-server-cloudflare-tunnel</link><guid isPermaLink="true">https://blog.etymology.me/blog/home-server-cloudflare-tunnel</guid><description>GPU 없는 노트북에서 한국어 임베딩 모델을 8배 빠르게 돌리고, 포트 개방 없이 외부 주소를 붙인 과정과 시행착오.</description><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;도서관 추천 앱에는 질문 문장을 벡터로 바꿔 책 14,278권과 비교하는 서버가 필요했다. 클라우드에 GPU 서버를 빌리는 대신, 집에 놀고 있던 노트북(i5 4코어, RAM 7.8GB, GPU 없음)을 서버로 썼다.&lt;/p&gt;
&lt;h2&gt;모델을 가볍게 만들기&lt;/h2&gt;
&lt;p&gt;한국어 임베딩 모델 KURE-v1을 그대로 돌리면 메모리를 약 2GB 쓰고 질문 하나에 0.3초가 걸렸다. ONNX로 변환한 뒤 int8로 양자화했다.&lt;/p&gt;
&lt;p&gt;| 방식 | 1위 일치(30문항) | 속도 | 결정 |
|---|---|---|---|
| PyTorch 동적 양자화 | 20/30 | 이득 거의 없음 | 불채택 |
| ONNX int8 | 29/30 | 질문 1개 0.31초 → 0.039초 | 채택 |&lt;/p&gt;
&lt;p&gt;결과 정확도는 상위 10권이 약 90% 겹칠 정도로 유지됐고, 서버 메모리는 약 1.3GB로 줄었다. 질문 여섯 개를 한 번에 보내도 60밀리초 안에 끝난다.&lt;/p&gt;
&lt;h2&gt;포트를 열지 않고 외부에 공개하기&lt;/h2&gt;
&lt;p&gt;Cloudflare Tunnel을 썼다. 구조는 이렇다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;집 PC의 &lt;code&gt;cloudflared&lt;/code&gt;가 Cloudflare로 &lt;strong&gt;나가는&lt;/strong&gt; 연결을 유지한다.&lt;/li&gt;
&lt;li&gt;방문자의 요청은 Cloudflare가 그 연결로 넘겨 준다.&lt;/li&gt;
&lt;li&gt;그래서 공유기 포트포워딩이 필요 없고, 집 IP가 드러나지 않고, HTTPS 인증서도 자동이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;대가도 알아 두어야 한다. 암호화가 Cloudflare에서 한 번 풀리므로 &lt;strong&gt;요청과 응답 내용은 Cloudflare가 볼 수 있다&lt;/strong&gt;. 그래서 민감한 내용은 이 경로에 싣지 않는다.&lt;/p&gt;
&lt;h2&gt;부팅하면 알아서 켜지게&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;서버는 작업 스케줄러로 부팅 시 자동 실행하고, 실패하면 1분 뒤 재시작하게 했다.&lt;/li&gt;
&lt;li&gt;터널은 윈도우 서비스로 등록했다.&lt;/li&gt;
&lt;li&gt;전원 설정에서 절전을 끄고 덮개를 닫아도 아무 일도 안 하게 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;설치하며 막혔던 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PyTorch가 &lt;code&gt;c10.dll&lt;/code&gt; 오류를 내며 안 뜸 → Visual C++ 재배포 패키지 설치&lt;/li&gt;
&lt;li&gt;모델 다운로드가 0바이트에서 멈춤 → 환경변수 &lt;code&gt;HF_HUB_DISABLE_XET=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;오프라인 모드에서 저장소 이름으로 ONNX 변환이 실패 → 내려받은 폴더 경로를 직접 지정&lt;/li&gt;
&lt;li&gt;점검 순서를 정해 두니 편했다: 로컬 상태 확인 → 외부 상태 확인 → 키 없이 호출하면 401 → 키를 넣고 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;한계&lt;/h2&gt;
&lt;p&gt;노트북이 꺼지거나 정전이 나면 서비스도 멈춘다. 개인용이나 실험용에는 충분하지만, 항상 켜져 있어야 하는 서비스는 Firebase 같은 관리형 서비스가 맞다. 그래서 서버가 꺼졌을 때를 대비해 앱 쪽에 대체 동작을 두었다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>코드로 그린 DIY 다이어리 속지와 중철 제본</title><link>https://blog.etymology.me/blog/diy-diary-layout</link><guid isPermaLink="true">https://blog.etymology.me/blog/diy-diary-layout</guid><description>파이썬 reportlab으로 A5 다이어리 속지를 직접 그리고, 책 펼침 면 때문에 설계를 바꾼 이유와 인쇄·제본 요령을 정리했다.</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;다이어리는 쓰는 것보다 &lt;strong&gt;다시 읽고 돌아보는 일&lt;/strong&gt;이 어렵다. 쓰는 부담은 낮추고 되돌아보는 부담은 쓰는 순간으로 옮길 수 있는 속지를 직접 만들었다. 실과 바늘로 직접 제본하는 A5 다이어리라서, 먼저 11월 한 달분만 만들어 본다.&lt;/p&gt;
&lt;h2&gt;설계 원칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;막노트(그날 쓰고 싶은 대로 쓰는 쪽)는 도트 모눈으로 비워 둔다. 날씨·기분 칸은 쓰기 부담이 되어서 뺐다.&lt;/li&gt;
&lt;li&gt;위클리는 일정표가 아니라 &lt;strong&gt;할 일 전용&lt;/strong&gt;으로 한다. 일정은 먼슬리에 모은다.&lt;/li&gt;
&lt;li&gt;주간회고는 줄만 그려 둔다. 일주일 뒤에 부담 없이 채울 수 있어야 하기 때문이다.&lt;/li&gt;
&lt;li&gt;흑백으로 인쇄하고, 선과 도트는 연한 회색이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;파이썬으로 직접 그린다&lt;/h2&gt;
&lt;p&gt;reportlab으로 PDF를 직접 그린다. 글꼴은 한글 TTF를 등록해서 쓰고, 한 번 정해 둔 값(여백, 도트 간격 5mm, 글자 크기)을 바꾸면 모든 쪽이 따라 바뀐다. 레이아웃 도구로 손으로 맞추는 것보다, 수정하고 다시 뽑는 속도가 훨씬 빠르다.&lt;/p&gt;
&lt;h2&gt;설계가 틀어졌던 순간: 펼침면&lt;/h2&gt;
&lt;p&gt;위클리와 주간회고를 한 펼침(마주 보는 두 쪽)에 합치려고 했다. 그런데 실제 책에서 마주 보는 쪽은 &lt;strong&gt;(짝수, 홀수)&lt;/strong&gt; 쌍이다. 한 주가 9쪽 단위라서 펼침이 계속 어긋났다.&lt;/p&gt;
&lt;p&gt;그래서 위클리는 주의 시작 쪽, 주간회고는 주의 끝 쪽에 따로 두고, 먼슬리와 월간회고처럼 2쪽 펼침이 꼭 필요한 면만 (짝수, 홀수) 쌍에 맞춰 배치했다. 화면에서 나란히 보이는 두 쪽이 책에서도 마주 보는지 확인하지 않으면 이런 일이 생긴다.&lt;/p&gt;
&lt;h2&gt;인쇄와 제본 요령&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A4 한 장에 A5 두 쪽을 가로로 담고, 4장(16쪽)을 한 묶음으로 접는다. 6장 이상은 접힌 쪽이 두꺼워져서 바느질이 뻑뻑하다.&lt;/li&gt;
&lt;li&gt;구멍은 다섯 개로 하고, 인쇄물에 접힘선과 구멍 위치를 표시해 둔다.&lt;/li&gt;
&lt;li&gt;제본하는 쪽 여백은 바깥쪽보다 5mm 더 준다.&lt;/li&gt;
&lt;li&gt;같은 이름의 파일은 버전을 늘리지 않고 덮어쓴다. 이전 파일이 쌓이면 어느 것이 최신인지 헷갈리기 때문이다.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Claude에 울프람 알파를 붙이는 MCP 서버 만들기</title><link>https://blog.etymology.me/blog/wolfram-mcp-server</link><guid isPermaLink="true">https://blog.etymology.me/blog/wolfram-mcp-server</guid><description>표준 라이브러리만 쓰는 파이썬 파일 하나로 Wolfram|Alpha를 Claude Code의 도구로 연결한 방법과 쓰임새, 한계.</description><pubDate>Thu, 22 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI에게 수치 계산을 시키면 그럴듯하지만 틀린 숫자를 말할 때가 있다. 정확한 계산과 천문 자료는 &lt;strong&gt;Wolfram|Alpha에 직접 물어보게&lt;/strong&gt; 하면 좋겠다고 생각해서, 이를 MCP(Model Context Protocol) 서버로 만들었다.&lt;/p&gt;
&lt;h2&gt;구성은 단순하다&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;파이썬 파일 하나(&lt;code&gt;server.py&lt;/code&gt;), 표준 라이브러리만 사용&lt;/li&gt;
&lt;li&gt;도구는 &lt;code&gt;wolfram_query&lt;/code&gt; 하나. 영어 질문을 받아 Wolfram의 LLM API에 보내고 응답 글을 그대로 돌려준다.&lt;/li&gt;
&lt;li&gt;앱 ID는 환경변수나 파일에서 읽는다. 코드와 대화에 내보내지 않는다.&lt;/li&gt;
&lt;li&gt;Claude Code 설정에 &lt;code&gt;python server.py&lt;/code&gt; 실행 명령만 등록하면 끝이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;계산을 서버가 직접 하지 않고 &lt;strong&gt;전달만&lt;/strong&gt; 한다. 그래서 코드가 짧고 고장 날 곳이 적다.&lt;/p&gt;
&lt;h2&gt;설치 확인&lt;/h2&gt;
&lt;p&gt;두 가지를 물어보고 알려진 값과 대조했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;integrate x^x from 0 to 1&lt;/code&gt; → 약 0.783431. 급수로 직접 계산한 0.7834305107과 일치했다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;distance from Earth to Mars on 2026-10-22&lt;/code&gt; → 1.515 au(약 2.27억 km, 빛으로 12.6분). 응답에 Wolfram 이미지 링크가 들어 있어서 실제로 서버에서 받아 온 값임을 확인했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;이런 데 쓴다&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;날짜별 행성 거리, 뜨고 지는 시각, 일식·월식 같은 천문 계산&lt;/li&gt;
&lt;li&gt;적분·방정식 풀이, 단위 변환, 물리 법칙에 값 대입(빈의 법칙, 케플러 법칙, 탈출 속도 등)&lt;/li&gt;
&lt;li&gt;원소 성질, 날짜 계산 같은 일반 자료&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;한계와 쓰는 원칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;짧은 영어 질문이 가장 잘 통한다.&lt;/li&gt;
&lt;li&gt;그래프는 이미지 링크로만 온다.&lt;/li&gt;
&lt;li&gt;무료 앱 ID는 월 사용 한도가 있다(보통 2,000회).&lt;/li&gt;
&lt;li&gt;결과는 &lt;strong&gt;검산용&lt;/strong&gt;으로만 쓴다. 자료를 공개하거나 문서에 실을 때는 NASA나 기상청 같은 원본 출처의 값을 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP 서버는 생각보다 작은 코드로 만들 수 있다. 자주 쓰는 외부 서비스가 있다면 &quot;질문을 받아 전달하는 얇은 서버&quot;부터 시작해 볼 만하다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>퇴근길 어느 길이 빠를까: 서울시 실시간 도로소통 웹앱</title><link>https://blog.etymology.me/blog/commute-traffic-app</link><guid isPermaLink="true">https://blog.etymology.me/blog/commute-traffic-app</guid><description>서울시 열린데이터광장의 링크별 통행시간을 더해 퇴근 경로 세 개를 비교하는 모바일 웹앱과, 연결 실패 때문에 겪은 장애 기록.</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;퇴근길에는 갈림길이 있다. 어느 쪽이 빠른지는 그날그날 달라서, 지금 가장 빠른 길을 알려 주는 개인용 웹앱을 만들었다.&lt;/p&gt;
&lt;h2&gt;어떻게 비교하나&lt;/h2&gt;
&lt;p&gt;서울 열린데이터광장의 실시간 도로소통 자료는 도로를 &lt;strong&gt;링크&lt;/strong&gt;(교차로에서 교차로까지)로 나눠 링크마다 속도와 통행시간을 준다. 경로를 구성하는 링크들의 통행시간을 더하면 경로 시간이 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;속도에 거리를 곱하지 않고 &lt;strong&gt;통행시간을 그대로 합산&lt;/strong&gt;한다. 링크가 교차로 사이라서 신호 대기가 이미 포함돼 있다.&lt;/li&gt;
&lt;li&gt;자료가 없는 골목은 시속 15km로 가정한다.&lt;/li&gt;
&lt;li&gt;경로 간 차이가 2분 미만이면 &quot;비슷하다&quot;고 표시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;한계도 적어 둔다&lt;/h2&gt;
&lt;p&gt;직진과 우회전 차로가 구분되지 않아서 갈림 교차로에서 오차가 가장 컸다. 신호 운은 교차로마다 0~2분 정도 차이가 난다. 그래서 앱이 실제로 얼마나 맞는지 알려면 &lt;strong&gt;예측과 실제를 기록&lt;/strong&gt;해야 했다.&lt;/p&gt;
&lt;p&gt;출발 버튼 하나만 누르면 휴대폰 위치로 체크포인트 통과 시각을 저장하고, 구간별로 &quot;예측 − 실제&quot; 평균을 모은다. 기록이 쌓이면 이 평균으로 예측을 보정할 계획이다.&lt;/p&gt;
&lt;h2&gt;구성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Firebase Hosting(화면), Cloud Functions(서울시 API 대리 호출), Firestore(기록)&lt;/li&gt;
&lt;li&gt;인증키와 접속 비밀번호는 Secret Manager에 보관하고, 브라우저에는 내려보내지 않는다.&lt;/li&gt;
&lt;li&gt;홈 화면에 추가해 쓰도록 PWA로 만들었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;장애 사례: 링크 하나가 앱 전체를 죽였다&lt;/h2&gt;
&lt;p&gt;어느 날부터 화면이 아예 뜨지 않았다. 원인을 따라가 보니 이랬다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;서울시 API가 구글 클라우드(서울 리전)에서 오는 요청을 간헐적으로 받지 않았다. 국내 네트워크에서 직접 호출하면 정상이었다.&lt;/li&gt;
&lt;li&gt;링크 89개를 &lt;code&gt;Promise.all&lt;/code&gt;로 한꺼번에 불렀기 때문에, &lt;strong&gt;하나만 실패해도 전체 응답이 500&lt;/strong&gt;이 됐다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;고친 방법은 세 가지였다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;호출마다 5초 제한과 최대 3회 재시도를 두고, 끝내 실패한 링크만 &quot;연결 실패&quot;로 표시한다. 전체 응답은 200으로 돌려준다.&lt;/li&gt;
&lt;li&gt;성공한 값은 Firestore에 캐시해 두고, 서울시가 실패하면 30분 안의 값으로 대신한다.&lt;/li&gt;
&lt;li&gt;요청 전체에 20초 마감을 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;고친 뒤에는 89개 링크 요청이 1~2초 안에 끝났다. 외부 API를 묶어 부를 때는 &lt;strong&gt;부분 실패를 허용하는 구조&lt;/strong&gt;가 기본이어야 한다는 걸 배웠다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>배드민턴 복식 판단 게임: 확률로 연습하는 포지셔닝</title><link>https://blog.etymology.me/blog/badminton-doubles-game</link><guid isPermaLink="true">https://blog.etymology.me/blog/badminton-doubles-game</guid><description>공이 오는 순간 멈추고 선택지별 랠리 승률로 피드백하는 3D 웹게임. 셔틀콕 물리와 확률 모델을 어떻게 맞췄는지 정리했다.</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;복식에서 어렵다고 느낀 건 샷 기술보다 &lt;strong&gt;어디에 서고 무엇을 칠지 판단하는 일&lt;/strong&gt;이었다. 이 판단을 연습할 수 있는 웹게임을 만들었다. 공이 오면 게임이 멈추고, 내가 고른 선택이 랠리 승률로 얼마나 좋은 선택이었는지 알려 준다.&lt;/p&gt;
&lt;h2&gt;게임 구성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Three.js로 만든 3D 코트. 아이레벨 시점과 중계 시점을 오갈 수 있다.&lt;/li&gt;
&lt;li&gt;판단 시점은 9종: 서브 종류, 리시브 위치, 내 샷과 코스, 샷 후 위치, 파트너 샷 후 위치, 가운데 공, 앞에서 끊기, 아웃 판단 등.&lt;/li&gt;
&lt;li&gt;선택하면 등급(최선·좋음·아쉬움·실수)과 선택지별 승률 막대가 나오고, 경기가 끝나면 어떤 종류의 판단이 약했는지 리포트가 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;엔진: 물리와 확률을 합치기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;셔틀 궤적&lt;/strong&gt;은 2차 공기저항을 넣어 수치 적분한다(종단속도 6.8 m/s). 목표 지점에 맞도록 속도와 발사각을 역산한다.&lt;/p&gt;
&lt;p&gt;**오차(지터)**는 목표 지점과 각도에 정규분포 오차를 준다. 그러면 네트 걸림과 아웃이 코드로 따로 지정하지 않아도 물리적으로 생긴다. 늦게 받거나, 백핸드이거나, 뒤로 물러나며 칠수록 오차가 커진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;승률&lt;/strong&gt;은 &quot;타점 품질 q&quot;가 높을수록 이길 확률이 올라가는 식, &lt;code&gt;V(q) = 0.314 + 0.446·q&lt;/code&gt;로 잡았다. 계수는 AI끼리 1,500랠리를 돌려 서브 팀 승률이 47% 정도가 되도록 반복해서 맞췄다. 선택지별 승률은 같은 난수열로 40번 시뮬레이션해서 비교한다.&lt;/p&gt;
&lt;h2&gt;검증: 코칭 원칙과 방향이 같은가&lt;/h2&gt;
&lt;p&gt;엔진이 만든 숫자가 상식과 맞는지 확인했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상대가 리프트(높이 띄운 공)를 보내면 스매시가 가장 좋고 드롭, 클리어 순이다.&lt;/li&gt;
&lt;li&gt;롱 서브 뒤에는 앞뒤보다 좌우 대형이 낫다.&lt;/li&gt;
&lt;li&gt;숏 서브 뒤에는 서버가 앞에 서는 쪽이 낫다.&lt;/li&gt;
&lt;li&gt;네트샷 뒤에는 전위를 유지하는 쪽이 낫다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;모두 방향이 맞았다. 다만 스매시 뒤 위치 선택지 사이의 차이는 58~61%로 아주 작아서, 이 부분은 판단 연습으로서 변별력이 약하다.&lt;/p&gt;
&lt;h2&gt;만들면서 느낀 점&lt;/h2&gt;
&lt;p&gt;확률 모델은 &quot;정답&quot;을 알려 주는 게 아니라 &lt;strong&gt;추정치를 일관되게 보여 주는 도구&lt;/strong&gt;다. 그래서 숫자 자체보다 &quot;같은 상황에서 선택지 사이의 차이가 얼마나 나는가&quot;를 보게 했고, 모델이 틀릴 수 있는 곳(스매시 후 위치)은 한계로 적어 두었다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>단열선도 해석 연습 앱: 기상청 자료로 먼저 풀고 채점받기</title><link>https://blog.etymology.me/blog/skew-t-practice-app</link><guid isPermaLink="true">https://blog.etymology.me/blog/skew-t-practice-app</guid><description>레윈존데와 일기도를 불러와 먼저 해석한 뒤 계산값·관측과 비교하고 AI 첨삭까지 받는 로컬 웹앱의 구성과 계산 방법.</description><pubDate>Fri, 16 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;단열선도와 일기도는 읽는 연습을 해야 늘어난다. 그런데 연습 문제는 정답지가 있는 정리된 자료뿐이라, 실제 날씨 자료를 &lt;strong&gt;먼저 내 힘으로 해석하고 → 계산값과 관측으로 채점 → 첨삭&lt;/strong&gt;까지 받는 앱을 만들었다.&lt;/p&gt;
&lt;h2&gt;쓰는 자료 (기상청 API허브)&lt;/h2&gt;
&lt;p&gt;| 자료 | 용도 |
|---|---|
| 레윈존데 | 단열선도(고도별 기온·이슬점·바람) |
| ASOS 시간 자료 | 해석 이후 12시간의 실제 날씨(채점 근거) |
| 천리안 2A 위성 | 적외·가시·수증기 채널 영상 |
| 지상·상층 분석일기도 | 사람이 분석한 전선과 기압 배치 |&lt;/p&gt;
&lt;h2&gt;화면 구성&lt;/h2&gt;
&lt;p&gt;한 화면에 세 칸이다. 왼쪽에 단열선도, 가운데에 일기도와 위성, 오른쪽에 내 해석 입력란이 있다. 분석 시각 이후의 관측값은 제출하기 전까지 흐리게 가려 둔다. 답을 미리 보면 연습이 안 되기 때문이다.&lt;/p&gt;
&lt;h2&gt;계산: 사람이 읽는 것과 컴퓨터가 읽는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;층 나누기&lt;/strong&gt;: 그래프가 꺾이는 지점을 Douglas-Peucker 알고리즘으로 찾아 층 A, B, C…로 나눈다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;안정도 판정&lt;/strong&gt;: 층마다 감률을 구해서 건조단열 10 °C/km, 습윤단열 5 °C/km와 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LCL&lt;/strong&gt;: Bolton 식으로 구하고, 간이식 &lt;code&gt;0.125×(T−Td)&lt;/code&gt;와 같이 보여 준다. 시험한 날 한 지점에서는 둘이 0.72 km로 같았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;공기 덩어리 상승&lt;/strong&gt;: LCL 위로는 실제 습윤단열로 5 hPa 간격 적분을 해서 LFC, EL, CAPE, CIN을 구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;층 판정은 교과서 기준(5 °C/km)이고 공기 덩어리 경로는 실제 습윤 감률(약 4~9 °C/km)을 쓴다. &lt;strong&gt;기준이 달라서 둘이 어긋나 보일 수 있다&lt;/strong&gt;는 것을 화면에 설명해 두었다.&lt;/p&gt;
&lt;h2&gt;키가 새지 않게: 로컬 실행&lt;/h2&gt;
&lt;p&gt;웹에 배포하면 기상청과 AI 서비스의 API 키가 브라우저에 노출된다. 그래서 파이썬 서버(표준 라이브러리와 Pillow만 사용)를 내 PC에서 돌리고, 브라우저는 그 서버에만 말한다. 더블클릭 한 번으로 서버가 뜨고 브라우저가 열린다.&lt;/p&gt;
&lt;h2&gt;일기도는 금방 사라진다&lt;/h2&gt;
&lt;p&gt;기상청 저장소에는 일기도가 2~3일치만 남는다. 레윈존데와 ASOS는 과거 값을 API로 받을 수 있지만 일기도는 그렇지 않다. 그래서 윈도우 작업 스케줄러가 매일 저녁 최근 일기도를 미리 받아 두게 했다. 저장된 날짜는 언제든 다시 풀 수 있다.&lt;/p&gt;
&lt;h2&gt;한계&lt;/h2&gt;
&lt;p&gt;위성 영상에 관측 지점 표시를 넣으려면 지도 투영을 맞춰야 하는데, 지상분석일기도는 아직 못 맞췄다. 그리고 PC가 3일 넘게 꺼져 있으면 그 사이 일기도는 빠진다.&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>