Golden Hour: 야외에서 40분을 찾고, 그 시간을 어디에 써야 할지 알려주는 오픈 모델
요약
Golden Hour는 사용자의 위치, 시간별 날씨 예보, 일몰 정보를 바탕으로 야외 활동에 가장 적합한 20~40분 시간을 추천하는 오픈 모델 기반의 앱입니다. OpenStreetMap을 활용해 주변 장소를 찾고, Gemma와 같은 LLM이 각 장소에서 할 수 있는 구체적인 액티비티 아이디어를 제공합니다.
핵심 포인트
- 날씨/일몰 정보를 분석하여 최적의 야외 활동 시간을 추천
- OpenStreetMap 기반으로 근처 상위 5개 실제 장소를 제시
- Gemma와 같은 오픈 모델이 각 장소별 구체적인 경험을 제안
- 단순한 시간 보내기 앱이 아닌, 행동 계획 수립에 초점
[Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass]에 제출하는 작품입니다.
내가 만든 것
저는 밖에 나가고 싶지 않아서 실내에 머무는 것이 아닙니다. 저는 두 가지 작은 결정, 즉 ‘언제’ 그리고 ‘무엇을 할지’를 미루기가 쉽기 때문에 실내에 머뭅니다. 날씨 앱은 바깥 날씨가 어떤지 알려줍니다. 하지만 "18시에 나가서 여기로 가라"라고 말해주는 것은 아무것도 없습니다.
Golden Hour는 정확히 그것, 그리고 그 무엇도 하지 않습니다:
- 오늘 당신이 자유로운 시간을 알려주세요. 또는 생략하면 "지금부터 일몰까지"를 가정합니다.
- 최적의 20~40분을 골라줍니다. 현재 위치의 시간별 예보와 일몰 정보를 읽고, 가능한 모든 시간대를 점수화하며 그 선택에 대한 설명을 제공합니다: "맑음(dry)", "골든 아워(golden hour)", "쌀쌀함(chilly, 13°C)".
- 사진 촬영, 역사 탐방 또는 야외 커피 등 최대 세 가지 취미를 고르면, OpenStreetMap을 기반으로 도보 거리에 있는 상위 5개 실제 장소를 찾아줍니다.
- 저의 서버에서 구동되는 오픈-웨이트 모델(open-weight model)인 Gemma가 각 장소에 대해 한 줄씩 글을 작성합니다: 그곳에서, 지금 이 빛과 날씨 속에서 무엇을 할 수 있는지.
- 당신은 카운트다운 타이머와 10분 알림이 설정된 캘린더 초대를 받습니다. 그리고 그것을 닫습니다.
이것은 시간을 보내는 앱이 아닙니다. 성공이란 탭을 닫는 것입니다.
데모
사용해 보기: https://golden-hour-ahl6.onrender.com
(휴대폰에서 가장 좋습니다: 위치 권한 허용 또는 도시 검색)
여기는 중앙 푸네(Pune)에서의 아침을 위해 이 앱이 만든 실제 계획입니다. 저는 사진 촬영과 역사 탐방을 선택했고, 자유 시간은 비워두었습니다:

