<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>minaz-rong 님의 블로그</title>
    <link>https://minaz-rong.tistory.com/</link>
    <description>minaz-rong 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 4 Jun 2026 12:59:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>minaz-rong</managingEditor>
    <item>
      <title>Django와 FastAPI를 활용한 MSA 기반 멀티모달 AI 챗봇 구축기</title>
      <link>https://minaz-rong.tistory.com/17</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;  1. 프로젝트 개요&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 목표는 단순한 API 호출을 넘어,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;5&quot;&gt;사용자의 입력(텍스트, 음성, 이미지)을 안전하게 검증하고 처리하는 풀스택 AI 서비스&lt;/b&gt;를 구축하는 것이었다. 이를 위해 프론트엔드(Vanilla JS)부터 API 게이트웨이(Django), AI 연동 전담 모델 서버(FastAPI)까지 역할이 명확히 분리된 MSA(마이크로서비스 아키텍처)를 설계했다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;주요 기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;Guardrail (안전망):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용자의 부적절한 요청(선정성, 폭력성 등)을 AI가 사전 차단.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;멀티모달 처리:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;텍스트 대화뿐만 아니라 이미지 생성(~그려줘), TTS 음성 변환 지원.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;답변 품질 평가:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;LLM의 답변이 사용자의 질문과 얼마나 적합한지 자체 채점(Scoring)하여 UX 신뢰도 향상.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt; ️ 2. 아키텍처 설계: 왜 Django와 FastAPI를 같이 썼을까?&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;초기 설계 단계에서 가장 많이 고민했던 부분이다. &quot;그냥 서버 하나로 다 만들면 안 되나?&quot;라는 의문이 들 수 있지만, 시스템의 확장성과 유지보수, 그리고 각 프레임워크의 장점을 극대화하기 위해 두 서버를 분리했다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;1) Django: 든든한 문지기 (API Gateway &amp;amp; Proxy)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;역할:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;클라이언트의 요청을 가장 먼저 맞이하는 게이트웨이이자, HTML 화면을 서빙(Serving)하는 역할.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;선택 이유:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Django는 무겁지만 체계적이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Serializers를 통해 들어오는 데이터의 형식을 엄격하게 검증하고, 잘못된 요청은 모델 서버로 가기 전에 즉시&lt;span&gt;&amp;nbsp;&lt;/span&gt;400 Bad Request로 차단한다. 향후 회원가입, 로그인, 결제, 대화 기록 저장(DB) 등의 비즈니스 로직이 추가될 때 진가를 발휘한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;2) FastAPI: 날렵한 AI 스페셜리스트 (Model Server)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;역할:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 모델(OpenAI/GMS)과 직접 통신하며 텍스트, 이미지, 음성을 생성하는 핵심 두뇌 역할.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;선택 이유:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이름 그대로 빠르고 가볍다. 비동기 처리(Async)에 특화되어 있어 응답 시간이 길어질 수 있는 AI 모델 API 호출을 병목 없이 처리하는 데 최적화되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;15&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;15,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0&quot;&gt;  핵심 인사이트 (관심사의 분리):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Django는 복잡한 '서비스/회원 로직'을, FastAPI는 무거운 'AI 추론 연산'을 담당한다. 만약 AI 트래픽이 몰리면 FastAPI 서버만 여러 대 늘리면(Scale-out) 되므로 인프라 자원을 매우 경제적이고 효율적으로 운영할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt; ️ 3. Trouble Shooting (이슈 및 해결 과정)&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;개발 과정에서 겪었던 주요 블로커(Blocker)들과 해결 과정을 기록한다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;Issue 1:&lt;span&gt;&amp;nbsp;&lt;/span&gt;TemplateDoesNotExist&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 장고가 화면을 찾지 못하는 현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;증상:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;장고 서버 구동 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;localhost:8000에 접속했으나&lt;span&gt;&amp;nbsp;&lt;/span&gt;index.html을 렌더링하지 못함.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;원인:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;장고의&lt;span&gt;&amp;nbsp;&lt;/span&gt;BASE_DIR&lt;span&gt;&amp;nbsp;&lt;/span&gt;기준점과 실제&lt;span&gt;&amp;nbsp;&lt;/span&gt;templates&lt;span&gt;&amp;nbsp;&lt;/span&gt;폴더의 위치가 어긋나 있었다. 폴더가&lt;span&gt;&amp;nbsp;&lt;/span&gt;proxy_server&lt;span&gt;&amp;nbsp;&lt;/span&gt;안쪽 깊숙이 들어가 있었던 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;해결:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;templates&lt;span&gt;&amp;nbsp;&lt;/span&gt;폴더를&lt;span&gt;&amp;nbsp;&lt;/span&gt;manage.py와 같은 레벨(최상위)로 꺼내고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;settings.py의&lt;span&gt;&amp;nbsp;&lt;/span&gt;TEMPLATES&lt;span&gt;&amp;nbsp;&lt;/span&gt;경로가&lt;span&gt;&amp;nbsp;&lt;/span&gt;[BASE_DIR / 'templates']를 정확히 바라보도록 디렉토리 구조를 재정렬했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;[에러 로그]&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjH5aray8yUAxUAAAAAHQAAAAAQng4&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;TemplateDoesNotExist at /
index.html
Exception Location: /.../django/template/loader.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16&quot;&gt;[원인 및 해결]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjH5aray8yUAxUAAAAAHQAAAAAQnw4&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;r&quot;&gt;&lt;code&gt;# settings.py 수정 내용
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], #   장고에게 폴더 위치를 정확히 명시!
        ...
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;Issue 2: 데이터 규격 불일치로 인한&lt;span&gt;&amp;nbsp;&lt;/span&gt;404 Not Found&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;amp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;422 Unprocessable Entity&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;증상:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프론트엔드에서 장고를 거쳐 FastAPI로 데이터가 넘어갈 때 에러 발생.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원인:&lt;/b&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Django(게이트웨이)와 FastAPI(모델 서버) 간의&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;23&quot;&gt;API 명세(Contract) 불일치 &lt;br /&gt;&lt;/b&gt;&lt;b data-path-to-node=&quot;24,0,0&quot; data-index-in-node=&quot;0&quot;&gt;404 에러:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Django는&lt;span&gt;&amp;nbsp;&lt;/span&gt;/chat/completions&lt;span&gt;&amp;nbsp;&lt;/span&gt;주소로 데이터를 쐈지만, FastAPI는&lt;span&gt;&amp;nbsp;&lt;/span&gt;/chat이라는 이름표를 달고 있었다. &lt;br /&gt;&lt;b data-path-to-node=&quot;24,1,0&quot; data-index-in-node=&quot;0&quot;&gt;422 에러:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;데이터 포맷이 달랐다. Django는 데이터를&lt;span&gt;&amp;nbsp;&lt;/span&gt;messages라는 이름(Key)으로 보냈는데, FastAPI의 Pydantic 모델은&lt;span&gt;&amp;nbsp;&lt;/span&gt;prompt라는 이름을 기다리고 있어서 처리를 거부(Unprocessable)한 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결:&lt;/b&gt; &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;장고의 통신 전담 파일인&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;services.py를 리팩토링했다. 하드코딩된 변수들을 FastAPI의 Pydantic 모델(ChatRequest) 규격에 맞춰&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;prompt로 통일하고 엔드포인트를 수정하여 두 서버 간의 언어를 일치시켰다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21&quot;&gt;[에러 로그]&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjH5aray8yUAxUAAAAAHQAAAAAQoA4&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;[서비스 에러 발생] 404 Client Error: Not Found for url: http://127.0.0.1:8001/api/v1/chat/completions
[서비스 에러 발생] 422 Client Error: Unprocessable Entity for url: http://127.0.0.1:8001/api/v1/chat/score
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjH5aray8yUAxUAAAAAHQAAAAAQoQ4&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;b data-path-to-node=&quot;16&quot; data-index-in-node=&quot;0&quot;&gt;[원인 및 해결]&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# Django: proxy_server/api/services.py

def get_chat_response(chat_request):
    &quot;&quot;&quot;FastAPI 모델 서버로 채팅 요청 전송&quot;&quot;&quot;
    # 수정 1: 'messages' -&amp;gt; 'prompt'로 Key 통일
    payload_data = {&quot;prompt&quot;: chat_request.get(&quot;prompt&quot;)}
    try:
        # 수정 2: '/chat/completions' -&amp;gt; '/chat'으로 URI 통일
        response = requests.post(
            f&quot;{MODEL_SERVER_URL}/chat&quot;, json=payload_data
        )
        response.raise_for_status()
        return response.json()
    except Exception as e:
        print(f&quot;[에러] {e}&quot;)
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;23&quot; data-ke-size=&quot;size23&quot;&gt;Issue 3: TTS 오디오 데이터 전송 포맷 문제 (Base64)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;증상:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;TTS API 호출 시 바이너리(이진) 형태의 mp3 데이터가 HTTP JSON 응답으로 제대로 전달되지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;원인:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;JSON 포맷은 텍스트만 담을 수 있기 때문에, 오디오 파일을 그대로 쑤셔 넣으면 데이터가 깨지거나 에러가 난다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,2,0&quot;&gt;해결:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;FastAPI에서 오디오 데이터를&lt;span&gt;&amp;nbsp;&lt;/span&gt;base64.b64encode()를 이용해 안전한 텍스트 형태로 인코딩(암호화)하여 전송했다. 프론트엔드에서는 이를 다시 디코딩하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;audio&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그의 소스로 활용해 브라우저에서 즉시 재생되도록 구현했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30&quot;&gt;[해결]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjH5aray8yUAxUAAAAAHQAAAAAQog4&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;# FastAPI: model_server/model_main.py

@app.post(&quot;/api/v1/audio/speech&quot;)
def generate_speech(req: TTSRequest):
    # (중략) 외부 API로 음성 생성 요청 ...
    res.raise_for_status()
    
    #   핵심: 넘어온 이진(Binary) mp3 데이터를 웹에서 읽을 수 있게 Base64로 인코딩!
    audio_data = base64.b64encode(res.content).decode(&quot;utf-8&quot;)
    return {&quot;audio_data&quot;: audio_data}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;이후 프론트엔드(Vanilla JS)에서는 받아온 Base64 텍스트를 다시 오디오 소스로 변환해 브라우저에서 즉시 재생(new Audio().play())하도록 구현했다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;26&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;26&quot; data-ke-size=&quot;size26&quot;&gt;  4. Retrospective (회고)&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;단순히 &quot;기능이 동작한다&quot;를 넘어, 실제 프로덕트 레벨에서 시스템이 어떻게 맞물려 돌아가야 하는지 뼈저리게 배운 하루였다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;특히&lt;span&gt;&amp;nbsp;&lt;/span&gt;views.py에 있던 외부 통신 로직을&lt;span&gt;&amp;nbsp;&lt;/span&gt;services.py로 분리하면서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;28&quot;&gt;코드를 어떻게 짜야 나중에 서비스 기획이나 스펙이 변경될 때 유연하게 대처할 수 있는지&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;아키텍처 설계의 중요성을 깨달았다. 프론트엔드(UI/UX), 프록시, AI 연동이라는 전체 데이터의 흐름을 통제해 본 경험은 향후 AI 서비스를 기획하고 리드하는 데 있어 엄청난 자산이 될 것 같다.&lt;/p&gt;</description>
      <category>프로젝트</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/17</guid>
      <comments>https://minaz-rong.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 22 May 2026 20:39:49 +0900</pubDate>
    </item>
    <item>
      <title>  Jira + GitHub 협업 규칙</title>
      <link>https://minaz-rong.tistory.com/16</link>
      <description>&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;1. 개념 이해하기&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;6&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;용어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0,0&quot;&gt;Jira Task&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,1,1,0&quot;&gt;해야 할 일 카드&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0,0&quot;&gt;Branch&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,2,1,0&quot;&gt;해당 작업을 위한 개인 작업 공간&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0,0&quot;&gt;Commit Message&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,3,1,0&quot;&gt;작업 내용을 저장하면서 남기는 기록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,4,0,0&quot;&gt;Pull Request (PR)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;6,4,1,0&quot;&gt;내 작업을 main 브랜치에 합쳐달라고 요청하는 것&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;2. 작업 예시&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;Jira에 다음 작업이 있다고 가정한다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;9&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;9,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0&quot;&gt;KAN-27 AI 해설 생성 API 구현&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;3. 브랜치명 규칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;형식:&lt;/b&gt; feature/KAN-번호-작업명&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;예시:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;feature/KAN-27-ai-api&lt;/li&gt;
