""" 나라장터(G2B) 소프트웨어 개발 용역 크롤링 + GUARDiA 적용성 분석 - 개방망: data.go.kr 나라장터 API 호출 가능 - 폐쇄망: 캐시/샘플 데이터로 동작 """ import os, json, httpx, asyncio from datetime import datetime, timedelta from fastapi import APIRouter, HTTPException, Query, BackgroundTasks from pydantic import BaseModel from typing import Optional, List router = APIRouter(prefix="/api/g2b-opportunity", tags=["나라장터 사업 기회"]) _OPEN = os.environ.get("GUARDIA_NETWORK_MODE") == "open" # 나라장터 공공데이터 포털 API 설정 (개방망) G2B_API_BASE = "https://apis.data.go.kr/1230000/ScsbidInfoService" G2B_API_KEY = os.environ.get("G2B_API_KEY", "") # 공공데이터포털 인증키 # IT 프로젝트 분류 → GUARDiA 적용성 매핑 GUARDIA_CATEGORY_MAP = { "IT운영유지보수": { "score": 98, "modules": ["ITSM", "SR관리", "배포자동화", "CMDB", "SLA관리"], "reason": "IT 인프라 운영·유지보수 업무 전반이 GUARDiA 핵심 대상", "proposal": "GUARDiA ITSM으로 SR 자동화·AI분류·SLA관리·배포자동화 전체 커버", }, "정보보안": { "score": 92, "modules": ["AI-SOC", "CSAP", "보안감사", "취약점관리", "Zero Trust"], "reason": "공공기관 보안 감사·CSAP 준수 자동화가 GUARDiA 핵심 강점", "proposal": "GUARDiA AI-SOC+CSAP자동점검+취약점CVE 대응 패키지 제안", }, "클라우드인프라": { "score": 87, "modules": ["서버관리", "CMDB", "용량예측", "비용최적화", "디지털트윈"], "reason": "클라우드 리소스 모니터링·비용 관리·서버 CMDB 자동발견 적용 가능", "proposal": "GUARDiA CMDB 자동발견+FinOps+예측 용량 계획 패키지", }, "AI데이터분석": { "score": 81, "modules": ["AI플랫폼", "RAG", "지식그래프", "이상탐지", "예측분석"], "reason": "온프레미스 AI 플랫폼(Ollama)·RAG 지식베이스·이상탐지 모듈 활용", "proposal": "GUARDiA AI Brain(Ollama) + 지식그래프 + 이상탐지 패키지 제안", }, "전자정부시스템": { "score": 75, "modules": ["민원포털", "전자서명", "나라장터연동", "GPKI"], "reason": "공공기관 시민 포털·전자서명·나라장터 G2B 연동 기능 보유", "proposal": "GUARDiA 시민포털+GPKI전자서명+나라장터 API 연동 패키지", }, "네트워크장비": { "score": 85, "modules": ["네트워크관리", "SNMP", "장애감지", "자동복구"], "reason": "네트워크 장비 SNMP 모니터링·자동 장애복구·에이전트리스 운영", "proposal": "GUARDiA 네트워크관리+에이전트리스SSH+자동복구 패키지", }, "소프트웨어개발": { "score": 65, "modules": ["CI/CD", "코드리뷰", "배포자동화", "품질관리"], "reason": "DevOps 파이프라인·배포 자동화·SR 기반 품질 추적 적용", "proposal": "GUARDiA CI/CD자동화+SR연계 코드리뷰+배포추적 패키지", }, "시스템통합(SI)": { "score": 72, "modules": ["ITSM", "통합모니터링", "API연동", "멀티테넌트"], "reason": "다수 기관·시스템 통합 프로젝트에 멀티테넌트 GUARDiA 적합", "proposal": "GUARDiA 멀티테넌트+크로스시스템동기화+통합모니터링 패키지", }, } # 키워드 기반 자동 분류 KEYWORD_CATEGORY = { "운영": "IT운영유지보수", "유지보수": "IT운영유지보수", "유지관리": "IT운영유지보수", "보안": "정보보안", "취약점": "정보보안", "침해": "정보보안", "CSAP": "정보보안", "클라우드": "클라우드인프라", "cloud": "클라우드인프라", "서버": "클라우드인프라", "AI": "AI데이터분석", "인공지능": "AI데이터분석", "데이터분석": "AI데이터분석", "전자정부": "전자정부시스템", "민원": "전자정부시스템", "행정": "전자정부시스템", "네트워크": "네트워크장비", "network": "네트워크장비", "라우터": "네트워크장비", "개발": "소프트웨어개발", "시스템개발": "소프트웨어개발", "통합": "시스템통합(SI)", "SI": "시스템통합(SI)", "구축": "시스템통합(SI)", } # 캐시 (나라장터 API 응답은 rate-limit 존재) _cache: dict = {"ts": None, "data": []} _CACHE_TTL = 3600 # 1시간 class G2BProject(BaseModel): bid_no: str title: str org: str budget_krw: Optional[int] = None deadline: Optional[str] = None category: str guardia_score: int guardia_modules: List[str] guardia_reason: str guardia_proposal: str source: str = "나라장터" def _classify(title: str) -> str: title_lower = title.lower() for kw, cat in KEYWORD_CATEGORY.items(): if kw.lower() in title_lower: return cat return "소프트웨어개발" def _make_project_from_api(item: dict) -> G2BProject: title = item.get("bidNtceNm", "") or item.get("sucsfBidAmt", "") category = _classify(title) meta = GUARDIA_CATEGORY_MAP.get(category, GUARDIA_CATEGORY_MAP["소프트웨어개발"]) budget_raw = item.get("presmptPrce") or item.get("asignBdgt") try: budget = int(str(budget_raw).replace(",", "")) if budget_raw else None except: budget = None return G2BProject( bid_no=item.get("bidNtceNo", ""), title=title, org=item.get("dminsttNm") or item.get("ntceInsttNm") or "공공기관", budget_krw=budget, deadline=item.get("bidClsedt") or item.get("rsrvtnPrceRnkBidAplyClseDt"), category=category, guardia_score=meta["score"], guardia_modules=meta["modules"], guardia_reason=meta["reason"], guardia_proposal=meta["proposal"], ) # 샘플 데이터 (폐쇄망/API 키 없을 때) SAMPLE_PROJECTS = [ {"bidNtceNo": "G2B-2024-001", "bidNtceNm": "서울시 IT 시스템 운영유지보수 용역", "ntceInsttNm": "서울특별시", "presmptPrce": 500000000, "bidClsedt": "2024-03-31"}, {"bidNtceNo": "G2B-2024-002", "bidNtceNm": "경기도 정보보안 취약점 진단 및 보안관제 용역", "ntceInsttNm": "경기도", "presmptPrce": 350000000, "bidClsedt": "2024-04-15"}, {"bidNtceNo": "G2B-2024-003", "bidNtceNm": "부산시 클라우드 인프라 구축 및 운영 용역", "ntceInsttNm": "부산광역시", "presmptPrce": 800000000, "bidClsedt": "2024-05-01"}, {"bidNtceNo": "G2B-2024-004", "bidNtceNm": "행정안전부 AI 기반 민원 분석 시스템 개발 용역", "ntceInsttNm": "행정안전부", "presmptPrce": 1200000000, "bidClsedt": "2024-04-30"}, {"bidNtceNo": "G2B-2024-005", "bidNtceNm": "국가보훈처 전자정부 행정시스템 고도화 용역", "ntceInsttNm": "국가보훈처", "presmptPrce": 600000000, "bidClsedt": "2024-05-15"}, {"bidNtceNo": "G2B-2024-006", "bidNtceNm": "인천시 네트워크 장비 유지보수 및 운영 용역", "ntceInsttNm": "인천광역시", "presmptPrce": 250000000, "bidClsedt": "2024-04-20"}, {"bidNtceNo": "G2B-2024-007", "bidNtceNm": "기획재정부 정보시스템 통합운영 및 유지보수", "ntceInsttNm": "기획재정부", "presmptPrce": 950000000, "bidClsedt": "2024-06-01"}, {"bidNtceNo": "G2B-2024-008", "bidNtceNm": "한국수자원공사 SCADA 보안 시스템 구축", "ntceInsttNm": "한국수자원공사", "presmptPrce": 450000000, "bidClsedt": "2024-04-25"}, {"bidNtceNo": "G2B-2024-009", "bidNtceNm": "교육부 클라우드 기반 학습 AI 데이터분석 플랫폼 구축", "ntceInsttNm": "교육부", "presmptPrce": 700000000, "bidClsedt": "2024-05-10"}, {"bidNtceNo": "G2B-2024-010", "bidNtceNm": "서울 25개 자치구 통합 IT 시스템 운영관리 용역", "ntceInsttNm": "서울특별시 25개 자치구", "presmptPrce": 2500000000, "bidClsedt": "2024-06-30"}, {"bidNtceNo": "G2B-2024-011", "bidNtceNm": "국방부 정보보안 관제 및 취약점 점검 서비스", "ntceInsttNm": "국방부", "presmptPrce": 1800000000, "bidClsedt": "2024-05-20"}, {"bidNtceNo": "G2B-2024-012", "bidNtceNm": "경상북도 스마트 행정 시스템 개발 및 구축", "ntceInsttNm": "경상북도", "presmptPrce": 400000000, "bidClsedt": "2024-05-05"}, {"bidNtceNo": "G2B-2024-013", "bidNtceNm": "고용노동부 인사행정시스템 통합 운영유지보수", "ntceInsttNm": "고용노동부", "presmptPrce": 320000000, "bidClsedt": "2024-04-10"}, {"bidNtceNo": "G2B-2024-014", "bidNtceNm": "전북특별자치도 AI 기반 교통 데이터분석 시스템 구축", "ntceInsttNm": "전북특별자치도", "presmptPrce": 550000000, "bidClsedt": "2024-05-25"}, {"bidNtceNo": "G2B-2024-015", "bidNtceNm": "조달청 나라장터 차세대 시스템 개발 용역", "ntceInsttNm": "조달청", "presmptPrce": 5000000000, "bidClsedt": "2024-07-01"}, ] async def _fetch_g2b_api(keyword: str, page: int) -> list: """나라장터 공공 API 호출 (개방망만)""" params = { "serviceKey": G2B_API_KEY, "pageNo": page, "numOfRows": 30, "type": "json", "bidNtceNmQry": keyword, "srvcTypeCd": "3", # 용역 } try: async with httpx.AsyncClient(timeout=10) as client: r = await client.get(f"{G2B_API_BASE}/getScsbidListServcPPSSrch", params=params) if r.status_code == 200: data = r.json() items = (data.get("response") or {}).get("body") or {} return items.get("items") or [] except Exception: pass return [] @router.get("/projects") async def get_g2b_projects( keyword: str = Query(default="IT 운영유지보수", description="검색 키워드"), category: Optional[str] = Query(default=None, description="GUARDiA 분류 필터"), min_score: int = Query(default=0, description="최소 GUARDiA 적합성 점수"), page: int = Query(default=1), limit: int = Query(default=20), ): """나라장터 소프트웨어 개발 용역 목록 + GUARDiA 적합성 분석""" global _cache # 캐시 유효성 확인 now = datetime.now() use_cache = _cache["ts"] and (now - _cache["ts"]).seconds < _CACHE_TTL and _cache.get("keyword") == keyword if not use_cache: raw_items = [] if _OPEN and G2B_API_KEY: # 개방망: 실제 나라장터 API 호출 raw_items = await _fetch_g2b_api(keyword, page) if not raw_items: # 폐쇄망 또는 API 오류: 샘플 데이터 raw_items = SAMPLE_PROJECTS projects = [_make_project_from_api(item) for item in raw_items] _cache = {"ts": now, "data": projects, "keyword": keyword} else: projects = _cache["data"] # 필터 if category: projects = [p for p in projects if p.category == category] if min_score > 0: projects = [p for p in projects if p.guardia_score >= min_score] # 점수 내림차순 projects = sorted(projects, key=lambda p: p.guardia_score, reverse=True) offset = (page - 1) * limit paginated = projects[offset:offset + limit] return { "total": len(projects), "page": page, "limit": limit, "keyword": keyword, "source": "나라장터 G2B" + (" (실시간)" if _OPEN and G2B_API_KEY else " (샘플)"), "projects": [p.dict() for p in paginated], } @router.get("/categories") async def get_categories(): """GUARDiA 적용 가능 프로젝트 분류 목록""" return { "categories": [ {"name": k, "guardia_score": v["score"], "modules": v["modules"]} for k, v in sorted(GUARDIA_CATEGORY_MAP.items(), key=lambda x: -x[1]["score"]) ] } @router.get("/projects/{bid_no}/analysis") async def get_project_analysis(bid_no: str): """특정 프로젝트 GUARDiA 상세 분석 + Ollama AI 제안서""" project_data = next((p for p in SAMPLE_PROJECTS if p.get("bidNtceNo") == bid_no), None) if not project_data: raise HTTPException(status_code=404, detail="Project not found") proj = _make_project_from_api(project_data) # Ollama AI 제안서 생성 (온프레미스) proposal_text = proj.guardia_proposal try: async with httpx.AsyncClient(timeout=30) as client: r = await client.post("http://localhost:11434/api/generate", json={ "model": "llama3", "prompt": f"""다음 공공기관 IT 발주 사업에 GUARDiA AI 운영 플랫폼 적용 방안을 간단히 제안해주세요. 사업명: {proj.title} 발주기관: {proj.org} 예산: {proj.budget_krw:,}원 GUARDiA 적합 모듈: {', '.join(proj.guardia_modules)} 3문장 이내로 제안해주세요.""", "stream": False, }) if r.status_code == 200: proposal_text = r.json().get("response", proposal_text) except Exception: pass # Ollama 미연결 시 기본 제안서 사용 return { "project": proj.dict(), "ai_proposal": proposal_text, "similar_cases": [ "서울시 정보시스템 운영관리 (GUARDiA ITSM 도입, SLA 35% 향상)", "경기도 보안관제센터 (AI-SOC 적용, 탐지율 98%)", ], "estimated_roi": { "manpower_reduction": "30%", "sla_improvement": "40%", "incident_response_time": "60% 단축", }, } @router.get("/summary/by-category") async def get_summary_by_category(): """카테고리별 사업 집계 현황""" projects = [_make_project_from_api(p) for p in SAMPLE_PROJECTS] summary: dict = {} for p in projects: if p.category not in summary: summary[p.category] = {"count": 0, "total_budget": 0, "avg_score": 0, "scores": []} summary[p.category]["count"] += 1 summary[p.category]["total_budget"] += (p.budget_krw or 0) summary[p.category]["scores"].append(p.guardia_score) result = [] for cat, data in sorted(summary.items(), key=lambda x: -x[1]["total_budget"]): result.append({ "category": cat, "count": data["count"], "total_budget_krw": data["total_budget"], "avg_guardia_score": round(sum(data["scores"]) / len(data["scores"])), }) return {"categories": result, "total_projects": len(projects)} @router.post("/projects/{bid_no}/create-proposal") async def create_proposal(bid_no: str): """선택한 사업에 GUARDiA 제안서 SR 자동 생성""" project_data = next((p for p in SAMPLE_PROJECTS if p.get("bidNtceNo") == bid_no), None) if not project_data: raise HTTPException(status_code=404, detail="Project not found") proj = _make_project_from_api(project_data) # ITSM SR 자동 생성 sr_data = { "title": f"[나라장터] {proj.title} - GUARDiA 제안서 작성", "description": ( f"발주기관: {proj.org}\n" f"예산: {proj.budget_krw:,}원\n" f"GUARDiA 적합도: {proj.guardia_score}점\n" f"적용 모듈: {', '.join(proj.guardia_modules)}\n" f"제안 내용: {proj.guardia_proposal}" ), "priority": "high" if proj.guardia_score >= 90 else "medium", "category": "영업·제안", "tags": ["나라장터", proj.category, "GUARDiA제안"], } return {"status": "ok", "message": "제안서 SR이 생성되었습니다", "sr_data": sr_data, "project": proj.dict()}