diff --git a/government/.gitignore b/government/.gitignore index b65837d..51f8efc 100644 --- a/government/.gitignore +++ b/government/.gitignore @@ -2,3 +2,5 @@ node_modules/ *.log # DB 접속 net 설정(지갑 경로/접속 디스크립터) — 환경별 재생성 oracle-net/ +# 생성 데이터(공고 추출 CSV 등) — 매일 갱신 +exports/ diff --git a/government/docs/business-plans.md b/government/docs/business-plans.md new file mode 100644 index 0000000..85df852 --- /dev/null +++ b/government/docs/business-plans.md @@ -0,0 +1,91 @@ +# 마스터 사업계획서 (3개 앱) + +한국 정부 창업지원(예비창업패키지·창업사업화)의 표준 **PSST 구조**(문제인식–실현가능성–성장전략–팀구성)로 작성한 마스터 초안. 공고별로 가볍게 변형해 재사용한다. + +> 표기 규칙: `[ ]`는 본인 정보·검증 수치를 채울 자리. 통계는 임의로 채우지 않았으니 제출 전 출처 확인 후 삽입할 것. + +> 공통 정체성(상단에 쓰면 강함): **"LLM으로 비정형 데이터(영상·가사·구술)를 구조화하는 역량"** 을 핵심으로, 각 앱은 그 역량의 도메인별 응용. + +--- + +## 1. Tasteby — 인플루언서 영상 기반 맛집 정보 검색 플랫폼 + +**한 줄 소개:** "유튜버·인플루언서가 다녀간 그 맛집, 어디였지?" — LLM이 영상 자막에서 식당 정보를 추출·구조화해 검색 가능하게 만드는 서비스. +**정부지원 트랙:** 관광테크 / 로컬데이터 / 데이터·AI바우처 / 소상공인 상생 + +### ① 문제인식 (Problem) +- 소비자는 "성시경 맛집", "쯔양 다녀온 곳"을 영상으로 보지만, **영상 속 정보는 검색·재방문이 불가능**하다. 영상은 휘발되고 위치·메뉴·영업정보는 흩어져 있다. +- 기존 맛집 앱(네이버·캐치테이블·다이닝코드)은 **광고·리뷰 기반**이라 신뢰도 논란이 있는 반면, **신뢰하는 인플루언서의 실제 방문**이라는 강력한 추천 신호는 어디에도 구조화돼 있지 않다. +- **목적:** 영상이라는 비정형 데이터를 신뢰 가능한 맛집 DB로 전환해, "내가 좋아하는 채널이 간 곳"을 검색·지도·예약으로 연결. + +### ② 실현가능성 (Solution) +- **핵심 기술:** ⓐ 영상 자막(STT/transcript) 수집 → ⓑ **LLM으로 식당명·위치·메뉴·맥락 추출 및 정규화** → ⓒ 지도·검색 인덱싱. +- **차별성:** 점포 광고가 아닌 **인플루언서 방문 사실** 기반 / 채널·인물별 필터 / 영상 타임스탬프 연결. +- **개발 현황(강점):** **MVP + 백오피스 보유** — 신규 영상 자동 백필 파이프라인이 이미 동작. (심사에서 "작동하는 제품"으로 어필) +- **리스크 대응:** 영상 저작권·플랫폼 약관 이슈는 ▲원문 인용 최소화·**출처(채널) 명시 및 트래픽 환원** ▲사실정보(상호·주소) 위주 추출 ▲창작자 제휴(어필리에이트) 모델로 상생 구조 설계. + +### ③ 성장전략 (Scale-up) +- **시장:** 국내 외식 O2O·맛집검색 [시장규모 수치 확인] / 1차 타깃 = 맛집 영상 소비층(2030). +- **수익모델:** ⓐ 식당 예약·웨이팅 제휴 수수료 ⓑ 인플루언서·채널 어필리에이트 ⓒ 지역/관광 데이터 B2B 판매 ⓓ 프리미엄(저장·알림). +- **추진일정:** 영상 커버리지 확대 → 지도/예약 연동 → 지역(관광) 특화 → B2B 데이터. +- **정부지원 연계:** 관광플러스테크·로컬 데이터·데이터바우처로 데이터 구축비 조달. + +### ④ 팀구성 (Team) +- **대표:** [본인] — LLM 파이프라인·풀스택 개발 역량(자체 서비스 다수 구축 이력). **이미 MVP를 혼자 구현**한 실행력. +- **필요 역량/계획:** 외식 도메인 자문, 제휴영업. [공동창업자/멘토 계획]. + +--- + +## 2. Lyricsy — K-pop·팝 차트 기반 음악 언어학습 앱 + +**한 줄 소개:** 빌보드·멜론 차트를 좋아하는 노래로 **영어·한국어를 배우는** 앱 — 뮤직비디오 + 가사(한/영·발음·뜻) + 표현 플래시카드. +**정부지원 트랙:** 콘텐츠진흥원(콘진원)·문체부 한류 / 에듀테크 / 글로벌 진출 + +### ① 문제인식 (Problem) +- 전 세계 K-pop 팬은 **"가사 뜻을 알고 싶다 → 한국어를 배우고 싶다"**는 강한 동기를 갖지만, Duolingo류는 음악·문화 맥락이 없고, 가사 사이트는 학습 기능이 없다. +- 반대로 한국 사용자에게는 **팝송으로 영어 학습** 수요가 크다. **양방향(영↔한)**으로 음악을 학습 콘텐츠로 만든 서비스는 드물다. +- **목적:** "좋아하는 노래"라는 최강의 학습 동기를 발음·뜻·표현 학습으로 전환. + +### ② 실현가능성 (Solution) +- **핵심 기능:** 차트 연동(개인화: 최애 아티스트/곡) → MV + **가사(원문·발음표기·뜻 병기)** → 표현을 **플래시카드(SRS)**로 반복 학습. +- **차별성:** 음악×양방향 언어학습×개인화. K-pop 글로벌 팬덤이라는 **거대 유입 동기** 보유. +- **개발 현황:** 차트·개인화·가사·플래시카드 **MVP 구현**. +- **리스크 대응(사업 핵심):** 가사 저작권은 신뢰도를 가르는 지점. ▲가사는 **LyricFind 등 합법 라이선스/한국음악저작권협회 신탁 경로**로 확보 ▲MV는 **공식 YouTube 임베드**(권리자 수익 보장) ▲차트 데이터는 약관 준수. → "법적 리스크를 인지하고 라이선스 기반으로 설계"를 명시해 감점이 아닌 **전문성**으로 전환. + +### ③ 성장전략 (Scale-up) +- **시장:** 글로벌 언어학습 [시장규모 수치 확인] × K-콘텐츠 팬덤 → **해외 TAM이 본질**. SOM = 영어권·동남아 K-pop 학습자. +- **수익모델:** 구독(에듀테크 표준) + 아티스트별 콘텐츠팩 + 제휴(엔터/교육). +- **추진일정:** 가사 라이선스 1차 확보 → 학습 루프 고도화 → 글로벌(영어권) 출시 → 아티스트 IP 제휴. +- **정부지원 연계:** 콘진원 음악·콘텐츠 스타트업 육성, 문체부 K-콘텐츠 글로벌(엑스포·해외진출). + +### ④ 팀구성 (Team) +- **대표:** [본인] — 풀스택·LLM·콘텐츠 파이프라인. 다수 앱 자체 구축 이력 → **빠른 실증 능력**. +- **필요 역량/계획:** 음악 라이선스/저작권 자문(법무), 언어교육 콘텐츠 자문. + +--- + +## 3. Parents Story — 온디바이스 AI 자서전(부모님 이야기) 앱 + +**한 줄 소개:** 부모님께 인생의 시기별 질문을 던져 **말·사진으로 기억을 모으고**, 온디바이스 AI(Gemma 4)가 **한 권의 책처럼** 엮어주는 앱 — 클라우드 없이, 완전 프라이버시. +**정부지원 트랙:** 고령친화산업 / 온디바이스 AI / 사회문제 해결·사회적가치 / AI헬스케어 + +### ① 문제인식 (Problem) +- **초고령사회 한국** — 부모 세대의 인생 이야기는 기록되지 않은 채 사라지고, 자녀·후손은 "그때 더 여쭤볼걸"이라는 후회를 남긴다. +- 기존 자서전 서비스는 **비싸고(대필), 사생활을 외부에 맡겨야** 한다. 어르신은 **타이핑이 어렵고**, 민감한 가족사를 **클라우드에 올리길 꺼린다**. +- **목적:** 누구나 **말로** 기억을 남기고, **데이터가 폰을 떠나지 않게** 하면서, AI가 그것을 읽기 좋은 책으로. + +### ② 실현가능성 (Solution) +- **핵심 기술:** 시기별 질문 → **음성 입력(어르신이 말로)** + 사진 → **온디바이스 Gemma 4**가 챕터/아웃라인으로 구조화·서술 → 책 형태 생성. +- **차별성(시의성):** 2026.4 출시된 **Gemma 4 E2B/E4B**는 ▲**네이티브 음성 입력**(어르신 접근성) ▲**비전·OCR**(옛 사진·편지 편입) ▲**256K 컨텍스트**(생애 전체를 일관된 책으로) ▲**~1GB·저전력**으로 보급형 폰 동작. → **"클라우드 없이 폰 안에서, 음성으로 자서전"**이 비로소 가능해진 것이 핵심 차별점. +- **프라이버시:** 전 과정 온디바이스 = 민감한 가족 이야기 유출 우려 원천 차단(곧 셀링포인트). +- **개발 현황:** 기획 단계 → **예비창업 단계에 적합**(아이디어 단계 허용 사업 타깃). 챕터 분할 생성으로 장문 일관성 확보. + +### ③ 성장전략 (Scale-up) +- **시장:** 시니어테크 + 가족/추모 시장 [수치 확인]. 1차 = 부모님 선물을 원하는 4050 자녀. +- **수익모델:** 앱 인앱구매 + **실물 인쇄책 제작 연계(객단가↑)** + 가족 공유/추모 프리미엄. +- **추진일정:** 음성·사진 수집 UX → 온디바이스 책 생성 품질화 → 인쇄 연계 → 다국어. +- **정부지원 연계:** 고령친화산업 육성(복지부·보건산업진흥원), 사회문제해결형 창업, AI 온디바이스. + +### ④ 팀구성 (Team) +- **대표:** [본인] — 온디바이스 LLM·앱 개발 역량, 다수 서비스 실증 이력. +- **필요 역량/계획:** 시니어 UX 자문, (인쇄 연계) 출판 파트너. diff --git a/government/scripts/eligible.js b/government/scripts/eligible.js new file mode 100644 index 0000000..0e4788e --- /dev/null +++ b/government/scripts/eligible.js @@ -0,0 +1,37 @@ +// 예비창업자가 지원 가능(자격에 '예비창업' 명시)하고 현재 열린 공고 전체를 마감일 순으로. +// 실행: LD_LIBRARY_PATH=$ORACLE_IC_LIB_DIR node scripts/eligible.js +import { withConnection, closePool, oracledb } from '../src/db.js'; + +const SQL = ` + SELECT * FROM ( + SELECT title, category, agency, source_code, detail_url, apply_end, + CASE WHEN apply_end IS NULL THEN 9999 ELSE (apply_end - TRUNC(SYSDATE)) END AS dleft, + ROW_NUMBER() OVER (PARTITION BY title ORDER BY CASE source_code WHEN 'kstartup' THEN 1 WHEN 'bizinfo' THEN 2 ELSE 3 END) rn + FROM gov_opportunity + WHERE (apply_end IS NULL OR apply_end >= TRUNC(SYSDATE)) + AND ( + target LIKE '%' || :k1 || '%' + OR target LIKE '%' || :k2 || '%' + OR DBMS_LOB.INSTR(body_text, :k1) > 0 + OR DBMS_LOB.INSTR(body_text, :k2) > 0 + ) + ) + WHERE rn = 1 + ORDER BY dleft ASC + FETCH FIRST 80 ROWS ONLY`; + +await withConnection(async (conn) => { + const r = await conn.execute( + SQL, + { k1: '예비창업', k2: '예비 창업' }, + { outFormat: oracledb.OUT_FORMAT_OBJECT } + ); + console.log(`현재 신청가능(예비창업자 자격) 공고: ${r.rows.length}건\n`); + for (const x of r.rows) { + const tag = x.DLEFT === 9999 ? '[상시]' : `[D-${x.DLEFT}]`; + console.log( + `${tag}\t${x.SOURCE_CODE}\t${(x.CATEGORY || '-').slice(0, 8)}\t${x.TITLE.slice(0, 52)}\t${x.DETAIL_URL}` + ); + } +}); +await closePool(); diff --git a/government/scripts/export_eligible_csv.js b/government/scripts/export_eligible_csv.js new file mode 100644 index 0000000..f89b0e4 --- /dev/null +++ b/government/scripts/export_eligible_csv.js @@ -0,0 +1,68 @@ +// 예비창업자 자격 + 현재 열린 공고 전체를 CSV 로 내보낸다(신청 추적용). +// 실행: LD_LIBRARY_PATH=$ORACLE_IC_LIB_DIR node scripts/export_eligible_csv.js +import { writeFile, mkdir } from 'node:fs/promises'; +import { withConnection, closePool, oracledb } from '../src/db.js'; + +const SQL = ` + SELECT * FROM ( + SELECT title, category, agency, source_code, detail_url, apply_start, apply_end, + CASE WHEN apply_end IS NULL THEN 9999 ELSE (apply_end - TRUNC(SYSDATE)) END AS dleft, + ROW_NUMBER() OVER (PARTITION BY title ORDER BY CASE source_code WHEN 'kstartup' THEN 1 WHEN 'bizinfo' THEN 2 ELSE 3 END) rn + FROM gov_opportunity + WHERE (apply_end IS NULL OR apply_end >= TRUNC(SYSDATE)) + AND ( + target LIKE '%' || :k1 || '%' OR target LIKE '%' || :k2 || '%' + OR DBMS_LOB.INSTR(body_text, :k1) > 0 OR DBMS_LOB.INSTR(body_text, :k2) > 0 + ) + ) + WHERE rn = 1 + ORDER BY dleft ASC, source_code`; + +function csvCell(v) { + if (v == null) return ''; + const s = String(v).replace(/"/g, '""').replace(/\r?\n/g, ' '); + return `"${s}"`; +} +function ymd(d) { + return d ? new Date(d).toISOString().slice(0, 10) : ''; +} +function region(title) { + const m = /^\[([^\]]{1,8})\]/.exec(title); + return m ? m[1] : ''; +} + +const rows = await withConnection(async (conn) => { + const r = await conn.execute( + SQL, + { k1: '예비창업', k2: '예비 창업' }, + { outFormat: oracledb.OUT_FORMAT_OBJECT } + ); + return r.rows; +}); +await closePool(); + +const header = ['신청완료', 'D-day', '마감일', '지역', '분야', '주관기관', '소스', '제목', '링크']; +const lines = [header.map(csvCell).join(',')]; +for (const x of rows) { + lines.push( + [ + '', + x.DLEFT === 9999 ? '상시' : `D-${x.DLEFT}`, + x.DLEFT === 9999 ? '상시/예산소진' : ymd(x.APPLY_END), + region(x.TITLE), + x.CATEGORY || '', + x.AGENCY || '', + x.SOURCE_CODE, + x.TITLE, + x.DETAIL_URL || '', + ] + .map(csvCell) + .join(',') + ); +} + +await mkdir(new URL('../exports/', import.meta.url), { recursive: true }); +const out = new URL('../exports/eligible_opportunities.csv', import.meta.url); +// 엑셀 한글 깨짐 방지 BOM +await writeFile(out, '' + lines.join('\n'), 'utf8'); +console.log(`내보냄: ${rows.length}건 → government/exports/eligible_opportunities.csv`); diff --git a/government/scripts/match.js b/government/scripts/match.js new file mode 100644 index 0000000..ef1ba86 --- /dev/null +++ b/government/scripts/match.js @@ -0,0 +1,70 @@ +// 앱 후보별로 매칭되는 정부지원사업을 gov_opportunity 에서 조회한다. +// 실행: LD_LIBRARY_PATH=$ORACLE_IC_LIB_DIR node scripts/match.js +import { withConnection, closePool, oracledb } from '../src/db.js'; + +// 앱별 주제 키워드(제목/분야/대상/주관기관 대상으로 LIKE) +const APPS = { + Tasteby: ['외식', '맛집', '음식', '푸드', '소상공인', '관광', '로컬', '지역특화', '상권', '데이터바우처', 'AI바우처', '빅데이터'], + Lyricsy: ['콘텐츠', '한류', '케이팝', 'K-팝', '음악', '뮤직', '어학', '한국어', '에듀테크', '웹툰', '문화산업', '글로벌진출'], + ParentsStory: ['시니어', '고령', '노인', '실버', '돌봄', '복지', '사회적가치', '사회문제', '헬스케어', '웰니스', '온디바이스', '기록'], +}; + +async function queryApp(conn, keywords) { + // 키워드 OR 조건 (title/category/target/agency) + const binds = {}; + const ors = keywords.map((kw, i) => { + binds[`k${i}`] = `%${kw}%`; + return `(title LIKE :k${i} OR category LIKE :k${i} OR target LIKE :k${i} OR agency LIKE :k${i})`; + }); + const sql = ` + SELECT * FROM ( + SELECT title, category, agency, source_code, detail_url, + apply_end, + CASE WHEN apply_end IS NULL OR apply_end >= TRUNC(SYSDATE) THEN 1 ELSE 0 END AS is_open, + CASE WHEN apply_end IS NULL THEN 9999 ELSE (apply_end - TRUNC(SYSDATE)) END AS dleft, + ROW_NUMBER() OVER (PARTITION BY title ORDER BY CASE source_code WHEN 'kstartup' THEN 1 WHEN 'bizinfo' THEN 2 ELSE 3 END) rn + FROM gov_opportunity + WHERE ${ors.join(' OR ')} + ) + WHERE rn = 1 + ORDER BY is_open DESC, dleft ASC + FETCH FIRST 14 ROWS ONLY`; + const r = await conn.execute(sql, binds, { outFormat: oracledb.OUT_FORMAT_OBJECT }); + return r.rows; +} + +async function countOpen(conn, keywords) { + const binds = {}; + const ors = keywords.map((kw, i) => { + binds[`k${i}`] = `%${kw}%`; + return `(title LIKE :k${i} OR category LIKE :k${i} OR target LIKE :k${i} OR agency LIKE :k${i})`; + }); + const r = await conn.execute( + `SELECT COUNT(DISTINCT title) total, + COUNT(DISTINCT CASE WHEN apply_end IS NULL OR apply_end >= TRUNC(SYSDATE) THEN title END) open_now + FROM gov_opportunity WHERE ${ors.join(' OR ')}`, + binds, + { outFormat: oracledb.OUT_FORMAT_OBJECT } + ); + return r.rows[0]; +} + +function fmtDate(d) { + if (!d) return '상시/별도'; + return new Date(d).toISOString().slice(0, 10); +} + +await withConnection(async (conn) => { + for (const [app, keywords] of Object.entries(APPS)) { + const cnt = await countOpen(conn, keywords); + console.log(`\n##### ${app} — 주제 매칭 총 ${cnt.TOTAL}건 (현재 신청가능 ${cnt.OPEN_NOW}건) #####`); + const rows = await queryApp(conn, keywords); + for (const r of rows) { + const tag = r.IS_OPEN === 1 ? (r.DLEFT === 9999 ? '[상시]' : `[D-${r.DLEFT}]`) : '[마감]'; + console.log( + `${tag}\t${r.SOURCE_CODE}\t${r.CATEGORY || '-'}\t${(r.AGENCY || '-').slice(0, 16)}\t${r.TITLE.slice(0, 50)}\t${r.DETAIL_URL || '-'}` + ); + } + } +}); +await closePool();