&lt;li&gt;feature/KAN-35-result-page&lt;/li&gt;
&lt;li&gt;feature/KAN-16-public-api-test&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;브랜치 생성 명령어&lt;/h4&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj8gOvwp7iUAxUAAAAAHQAAAAAQaA&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;git checkout main
git pull origin main
git checkout -b feature/KAN-27-ai-api
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;git checkout main&lt;/b&gt;: 현재 브랜치를 main으로 이동 (팀의 기준 코드가 있는 곳).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;git pull origin main&lt;/b&gt;: GitHub에 있는 최신 코드를 내 컴퓨터로 다운로드.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;git checkout -b feature/KAN-27-ai-api&lt;/b&gt;: 새로운 브랜치를 만들고 이동 (KAN-27 전용 공간).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;15&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;15,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0&quot;&gt;세 줄 요약:&lt;/b&gt; main 브랜치로 이동 &amp;rarr; 최신 코드 받기 &amp;rarr; 내 작업용 브랜치 생성&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;4. 커밋 메시지 규칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;형식:&lt;/b&gt; KAN-번호 작업 내용&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;예시:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KAN-27 AI 해설 생성 API 구현&lt;/li&gt;
&lt;li&gt;KAN-35 결과 페이지 UI 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size20&quot;&gt;커밋 명령어&lt;/h4&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj8gOvwp7iUAxUAAAAAHQAAAAAQaQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add .
git commit -m &quot;KAN-27 AI 해설 생성 API 구현&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;git add .&lt;/b&gt;: 수정한 파일들을 커밋 대상으로 등록.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;git commit -m &quot;...&quot;&lt;/b&gt;: 작업 내용을 저장하고 기록을 남김.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;5. GitHub에 Push&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj8gOvwp7iUAxUAAAAAHQAAAAAQag&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git push origin feature/KAN-27-ai-api
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;의미:&lt;/b&gt; 내 브랜치를 GitHub에 업로드한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;6. Pull Request(PR) 제목 규칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;형식:&lt;/b&gt; KAN-번호 작업 내용&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;예시:&lt;/b&gt; KAN-27 AI 해설 생성 API 구현&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,2,0&quot;&gt;의미:&lt;/b&gt; KAN-27 작업이 끝났으니 main에 합쳐달라는 요청.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;7. Jira 상태 변경 규칙&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27&quot;&gt;To Do &amp;rarr; In Progress &amp;rarr; In Review &amp;rarr; Done&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;28&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Jira 상태&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,1,0,0&quot;&gt;작업 시작&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,1,1,0&quot;&gt;In Progress&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,2,0,0&quot;&gt;PR 생성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,2,1,0&quot;&gt;In Review&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,3,0,0&quot;&gt;PR Merge 완료&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;28,3,1,0&quot;&gt;Done&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-path-to-node=&quot;29&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size23&quot;&gt;8. 전체 작업 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;Jira에서 작업 확인&lt;/b&gt;: KAN-27 AI 해설 생성 API 구현&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;브랜치 생성&lt;/b&gt;: git checkout main &amp;rarr; git pull &amp;rarr; git checkout -b ...&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,2,0&quot;&gt;코드 작성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,3,0&quot;&gt;커밋&lt;/b&gt;: git add . &amp;rarr; git commit -m &quot;KAN-27 ...&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,4,0&quot;&gt;GitHub에 Push&lt;/b&gt;: git push origin ...&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,5,0&quot;&gt;Pull Request 생성&lt;/b&gt;: 제목에 Jira 번호 포함&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,6,0&quot;&gt;리뷰 후 Merge&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,7,0&quot;&gt;Jira 상태 변경&lt;/b&gt;: Done으로 완료 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-path-to-node=&quot;32&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size23&quot;&gt;9. 역할별 실제 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,0,0&quot;&gt;AI 담당자&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task: KAN-23 프롬프트 설계&lt;/li&gt;
&lt;li&gt;Branch: feature/KAN-23-prompt-design&lt;/li&gt;
&lt;li&gt;PR: KAN-23 프롬프트 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,1,0&quot;&gt;Backend 담당자&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task: KAN-27 AI 해설 생성 API 구현&lt;/li&gt;
&lt;li&gt;Branch: feature/KAN-27-ai-api&lt;/li&gt;
&lt;li&gt;PR: KAN-27 AI 해설 생성 API 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,2,0&quot;&gt;Frontend 담당자&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task: KAN-35 결과 페이지 구현&lt;/li&gt;
&lt;li&gt;Branch: feature/KAN-35-result-page&lt;/li&gt;
&lt;li&gt;PR: KAN-35 결과 페이지 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;35&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;36&quot; data-ke-size=&quot;size23&quot;&gt;10. 핵심 규칙 한 줄 요약&lt;/h3&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37&quot;&gt;브랜치명, 커밋 메시지, PR 제목에 반드시 Jira 번호(KAN-xx)를 포함한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-path-to-node=&quot;38&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;39&quot; data-ke-size=&quot;size23&quot;&gt;11. 팀원 체크리스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;40&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[ ] &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;40,0,0&quot;&gt;작업 시작 전&lt;/b&gt;: Jira 번호 확인 및 상태를 'In Progress'로 변경. 최신 코드 pull 후 브랜치 생성.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;40,1,0&quot;&gt;작업 중&lt;/b&gt;: 코드 작성 및 수시로 커밋.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;40,2,0&quot;&gt;작업 완료 후&lt;/b&gt;: Push 후 PR 생성. 제목에 번호 포함. Jira 상태를 'In Review'로 변경.&lt;/li&gt;
&lt;li&gt;[ ] &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;40,3,0&quot;&gt;리뷰 완료 후&lt;/b&gt;: Merge 확인 후 Jira 상태를 'Done'으로 변경.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;41&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;41,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,0&quot;&gt;요약:&lt;/b&gt; 번호 확인 &amp;rarr; 브랜치 생성 &amp;rarr; 작업 &amp;rarr; 커밋 &amp;rarr; Push &amp;rarr; PR &amp;rarr; 리뷰 &amp;rarr; Merge &amp;rarr; Done&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;42&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size23&quot;&gt;12. Jira 사용법 요약&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;44&quot; data-ke-size=&quot;size20&quot;&gt;Epic (큰 기능 단위)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;45&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기획 및 자료조사 / 데이터 수집 / AI 해설 생성 / 백엔드 / 프론트엔드 / 배포 / 발표 준비&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;46&quot; data-ke-size=&quot;size20&quot;&gt;Task (실제 세부 작업)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;47&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 페르소나 정의 / 공공 API 조사 / 프롬프트 설계 / 결과 페이지 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;48&quot; data-ke-size=&quot;size20&quot;&gt;Priority (우선순위)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;49&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,0,0&quot;&gt;Highest&lt;/b&gt;: 지금 당장 해야 함&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,1,0&quot;&gt;High&lt;/b&gt;: 매우 중요&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,2,0&quot;&gt;Medium&lt;/b&gt;: 일반&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,3,0&quot;&gt;Low&lt;/b&gt;: 여유 있을 때&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로젝트/richman</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/16</guid>
      <comments>https://minaz-rong.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 14 May 2026 17:02:25 +0900</pubDate>
    </item>
    <item>
      <title>2026 정보처리기사 필기 시험 소개 및 일정 정리</title>
      <link>https://minaz-rong.tistory.com/15</link>
      <description>&lt;h2 data-end=&quot;116&quot; data-start=&quot;102&quot; data-ke-size=&quot;size26&quot;&gt;  정보처리기사란?&lt;/h2&gt;