06:30–07:10, 94/100. 건조하고 잔잔하며, 새벽에 26°C라는 점을 제외하면 감점 요인이 없습니다. 그리고 도보로 13분 거리에 있는 다섯 곳의 장소들:
18세기 요새인 Shaniwarwada는 3분 거리에 있으며, Gemma가 제시한 문구는 "아침 분위기를 담기 위해 주변 건축물을 빠르게 사진으로 찍으며 시작하세요."였습니다. 두 곳의 장소(두 번째 공원과 기념관)에 대한 문구는 제가 직접 작성한 템플릿입니다. 모델이 시도했던 내용은 아래에서 설명할 검증을 통과하지 못했기 때문에, 앱은 조용히 안전한 문구를 사용했습니다.
코드
GitHub logo Tanay3484 / golden-hour
오픈 가중치 모델이 계획한 오늘 최고의 20~40분 야외 활동. Hacktoberfest 2026 '풀밭에 발 담그기(Touch Grass)' 챌린지.
🌅 골든 아워 (Golden Hour)
오늘 최고의 20~40분 야외 활동과, 그 시간을 활용할 수 있는 작은 활동 하나.
Golden Hour에 언제 시간이 되는지 알려주세요. 이 앱은 현재 위치의 시간별 일기 예보와 일몰 시간을 확인하고, 외부에 있기 가장 좋은 시간을 선택하며, OpenStreetMap에서 취미에 맞는 도보 거리에 있는 상위 5개 실제 장소를 찾아줍니다. 오픈 가중치 모델(open-weight model)인 Gemma(Ollama을 통해 서비스됨)이 그곳에서 지금 무엇을 할 수 있는지에 대한 문구를 각 장소마다 작성해 줍니다. 사용자는 카운트다운과 10분 알림이 포함된 캘린더 초대를 받게 됩니다.
DEV Hacktoberfest 2026 오픈소스 AI 챌린지, 1주차: "풀밭에 발 담그기"를 위해 제작되었습니다.
작동 방식
Browser ──▶ FastAPI ──▶ Open-Meteo (예보 + 일몰 시간, 오픈 데이터, 키 불필요)
│
├─ scoring.py 결정론적: 여가 시간에 있는 매 20~40분 간격의 슬롯을 점수화하며 0–100점을 부여합니다.
...
…
구축 과정 (How I Built It)
사용 스택: FastAPI · vanilla JS (약 30 KB, 빌드 단계 없음) · Gemma 3을 제공하는 Ollama (운영 환경에서는 gemma3:1b, 제 노트북에서는 gemma3:4b) · 날씨를 위한 Open-Meteo와 장소를 위한 OpenStreetMap (둘 다 오픈 데이터이며 API 키 불필요) · Render에 배포된 두 개의 서비스.
여가 시간 + 예보 ──▶ 일반 Python이 창을 선택합니다 (테스트 완료, 설명 가능)
취미 + 장소 ──▶ OpenStreetMap → Python이 상위 5개를 순위 매깁니다 (모델 미사용)
창(시간) + 5개 장소 ──▶ Gemma가 장소당 한 줄씩 작성합니다 (JSON 스키마, 검증됨)
이 프로젝트를 구축하면서 예상치 못한 네 가지 것을 배웠습니다.
1. 수학에게 시간 선택을 맡기고, 모델에게 작성을 맡기세요.
처음에는 Gemma에게 전체 예보를 주고 “언제 밖에 나가야 할까요?”라고 물어보는 것이 제 본능이었습니다. 하지만 세 가지 이유 때문에 그렇게 하지 않았습니다. 첫째, 공유 CPU에서 구동되는 1B 모델은 느립니다. 둘째, 48개 행에 달하는 숫자들에 대한 산술 계산을 제대로 수행하지 못합니다. 셋째, 제가 왜 그 시간이 18:00인지 설명할 수 있기를 바랐기 때문입니다.
그래서 이 시간 창(window)은 단순하고 테스트가 완료된 Python 코드 200줄 미만으로 선택됩니다. 여가 시간에 있는 모든 15분 간격 슬롯에 대해 해당 시간이 접하는 최악의 날씨를 가져온 다음 점수를 매깁니다: 기본 점수는 100점에서 시작하여, 비 올 확률 %당 0.8점씩 감점하고, 15–24°C 범위를 벗어날 때마다 °C당 3점씩, 풍속이 20km/h를 초과할 때마다 km/h당 2점씩, 자외선 지수(UV point)가 6을 초과할 때마다 5점씩 감점하고, 일몰 시간 전 한 시간을 포함하면 15점을 얻습니다. 카드에 적힌 이유는 점수를 매긴 데 사용된 요인 목록과 동일하므로 설명이 숫자와 모순될 수 없습니다.
테스트를 통해 데모에서는 발견할 수 없었던 버그가 포착되었습니다. 완벽한 날에는 모든 시간대가 상한선 적용 후 100점을 기록했기 때문에, 골든 아워 보너스가 사라지고 가장 이른 시간대가 승리했습니다. 실제로 원했던 완벽한 저녁에만 알게 될 것입니다. 골든 아워를 선호하는 동점 처리 규칙이 이를 수정했습니다.
2. 소형 모델은 더 나은 프롬프트뿐 아니라 가드레일(guardrails)이 필요하다
Gemma는 창의적인 부분만 담당하며, 그 답변은 JSON 스키마와 일치해야 합니다 (Ollama의 구조화된 출력 기능 덕분에 1B에서도 신뢰성이 높습니다). 실제 데이터를 사용하여 로컬에서 테스트하는 것은 겸손함을 느끼게 했습니다:
| Gemma가 한 것 | 이를 수정한 것 |
|---|---|
| 위치 정보 없이, 존재하지 않는 "Elm Street"로 보내주었습니다. | 주어진 곳이 아닌 장소 이름을 언급할 수 없도록 제한했습니다. 대신 장소의 종류를 묘사합니다. |
| ... |
The 솔직한 결과는 다음과 같습니다: gemma3:1b는 이제 모든 줄을 올바른 위치에 배치하지만, 여전히 특이하고 그럴듯한 세부 정보("카페에서의 페이스트리")를 추가합니다. gemma3:4b는 그렇지 않지만, 속도가 약 5배 느립니다. 1-CPU 서버에서는 저는 빠른 것을 선택했고, 이를 숨기는 대신 사양(spec)에 격차를 명시했습니다.
3. 공개 데이터는 무료이지만 쉽지 않다
공개 OpenStreetMap 질의 서버(Overpass)는 자원봉사자들이 운영하는 공유 리소스입니다. 테스트하던 어느 저녁, 세 개의 공용 인스턴스 중 두 개가 모든 도시에서 실패했고, 주요 인스턴스는 Pune와 Mumbai에 대해서는 계속 "서버가 너무 바쁨"이라고 응답했지만 Berlin은 정상적으로 작동했습니다.
첫 번째 수정 사항은 하나의 지시문이었습니다. Overpass는 질의가 필요로 할 수 있는 메모리 양을 기준으로 허용 여부를 결정하며, 기본값은 512MB입니다. 64MB([maxsize:67108864])로 선언하자 Pune에서 3초 만에 통과했습니다.
그러자 저는 배포했고, 라이브 사이트는 연속으로 열두 번이나 제로개의 장소를 찾았습니다. 집 연결에서 동일한 쿼리를 실행했을 때는 3초 만에 44개의 장소가 반환되었습니다. Overpass는 각 IP 주소가 실행할 수 있는 쿼리 수를 제한하는데, 제 Render 서버는 다른 많은 사람들의 앱과 아웃바운드 IP를 공유합니다. 따라서 이제 서버가 OpenStreetMap에 연결하지 못하면, 사용자의 _브라우저_가 동일한 쿼리로 직접 요청하여 원시 결과를 서버로 전달하고 순위를 매기게 합니다. 방문자마다 자신만의 속도 제한을 가져옵니다. 브라우저가 보내는 모든 것은 신뢰할 수 없는 것으로 간주되어 캐싱되지 않으므로, 아무도 다음 사람을 위해 가짜 장소를 심을 수 없습니다. 두 경로 모두 실패하면, 대신 Gemma가 작성한 활동 정보 하나를 받게 되고 페이지는 절대 깨지지 않습니다.
개인 정보 보호를 위해 OpenStreetMap은 사용자의 약 1km 지역 중심만 볼 뿐입니다. 정확한 도보 시간은 제 서버에서 계산되며 아무것도 저장되지 않습니다.
4. Render에서 속도를 두 배로 높여준 설정
render.yaml은 두 개의 서비스 블루프린트입니다: 공개 FastAPI 웹 서비스와 Gemma가 Docker 이미지에 내장된 비공개 Ollama 서비스입니다. 이 웹 앱은 Render의 비공개 네트워크를 통해 이 서비스에 접근하므로, 모델에는 공용 엔드포인트 자체가 없습니다.
첫 라이브 실행은 고통스러울 정도로 느렸습니다: 제 계획이 제공하는 단일 CPU로 Docker에서 측정한 시간보다 약 두 배인, 제안당 3880초가 걸렸습니다. 원인은 하나의 설정 때문이었습니다. Ollama는 _호스트 머신_의 CPU 개수만큼 스레드 풀 크기를 조정하기 때문에, 큰 공유 서버에서는 수십 개의 스레드가 제가 제공하는 단일 CPU를 두고 경쟁했습니다. 30초가 걸립니다. 작은 클라우드 인스턴스에 소형 모델을 실행할 경우, 이 설정을 먼저 확인해 보세요.num_thread를 1로 설정하자 속도가 두 배가 되었습니다: 제안이 이제 최대 80초 대신 보통 15
AI 페어와 함께 빌드된 스펙 우선 방식
저는 Golden Hour를 spec-first 방식으로 구축했습니다. 즉, 테스트 가능한 수용 기준(acceptance criteria)을 가진 요구사항을 먼저 정의하고, 그다음 설계를 하고, 그 다음 작업 목록을 만들었으며, 이 모든 과정은 코드를 작성하기 전에 승인받았습니다. 모든 내용은 specs/에서 확인할 수 있습니다. 모든 작업에는 담당자가 있었습니다. 저 자신이었거나, 아니면 제 AI 파트너인 Claude Code였습니다.
저는 최초의 점수 계산 규칙을 직접 작성했습니다. 그러다 GRE가 같은 주에 도착했고, 저는 승인된 사양(spec)을 바탕으로 Claude에게 빌드의 대부분을 맡겼고, 저는 검토하고 병합하는 작업을 했습니다. 골든아워 동점 처리 방식부터 OpenStreetMap용 브라우저 폴백까지, 계획이 변경되는 모든 내용은 코드에 반영되기 전에 사양 변경 로그(spec changelog)로 기록되었습니다. 따라서 이 저장소는 단순히 무엇이 바뀌었는지뿐만 아니라 왜 바뀌었는지를 보여줍니다. 이는 154개의 Python 테스트, 카운트다운 및 달력 내보내기용 Node 테스트, 그리고 모든 풀 리퀘스트에 대한 CI를 통해 검증됩니다.
오픈 혁신(Open Innovation)이 중요한 이유?
'언제 시간이 되세요'와 '어디 계세요'가 개인적인 정보이기 때문입니다. 이 앱의 폐쇄형 API 버전은 사용자가 열 때마다 위치와 일정을 제3자에게 전송할 것입니다. 여기서는 모델이 제가 통제하는 서버에서 실행되며, 좌표를 전혀 받지 않습니다. 날씨나 장소는 키(key)나 계정 없이 오픈 데이터로부터 가져옵니다.
존재하기에 충분히 저렴해야 하기 때문입니다. 하루에 한 번 외출하도록 유도하는 앱이 토큰당 비용이나 사용량 제한을 가지고 있어서는 안 됩니다. 전체 운영 스택은 Render에서 실행되는 두 개의 작은 CPU 서비스로, 월 약 $32이며, 측정되는 API가 전혀 없습니다.
제가 트레이드오프를 선택할 수 있기 때문입니다. 동일한 코드, 동일한 프롬프트: 더 풍부한 라인을 위해 제 노트북의 4B 모델을 사용하고, 작은 장치에 맞추기 위해 운영 환경에서는 1B 모델을 사용합니다. 전환은 단지 하나의 환경 변수 변경으로 이루어집니다. 폐쇄형 모델을 사용할 경우, 다른 누군가가 크기, 가격, 그리고 다음 달에도 이용 가능한지에 대해 결정하게 됩니다.
왜 18:00이라고 했는지 확인할 수 있기 때문입니다. 점수 계산 규칙, 프롬프트, 가드레일(guardrails), 그리고 모델 가중치 모두가 공개되어 있습니다. 만약 이 앱이 비 오는 날 외출하라고 한다면, 그것을 가능하게 한 정확한 규칙을 찾을 수 있고, 풀 리퀘스트를 보낼 수도 있습니다.
다음 단계
- 탭이 닫혀 있어도 알림을 받을 수 있는 로컬 "당신의 골든 아워는 10분 후에 시작됩니다" 기능이 탑재된 설치형 휴대폰 버전
- Gemma를 휴대폰 자체에서 구동하여, 오프라인 환경에서도 완벽하게 개인 정보가 보호되는 방식으로 작동
- GRE 직후에 이 프로젝트 덕분에 진행할 현장 테스트
시상 부문
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
