렌탈
스페이스락(SpaceRock) 오버레이·대형텐트 렌탈 전문기업의 입찰 인텔리전스 자동화 스킬. 나라장터 및 민간 채널에서 천막·오버레이·임시시설물 관련 입찰공고를 자동 수집·분석하고 HTML 보고서를 생성해 GitHub에 업로드하고 지정 이메일로 발송한다. 반드시 이 스킬을 사용해야 하는 상황: - "렌탈 RENT" 입력 시 (핵심 트리거 — 반드시 즉시 실행) - "렌탈 스킬", "입찰 찾아줘", "나라장터 검색", "공고 분석" 언급 시 - "오버레이", "대형텐트", "천막 렌탈", "임시시설물" + 입찰/공고 맥락 - "이번 주 공고", "신규 입찰 있어?", "스페이스락 입찰" 언급 시 - "보고서 보내줘", "대표님께 공유", "이메일 발송" + 입찰 맥락 - Q가 영업 준비, 마케팅 대응, 투두 리스트 작성을 요청하는 모든 상황
렌탈 스킬 — SpaceRock 입찰 인텔리전스 자동 실행 시스템
회사 고정 정보 (항상 참조)
회사명 : ㈜스페이스락® (SpaceRock)
홈페이지: www.spacerock.kr
핵심제품: 오버레이(Overlay), 대형텐트, MQ텐트, 몽골텐트, 임시관람석, 가설구조물
주요실적: 2018 평창동계올림픽, 아시안게임, 충주세계조정선수권, 국제무예마스터쉽, 국방산업대전
담당자 : 마케팅 영업 Q
GitHub : sjbaik0431/claude
토큰 : userPreferences의 GITHUB_TOKEN 사용
실행 전 필수 확인
스킬 시작 시 Q에게 아래를 간단히 확인한다 (이미 대화에서 언급됐으면 생략):
- 검색 기간: 오늘 기준 최근 몇 주? (기본값: 4주)
- 이메일 수신자: 보고서를 보낼 메일 주소 (미지정이면 생성 후 URL만 전달)
- G2B API 키: 보유 여부 (없으면 web_search 폴백 자동 적용)
확인 즉시 아래 PHASE를 순서대로 완전 자동 실행한다.
PHASE 1 — 입찰 공고 수집
키워드 목록 (순서대로 전부 검색)
KEYWORDS_1 = [
"오버레이", "overlay", "대형텐트", "MQ텐트", "몽골텐트",
"임시시설물", "임시관람석", "천막 임대", "가설구조물",
"야외행사시설", "캐노피 설치", "행사용 텐트"
]
KEYWORDS_2 = [
"행사 렌탈", "이벤트 시설", "박람회 시설",
"축제 시설", "관람석 설치", "무대 구조물", "임시 부스"
]
1-A: G2B API 수집 (API 키 보유 시)
import urllib.request, json, urllib.parse
G2B_URL = "https://apis.data.go.kr/1230000/BidPublicInfoService04/getBidPblancListInfoServc"
def fetch_g2b(keyword, service_key, days=28):
from datetime import datetime, timedelta
today = datetime.now()
start = (today - timedelta(days=days)).strftime("%Y%m%d") + "0000"
end = today.strftime("%Y%m%d") + "2359"
params = urllib.parse.urlencode({
"serviceKey": service_key,
"pageNo": 1, "numOfRows": 100,
"type": "json", "inqryDiv": 1,
"bidNtceNm": keyword,
"inqryBgnDt": start, "inqryEndDt": end
})
req = urllib.request.Request(f"{G2B_URL}?{params}")
try:
res = json.loads(urllib.request.urlopen(req, timeout=10).read())
items = res.get("response",{}).get("body",{}).get("items",[])
return items if isinstance(items, list) else [items] if items else []
except:
return []
각 키워드로 fetch_g2b() 호출 → 결과를 raw_bids 리스트에 합산 (중복 제거: bidNtceNo 기준)
1-B: web_search 폴백 (API 키 없거나 결과 부족 시)
web_search 도구로 아래 쿼리를 모두 실행한다:
나라장터 입찰공고 천막 오버레이 임시시설물 2026
나라장터 입찰공고 대형텐트 행사시설 렌탈 2026
나라장터 "임시관람석" OR "가설구조물" OR "MQ텐트" 입찰
나라장터 "박람회" OR "축제" OR "스포츠대회" 천막 설치 용역 2026
site:g2b.go.kr 오버레이 텐트 임시시설
스페이스락 경쟁 오버레이 렌탈 입찰 2026
검색 결과에서 아래 정보를 추출해 raw_bids에 추가:
- 공고명, 발주처, 공고일, 마감일, 추정금액, URL
1-C: 수집 결과 정규화
수집된 모든 항목을 아래 스키마로 정규화:
BID = {
"id" : "공고번호 또는 임시 ID",
"title" : "공고명",
"issuer" : "발주처명",
"issuer_type" : "중앙부처|지자체|공공기관|민간",
"region" : "지역",
"announce_dt" : "YYYY-MM-DD",
"deadline" : "YYYY-MM-DD",
"amount_won" : 숫자 (원, 없으면 0),
"amount_str" : "X억" 또는 "미상",
"contract" : "일반경쟁|제한경쟁|수의계약|미상",
"keywords" : ["매칭된 키워드 목록"],
"score" : 0, # PHASE 2에서 채움
"priority" : "HIGH", # PHASE 2에서 채움
"reason" : "", # PHASE 2에서 채움
"url" : "공고 URL"
}
PHASE 2 — 연관성 자동 채점 및 우선순위 분류
각 공고에 대해 아래 5개 기준으로 점수 합산:
def score_bid(bid):
score = 0
# ① 키워드 매칭 (최대 40점)
kw1 = ["오버레이","대형텐트","임시시설물","임시관람석","MQ텐트","몽골텐트","가설구조물","천막임대"]
kw2 = ["행사렌탈","박람회시설","관람석","무대구조물","임시부스","캐노피"]
title_low = bid["title"].replace(" ","")
hit1 = sum(1 for k in kw1 if k.replace(" ","") in title_low)
hit2 = sum(1 for k in kw2 if k.replace(" ","") in title_low)
score += min(hit1 * 12 + hit2 * 6, 40)
# ② 사업 규모 (최대 20점)
amt = bid["amount_won"]
if amt >= 1_000_000_000: score += 20
elif amt >= 500_000_000: score += 15
elif amt >= 100_000_000: score += 10
elif amt > 0: score += 5
else: score += 3 # 미상
# ③ 발주처 성격 (최대 15점)
itype = bid["issuer_type"]
if itype == "중앙부처": score += 15
elif itype == "공공기관": score += 12
elif itype == "지자체": score += 10
else: score += 7
# ④ 시기 적합성 (최대 15점)
from datetime import datetime
try:
dl = datetime.strptime(bid["deadline"], "%Y-%m-%d")
days_left = (dl - datetime.now()).days
if days_left <= 30: score += 15
elif days_left <= 60: score += 12
elif days_left <= 90: score += 8
else: score += 5
except: score += 5
# ⑤ 지역 접근성 (최대 10점)
region = bid["region"]
if "서울" in region or "경기" in region: score += 9
elif "인천" in region or "충청" in region: score += 10
elif "전국" in region: score += 10
else: score += 7
# 우선순위 결정
if score >= 80: priority = "HIGH"
elif score >= 60: priority = "MEDIUM"
elif score >= 40: priority = "LOW"
else: priority = "SKIP"
return min(score, 100), priority
for bid in raw_bids:
bid["score"], bid["priority"] = score_bid(bid)
# SKIP 제외 후 점수 내림차순 정렬
bids = sorted([b for b in raw_bids if b["priority"] != "SKIP"],
key=lambda x: x["score"], reverse=True)
PHASE 3 — HTML 보고서 생성
아래 Python 스크립트를 /home/claude/에 generate_rental_report.py로 저장 후 실행:
# generate_rental_report.py
import json
from datetime import datetime
TODAY = datetime.now().strftime("%Y.%m.%d")
TODAY_FILE = datetime.now().strftime("%Y%m%d")
FILENAME = f"rental_bid_report_{TODAY_FILE}.html"
def build_html(bids):
total = len(bids)
high = sum(1 for b in bids if b["priority"]=="HIGH")
medium = sum(1 for b in bids if b["priority"]=="MEDIUM")
total_amt = sum(b["amount_won"] for b in bids)
avg_score = round(sum(b["score"] for b in bids)/total,1) if total else 0
amt_str = f"{total_amt/100_000_000:.1f}억" if total_amt>0 else "집계중"
def badge(p):
color = {"HIGH":"#ef4444","MEDIUM":"#f59e0b","LOW":"#22c55e"}.get(p,"#94a3b8")
icon = {"HIGH":"🔴","MEDIUM":"🟡","LOW":"🟢"}.get(p,"⚪")
return f'<span style="background:{color}20;color:{color};padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;white-space:nowrap">{icon} {p}</span>'
def row(b):
bar_color = "#ef4444" if b["score"]>=80 else "#f59e0b" if b["score"]>=60 else "#22c55e"
return f"""
<tr>
<td style="padding:12px 14px">{badge(b["priority"])}</td>
<td style="padding:12px 14px">
<div style="font-weight:600;font-size:13px;margin-bottom:4px">{b["title"]}</div>
<div style="display:flex;flex-wrap:wrap;gap:3px">
{"".join(f'<span style="background:#f1f5f9;color:#475569;font-size:10px;padding:1px 6px;border-radius:3px">{k}</span>' for k in b.get("keywords",[])[:4])}
</div>
</td>
<td style="padding:12px 14px;font-size:12px">{b["issuer"]}<br>
<span style="background:#eff6ff;color:#1565c0;font-size:10px;padding:1px 7px;border-radius:3px;font-weight:600">{b["issuer_type"]}</span>
</td>
<td style="padding:12px 14px;font-size:12px">{b["region"]}</td>
<td style="padding:12px 14px;font-size:12px;font-weight:700;color:{"#ef4444" if b["priority"]=="HIGH" else "#b45309" if b["priority"]=="MEDIUM" else "#475569"}">{b["deadline"]}</td>
<td style="padding:12px 14px;font-weight:700;color:#0a3d8f">{b["amount_str"]}</td>
<td style="padding:12px 14px">
<div style="display:flex;align-items:center;gap:6px">
<div style="flex:1;height:6px;background:#e2e8f0;border-radius:3px;overflow:hidden">
<div style="width:{b["score"]}%;height:100%;background:{bar_color};border-radius:3px"></div>
</div>
<span style="font-weight:700;font-size:12px;min-width:26px">{b["score"]}</span>
</div>
</td>
</tr>"""
def todo_card(b):
if b["priority"] not in ("HIGH","MEDIUM"): return ""
border = "#ef4444" if b["priority"]=="HIGH" else "#f59e0b"
items = [
("공고 원문 다운로드 및 참가 자격 확인","D-0"),
("발주처 담당자 연락처 확인 및 사전 컨택","D+1"),
("유사 레퍼런스 선별 (평창·아시안게임 등)","D+2"),
("견적 초안 작성 및 장비 가용성 확인","D+3"),
("제안서 초안 완성 + 내부 가격 협의","D+5"),
("전자입찰서 제출 (나라장터 로그인)","D-3"),
]
rows = "".join(f"""
<div onclick="this.classList.toggle('done')" style="display:flex;align-items:flex-start;gap:10px;padding:8px 0;border-bottom:1px solid #f8faff;cursor:pointer">
<div style="width:18px;height:18px;border:2px solid #cbd5e1;border-radius:4px;flex-shrink:0;margin-top:1px"></div>
<div style="font-size:12px;line-height:1.5"><span style="color:#94a3b8;font-size:10px;font-weight:600">[{tag}]</span> {txt}</div>
</div>""" for txt,tag in items)
return f"""
<div style="background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,0.06);border-top:4px solid {border}">
<div style="font-size:13px;font-weight:800;margin-bottom:4px">{b["title"][:40]}{"..." if len(b["title"])>40 else ""}</div>
<div style="font-size:11px;color:#64748b;margin-bottom:12px">{b["issuer"]} | 마감: {b["deadline"]} | {b["amount_str"]}</div>
{rows}
</div>"""
bid_rows = "\n".join(row(b) for b in bids)
todo_cards = "\n".join(todo_card(b) for b in bids[:6])
# Chart.js data
score_dist = [sum(1 for b in bids if 40<=b["score"]<60),
sum(1 for b in bids if 60<=b["score"]<80),
sum(1 for b in bids if 80<=b["score"]<90),
sum(1 for b in bids if b["score"]>=90)]
issuer_cnt = {"중앙부처":0,"지자체":0,"공공기관":0,"민간":0}
for b in bids: issuer_cnt[b["issuer_type"]] = issuer_cnt.get(b["issuer_type"],0)+1
amt_dist = [sum(1 for b in bids if b["amount_won"]>=1e9),
sum(1 for b in bids if 5e8<=b["amount_won"]<1e9),
sum(1 for b in bids if 1e8<=b["amount_won"]<5e8),
sum(1 for b in bids if 0<b["amount_won"]<1e8)]
return f"""<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>스페이스락® 입찰 인텔리전스 {TODAY}</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<style>
*{{box-sizing:border-box;margin:0;padding:0}}
body{{font-family:'Noto Sans KR',sans-serif;background:#f4f7fc;color:#1a1a2e}}
.hd{{background:linear-gradient(135deg,#0a3d8f,#1565c0);color:#fff;padding:24px 36px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}}
.hd-l{{display:flex;align-items:center;gap:14px}}
.logo{{width:52px;height:52px;border-radius:10px;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:900}}
.ct{{max-width:1280px;margin:0 auto;padding:28px 20px}}
.kpi-g{{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:24px}}
.kpi{{background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.06);border-left:4px solid #0a3d8f;position:relative;overflow:hidden}}
.kpi.r{{border-left-color:#ef4444}}.kpi.y{{border-left-color:#f59e0b}}.kpi.p{{border-left-color:#8b5cf6}}
.kv{{font-size:32px;font-weight:900;color:#0a3d8f;margin:6px 0 3px}}
.kl{{font-size:11px;color:#64748b;font-weight:600;text-transform:uppercase}}
.ks{{font-size:11px;color:#94a3b8}}
.ic{{position:absolute;top:16px;right:16px;font-size:28px;opacity:.12}}
.cg{{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:24px}}
.cc{{background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.06)}}
.ct2{{font-size:13px;font-weight:700;margin-bottom:14px}}
.cw{{position:relative;height:190px}}
.sec{{margin-bottom:28px}}
.sh{{display:flex;align-items:center;gap:10px;margin-bottom:14px}}
.st{{font-size:17px;font-weight:800;color:#0a3d8f}}
.sb{{background:#0a3d8f;color:#fff;font-size:11px;font-weight:700;padding:2px 10px;border-radius:20px}}
.tw{{background:#fff;border-radius:14px;box-shadow:0 2px 10px rgba(0,0,0,.06);overflow:hidden}}
.tb{{padding:14px 18px;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;flex-wrap:wrap;align-items:center}}
.fb{{padding:6px 14px;border-radius:20px;border:2px solid #e2e8f0;background:#fff;cursor:pointer;font-size:12px;font-weight:600}}
.fb.a{{background:#0a3d8f;color:#fff;border-color:#0a3d8f}}
.sb2{{padding:6px 14px;border-radius:20px;border:2px solid #e2e8f0;font-size:13px;outline:none;margin-left:auto}}
table{{width:100%;border-collapse:collapse}}
thead{{background:#f8faff}}
th{{padding:11px 14px;text-align:left;font-size:10px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px}}
td{{border-top:1px solid #e2e8f0;vertical-align:middle}}
tr:hover td{{background:#f8faff}}
.tg{{display:grid;grid-template-columns:1fr 1fr;gap:16px}}
.ft{{background:#0a3d8f;color:rgba(255,255,255,.75);padding:20px 36px;margin-top:32px;display:flex;justify-content:space-between;flex-wrap:wrap;gap:10px}}
.done > div:last-child{{text-decoration:line-through;color:#94a3b8}}
.done > div:first-child{{background:#0a3d8f!important;border-color:#0a3d8f!important}}
.live{{display:inline-block;background:#ff6b35;color:#fff;font-size:11px;font-weight:700;padding:2px 10px;border-radius:20px;animation:pl 2s infinite}}
@keyframes pl{{0%,100%{{opacity:1}}50%{{opacity:.5}}}}
@media(max-width:900px){{.kpi-g,.cg,.tg{{grid-template-columns:1fr 1fr}}}}</style>
</head>
<body>
<div class="hd">
<div class="hd-l">
<div class="logo">SR</div>
<div>
<div style="font-size:18px;font-weight:800">㈜스페이스락®</div>
<div style="font-size:12px;opacity:.75">SpaceRock · 오버레이&대형텐트 전문기업</div>
</div>
</div>
<div style="text-align:right">
<div style="font-size:12px;opacity:.7">📅 {TODAY} 생성</div>
<div style="font-size:17px;font-weight:700;margin-top:3px">🎯 입찰 인텔리전스 보고서</div>
<span class="live">● LIVE</span>
</div>
</div>
<div class="ct">
<!-- KPI -->
<div class="kpi-g">
<div class="kpi"><div class="ic">📋</div><div class="kl">수집 공고</div><div class="kv">{total}</div><div class="ks">전체 분석 완료</div></div>
<div class="kpi r"><div class="ic">🔴</div><div class="kl">즉시 대응</div><div class="kv">{high}</div><div class="ks">HIGH 우선순위</div></div>
<div class="kpi y"><div class="ic">🟡</div><div class="kl">검토 대상</div><div class="kv">{medium}</div><div class="ks">MEDIUM 우선순위</div></div>
<div class="kpi p"><div class="ic">💰</div><div class="kl">총 추정 금액</div><div class="kv" style="font-size:24px">{amt_str}</div><div class="ks">평균 연관도 {avg_score}점</div></div>
</div>
<!-- CHARTS -->
<div class="cg">
<div class="cc"><div class="ct2">📊 연관성 점수 분포</div><div class="cw"><canvas id="c1"></canvas></div></div>
<div class="cc"><div class="ct2">🏢 발주처 유형별</div><div class="cw"><canvas id="c2"></canvas></div></div>
<div class="cc"><div class="ct2">💵 사업 규모 분포</div><div class="cw"><canvas id="c3"></canvas></div></div>
</div>
<!-- BID TABLE -->
<div class="sec">
<div class="sh"><div class="st">📋 입찰 공고 목록</div><span class="sb">{total}건</span></div>
<div class="tw">
<div class="tb">
<button class="fb a" onclick="ft('ALL',this)">전체</button>
<button class="fb" onclick="ft('HIGH',this)">🔴 HIGH ({high})</button>
<button class="fb" onclick="ft('MEDIUM',this)">🟡 MEDIUM ({medium})</button>
<button class="fb" onclick="ft('LOW',this)">🟢 LOW</button>
<input class="sb2" type="text" placeholder="🔍 검색..." oninput="fs(this.value)">
</div>
<div style="overflow-x:auto">
<table><thead><tr>
<th>우선순위</th><th>공고명</th><th>발주처</th><th>지역</th>
<th>마감일</th><th>추정금액</th><th>연관도</th>
</tr></thead>
<tbody id="tb">{bid_rows}</tbody></table>
</div>
</div>
</div>
<!-- TODO -->
<div class="sec">
<div class="sh"><div class="st">✅ To-Do 리스트</div></div>
<div class="tg">{todo_cards}</div>
</div>
<!-- MARKETING PLAN -->
<div class="sec">
<div class="sh"><div class="st">🚀 마케팅 대응 전략</div></div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px">
<div style="background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.06)">
<div style="font-size:13px;font-weight:800;color:#0a3d8f;margin-bottom:12px">📨 발주처별 맞춤 접근</div>
<div style="font-size:12px;line-height:1.8;color:#475569">• 지자체 → 기술 안전성 강조 (구조계산서·KC인증)<br>• 스포츠 대회 → 평창올림픽·아시안게임 레퍼런스<br>• 박람회/민간 → 디자인 커스터마이징 강조<br>• 국방/공공기관 → 납기 준수·24시간 AS 체계</div>
</div>
<div style="background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.06)">
<div style="font-size:13px;font-weight:800;color:#0a3d8f;margin-bottom:12px">📑 제안서 차별화 포인트</div>
<div style="font-size:12px;line-height:1.8;color:#475569">• 20년 실적 + 국제대회 수행 경험<br>• 원스탑 솔루션 (설계→설치→철거→원상복구)<br>• 친환경 오버레이 (재사용·폐기물 제로) 소구<br>• 현장 답사 후 1주일 내 견적 제시 약속</div>
</div>
<div style="background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.06)">
<div style="font-size:13px;font-weight:800;color:#0a3d8f;margin-bottom:12px">🔑 나라장터 API 키 발급</div>
<div style="font-size:12px;line-height:1.8;color:#475569">① data.go.kr 접속 후 회원가입<br>② "입찰공고정보서비스" 검색<br>③ 활용신청 (즉시~1일 승인)<br>④ 발급 키 → Q에게 전달 → 자동수집 시작!</div>
</div>
</div>
</div>
</div>
<div class="ft">
<div>
<div style="color:#fff;font-size:15px;font-weight:800">㈜스페이스락® | www.spacerock.kr</div>
<div style="margin-top:3px;font-size:12px">담당: 마케팅 영업 Q | 본 보고서는 렌탈 스킬에 의해 자동 생성됨</div>
</div>
<div style="text-align:right;font-size:12px">
연락처: 010-3404-5955 / [email protected]
</div>
</div>
<script>
const ALL_ROWS=[...document.querySelectorAll("#tb tr")];
function ft(p,btn){{
document.querySelectorAll(".fb").forEach(b=>b.classList.remove("a"));
btn.classList.add("a");
ALL_ROWS.forEach(r=>r.style.display=p==="ALL"||r.innerText.includes(p)?"":"none");
}}
function fs(q){{ALL_ROWS.forEach(r=>r.style.display=r.innerText.toLowerCase().includes(q.toLowerCase())?"":"none");}}
const opt={{responsive:true,maintainAspectRatio:false,plugins:{{legend:{{position:"bottom",labels:{{font:{{size:11}},boxWidth:12}}}}}}}};
new Chart(document.getElementById("c1"),{{type:"bar",data:{{labels:["40~59 LOW","60~79 MED","80~89 HIGH","90+ TOP"],datasets:[{{label:"공고수",data:{score_dist},backgroundColor:["#86efac","#fcd34d","#f87171","#dc2626"],borderRadius:6}}]}},options:{{...opt,plugins:{{legend:{{display:false}}}},scales:{{y:{{beginAtZero:true}}}}}}}});
new Chart(document.getElementById("c2"),{{type:"doughnut",data:{{labels:{list(issuer_cnt.keys())},datasets:[{{data:{list(issuer_cnt.values())},backgroundColor:["#3b82f6","#8b5cf6","#ef4444","#f59e0b"],borderWidth:2,borderColor:"#fff"}}]}},options:opt}});
new Chart(document.getElementById("c3"),{{type:"pie",data:{{labels:["10억+","5~10억","1~5억","1억미만"],datasets:[{{data:{amt_dist},backgroundColor:["#1d4ed8","#3b82f6","#93c5fd","#dbeafe"],borderWidth:2,borderColor:"#fff"}}]}},options:opt}});
</script>
</body></html>"""
if __name__ == "__main__":
import sys
bids_json = sys.argv[1] if len(sys.argv)>1 else "[]"
bids = json.loads(bids_json)
if not bids: # 샘플 데이터 (API/검색 결과 없을 때 데모)
bids = [
{"id":"DEMO-001","title":"2026 전국체육대회 임시시설물 설치공사","issuer":"경기도체육회","issuer_type":"공공기관","region":"경기도","announce_dt":"2026-04-01","deadline":"2026-04-20","amount_won":1800000000,"amount_str":"18억","contract":"일반경쟁","keywords":["임시시설물","대형텐트","관람석"],"score":92,"priority":"HIGH","reason":"임시시설물 대규모 공공기관","url":"#"},
{"id":"DEMO-002","title":"2026 세계수영선수권 오버레이 시설 렌탈","issuer":"문화체육관광부","issuer_type":"중앙부처","region":"광주","announce_dt":"2026-03-28","deadline":"2026-04-25","amount_won":2500000000,"amount_str":"25억","contract":"제한경쟁","keywords":["오버레이","임시관람석","MQ텐트"],"score":97,"priority":"HIGH","reason":"오버레이 직접 명시 국제대회","url":"#"},
{"id":"DEMO-003","title":"대전 국제박람회 야외 전시 천막 설치용역","issuer":"대전광역시","issuer_type":"지자체","region":"대전","announce_dt":"2026-04-02","deadline":"2026-05-15","amount_won":1200000000,"amount_str":"12억","contract":"일반경쟁","keywords":["천막","대형텐트","박람회시설"],"score":88,"priority":"HIGH","reason":"천막 설치 대형 박람회","url":"#"},
{"id":"DEMO-004","title":"2026 국방산업대전 임시시설물 설치","issuer":"방위사업청","issuer_type":"중앙부처","region":"경남 창원","announce_dt":"2026-04-01","deadline":"2026-05-10","amount_won":900000000,"amount_str":"9억","contract":"제한경쟁","keywords":["임시시설물","가설구조물","행사렌탈"],"score":85,"priority":"HIGH","reason":"임시시설물 중앙부처 대규모","url":"#"},
{"id":"DEMO-005","title":"부산 국제모터쇼 임시 천막 및 구조물 렌탈","issuer":"부산시설공단","issuer_type":"공공기관","region":"부산","announce_dt":"2026-04-03","deadline":"2026-05-30","amount_won":800000000,"amount_str":"8억","contract":"일반경쟁","keywords":["천막","임시구조물","행사렌탈"],"score":82,"priority":"HIGH","reason":"천막 렌탈 대형 국제행사","url":"#"},
{"id":"DEMO-006","title":"광주 김치세계화 박람회 야외 부스 설치","issuer":"광주광역시","issuer_type":"지자체","region":"광주","announce_dt":"2026-04-04","deadline":"2026-06-15","amount_won":400000000,"amount_str":"4억","contract":"일반경쟁","keywords":["박람회시설","전시천막","야외전시"],"score":74,"priority":"MEDIUM","reason":"박람회 야외시설 지자체","url":"#"},
{"id":"DEMO-007","title":"2026 충주세계무술선수권 임시구조물","issuer":"충주시","issuer_type":"지자체","region":"충북 충주","announce_dt":"2026-04-05","deadline":"2026-07-01","amount_won":600000000,"amount_str":"6억","contract":"일반경쟁","keywords":["임시구조물","관람석","스포츠"],"score":77,"priority":"MEDIUM","reason":"임시구조물 국제스포츠","url":"#"},
{"id":"DEMO-008","title":"서울 한강 페스티벌 야외 천막 렌탈","issuer":"서울시설공단","issuer_type":"공공기관","region":"서울","announce_dt":"2026-04-05","deadline":"2026-07-10","amount_won":500000000,"amount_str":"5억","contract":"일반경쟁","keywords":["천막","야외행사","행사렌탈"],"score":68,"priority":"MEDIUM","reason":"천막 렌탈 서울 대형","url":"#"},
{"id":"DEMO-009","title":"속초 설악문화제 행사 부스 렌탈","issuer":"속초시","issuer_type":"지자체","region":"강원 속초","announce_dt":"2026-04-06","deadline":"2026-08-15","amount_won":80000000,"amount_str":"8천만","contract":"수의계약","keywords":["행사렌탈","천막"],"score":52,"priority":"LOW","reason":"소규모 지역 행사","url":"#"},
]
html = build_html(bids)
out = f"/home/claude/{FILENAME}"
with open(out,"w",encoding="utf-8") as f:
f.write(html)
print(f"SAVED:{out}:{FILENAME}")
스크립트 실행:
python3 /home/claude/generate_rental_report.py '[]'
실제 공고 데이터가 있으면 JSON 배열로 전달, 없으면
'[]'로 실행 (샘플 데이터 사용)
출력에서 SAVED:/home/claude/{파일명}:{파일명} 형태로 경로 확인 후 PHASE 4로 이동.
PHASE 4 — GitHub 업로드
# upload_to_github.py
import urllib.request, base64, json, sys
def github_upload(filepath, filename, token, repo="sjbaik0431/claude"):
api_url = f"https://api.github.com/repos/{repo}/contents/{filename}"
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
with open(filepath, "rb") as f:
content_b64 = base64.b64encode(f.read()).decode()
# SHA 조회 (기존 파일 있으면 필요)
sha = ""
req = urllib.request.Request(api_url, headers=headers)
try:
res = urllib.request.urlopen(req)
sha = json.loads(res.read()).get("sha", "")
except: pass
payload = {"message": f"🏕️ 렌탈 입찰 보고서 {filename}", "content": content_b64}
if sha: payload["sha"] = sha
req2 = urllib.request.Request(api_url,
data=json.dumps(payload).encode(),
headers=headers, method="PUT")
urllib.request.urlopen(req2)
print(f"OK:https://sjbaik0431.github.io/claude/{filename}")
if __name__ == "__main__":
github_upload(sys.argv[1], sys.argv[2], sys.argv[3])
python3 /home/claude/upload_to_github.py \
/home/claude/{FILENAME} \
{FILENAME} \
{GITHUB_TOKEN}
출력 URL https://sjbaik0431.github.io/claude/{FILENAME} 을 기록한다.
PHASE 5 — 이메일 발송
Gmail MCP gmail_send_email 도구 사용. 발송 전 반드시 Q에게 수신자를 확인한다.
이메일 형식:
제목: [SpaceRock 입찰 인텔리전스] {TODAY} 주간 공고 분석 보고서
안녕하세요,
스페이스락 입찰 인텔리전스 시스템이 금주 관련 공고 {total}건을 분석했습니다.
━━━━━━━━━━━━━━━━━━━━━━━━━
📊 금일 분석 요약
• 수집 공고: {total}건
• 즉시 대응 필요(HIGH): {high}건
• 총 추정 사업 금액: {amt_str}
━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 긴급 대응 필요 TOP 3
{HIGH 공고 상위 3건: 공고명 / 발주처 / 마감일 / 금액}
📎 상세 보고서 (클릭하여 열기):
{GitHub Pages URL}
담당: 마케팅 영업 Q
㈜스페이스락® | www.spacerock.kr
PHASE 6 — 실행 결과 최종 보고
Q에게 아래 형식으로 간결하게 결과를 보고한다:
✅ 렌탈 스킬 실행 완료 — {TODAY}
📋 분석 결과
• 수집: {total}건 | 🔴 HIGH: {high}건 | 🟡 MEDIUM: {medium}건
• 총 추정 금액: {amt_str}
🔴 즉시 대응 TOP 3
1. {공고명} — {발주처} / D-{남은일수}일
2. {공고명} — {발주처} / D-{남은일수}일
3. {공고명} — {발주처} / D-{남은일수}일
🌐 보고서 URL
{GitHub Pages 공개 URL}
📧 이메일 발송: {완료/대기(수신자 미지정)}
오류 대응표
| 상황 | 자동 대응 |
|---|---|
| G2B API 응답 오류 | web_search 폴백 즉시 전환 |
| 검색 결과 0건 | 키워드 확장 후 재시도, 그래도 없으면 "이번 주 해당 공고 없음" 명시 |
| GitHub 업로드 실패 | /mnt/user-data/outputs/에 저장 후 수동 업로드 안내 |
| 이메일 수신자 미지정 | URL만 제공, 발송 생략 |
| 추정금액 파싱 불가 | amount_won=0, amount_str="미상" 처리 |
정기 실행 권장
| 주기 | 내용 |
|---|---|
| 매일 08:00 | 렌탈 RENT 입력 → 신규 공고 자동 수집 |
| 매주 월요일 | 주간 종합 보고서 생성 + 이메일 발송 |
| 매월 1일 | 월간 시장 트렌드 분석 |
연락처: 010-3404-5955 (백상진 행정사) / [email protected]