&lt;p data-end=&quot;221&quot; data-start=&quot;118&quot; data-ke-size=&quot;size16&quot;&gt;정보처리기사는 &lt;b&gt;IT 전반의 기초 지식과 실무 능력을 평가하는 국가기술자격증&lt;/b&gt;&lt;br /&gt;소프트웨어 개발, 데이터베이스, 시스템 운영 등 IT 직무 전반에서 기본 자격증처럼 취급된다&lt;/p&gt;
&lt;p data-end=&quot;286&quot; data-start=&quot;223&quot; data-ke-size=&quot;size16&quot;&gt;  주관: &lt;span&gt;&lt;span&gt;한국산업인력공단&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  시행: 연 3회 (보통)&lt;/p&gt;
&lt;h2 data-end=&quot;305&quot; data-start=&quot;293&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;102&quot; data-end=&quot;116&quot; data-ke-size=&quot;size26&quot;&gt; 왜 따는가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;377&quot; data-start=&quot;307&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;326&quot; data-start=&quot;307&quot;&gt;IT 취업 시 &lt;b&gt;기본 스펙&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;347&quot; data-start=&quot;327&quot;&gt;공기업 / 공공기관 &lt;b&gt;가산점&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;377&quot; data-start=&quot;348&quot;&gt;개발자, 데이터 직무 준비 시 &lt;b&gt;기초 정리용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;379&quot; data-ke-size=&quot;size16&quot;&gt;  특히 비전공자도 많이 도전하는 시험&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;419&quot; data-start=&quot;408&quot; data-ke-size=&quot;size26&quot;&gt;  시험 구성&lt;/h2&gt;
&lt;h3 data-end=&quot;431&quot; data-start=&quot;421&quot; data-ke-size=&quot;size23&quot;&gt;✔ 필기시험&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;463&quot; data-start=&quot;432&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;442&quot; data-start=&quot;432&quot;&gt;객관식 4지선다&lt;/li&gt;
&lt;li data-end=&quot;463&quot; data-start=&quot;443&quot;&gt;총 100문제 (과목당 20문제)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;471&quot; data-start=&quot;465&quot; data-ke-size=&quot;size16&quot;&gt;과목 구성:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;537&quot; data-start=&quot;472&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;483&quot; data-start=&quot;472&quot;&gt;소프트웨어 설계&lt;/li&gt;
&lt;li data-end=&quot;495&quot; data-start=&quot;484&quot;&gt;소프트웨어 개발&lt;/li&gt;
&lt;li data-end=&quot;508&quot; data-start=&quot;496&quot;&gt;데이터베이스 구축&lt;/li&gt;
&lt;li data-end=&quot;523&quot; data-start=&quot;509&quot;&gt;프로그래밍 언어 활용&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;524&quot;&gt;정보시스템 구축관리&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-end=&quot;555&quot; data-start=&quot;544&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;555&quot; data-start=&quot;544&quot; data-ke-size=&quot;size23&quot;&gt;✔ 합격 기준&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;591&quot; data-start=&quot;556&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;572&quot; data-start=&quot;556&quot;&gt;과목별 &lt;b&gt;40점 이상&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;591&quot; data-start=&quot;573&quot;&gt;전체 평균 &lt;b&gt;60점 이상&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;611&quot; data-start=&quot;593&quot; data-ke-size=&quot;size16&quot;&gt;  한 과목이라도 과락이면 탈락&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;647&quot; data-start=&quot;618&quot; data-ke-size=&quot;size26&quot;&gt;  2026년 정보처리기사 시험 일정 (예시)&lt;/h2&gt;
&lt;p data-end=&quot;669&quot; data-start=&quot;649&quot; data-ke-size=&quot;size16&quot;&gt;※ 정확한 일정은 큐넷에서 확인 필요&lt;/p&gt;
&lt;p data-end=&quot;720&quot; data-start=&quot;671&quot; data-ke-size=&quot;size16&quot;&gt;  접수 및 확인: &lt;span&gt;&lt;span&gt;큐넷(&lt;a title=&quot;큐넷&quot; href=&quot;https://www.q-net.or.kr/crf005.do?id=crf00503&amp;amp;jmCd=1320&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.q-net.or.kr/crf005.do?id=crf00503&amp;amp;jmCd=1320)&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;731&quot; data-start=&quot;722&quot; data-ke-size=&quot;size23&quot;&gt;✔ 1회차&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;753&quot; data-start=&quot;732&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;742&quot; data-start=&quot;732&quot;&gt;접수: 1월 12일 ~ 1월 15일&lt;/li&gt;
&lt;li data-end=&quot;753&quot; data-start=&quot;743&quot;&gt;필기시험: 3월&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;764&quot; data-start=&quot;755&quot; data-ke-size=&quot;size23&quot;&gt;✔ 2회차&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;786&quot; data-start=&quot;765&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;775&quot; data-start=&quot;765&quot;&gt;접수: 4월 20일 ~ 4월 23일&lt;/li&gt;
&lt;li data-end=&quot;786&quot; data-start=&quot;776&quot;&gt;필기시험: 5월&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;797&quot; data-start=&quot;788&quot; data-ke-size=&quot;size23&quot;&gt;✔ 3회차&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;819&quot; data-start=&quot;798&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;808&quot; data-start=&quot;798&quot;&gt;접수: 7월 20일 ~ 7월 23일&lt;/li&gt;
&lt;li data-end=&quot;819&quot; data-start=&quot;809&quot;&gt;필기시험: 9월&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgCUo/dJMb99M0IQe/JjzkkPNsYGIKAyJkc2aivK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgCUo/dJMb99M0IQe/JjzkkPNsYGIKAyJkc2aivK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgCUo/dJMb99M0IQe/JjzkkPNsYGIKAyJkc2aivK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgCUo%2FdJMb99M0IQe%2FJjzkkPNsYGIKAyJkc2aivK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;591&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;838&quot; data-start=&quot;821&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;855&quot; data-start=&quot;845&quot; data-ke-size=&quot;size26&quot;&gt;⏱ 시험 시간&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;894&quot; data-start=&quot;857&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;875&quot; data-start=&quot;857&quot;&gt;총 150분 (2시간 30분)&lt;/li&gt;
&lt;li data-end=&quot;894&quot; data-start=&quot;876&quot;&gt;과목 구분 없이 한 번에 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;915&quot; data-start=&quot;901&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;915&quot; data-start=&quot;901&quot; data-ke-size=&quot;size26&quot;&gt;  난이도 &amp;amp; 특징&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;966&quot; data-start=&quot;917&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;933&quot; data-start=&quot;917&quot;&gt;암기 + 이해 혼합형 시험&lt;/li&gt;
&lt;li data-end=&quot;950&quot; data-start=&quot;934&quot;&gt;기출 문제 반복 비중 높음&lt;/li&gt;
&lt;li data-end=&quot;966&quot; data-start=&quot;951&quot;&gt;실무보다는 &amp;ldquo;이론 중심&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;999&quot; data-start=&quot;968&quot; data-ke-size=&quot;size16&quot;&gt;  느낌:&lt;br /&gt;&amp;ldquo;얕고 넓게 많이 아는 사람이 유리한 시험&amp;rdquo;&lt;/p&gt;
&lt;h2 data-end=&quot;1020&quot; data-start=&quot;1006&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1020&quot; data-start=&quot;1006&quot; data-ke-size=&quot;size26&quot;&gt;  합격률 (참고)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1051&quot; data-start=&quot;1022&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1036&quot; data-start=&quot;1022&quot;&gt;필기: 약 50~60%&lt;/li&gt;
&lt;li data-end=&quot;1051&quot; data-start=&quot;1037&quot;&gt;실기: 약 20~30%&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1066&quot; data-start=&quot;1053&quot; data-ke-size=&quot;size16&quot;&gt;  실기가 훨씬 어려움&lt;/p&gt;
&lt;h2 data-end=&quot;1089&quot; data-start=&quot;1073&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;901&quot; data-end=&quot;915&quot; data-ke-size=&quot;size26&quot;&gt; 이런 사람에게 추천&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1133&quot; data-start=&quot;1091&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1103&quot; data-start=&quot;1091&quot;&gt;개발자 취업 준비생&lt;/li&gt;
&lt;li data-end=&quot;1123&quot; data-start=&quot;1104&quot;&gt;IT 기초를 정리하고 싶은 사람&lt;/li&gt;
&lt;li data-end=&quot;1133&quot; data-start=&quot;1124&quot;&gt;공기업 준비생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1186&quot; data-start=&quot;1154&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자격증</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/15</guid>
      <comments>https://minaz-rong.tistory.com/15#entry15comment</comments>
      <pubDate>Wed, 6 May 2026 11:01:44 +0900</pubDate>
    </item>
    <item>
      <title>협업을 위한 폴더 구조와 네이밍 규칙 총정리</title>
      <link>https://minaz-rong.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;팀 프로젝트를 하다 보면 이런 상황을 자주 겪습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&amp;ldquo;이 코드 어디에 있어?&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&amp;ldquo;파일 이름 왜 이렇게 지었지?&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&amp;ldquo;같은 기능인데 파일이 여기저기 흩어져 있네&amp;hellip;&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  이런 문제의 원인은 대부분 &lt;/span&gt;&lt;b&gt;&lt;span&gt;폴더 구조와 네이밍이 통일되지 않았기 때문&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. 잘못된 폴더 구조 vs 좋은 폴더 구조&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;❌ 잘못된 예 (사람 기준 구조)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;mina/
koo/
boo/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;span&gt;문제점&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;기능이 사람 기준으로 흩어짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;코드 찾기 어려움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;협업이 아닌 개인 작업처럼 변함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;❌ 또 다른 나쁜 예 (섞여있는 구조)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;utils/
api/
components/
pages/
stock/
chat/
data/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  기준이 섞여 있음 (기능 + 역할 혼합)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;✅ 좋은 예 (기능 기준 구조)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;backend/
  user/
  order/
  payment/

frontend/
  user/
  order/
  payment/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;기능 단위로 코드가 모여 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;유지보수 쉬움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;협업 시 역할 분리 명확&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 네이밍 컨벤션, 왜 중요한가?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코드는 결국 &amp;ldquo;읽는 것&amp;rdquo;이 더 많습니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;  이름이 곧 문서입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;❌ 나쁜 네이밍 예&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;data1
temp
abc
getD&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  문제:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;의미를 알 수 없음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;협업 시 해석 필요 &amp;rarr; 시간 낭비&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;✅ 좋은 네이밍 예&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;userList
getUserData
fetchOrderHistory
calculateTotalPrice&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  이름만 보고 역할이 바로 이해됨&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. 네이밍 규칙 정리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  변수 / 함수 &amp;rarr; camelCase&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;userName
getUserProfile
calculateDiscountPrice&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  클래스 / 컴포넌트 &amp;rarr; PascalCase&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;UserService
OrderController
PaymentCard&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  파일명 &amp;rarr; kebab-case (추천)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;user-service.js
order-controller.js
payment-utils.js&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  DB 컬럼 &amp;rarr; snake_case&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;user_id
created_at
order_price&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  API 경로 &amp;rarr; 소문자 + kebab-case&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/api/user-profile
/api/order-history
/api/payment-status&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. 흔히 하는 실수&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;camelCase / snake_case 섞어 쓰기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;파일명 규칙 사람마다 다르게 쓰기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;의미 없는 이름 사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;축약어 남발 (usr, dt 등)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  결국 코드 이해도가 떨어짐&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. 좋은 구조와 네이밍의 효과&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;코드 찾는 시간 감소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;협업 효율 증가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;유지보수 쉬움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;코드 리뷰 속도 향상&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  한 줄 정리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;b&gt;&lt;span&gt;&amp;ldquo;폴더는 기능 기준으로, 이름은 일관되게&amp;rdquo; 이 두 가지만 지켜도 프로젝트 퀄리티가 달라진다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>프로젝트/richman</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/14</guid>
      <comments>https://minaz-rong.tistory.com/14#entry14comment</comments>
      <pubDate>Wed, 29 Apr 2026 17:05:10 +0900</pubDate>
    </item>
    <item>
      <title>팀 프로젝트 시작 전, 반드시 정해야 할 협업 규칙2 (폴더구조, 네이밍 컨벤션)</title>
      <link>https://minaz-rong.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 시 아래 기준으로 &lt;b&gt;반드시 통일&lt;/b&gt;합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  1. 폴더 구조 (기능 기준)&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✅ 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;backend/
  stock/        # 주식
  crypto/       # 크립토
  consumption/  # 소비 분석
  ai/           # 공통 AI

frontend/
  chatbot/      # 챗봇 UI
  dashboard/    # 소비/데이터 시각화
  stock/        # 주식 화면
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  규칙&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각자 담당 기능 폴더 중심으로 작업&lt;/li&gt;
&lt;li&gt;다른 폴더 수정 시 팀원과 공유&lt;/li&gt;
&lt;li&gt;공통 폴더(ai)는 역할 나눠서 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  2. 네이밍 컨벤션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  변수 / 함수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;camelCase&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;userName
getUserData
fetchStockPrice
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  클래스 / 컴포넌트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PascalCase&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;UserCard
LoginPage
StockChart
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  파일명&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;kebab-case&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;user-profile.js
stock-chart.vue
chatbot-service.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  DB 컬럼&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;snake_case&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;user_id
created_at
stock_price
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  API 경로&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소문자 + kebab-case&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/api/stock-price
/api/crypto-buzz
/api/user-data
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;⚠️ 필수 규칙&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이밍 방식 혼용 금지&lt;/li&gt;
&lt;li&gt;의미 없는 이름 금지 (data1, temp ❌)&lt;/li&gt;
&lt;li&gt;축약어 남발 금지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/richman</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/13</guid>
      <comments>https://minaz-rong.tistory.com/13#entry13comment</comments>
      <pubDate>Wed, 29 Apr 2026 17:04:32 +0900</pubDate>
    </item>
    <item>
      <title>Git PR(Pull Request) 개념부터 실전까지 한 번에 정리</title>
      <link>https://minaz-rong.tistory.com/12</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;  우리 팀 PR (Pull Request) 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;팀 프로젝트에서 PR은&lt;/span&gt;&lt;br /&gt;&lt;span&gt;  코드를 합치기 전 반드시 거치는&lt;b&gt; &amp;ldquo;검토 과정&amp;rdquo;&lt;/b&gt;입니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;아래 규칙을 반드시 지킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;   언제 사용하나요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;feature 브랜치에서 작업 완료 후&lt;/span&gt;&lt;br /&gt;&lt;span&gt;  develop 브랜치에 반영할 때 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;어디서 작성하나요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;GitHub 웹사이트에서 작성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;경로:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Repository 접속&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Pull requests 클릭&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;New pull request 버튼 클릭&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  1. 기본 원칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;PR 없이 merge 금지 ❌&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;모든 코드는 PR을 통해서만 develop에 반영 ⭕&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;PR = &amp;ldquo;내 코드 리뷰 요청&amp;rdquo;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  2. PR 방향 (중요 ⭐)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;feature/* &amp;rarr; develop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;feature/mina-chatbot &amp;rarr; develop
feature/boo-stock-analysis &amp;rarr; develop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  3. PR 작성 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;span&gt;템플릿 (필수 사용)&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 작업 내용
## 변경 사항
## 리뷰 포인트
## 관련 이슈&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;span&gt;작성 기준&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;작업 내용을 한눈에 이해 가능하게 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;변경 사항은 핵심만 정리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰 포인트는 꼭 확인받고 싶은 부분 작성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;span&gt;진행 흐름&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;feature 브랜치에서 작업&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;commit &amp;amp; push&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;PR 생성 (&amp;rarr; develop)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;코드 리뷰&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;승인 후 merge&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  4. 리뷰 &amp;amp; Merge 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;최소 1명 이상 승인 후 Merge&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;승인 없이 merge 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰 요청 시 팀원 태그 필수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  Merge 방식: &lt;/span&gt;&lt;b&gt;&lt;span&gt;Squash Merge 사용(권장)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  5. PR 크기 규칙 (중요)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;기능 단위로 PR 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;너무 큰 PR 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하루 작업 단위로 PR 권장&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;❌ 나쁜 예&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;기능 여러 개를 한 번에 PR&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;✅ 좋은 예&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;로그인 기능 PR&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회원가입 기능 PR&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  6. 작업 흐름&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;feature 브랜치 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;기능 개발&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;commit &amp;amp; push&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;PR 생성 (&amp;rarr; develop)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;코드 리뷰&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;승인 후 merge&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  7. 금지 사항&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;main 브랜치로 PR 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;develop에서 직접 작업 후 merge 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰 없이 merge 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;의미 없는 PR 생성 금지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  8. 권장 사항&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;하루 1회 이상 PR&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;오래된 브랜치 방치 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;충돌 발생 시 직접 해결 후 PR&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  9. 작성예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span&gt;예시 1. 챗봇 기능&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## 작업 내용
크립토 챗봇 기본 기능 구현

## 변경 사항
- 뉴스 요약 기능 추가
- 감정 분석 결과 반환
- 챗봇 API 연결

## 리뷰 포인트
- 응답 속도 괜찮은지 확인 부탁드립니다
- 감정 분석 결과 정확도 체크해주세요

## 관련 이슈
- PROJ-12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span&gt;예시 2. 주식 분석 기능&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## 작업 내용
주식 데이터 수집 및 기본 분석 기능 구현

## 변경 사항
- 주가 API 연동
- 데이터 전처리 로직 추가
- 매수/매도 감정 계산 로직 구현

## 리뷰 포인트
- 감정 분류 기준이 적절한지 확인 부탁드립니다

## 관련 이슈
- PROJ-18&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span&gt;예시 3. 소비 분석 기능&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 작업 내용
소비 데이터 시각화 기능 구현

## 변경 사항
- 카테고리별 소비 그래프 추가
- 월별 소비 통계 계산 로직 구현

## 리뷰 포인트
- 그래프 UI/UX 자연스러운지 확인 부탁드립니다

## 관련 이슈
- PROJ-25&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span&gt;  한 줄 정리&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;b&gt;&lt;span&gt;&amp;ldquo;작게 만들고, 자주 올리고, 리뷰 받고 합친다&amp;rdquo;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt; PR = &amp;ldquo;내 코드 합쳐도 되는지 팀에게 묻는 과정&amp;rdquo; &lt;/b&gt;&lt;/p&gt;</description>
      <category>프로젝트/richman</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/12</guid>
      <comments>https://minaz-rong.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 29 Apr 2026 16:45:04 +0900</pubDate>
    </item>
    <item>
      <title>팀 프로젝트 시작 전, 반드시 정해야 할 협업 규칙1 (Git)</title>
      <link>https://minaz-rong.tistory.com/11</link>
      <description>&lt;h1&gt;&lt;b&gt;&lt;span&gt;1. 깃(Git) 브랜치 전략&lt;/span&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;p data-end=&quot;173&quot; data-start=&quot;146&quot; data-section-id=&quot;1h0ztln&quot; data-ke-size=&quot;size18&quot;&gt;  최종 브랜치 전략&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;main                 # 배포용 (직접 수정 금지) 
develop              # 통합 개발 브랜치 
feature/이름-기능    # 기능 개발 브랜치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; letter-spacing: -1px;&quot;&gt;   브랜치 네이밍 (기능 기준 + 사람)&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-section-id=&quot;10svair&quot; data-start=&quot;430&quot; data-end=&quot;460&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; Boo (공병우 - 주식 &amp;amp; 일부 데이터)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;feature/boo-stock-data
feature/boo-stock-prediction
feature/boo-stock-news-analysis
feature/boo-stock-sentiment&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;460&quot; data-start=&quot;430&quot; data-section-id=&quot;10svair&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  Min (정민아 - 크립토 + 챗봇 핵심)&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;feature/min-crypto-price
feature/min-crypto-buzz
feature/min-chatbot-core
feature/min-chatbot-summary
feature/min-chatbot-sentiment&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;Koo (구도연 - 소비 + 데이터)&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;feature/koo-consumption-analysis
feature/koo-consumption-visualization
feature/koo-consumption-prediction
feature/koo-recommendation&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;815&quot; data-start=&quot;796&quot; data-section-id=&quot;6kq4c4&quot; data-ke-size=&quot;size26&quot;&gt;공통 AI 기능 (중요) - 미정&lt;/h2&gt;
&lt;p data-end=&quot;842&quot; data-start=&quot;817&quot; data-ke-size=&quot;size16&quot;&gt;  여기 충돌 많이 남 &amp;rarr; 명확히 나눠야 함&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;feature/ai-common-qa
feature/ai-common-summary&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;921&quot; data-start=&quot;904&quot; data-ke-size=&quot;size16&quot;&gt;  대신 내부 역할 분리  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;978&quot; data-start=&quot;923&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;944&quot; data-start=&quot;923&quot; data-section-id=&quot;1ko0dgv&quot;&gt;min: 챗봇 UX + 인터페이스&lt;/li&gt;
&lt;li data-end=&quot;961&quot; data-start=&quot;945&quot; data-section-id=&quot;1dj1t9h&quot;&gt;Boo: 주식 데이터 연결&lt;/li&gt;
&lt;li data-end=&quot;978&quot; data-start=&quot;962&quot; data-section-id=&quot;xfp6px&quot;&gt;Koo: 소비 데이터 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;&lt;b&gt;&lt;span&gt;2. 커밋 메시지 컨벤션 (필수 규칙 정리)&lt;/span&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;팀 프로젝트에서는&lt;/span&gt;&lt;br /&gt;&lt;span&gt;  &lt;/span&gt;&lt;b&gt;&lt;span&gt;커밋 메시지만 보고도 &amp;ldquo;누가, 무엇을, 왜 했는지&amp;rdquo; 알 수 있어야 합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  1. 기본 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;type: Subject&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: 작업 종류&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Subject&lt;/span&gt;&lt;span&gt;: 작업 내용 (간결하게)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  2. 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;feat: 로그인 기능 구현
fix: 회원가입 에러 수정
refactor: 인증 로직 구조 개선&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  3. 타입 (Type) 정의&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;feat: 새로운 기능 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;fix: 버그 수정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;docs: 문서 수정 (README 등)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;style: 코드 스타일 변경 (기능 영향 없음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;refactor: 코드 구조 개선 (기능 변화 없음)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;test: 테스트 코드 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;chore: 설정, 빌드 등 기타 작업&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  4. 작성 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;한 줄로 간결하게 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;50자 이내 권장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;동사로 시작 (구현, 수정, 추가 등)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;불필요한 설명 ❌ (상세 내용은 PR에 작성)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  5. 좋은 vs 나쁜 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span&gt;❌ 나쁜 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;수정
로그인 고침
코드 변경&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span&gt;  무엇을 했는지 알 수 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span&gt;✅ 좋은 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;feat: JWT 기반 로그인 기능 구현
fix: 로그인 시 토큰 만료 오류 수정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span&gt;  작업 내용이 명확함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  6. 팀 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;커밋 메시지 형식 통일 (반드시 준수)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;의미 없는 커밋 메시지 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하나의 커밋 = 하나의 작업&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2-1. PR 규칙 (초간단 요약)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  기본&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;PR 없이 merge 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;모든 코드는 PR &amp;rarr; develop&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  방향&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;feature/* &amp;rarr; develop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  작성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 작업 내용
## 변경 사항
## 리뷰 포인트
## 관련 이슈&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;팀장 승인 후 merge&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;PR은 작게 (기능 단위)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하루 1회 이상 PR 권장&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  금지&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;main으로 PR ❌&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰 없이 merge ❌&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;  PR 작성 내용 (템플릿)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## 작업 내용
챗봇 기본 기능 구현

## 변경 사항
- 뉴스 요약 기능 추가
- 감정 분석 연결

## 리뷰 포인트
- 응답 속도 확인 부탁드립니다

## 관련 이슈
- PROJ-12&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/richman</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/11</guid>
      <comments>https://minaz-rong.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 29 Apr 2026 16:35:59 +0900</pubDate>
    </item>
    <item>
      <title>빅데이터 분석기사 준비</title>
      <link>https://minaz-rong.tistory.com/10</link>
      <description>&lt;h1 data-end=&quot;123&quot; data-start=&quot;91&quot; data-section-id=&quot;1411kp3&quot;&gt;  빅데이터분석기사 필기 완벽 정리 (시험일 4/4)&lt;/h1&gt;
&lt;h2 data-end=&quot;140&quot; data-start=&quot;125&quot; data-section-id=&quot;hlpix7&quot; data-ke-size=&quot;size26&quot;&gt;✅ 빅데이터분석기사란?&lt;/h2&gt;
&lt;p data-end=&quot;210&quot; data-start=&quot;142&quot; data-ke-size=&quot;size16&quot;&gt;빅데이터분석기사(빅분기)는&lt;br /&gt;데이터를 수집하고, 분석하고, 인사이트를 도출하는 능력을 평가하는 국가기술자격증이다.&lt;/p&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;212&quot; data-ke-size=&quot;size16&quot;&gt;  쉽게 말하면&lt;/p&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;212&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;데이터를 활용해서 의미 있는 결과를 만들어내는 능력&amp;rdquo;을 보는 시험&lt;/p&gt;
&lt;hr data-end=&quot;270&quot; data-start=&quot;267&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;282&quot; data-start=&quot;272&quot; data-section-id=&quot;1f31lw9&quot; data-ke-size=&quot;size26&quot;&gt;✅ 시험 구성&lt;/h2&gt;
&lt;h3 data-end=&quot;301&quot; data-start=&quot;284&quot; data-section-id=&quot;14xkexf&quot; data-ke-size=&quot;size23&quot;&gt;✔ 필기 과목 (4과목)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;360&quot; data-start=&quot;302&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;317&quot; data-start=&quot;302&quot; data-section-id=&quot;1t5eqj3&quot;&gt;빅데이터 분석 기획&lt;/li&gt;
&lt;li data-end=&quot;330&quot; data-start=&quot;318&quot; data-section-id=&quot;1pj2h81&quot;&gt;빅데이터 탐색&lt;/li&gt;
&lt;li data-end=&quot;344&quot; data-start=&quot;331&quot; data-section-id=&quot;1a511dk&quot;&gt;빅데이터 모델링&lt;/li&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;345&quot; data-section-id=&quot;1uvwasr&quot;&gt;빅데이터 결과 해석&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;362&quot; data-ke-size=&quot;size16&quot;&gt;  객관식 시험&lt;br /&gt;  과목당 과락 있음 (40점 미만 탈락)&lt;br /&gt;  평균 60점 이상 합격&lt;/p&gt;
&lt;hr data-end=&quot;421&quot; data-start=&quot;418&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;434&quot; data-start=&quot;423&quot; data-section-id=&quot;1ip0yds&quot; data-ke-size=&quot;size26&quot;&gt;  시험 일정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;461&quot; data-start=&quot;436&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;436&quot; data-section-id=&quot;593kj3&quot;&gt;&lt;b&gt;시험일: 4월 4일 (D-14)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;503&quot; data-start=&quot;463&quot; data-ke-size=&quot;size16&quot;&gt;  지금 시점 기준&lt;br /&gt;&lt;b&gt;&amp;ldquo;개념 정리보다 기출 반복이 훨씬 중요&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;508&quot; data-start=&quot;505&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;524&quot; data-start=&quot;510&quot; data-section-id=&quot;1vvw0d2&quot;&gt;  과목별 핵심 요약&lt;/h1&gt;
&lt;h2 data-end=&quot;543&quot; data-start=&quot;526&quot; data-section-id=&quot;1ig5rf1&quot; data-ke-size=&quot;size26&quot;&gt;1️⃣ 빅데이터 분석 기획&lt;/h2&gt;
&lt;h3 data-end=&quot;563&quot; data-start=&quot;545&quot; data-section-id=&quot;1cvm30r&quot; data-ke-size=&quot;size23&quot;&gt;✔ 빅데이터 특징 (3V)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;612&quot; data-start=&quot;564&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;580&quot; data-start=&quot;564&quot; data-section-id=&quot;1ouvfc4&quot;&gt;Volume (데이터 양)&lt;/li&gt;
&lt;li data-end=&quot;596&quot; data-start=&quot;581&quot; data-section-id=&quot;2b64zb&quot;&gt;Velocity (속도)&lt;/li&gt;
&lt;li data-end=&quot;612&quot; data-start=&quot;597&quot; data-section-id=&quot;1y1uaax&quot;&gt;Variety (다양성)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;624&quot; data-start=&quot;614&quot; data-ke-size=&quot;size16&quot;&gt;  추가 개념&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;656&quot; data-start=&quot;625&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;643&quot; data-start=&quot;625&quot; data-section-id=&quot;59sdz3&quot;&gt;Veracity (정확성)&lt;/li&gt;
&lt;li data-end=&quot;656&quot; data-start=&quot;644&quot; data-section-id=&quot;1qfk73u&quot;&gt;Value (가치)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;661&quot; data-start=&quot;658&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;679&quot; data-start=&quot;663&quot; data-section-id=&quot;12se5e7&quot; data-ke-size=&quot;size23&quot;&gt;✔ 분석 절차 (중요)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;730&quot; data-start=&quot;680&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;690&quot; data-start=&quot;680&quot; data-section-id=&quot;1xyxcdv&quot;&gt;문제 정의&lt;/li&gt;
&lt;li data-end=&quot;702&quot; data-start=&quot;691&quot; data-section-id=&quot;1r5wojo&quot;&gt;데이터 수집&lt;/li&gt;
&lt;li data-end=&quot;711&quot; data-start=&quot;703&quot; data-section-id=&quot;iwurfs&quot;&gt;전처리&lt;/li&gt;
&lt;li data-end=&quot;719&quot; data-start=&quot;712&quot; data-section-id=&quot;1aaa35y&quot;&gt;분석&lt;/li&gt;
&lt;li data-end=&quot;730&quot; data-start=&quot;720&quot; data-section-id=&quot;1tc9axn&quot;&gt;결과 해석&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;732&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;순서 문제 자주 출제됨&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;756&quot; data-start=&quot;753&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;772&quot; data-start=&quot;758&quot; data-section-id=&quot;1k6syj7&quot; data-ke-size=&quot;size26&quot;&gt;2️⃣ 빅데이터 탐색&lt;/h2&gt;
&lt;h3 data-end=&quot;795&quot; data-start=&quot;774&quot; data-section-id=&quot;lloumi&quot; data-ke-size=&quot;size23&quot;&gt;✔ 데이터 전처리 (시험 핵심)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;839&quot; data-start=&quot;796&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;817&quot; data-start=&quot;796&quot; data-section-id=&quot;1hclejb&quot;&gt;결측치 처리 (삭제 / 평균 대체)&lt;/li&gt;
&lt;li data-end=&quot;826&quot; data-start=&quot;818&quot; data-section-id=&quot;18jhnbl&quot;&gt;이상치 처리&lt;/li&gt;
&lt;li data-end=&quot;839&quot; data-start=&quot;827&quot; data-section-id=&quot;17h6w2g&quot;&gt;정규화 vs 표준화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;846&quot; data-start=&quot;841&quot; data-ke-size=&quot;size16&quot;&gt;  차이&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;881&quot; data-start=&quot;847&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;859&quot; data-start=&quot;847&quot; data-section-id=&quot;12maofk&quot;&gt;정규화: 0~1&lt;/li&gt;
&lt;li data-end=&quot;881&quot; data-start=&quot;860&quot; data-section-id=&quot;12y67xu&quot;&gt;표준화: 평균 0, 표준편차 1&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;886&quot; data-start=&quot;883&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;906&quot; data-start=&quot;888&quot; data-section-id=&quot;od1gfm&quot; data-ke-size=&quot;size23&quot;&gt;✔ EDA (탐색적 분석)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;940&quot; data-start=&quot;907&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;918&quot; data-start=&quot;907&quot; data-section-id=&quot;n4nx2x&quot;&gt;데이터 분포 확인&lt;/li&gt;
&lt;li data-end=&quot;931&quot; data-start=&quot;919&quot; data-section-id=&quot;xun9c9&quot;&gt;변수 간 관계 분석&lt;/li&gt;
&lt;li data-end=&quot;940&quot; data-start=&quot;932&quot; data-section-id=&quot;18jbybg&quot;&gt;이상치 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;945&quot; data-start=&quot;942&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;962&quot; data-start=&quot;947&quot; data-section-id=&quot;1hrlosa&quot; data-ke-size=&quot;size26&quot;&gt;3️⃣ 빅데이터 모델링&lt;/h2&gt;
&lt;h3 data-end=&quot;983&quot; data-start=&quot;964&quot; data-section-id=&quot;1qmmayb&quot; data-ke-size=&quot;size23&quot;&gt;✔ 지도학습 vs 비지도학습&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1016&quot; data-start=&quot;984&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;999&quot; data-start=&quot;984&quot; data-section-id=&quot;1dohyvy&quot;&gt;지도학습: 정답 있음&lt;/li&gt;
&lt;li data-end=&quot;1016&quot; data-start=&quot;1000&quot; data-section-id=&quot;10eyhhw&quot;&gt;비지도학습: 정답 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1021&quot; data-start=&quot;1018&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1034&quot; data-start=&quot;1023&quot; data-section-id=&quot;j5lyiv&quot; data-ke-size=&quot;size23&quot;&gt;✔ 핵심 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1063&quot; data-start=&quot;1035&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1049&quot; data-start=&quot;1035&quot; data-section-id=&quot;nw6is8&quot;&gt;회귀: 연속값 예측&lt;/li&gt;
&lt;li data-end=&quot;1063&quot; data-start=&quot;1050&quot; data-section-id=&quot;mrv9qn&quot;&gt;분류: 범주 예측&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1068&quot; data-start=&quot;1065&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1087&quot; data-start=&quot;1070&quot; data-section-id=&quot;18g1kz2&quot; data-ke-size=&quot;size23&quot;&gt;✔ 자주 나오는 알고리즘&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1118&quot; data-start=&quot;1088&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1095&quot; data-start=&quot;1088&quot; data-section-id=&quot;16rb277&quot;&gt;KNN&lt;/li&gt;
&lt;li data-end=&quot;1106&quot; data-start=&quot;1096&quot; data-section-id=&quot;6yxn1p&quot;&gt;의사결정트리&lt;/li&gt;
&lt;li data-end=&quot;1118&quot; data-start=&quot;1107&quot; data-section-id=&quot;1o25god&quot;&gt;로지스틱 회귀&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1123&quot; data-start=&quot;1120&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1141&quot; data-start=&quot;1125&quot; data-section-id=&quot;m46a1o&quot; data-ke-size=&quot;size23&quot;&gt;✔ 과적합 / 언더피팅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1178&quot; data-start=&quot;1142&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1162&quot; data-start=&quot;1142&quot; data-section-id=&quot;1t0obao&quot;&gt;과적합: 학습 데이터에만 맞음&lt;/li&gt;
&lt;li data-end=&quot;1178&quot; data-start=&quot;1163&quot; data-section-id=&quot;7qp4dr&quot;&gt;언더피팅: 너무 단순&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1190&quot; data-start=&quot;1180&quot; data-ke-size=&quot;size16&quot;&gt;  해결 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1217&quot; data-start=&quot;1191&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1201&quot; data-start=&quot;1191&quot; data-section-id=&quot;1hd2mkh&quot;&gt;데이터 증가&lt;/li&gt;
&lt;li data-end=&quot;1208&quot; data-start=&quot;1202&quot; data-section-id=&quot;1x7w6aw&quot;&gt;규제&lt;/li&gt;
&lt;li data-end=&quot;1217&quot; data-start=&quot;1209&quot; data-section-id=&quot;1mh49u5&quot;&gt;교차검증&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1222&quot; data-start=&quot;1219&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1244&quot; data-start=&quot;1224&quot; data-section-id=&quot;vuddxc&quot; data-ke-size=&quot;size26&quot;&gt;4️⃣ 결과 해석 (모델 평가)&lt;/h2&gt;
&lt;h3 data-end=&quot;1267&quot; data-start=&quot;1246&quot; data-section-id=&quot;166o6fn&quot; data-ke-size=&quot;size23&quot;&gt;✔ 분류 평가 지표 (⭐ 핵심)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1291&quot; data-start=&quot;1268&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1275&quot; data-start=&quot;1268&quot; data-section-id=&quot;1jkvzd8&quot;&gt;정확도&lt;/li&gt;
&lt;li data-end=&quot;1283&quot; data-start=&quot;1276&quot; data-section-id=&quot;1mjong9&quot;&gt;정밀도&lt;/li&gt;
&lt;li data-end=&quot;1291&quot; data-start=&quot;1284&quot; data-section-id=&quot;j59rc8&quot;&gt;재현율&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1298&quot; data-start=&quot;1293&quot; data-ke-size=&quot;size16&quot;&gt;  차이&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1336&quot; data-start=&quot;1299&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1317&quot; data-start=&quot;1299&quot; data-section-id=&quot;1vdjh52&quot;&gt;정밀도: 예측 중 맞은 것&lt;/li&gt;
&lt;li data-end=&quot;1336&quot; data-start=&quot;1318&quot; data-section-id=&quot;1vio38e&quot;&gt;재현율: 실제 중 맞춘 것&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1341&quot; data-start=&quot;1338&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1357&quot; data-start=&quot;1343&quot; data-section-id=&quot;1vyjdhp&quot; data-ke-size=&quot;size23&quot;&gt;✔ F1 Score&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1375&quot; data-start=&quot;1358&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1375&quot; data-start=&quot;1358&quot; data-section-id=&quot;16l7s4b&quot;&gt;정밀도 + 재현율 균형 지표&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1380&quot; data-start=&quot;1377&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1396&quot; data-start=&quot;1382&quot; data-section-id=&quot;4afqsr&quot; data-ke-size=&quot;size23&quot;&gt;✔ 회귀 평가 지표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1421&quot; data-start=&quot;1397&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1404&quot; data-start=&quot;1397&quot; data-section-id=&quot;16vlzyr&quot;&gt;MSE&lt;/li&gt;
&lt;li data-end=&quot;1413&quot; data-start=&quot;1405&quot; data-section-id=&quot;7h2pj5&quot;&gt;RMSE&lt;/li&gt;
&lt;li data-end=&quot;1421&quot; data-start=&quot;1414&quot; data-section-id=&quot;16v55a9&quot;&gt;MAE&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1436&quot; data-start=&quot;1423&quot; data-ke-size=&quot;size16&quot;&gt;  값이 작을수록 좋음&lt;/p&gt;
&lt;hr data-end=&quot;1441&quot; data-start=&quot;1438&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;1459&quot; data-start=&quot;1443&quot; data-section-id=&quot;928x2u&quot;&gt;  시험 대비 핵심 전략&lt;/h1&gt;
&lt;h3 data-end=&quot;1473&quot; data-start=&quot;1461&quot; data-section-id=&quot;1he2ua7&quot; data-ke-size=&quot;size23&quot;&gt;✔ 반드시 기억&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1542&quot; data-start=&quot;1474&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1491&quot; data-start=&quot;1474&quot; data-section-id=&quot;wvk8v5&quot;&gt;전처리 = 무조건 나온다&lt;/li&gt;
&lt;li data-end=&quot;1511&quot; data-start=&quot;1492&quot; data-section-id=&quot;1pr8azo&quot;&gt;평가 지표 = 무조건 나온다&lt;/li&gt;
&lt;li data-end=&quot;1529&quot; data-start=&quot;1512&quot; data-section-id=&quot;vim1a4&quot;&gt;회귀 vs 분류 = 필수&lt;/li&gt;
&lt;li data-end=&quot;1542&quot; data-start=&quot;1530&quot; data-section-id=&quot;qt756p&quot;&gt;과적합 = 단골&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1547&quot; data-start=&quot;1544&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1571&quot; data-start=&quot;1549&quot; data-section-id=&quot;hep8b0&quot; data-ke-size=&quot;size23&quot;&gt;✔ 공부 방법 (2주 남았을 때)&lt;/h3&gt;
&lt;p data-end=&quot;1583&quot; data-start=&quot;1572&quot; data-ke-size=&quot;size16&quot;&gt;  이게 진짜 중요&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1614&quot; data-start=&quot;1585&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1593&quot; data-start=&quot;1585&quot; data-section-id=&quot;sv0boe&quot;&gt;기출 3회독&lt;/li&gt;
&lt;li data-end=&quot;1604&quot; data-start=&quot;1594&quot; data-section-id=&quot;1nvkxox&quot;&gt;틀린 문제 반복&lt;/li&gt;
&lt;li data-end=&quot;1614&quot; data-start=&quot;1605&quot; data-section-id=&quot;1c76h7k&quot;&gt;오답노트 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1619&quot; data-start=&quot;1616&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1 data-end=&quot;1631&quot; data-start=&quot;1621&quot; data-section-id=&quot;1rqcpnv&quot;&gt;  한줄 요약&lt;/h1&gt;
&lt;p data-end=&quot;1665&quot; data-start=&quot;1633&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;&amp;ldquo;빅분기는 기출 반복으로 합격하는 시험이다.&amp;rdquo;&lt;/b&gt;&lt;/p&gt;</description>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/10</guid>
      <comments>https://minaz-rong.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 23 Mar 2026 16:24:44 +0900</pubDate>
    </item>
    <item>
      <title>  AI 가구 이미지 생성기 개발기: FastAPI와 Stability AI, S3 연동!</title>
      <link>https://minaz-rong.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 오늘은 Stability AI의 Stable Image Core 모델을 활용하여 가구 이미지를 생성하고, 이를 S3 버킷에 저장하며, 클라이언트가 이미지를 바로 다운로드할 수 있는 FastAPI 기반의 API를 개발한 과정을 공유하려고 합니다. 간단한 이미지 생성 API에서 시작하여 점차 기능을 확장하는 흥미로운 작업이었습니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  1단계: Stability AI 이미지 생성 기본 API 구축 (초기 코드)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 Stability AI의 text-to-image 기능을 활용하여 프롬프트를 입력하면 이미지를 생성해주는 기본 API를 만들었습니다. FastAPI를 사용하여 엔드포인트를 정의하고, requests 라이브러리로 Stability AI API에 요청을 보냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 코드 및 설명:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Python&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import os
import requests
from dotenv import load_dotenv
from fastapi import FastAPI, Query, HTTPException, Response
from io import BytesIO
from PIL import Image # (현재 코드에서는 직접 사용되지 않지만, 이미지 처리에 유용)

# .env 파일 로드 (API 키 등 민감 정보 관리)
load_dotenv()

# Stability AI API 키 가져오기 및 유효성 검사
STABILITY_KEY = os.getenv(&quot;STABILITY_KEY&quot;)
if not STABILITY_KEY:
    raise ValueError(&quot;STABILITY_KEY not found in .env file. Please set it.&quot;)

# FastAPI 애플리케이션 초기화
app = FastAPI(
    title=&quot;Furniture Image Generator with Stability AI&quot;,
    description=&quot;API to generate furniture images using Stability AI's Stable Image Core model.&quot;,
    version=&quot;1.0.0&quot;
)

# Stability AI에 이미지 생성 요청을 보내는 핵심 함수
def send_generation_request(host: str, params: dict) -&amp;gt; bytes:
    headers = {
        &quot;Accept&quot;: &quot;image/*&quot;, # 이미지 반환을 기대
        &quot;Authorization&quot;: f&quot;Bearer {STABILITY_KEY}&quot; # API 인증
    }
    files = {&quot;none&quot;: ''} # Stability AI API의 특정 요구사항
    print(f&quot;Sending REST request to {host} with params: {params}&quot;)
    try:
        response = requests.post(host, headers=headers, files=files, data=params)
        response.raise_for_status() # HTTP 오류 발생 시 예외 처리
    except requests.exceptions.RequestException as e:
        raise HTTPException(status_code=500, detail=f&quot;Failed to connect to Stability AI API: {e}&quot;)

    output_image = response.content # 생성된 이미지 바이트
    finish_reason = response.headers.get(&quot;finish-reason&quot;) # 생성 완료 이유 (컨텐츠 필터링 등)

    if finish_reason == 'CONTENT_FILTERED':
        raise HTTPException(status_code=400, detail=&quot;Generation failed due to content filter.&quot;)

    print(f&quot;Image generated with seed: {response.headers.get('seed')}&quot;)
    return output_image

# 가구 이미지 생성을 위한 FastAPI 엔드포인트
@app.post(&quot;/generate-furniture&quot;, summary=&quot;Generate a furniture image&quot;, response_description=&quot;The generated image&quot;)
async def generate_furniture(
    prompt: str = Query(..., description=&quot;The prompt for generating the furniture image.&quot;),
    negative_prompt: str = Query(&quot;&quot;, description=&quot;Optional: Negative prompt to guide the generation away from certain things.&quot;),
    aspect_ratio: str = Query(&quot;1:1&quot;, description=&quot;Aspect ratio of the generated image.&quot;,
                               enum=[&quot;21:9&quot;, &quot;16:9&quot;, &quot;3:2&quot;, &quot;5:4&quot;, &quot;1:1&quot;, &quot;4:5&quot;, &quot;2:3&quot;, &quot;9:16&quot;, &quot;9:21&quot;]),
    style_preset: str = Query(&quot;None&quot;, description=&quot;Optional: Style preset to apply to the generation.&quot;,
                               enum=[&quot;None&quot;, &quot;3d-model&quot;, &quot;analog-film&quot;, &quot;anime&quot;, &quot;cinematic&quot;, &quot;comic-book&quot;, &quot;digital-art&quot;, &quot;enhance&quot;, &quot;fantasy-art&quot;, &quot;isometric&quot;, &quot;line-art&quot;, &quot;low-poly&quot;, &quot;modeling-compound&quot;, &quot;neon-punk&quot;, &quot;origami&quot;, &quot;photographic&quot;, &quot;pixel-art&quot;, &quot;tile-texture&quot;]),
    seed: int = Query(0, description=&quot;Optional: Seed for reproducible generations. Use 0 for random.&quot;),
    output_format: str = Query(&quot;jpeg&quot;, description=&quot;Output format of the image.&quot;,
                                enum=[&quot;webp&quot;, &quot;jpeg&quot;, &quot;png&quot;])
):
    host = &quot;https://api.stability.ai/v2beta/stable-image/generate/core&quot;
    params = {
        &quot;prompt&quot;: prompt,
        &quot;negative_prompt&quot;: negative_prompt,
        &quot;aspect_ratio&quot;: aspect_ratio,
        &quot;seed&quot;: seed,
        &quot;output_format&quot;: output_format,
        &quot;mode&quot;: &quot;text-to-image&quot;
    }
    if style_preset != &quot;None&quot;:
        params[&quot;style_preset&quot;] = style_preset

    try:
        image_bytes = send_generation_request(host, params)
        media_type = f&quot;image/{output_format}&quot; # 반환할 이미지의 MIME 타입 결정
        return Response(content=image_bytes, media_type=media_type) # 이미지 바이트 직접 반환
    except HTTPException as e:
        raise e
    except Exception as e:
        raise HTTPException(status_code=500, detail=f&quot;An unexpected error occurred: {e}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.env 파일에서 STABILITY_KEY를 로드하여 API 키를 안전하게 관리합니다.&lt;/li&gt;
&lt;li&gt;send_generation_request 함수는 Stability AI API에 HTTP POST 요청을 보내고, 생성된 이미지 데이터를 바이트 형태로 반환합니다. 오류 처리 및 콘텐츠 필터링 감지 로직이 포함되어 있습니다.&lt;/li&gt;
&lt;li&gt;generate_furniture 엔드포인트는 prompt, negative_prompt, aspect_ratio, style_preset, seed, output_format 등의 파라미터를 받아 Stability AI에 전달하고, 생성된 이미지를 클라이언트에게 직접 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  2단계: 생성된 이미지 S3 저장 기능 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 이미지를 단순히 반환하는 것을 넘어, AWS S3 버킷에 자동으로 저장하도록 기능을 확장했습니다. 이를 위해 boto3 라이브러리를 추가하고 S3 클라이언트를 설정하는 로직을 통합했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 코드 및 설명 (1단계 코드에서 추가된 부분):&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Python&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import boto3 # S3 연동을 위한 라이브러리 추가
import uuid # 고유한 파일명 생성을 위한 라이브러리 추가

# ... (기존 코드 유지) ...

# AWS S3 관련 환경 변수 추가
AWS_ACCESS_KEY_ID = os.getenv(&quot;AWS_ACCESS_KEY_ID&quot;)
AWS_SECRET_ACCESS_KEY = os.getenv(&quot;AWS_SECRET_ACCESS_KEY&quot;)
S3_BUCKET_NAME = &quot;버킷이름&quot; # 직접 버킷 이름을 하드코딩하거나 .env에서 가져오기

# AWS S3 자격 증명 확인 및 S3 클라이언트 초기화
if not AWS_ACCESS_KEY_ID:
    raise ValueError(&quot;.env 파일에 AWS_ACCESS_KEY_ID가 없습니다. 설정해주세요.&quot;)
if not AWS_SECRET_ACCESS_KEY:
    raise ValueError(&quot;.env 파일에 AWS_SECRET_ACCESS_KEY가 없습니다. 설정해주세요.&quot;)
if not S3_BUCKET_NAME:
    raise ValueError(&quot;.env 파일에 S3_BUCKET_NAME이 없습니다. 설정해주세요.&quot;)

s3_client = boto3.client(
    &quot;s3&quot;,
    aws_access_key_id=AWS_ACCESS_KEY_ID,
    aws_secret_access_key=AWS_SECRET_ACCESS_KEY
)

# ... (기존 send_generation_request 함수 유지) ...

# S3에 이미지 업로드하는 비동기 함수 추가
async def upload_image_to_s3(image_bytes: bytes, file_name: str, content_type: str):
    try:
        s3_client.put_object(
            Bucket=S3_BUCKET_NAME,
            Key=file_name, # S3에 저장될 파일명 (경로 포함 가능)
            Body=image_bytes, # 업로드할 이미지 데이터
            ContentType=content_type # 이미지의 MIME 타입
        )
        print(f&quot;S3://{S3_BUCKET_NAME}/{file_name}에 이미지가 성공적으로 업로드되었습니다.&quot;)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f&quot;S3에 이미지 업로드에 실패했습니다: {e}&quot;)

# generate_furniture 엔드포인트 수정 (S3 업로드 로직 추가)
@app.post(&quot;/generate-furniture&quot;, summary=&quot;가구 이미지를 생성하고 S3에 저장 및 다운로드 제공&quot;, response_description=&quot;생성된 이미지 및 S3 URL&quot;)
async def generate_furniture(
    # ... (기존 파라미터 유지) ...
):
    # ... (기존 host 및 params 설정 유지) ...

    try:
        image_bytes = send_generation_request(host, params)
        media_type = f&quot;image/{output_format}&quot;
        
        folder_name = &quot;furniture&quot; # S3 버킷 내의 폴더명 (선택 사항)
        file_name = f&quot;{folder_name}/furniture_image_{uuid.uuid4()}.{output_format}&quot; # 고유한 파일명 생성

        await upload_image_to_s3(image_bytes, file_name, media_type) # S3에 이미지 업로드

        s3_url = f&quot;https://{S3_BUCKET_NAME}.s3.amazonaws.com/{file_name}&quot; # S3 이미지 URL 생성
        
        # ... (다음 단계에서 추가될 Content-Disposition 헤더 부분은 잠시 생략) ...

        return Response(content=image_bytes, media_type=media_type) # 이미지와 함께 S3 URL을 헤더로 반환하는 것은 아직 안함
    except HTTPException as e:
        raise e
    except Exception as e:
        raise HTTPException(status_code=500, detail=f&quot;예상치 못한 오류가 발생했습니다: {e}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;boto3 라이브러리를 사용하여 AWS S3와 상호작용합니다.&lt;/li&gt;
&lt;li&gt;.env 파일에서 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET_NAME을 가져와 S3 클라이언트를 설정합니다. (코드 예시에서는 S3_BUCKET_NAME이 직접 하드코딩되어 있으니 .env 파일에서 가져오도록 수정하는 것이 좋습니다.)&lt;/li&gt;
&lt;li&gt;upload_image_to_s3 비동기 함수는 생성된 이미지 바이트를 지정된 S3 버킷의 특정 경로에 업로드합니다. uuid를 사용하여 파일명 충돌을 방지합니다.&lt;/li&gt;
&lt;li&gt;generate_furniture 엔드포인트에서 이미지 생성 후 upload_image_to_s3 함수를 호출하여 이미지를 S3에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3단계: 다운로드 기능 및 S3 URL 정보 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 생성된 이미지를 바로 다운로드할 수 있도록 하고, S3에 저장된 이미지의 URL도 함께 제공하도록 Response 헤더를 수정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 코드 및 설명 (2단계 코드에서 추가된 부분):&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Python&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# ... (기존 코드 유지) ...

@app.post(&quot;/generate-furniture&quot;, summary=&quot;가구 이미지를 생성하고 S3에 저장 및 다운로드 제공&quot;, response_description=&quot;생성된 이미지 및 S3 URL&quot;)
async def generate_furniture(
    # ... (기존 파라미터 유지) ...
):
    # ... (기존 host 및 params 설정 유지) ...

    try:
        image_bytes = send_generation_request(host, params)
        media_type = f&quot;image/{output_format}&quot;
        
        folder_name = &quot;furniture&quot; 
        file_name = f&quot;{folder_name}/furniture_image_{uuid.uuid4()}.{output_format}&quot;
        
        await upload_image_to_s3(image_bytes, file_name, media_type)

        s3_url = f&quot;https://{S3_BUCKET_NAME}.s3.amazonaws.com/{file_name}&quot; # S3 이미지 URL 생성
        
        headers = {
            &quot;X-S3-URL&quot;: s3_url, # 커스텀 헤더로 S3 URL 정보 제공
            &quot;Content-Disposition&quot;: f&quot;attachment; filename=\&quot;{file_name.split('/')[-1]}\&quot;&quot; # 다운로드 파일명 제안
        }
        
        return Response(content=image_bytes, media_type=media_type, headers=headers) # 헤더와 함께 응답
    except HTTPException as e:
        raise e
    except Exception as e:
        raise HTTPException(status_code=500, detail=f&quot;예상치 못한 오류가 발생했습니다: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 헤더에 X-S3-URL을 추가하여 클라이언트에게 S3에 저장된 이미지의 직접적인 URL을 제공합니다.&lt;/li&gt;
&lt;li&gt;Content-Disposition: attachment; filename=&quot;...&quot; 헤더를 추가하여 웹 브라우저가 이 응답을 다운로드 가능한 파일로 인식하고, 제안된 파일명으로 저장하도록 유도합니다. file_name.split('/')[-1]을 사용하여 폴더명 없이 실제 파일명만 filename에 넣도록 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  .env 파일 설정 가이드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 실행하기 전에 프로젝트 루트 디렉터리에 .env 파일을 생성하고 다음 정보를 입력해야 합니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;코드 스니펫&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;STABILITY_KEY=&quot;YOUR_STABILITY_AI_API_KEY&quot;
AWS_ACCESS_KEY_ID=&quot;YOUR_AWS_ACCESS_KEY_ID&quot;
AWS_SECRET_ACCESS_KEY=&quot;YOUR_AWS_SECRET_ACCESS_KEY&quot;
S3_BUCKET_NAME=&quot;버킷이름&quot; # 여러분의 S3 버킷 이름&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;YOUR_STABILITY_AI_API_KEY: Stability AI에서 발급받은 API 키&lt;/li&gt;
&lt;li&gt;YOUR_AWS_ACCESS_KEY_ID: AWS Access Key ID&lt;/li&gt;
&lt;li&gt;YOUR_AWS_SECRET_ACCESS_KEY: AWS Secret Access Key&lt;/li&gt;
&lt;li&gt;S3_BUCKET_NAME: 사용할 S3 버킷의 이름 (코드에 하드코딩되어 있으나, .env에 정의하는 것이 더 권장됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 애플리케이션 실행 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;필수 라이브러리 설치:&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;uv pip install fastapi uvicorn python-dotenv requests pillow boto3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파이썬 파일 저장:&lt;/b&gt; 위의 모든 코드를 main.py와 같은 이름으로 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 실행:&lt;/b&gt; 터미널에서 다음 명령어를 실행합니다.(--reload 옵션은 코드 변경 시 자동으로 서버를 재시작해 개발에 편리합니다.)&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span&gt;Bash&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;uvicorn main:app --host 0.0.0.0 --port 3002&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  사용 예시 (API 요청)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI 서버가 실행되면, http://localhost:3002/docs로 접속하여 Swagger UI를 통해 API를 테스트할 수 있습니다. generate-furniture 엔드포인트에 POST 요청을 보낼 때 다음과 같은 JSON 본문을 사용할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;JSON&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
  &quot;prompt&quot;: &quot;Single wooden style chair, top-down view, 2D, empty surface, transparent background, clean lines, clear details, studio lighting, size 70x70mm&quot;,
  &quot;negative_prompt&quot;: &quot;realistic, blurry, distorted, old, dark, ugly, complex background, busy background, shadows, textures, multiple objects, cropped, cut off, low resolution, 3d render, photographic, realistic shading, harsh lines, text, watermark&quot;,
  &quot;aspect_ratio&quot;: &quot;1:1&quot;,
  &quot;style_preset&quot;: &quot;None&quot;,
  &quot;seed&quot;: 0,
  &quot;output_format&quot;: &quot;png&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고: size 1000x800mm과 같은 구체적인 픽셀 크기는 AI 모델이 직접적으로 제어하기 어렵고, aspect_ratio가 주된 비율 제어 수단입니다. 하지만 프롬프트에 포함하여 모델이 참고하도록 할 수는 있습니다.)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  이미지 생성 매개변수 설명&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&quot;prompt&quot;&lt;/b&gt;: &lt;b&gt;&quot;Single wooden style chair, top-down view, 2D, empty surface, transparent background, clean lines, clear details, studio lighting, size 70x70mm&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것은 **생성하고자 하는 이미지에 대한 긍정적인 지시(Positive Prompt)**입니다. AI에게 어떤 이미지를 만들지 구체적으로 설명하는 부분이죠.&lt;/li&gt;
&lt;li&gt;여기서는 &quot;단일 목재 스타일 의자&quot;, &quot;위에서 본 모습(탑다운 뷰)&quot;, &quot;2D&quot;, &quot;빈 표면&quot;, &quot;투명한 배경&quot;, &quot;깔끔한 선&quot;, &quot;선명한 디테일&quot;, &quot;스튜디오 조명&quot;, &quot;70x70mm 크기&quot;를 포함하라는 지시가 담겨 있습니다. 즉, 쇼핑몰 상세 페이지나 디자인 작업에 활용할 법한 깔끔한 목재 의자 이미지를 요청하고 있다고 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;negative_prompt&quot;&lt;/b&gt;: &lt;b&gt;&quot;realistic, blurry, distorted, old, dark, ugly, complex background, busy background, shadows, textures, multiple objects, cropped, cut off, low resolution, 3d render, photographic, realistic shading, harsh lines, text, watermark&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것은 **생성하지 않기를 바라는 이미지 특징에 대한 지시(Negative Prompt)**입니다. AI가 프롬프트 내용에 집중하도록 방해 요소를 제거하는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;&quot;사실적인(realistic)&quot;, &quot;흐릿한(blurry)&quot;, &quot;왜곡된(distorted)&quot;, &quot;오래된(old)&quot;, &quot;어두운(dark)&quot;, &quot;못생긴(ugly)&quot;, &quot;복잡한 배경(complex background)&quot;, &quot;바쁜 배경(busy background)&quot;, &quot;그림자(shadows)&quot;, &quot;텍스처(textures)&quot;, &quot;여러 객체(multiple objects)&quot;, &quot;잘린(cropped), &quot;잘려나간(cut off)&quot;, &quot;저해상도(low resolution)&quot;, &quot;3D 렌더(3d render)&quot;, &quot;사진 같은(photographic)&quot;, &quot;사실적인 음영(realistic shading)&quot;, &quot;거친 선(harsh lines)&quot;, &quot;텍스트(text)&quot;, &quot;워터마크(watermark)&quot; 등, 원치 않는 결과물 요소를 명확히 지정하여 깔끔하고 특정 스타일에 맞는 이미지가 나오도록 유도합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;aspect_ratio&quot;&lt;/b&gt;: &lt;b&gt;&quot;1:1&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성될 이미지의 가로세로 비율&lt;/b&gt;을 나타냅니다. &quot;1:1&quot;은 정사각형 이미지를 의미합니다. 다른 옵션으로는 &quot;16:9&quot;, &quot;4:3&quot; 등이 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;style_preset&quot;&lt;/b&gt;: &lt;b&gt;&quot;None&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 생성에 적용할 사전 설정 스타일&lt;/b&gt;을 의미합니다. &quot;None&quot;은 특별한 스타일 프리셋을 적용하지 않고, 프롬프트 지시에 따라 이미지를 생성하겠다는 뜻입니다. 일반적으로 &quot;photographic&quot;, &quot;anime&quot;, &quot;digital-art&quot; 등 다양한 스타일 옵션이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;seed&quot;&lt;/b&gt;: &lt;b&gt;0&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 생성의 재현성을 위한 시드(Seed) 값&lt;/b&gt;입니다. AI 모델이 이미지를 생성할 때 초기 노이즈 패턴을 결정하는 숫자입니다.&lt;/li&gt;
&lt;li&gt;0으로 설정하면 매번 다른 무작위 이미지를 생성합니다. 만약 특정 시드 값을 사용하면, 동일한 프롬프트로 이미지를 다시 생성했을 때 거의 동일한 결과물을 얻을 수 있습니다. 이는 특정 이미지를 기반으로 미세 조정하거나 여러 번 시도할 때 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;output_format&quot;&lt;/b&gt;: &lt;b&gt;&quot;png&quot;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성될 이미지의 파일 형식&lt;/b&gt;을 나타냅니다. 여기서는 &quot;png&quot;로 설정되어 있으며, 이는 투명한 배경을 지원하는 형식이기 때문에 &quot;transparent background&quot; 프롬프트와 잘 어울립니다. 다른 옵션으로는 &quot;jpeg&quot;, &quot;webp&quot; 등이 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; 가구 이미지 생성 시 발생하는 이슈 및 프롬프트 개선 방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stability AI를 통해 가구 이미지를 생성할 때 발생할 수 있는 일반적인 문제점과 이를 &lt;b&gt;프롬프트 수정을 통해 어떻게 해결했는지&lt;/b&gt; 상세히 설명해 드릴게요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; 발생했던 주요 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 가구 이미지를 생성했을 때 다음과 같은 원치 않는 결과물이 나왔습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;사람이 추가됨:&lt;/b&gt; 가구 이미지에 사람이 함께 생성되어 가구 자체에 집중하기 어려웠습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가구 위에 다른 물건이 올라가 있음:&lt;/b&gt; 의자 위에 책이나 다른 장식품 등이 놓여 있어 가구의 순수한 형태를 확인하기 어려웠습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여러가구가 같이 추출:&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가구 하나가 아닌 방의 이미지가 추출되어, 용도에 맞지 않는 사진이 계속해서 추출되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가구 여러 개가 같이 붙어있거나 이미지가 이상하게 추출됨:&lt;/b&gt; 단일 가구를 원했지만, 여러 개의 가구가 붙어있거나 형태가 왜곡되어 생성되는 경우가 있었습니다. 이는 제품 이미지로 활용하기에 부적합했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv3IJZ/btsPfrm9ELV/u0McM9bxtAkomB0vGKTY2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv3IJZ/btsPfrm9ELV/u0McM9bxtAkomB0vGKTY2k/img.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1536&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv3IJZ/btsPfrm9ELV/u0McM9bxtAkomB0vGKTY2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv3IJZ%2FbtsPfrm9ELV%2Fu0McM9bxtAkomB0vGKTY2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baZwYG/btsPeudOdp6/LkIkJ5TbxMIw5EqiK4R9pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baZwYG/btsPeudOdp6/LkIkJ5TbxMIw5EqiK4R9pK/img.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1536&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baZwYG/btsPeudOdp6/LkIkJ5TbxMIw5EqiK4R9pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaZwYG%2FbtsPeudOdp6%2FLkIkJ5TbxMIw5EqiK4R9pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w2RgY/btsPfq9AcBg/VJQ31P421Crt5ZvEa7kLC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w2RgY/btsPfq9AcBg/VJQ31P421Crt5ZvEa7kLC1/img.png&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;2016&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.9408%; margin-right: 10px; margin-top: 10px;&quot; data-widthpercent=&quot;36.36&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w2RgY/btsPfq9AcBg/VJQ31P421Crt5ZvEa7kLC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw2RgY%2FbtsPfq9AcBg%2FVJQ31P421Crt5ZvEa7kLC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1152&quot; height=&quot;2016&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct8AUg/btsPfMqKtfe/D4T1GzVgc7RmHa9MCc1q0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct8AUg/btsPfMqKtfe/D4T1GzVgc7RmHa9MCc1q0K/img.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1536&quot; data-is-animation=&quot;false&quot; style=&quot;width: 62.8964%; margin-top: 10px;&quot; data-widthpercent=&quot;63.64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct8AUg/btsPfMqKtfe/D4T1GzVgc7RmHa9MCc1q0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct8AUg%2FbtsPfMqKtfe%2FD4T1GzVgc7RmHa9MCc1q0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;잘못추출된 가구들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt; 프롬프트 개선을 통한 해결 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제들을 해결하기 위해 다음과 같이 **prompt (긍정 프롬프트)**와 **negative_prompt (부정 프롬프트)**를 상세하게 조정했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. prompt (긍정 프롬프트) 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 순수한 가구 이미지, 특정 뷰, 깨끗한 배경 등 원하는 요소를 명확히 지시하여 AI가 정확히 그릴 수 있도록 유도했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&quot;Single wooden style chair&quot;&lt;/b&gt;: &quot;단일(Single)&quot;이라는 단어를 추가하여 &lt;b&gt;여러 개의 가구가 아닌 하나만 생성&lt;/b&gt;되도록 명확히 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;top-down view, 2D&quot;&lt;/b&gt;: 가구의 특정 시점(위에서 내려다본 시점)과 그림 스타일(2D)을 지정하여 &lt;b&gt;예측 가능하고 균일한 결과물&lt;/b&gt;을 얻도록 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;empty surface&quot;&lt;/b&gt;: 가구 위에 &lt;b&gt;아무것도 없는 깨끗한 상태&lt;/b&gt;를 명시하여 불필요한 물건이 올라가지 않도록 지시했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;transparent background&quot;&lt;/b&gt;: 배경을 &lt;b&gt;투명하게&lt;/b&gt; 요청하여 가구 자체를 깔끔하게 분리하고, 추후 편집 용이성을 높였습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;clean lines, clear details, studio lighting&quot;&lt;/b&gt;: 이미지의 품질과 스타일을 높이기 위해 &lt;b&gt;깔끔한 선, 선명한 디테일, 전문적인 스튜디오 조명&lt;/b&gt;을 요구했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;size 70x70mm&quot;&lt;/b&gt;: 특정 크기를 명시하여 &lt;b&gt;제품 이미지 규격에 맞는 출력&lt;/b&gt;을 유도했습니다 (물론 AI가 물리적 크기를 정확히 맞추기보다는 그 비율과 느낌을 참고합니다).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개선된 prompt:&lt;/b&gt; &quot;Single wooden style chair, top-down view, 2D, empty surface, transparent background, clean lines, clear details, studio lighting, size 70x70mm&quot;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. negative_prompt (부정 프롬프트) 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 원치 않는 요소(사람, 추가 물건, 복잡한 배경, 왜곡 등)를 명시적으로 제거하여 AI가 생성 과정에서 해당 요소를 회피하도록 지시했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&quot;realistic, photographic, 3d render, realistic shading&quot;&lt;/b&gt;: 불필요한 현실감이나 사진 같은 느낌, 3D 렌더링 효과를 제외하여 &lt;b&gt;2D 스타일에 집중&lt;/b&gt;하도록 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;complex background, busy background, shadows, textures&quot;&lt;/b&gt;: 깔끔하고 투명한 배경을 위해 &lt;b&gt;복잡하거나 방해되는 배경, 불필요한 그림자나 텍스처&lt;/b&gt;를 배제했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;multiple objects, text, watermark&quot;&lt;/b&gt;: &lt;b&gt;여러 개의 객체(가구 또는 기타 물건)가 생성되는 것을 방지&lt;/b&gt;하고, 이미지에 &lt;b&gt;텍스트나 워터마크가 포함되지 않도록&lt;/b&gt; 했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;blurry, distorted, old, dark, ugly, cropped, cut off, low resolution, harsh lines&quot;&lt;/b&gt;: 이미지 품질을 저하시키는 &lt;b&gt;흐릿함, 왜곡, 저해상도, 거친 선&lt;/b&gt; 등을 사전에 차단했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개선된 negative_prompt:&lt;/b&gt; &quot;realistic, blurry, distorted, old, dark, ugly, complex background, busy background, shadows, textures, multiple objects, cropped, cut off, low resolution, 3d render, photographic, realistic shading, harsh lines, text, watermark&quot;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 AI 이미지 생성 API를 구축하고, AWS S3와의 연동, 그리고 사용자 친화적인 파일 다운로드 기능을 구현하는 전반적인 과정을 경험할 수 있었습니다. 특히, 프롬프트 엔지니어링을 통해 '아기자기한 일러스트'와 '탑 뷰', '배경 제거'와 같은 특정 요구사항을 이미지에 반영하는 과정이 흥미로웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 생성된 이미지를 웹 서비스에 바로 활용하거나, 더 복잡한 이미지 처리 기능을 추가하는 방향으로 확장해볼 예정입니다. 긴 글 읽어주셔서 감사합니다!&lt;/p&gt;</description>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/9</guid>
      <comments>https://minaz-rong.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 11 Jul 2025 16:22:17 +0900</pubDate>
    </item>
    <item>
      <title>공공데이터포털에서 아파트 평면도 이미지 불러오기: 파이썬으로 자동화하기</title>
      <link>https://minaz-rong.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;안녕하세요! 이번 포스팅에서는 공공데이터포털에서 제공하는 아파트 평면도 이미지를 파이썬을 이용해 자동으로 불러오고 PNG 파일로 변환하는 방법에 대해 알아보겠습니다. 부동산 관련 데이터를 분석하거나, 건축/인테리어 분야에서 평면도 이미지가 필요한 경우 유용하게 활용할 수 있는 팁입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. 공공데이터포털이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공공데이터포털은 정부 및 공공기관이 보유한 다양한 공공데이터를 한곳에 모아 국민에게 개방하는 서비스입니다. 이곳에서 제공하는 데이터는 누구나 자유롭게 이용할 수 있으며, API 형태로 제공되는 경우도 많아 개발자들이 프로그램으로 쉽게 접근할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. 왜 아파트 평면도 이미지를 불러와야 할까요?&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;데이터베이스 구축:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 자체적인 평면도 이미지 데이터베이스를 구축하여 다양한 서비스에 활용할 수 있습니다. 특히 전 가구를 배치하는 용도로 평면도를 이용하려 하였습니다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. 공공데이터포털에서 사용된 데이터 개요&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 포스팅에서 사용된 데이터는 공공데이터포털의 &lt;/span&gt;&lt;b&gt;&lt;span&gt;&quot;한국토지주택공사 주택 평면도 현황&quot;&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 데이터셋입니다. 이 데이터셋은 한국토지주택공사(LH)가 관리하는 주택(공공분양, 공공임대 등)의 평면도 정보를 JSON(이미지 파일 변환) 형태로 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;데이터셋 명칭:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 한국토지주택공사 주택 평면도 현황&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;제공 기관:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 한국토지주택공사&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;데이터 유형:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; JSON (내부에 Base64 인코딩된 이미지 포함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;설명:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 평면도는 집의 구조적 모습을 보기 위해 수평으로 나타내어진 그림입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;최신 업데이트:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 2024년 12월 18일 (수정일 기준)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;누적 다운로드:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 13953회 (주간 다운로드 3872회)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;이용 허락 범위:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 공공누리 제1유형 (출처표시)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 데이터셋은 연도별, 지역별로 구분되어 있으며, 각 폴더 내에 개별 평면도에 대한 JSON 파일이 포함되어 있습니다. 각 JSON 파일은 평면도 이미지를 Base64 문자열 형태로 담고 있어, 이를 파이썬 코드로 추출하고 변환하는 과정이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/neCwd/btsPd6CNDy7/qVHWyEcHVJ6KkiJVvYWXm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/neCwd/btsPd6CNDy7/qVHWyEcHVJ6KkiJVvYWXm0/img.png&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;407&quot; data-is-animation=&quot;false&quot; style=&quot;width: 68.2583%; margin-right: 10px;&quot; data-widthpercent=&quot;69.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/neCwd/btsPd6CNDy7/qVHWyEcHVJ6KkiJVvYWXm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FneCwd%2FbtsPd6CNDy7%2FqVHWyEcHVJ6KkiJVvYWXm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O05FG/btsPb2I2MKI/ZDrzk3nLyBrkB97rqETEv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O05FG/btsPb2I2MKI/ZDrzk3nLyBrkB97rqETEv0/img.png&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;910&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.5789%;&quot; data-widthpercent=&quot;30.94&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O05FG/btsPb2I2MKI/ZDrzk3nLyBrkB97rqETEv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO05FG%2FbtsPb2I2MKI%2FZDrzk3nLyBrkB97rqETEv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1218&quot; height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccew0q/btsPdA5tYgv/OSoIkO3Ut8iRIp78IaKzT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccew0q/btsPdA5tYgv/OSoIkO3Ut8iRIp78IaKzT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccew0q/btsPdA5tYgv/OSoIkO3Ut8iRIp78IaKzT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccew0q%2FbtsPdA5tYgv%2FOSoIkO3Ut8iRIp78IaKzT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1217&quot; height=&quot;240&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ooPqc/btsPd30mY2X/deRgKS1geaYRdtCffuJBOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ooPqc/btsPd30mY2X/deRgKS1geaYRdtCffuJBOK/img.png&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;332&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.8&quot; style=&quot;width: 49.222%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ooPqc/btsPd30mY2X/deRgKS1geaYRdtCffuJBOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FooPqc%2FbtsPd30mY2X%2FdeRgKS1geaYRdtCffuJBOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuRE0G/btsPcwJArqS/kNOKQX1EFQKH7g3t5LKiq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuRE0G/btsPcwJArqS/kNOKQX1EFQKH7g3t5LKiq0/img.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;357&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.6152%;&quot; data-widthpercent=&quot;50.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuRE0G/btsPcwJArqS/kNOKQX1EFQKH7g3t5LKiq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuRE0G%2FbtsPcwJArqS%2FkNOKQX1EFQKH7g3t5LKiq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. &lt;span&gt;파이썬 코드로 평면도 이미지 불러오기 및 변환하기&lt;/span&gt; &lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공공데이터포털의 API를 통해 평면도 데이터를 요청하면, 이미지 데이터가 Base64 문자열 형태로 JSON 응답에 포함되어 있는 경우가 많습니다. 이 Base64 문자열을 디코딩하여 실제 이미지 파일(PNG)로 저장하는 과정이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아래 코드는 이러한 과정을 자동화하는 예시입니다. 이 코드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;특정 JSON 파일에 Base64 인코딩된 이미지 데이터가 포함되어 있다고 가정&lt;/span&gt;&lt;/b&gt;&lt;span&gt;하고, 해당 데이터를 추출하여 PNG 파일로 저장합니다. 공공데이터포털에서 직접 API를 호출하여 JSON 응답을 받는 부분은 API마다 구조가 다르므로, 해당 API의 문서를 참고하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;requests&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이브러리로 호출하는 코드를 추가해야 합니다.&lt;br /&gt;json 파일의 형태에 따라 추출하는 방식이 달라 코드를 두가지 사용하여 png 파일로 변환하였습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  특정 JSON 구조 (예:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;59A.json&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태) 추출 코드 설명&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;단일 JSON 파일, 예를 들어 &lt;/span&gt;&lt;span&gt;59A.json&lt;/span&gt;&lt;span&gt;과 같은 파일에서 Base64 인코딩된 이미지 데이터를 추출하여 PNG로 변환하는 데 사용될 수 있습니다. 이 코드는 특히 JSON 파일 내에서 Base64 문자열이 어떤 키에 포함되어 있는지에 대한 여러 시나리오를 처리하도록 설계되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsQtWq/btsPc6cR3cF/8D7ove8XW8gqLkKDClq2Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsQtWq/btsPc6cR3cF/8D7ove8XW8gqLkKDClq2Q1/img.png&quot; data-widthpercent=&quot;70.34&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;688&quot; data-origin-width=&quot;1431&quot; style=&quot;width: 69.5201%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsQtWq/btsPc6cR3cF/8D7ove8XW8gqLkKDClq2Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsQtWq%2FbtsPc6cR3cF%2F8D7ove8XW8gqLkKDClq2Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1431&quot; height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFxmZq/btsPeylxSVt/FFWBWjp0F6NvEsMEH80N9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFxmZq/btsPeylxSVt/FFWBWjp0F6NvEsMEH80N9k/img.png&quot; data-widthpercent=&quot;29.66&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1058&quot; data-origin-width=&quot;928&quot; style=&quot;width: 29.3171%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFxmZq/btsPeylxSVt/FFWBWjp0F6NvEsMEH80N9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFxmZq%2FbtsPeylxSVt%2FFFWBWjp0F6NvEsMEH80N9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import json
import base64
import os
# re 모듈은 이 특정 코드 스니펫에는 사용되지 않았지만,
# 일반적으로 Base64 데이터 URI 접두사를 처리하는 데 유용합니다.
# 이 코드 스니펫에서는 'base64,' 문자열을 기준으로 직접 분할합니다.

# 59A.json 파일 경로 지정
# 현재 작업 디렉토리(os.getcwd()) 아래 'floorplan_json' 폴더에 '59A.json' 파일이 있다고 가정합니다.
json_path = os.path.join(os.getcwd(), 'floorplan_json', '59A.json')

# 저장할 폴더 및 파일 경로 지정
# 현재 작업 디렉토리 아래 'floorplan_image' 폴더에 '59A.png'로 저장할 경로를 정의합니다.
output_dir = os.path.join(os.getcwd(), 'floorplan_image') 
output_png = os.path.join(output_dir, '59A.png')

# 폴더가 없으면 생성
# 이미지를 저장할 'floorplan_image' 폴더가 존재하지 않으면 새로 생성합니다.
os.makedirs(output_dir, exist_ok=True)

# JSON 파일 열기
# 지정된 경로의 JSON 파일을 읽기 모드('r')로 열고, UTF-8 인코딩을 사용합니다.
# 'with' 문을 사용하여 파일이 자동으로 닫히도록 합니다.
with open(json_path, 'r', encoding='utf-8') as f:
    # json.load(f)를 사용하여 파일 객체 f에서 JSON 데이터를 로드하여 파이썬 딕셔너리 'data'에 저장합니다.
    data = json.load(f)

# base64 문자열 추출 (구조에 따라 경로 수정 필요)
# JSON 'data' 딕셔너리에서 Base64 문자열을 찾습니다.
if 'data' in data:
    # 만약 'data' 키가 최상위에 직접 존재하면 그 값을 Base64 문자열로 사용합니다.
    base64_str = data['data']
elif 'image' in data and '@attributes' in data['image'] and 'xlink:href' in data['image']['@attributes']:
    # 만약 'image' -&amp;gt; '@attributes' -&amp;gt; 'xlink:href' 경로에 Base64 문자열이 있다면 그 값을 사용합니다.
    # 이는 SVG 이미지 데이터에서 흔히 볼 수 있는 구조입니다.
    base64_str = data['image']['@attributes']['xlink:href']
else:
    # 위의 두 가지 일반적인 경로에서 Base64 문자열을 찾지 못했을 경우,
    # 딕셔너리의 모든 최상위 값들을 순회하며 'base64,' 문자열을 포함하는 첫 번째 문자열을 찾습니다.
    base64_str = None
    for value in data.values():
        if isinstance(value, str) and 'base64,' in value:
            base64_str = value
            break # 찾으면 루프를 종료합니다.
    if base64_str is None:
        # Base64 문자열을 끝까지 찾지 못하면 ValueError를 발생시킵니다.
        raise ValueError('Base64 문자열을 찾을 수 없습니다.')

# 'base64,' 이후 부분만 추출
# Base64 문자열에 'base64,' 접두사가 포함되어 있다면, 이 접두사를 제거하고 실제 Base64 데이터만 추출합니다.
if 'base64,' in base64_str:
    base64_data = base64_str.split('base64,')[1]
else:
    # 접두사가 없다면 Base64 문자열 전체를 사용합니다.
    base64_data = base64_str

# 디코딩 및 PNG 파일로 저장
# Base64 데이터를 바이너리 형식으로 디코딩합니다.
# 디코딩된 데이터를 'output_png' 경로에 바이너리 쓰기 모드('wb')로 PNG 파일로 저장합니다.
with open(output_png, 'wb') as img_file:
    img_file.write(base64.b64decode(base64_data))

# 이미지 저장 완료 메시지 출력
print(f'이미지 저장 완료: {output_png}')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;   특정 JSON 구조 (예: &lt;/span&gt;&lt;span&gt;84C.json&lt;/span&gt;&lt;span&gt; 형태) 추출 코드 설명&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공공데이터포털에서 제공하는 일부 JSON 파일은 이미지 데이터가 &lt;/span&gt;&lt;span&gt;{&quot;image&quot;: {&quot;mime&quot;: &quot;image/png&quot;, &quot;data&quot;: &quot;...&quot;}}&lt;/span&gt;&lt;span&gt;와 같은 특정 구조로 포함되어 있습니다. 특히 &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; 키의 값이 Base64 인코딩된 이미지 문자열인 경우, 이를 직접 추출하여 PNG 파일로 변환할 수 있습니다. 아래 코드는 이러한 형태의 JSON 파일을 처리하는 데 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJrKNZ/btsPcxPifX6/cgoPaxSRL7MoFIMDIZXPHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJrKNZ/btsPcxPifX6/cgoPaxSRL7MoFIMDIZXPHK/img.png&quot; style=&quot;width: 67.5833%; margin-right: 10px;&quot; data-widthpercent=&quot;68.38&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;686&quot; data-origin-width=&quot;1436&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJrKNZ/btsPcxPifX6/cgoPaxSRL7MoFIMDIZXPHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJrKNZ%2FbtsPcxPifX6%2FcgoPaxSRL7MoFIMDIZXPHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1436&quot; height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAk3Fg/btsPdaTArkY/0T1RU04KddSdqnEFYi0A7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAk3Fg/btsPdaTArkY/0T1RU04KddSdqnEFYi0A7K/img.png&quot; style=&quot;width: 31.2539%;&quot; data-widthpercent=&quot;31.62&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;751&quot; data-origin-width=&quot;727&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAk3Fg/btsPdaTArkY/0T1RU04KddSdqnEFYi0A7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAk3Fg%2FbtsPdaTArkY%2F0T1RU04KddSdqnEFYi0A7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;751&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import json # JSON 데이터 처리를 위한 모듈
import base64 # Base64 인코딩/디코딩을 위한 모듈
import re # 정규 표현식 사용을 위한 모듈
import os # 파일 시스템 경로 조작을 위한 모듈

# 84C.json 파일 경로 지정
# 현재 작업 디렉토리(os.getcwd()) 아래 'floorplan_json' 폴더에 '84C.json' 파일이 있다고 가정합니다.
json_path = os.path.join(os.getcwd(), 'floorplan_json', '84C.json')

# 저장할 폴더 및 파일 경로 지정
# 현재 작업 디렉토리 아래 'floorplan_image' 폴더에 '84C.png'로 저장할 경로를 정의합니다.
output_dir = os.path.join(os.getcwd(), 'floorplan_image') 
output_png = os.path.join(output_dir, '84C.png')

# 폴더가 없으면 생성
# 이미지를 저장할 'floorplan_image' 폴더가 존재하지 않으면 새로 생성합니다.
os.makedirs(output_dir, exist_ok=True)

# JSON 파일 열기 및 전체 텍스트 읽기
# 지정된 경로의 JSON 파일을 읽기 모드('r')로 열고, UTF-8 인코딩을 사용합니다.
# 'with' 문을 사용하여 파일이 자동으로 닫히도록 합니다.
with open(json_path, 'r', encoding='utf-8') as f:
    # f.read()를 사용하여 파일의 전체 내용을 문자열로 읽어와 'json_text' 변수에 저장합니다.
    # 이는 JSON 파싱 전에 특정 패턴을 검색하기 위함입니다.
    json_text = f.read()

# &quot;data&quot;:&quot; 다음부터 마지막 따옴표(&quot;) 또는 작은따옴표(') 전까지 추출 (줄바꿈 포함)
# re.search()를 사용하여 'json_text'에서 특정 정규 표현식 패턴을 검색합니다.
# r'&quot;data&quot;\s*:\s*[&quot;\']([^&quot;\']+)[&quot;\']' 패턴 설명:
#   - '&quot;data&quot;\s*:\s*': &quot;data&quot; 문자열, 콜론(:), 그리고 그 사이에 있을 수 있는 공백(\s*)을 찾습니다.
#   - '[&quot;\']': 큰따옴표(&quot;) 또는 작은따옴표(') 중 하나를 찾습니다.
#   - '([^&quot;\']+)': 이 부분이 핵심입니다. 큰따옴표나 작은따옴표를 제외한 모든 문자(.)가 하나 이상(+) 반복되는 것을 캡처 그룹으로 지정합니다.
#     이는 Base64 데이터가 포함된 부분입니다. 줄바꿈 문자도 포함될 수 있습니다.
#   - '[&quot;\']': 닫는 큰따옴표(&quot;) 또는 작은따옴표(')를 찾습니다.
match = re.search(r'&quot;data&quot;\s*:\s*[&quot;\']([^&quot;\']+)[&quot;\']', json_text)
if not match:
    # 만약 패턴을 찾지 못했다면, Base64 문자열을 찾을 수 없다는 ValueError를 발생시킵니다.
    raise ValueError('&quot;data&quot;:&quot; 다음에 base64 문자열을 찾을 수 없습니다.')

# match.group(1)은 정규 표현식의 첫 번째 캡처 그룹(즉, Base64 데이터 부분)을 반환합니다.
base64_data = match.group(1)

# base64 디코딩 및 이미지 파일로 저장
# base64.b64decode()를 사용하여 Base64 데이터를 바이너리 형식으로 디코딩합니다.
# 디코딩된 데이터를 'output_png' 경로에 바이너리 쓰기 모드('wb')로 PNG 파일로 저장합니다.
with open(output_png, 'wb') as img_file:
    img_file.write(base64.b64decode(base64_data))

# 이미지 저장 완료 메시지 출력
print(f'이미지 저장 완료: {output_png}')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 이용하여 아파트 평면도를 추출하였습니다.&lt;/p&gt;</description>
      <category>프로젝트/이집맞집</category>
      <author>minaz-rong</author>
      <guid isPermaLink="true">https://minaz-rong.tistory.com/8</guid>
      <comments>https://minaz-rong.tistory.com/8#entry8comment</comments>
      <pubDate>Thu, 10 Jul 2025 21:32:56 +0900</pubDate>
    </item>
  </channel>
</rss>