You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
4.2 KiB
Python

# backend/app/services/scaffold.py
import re
import uuid
from sqlmodel import Session, select
from ..models import Task, TaskStatus
SCAFFOLDS = {
"research": {
"kind": "연구 · 논문 작성",
"icon": "brain",
"items": [
{"title": "선행 연구 조사", "est": "1일"},
{"title": "실험 데이터 취합", "est": "1일"},
{"title": "아키텍처 다이어그램 작성", "est": "반나절"},
{"title": "서론 초안", "est": "반나절"},
{"title": "결론 작성", "est": "반나절"},
],
},
"dev": {
"kind": "개발 업무",
"icon": "branch",
"items": [
{"title": "API 엔드포인트 설계", "est": "2시간"},
{"title": "UI 폼 컴포넌트 개발", "est": "3시간"},
{"title": "토큰 저장소 세팅", "est": "1.5시간"},
{"title": "테스트 코드 작성", "est": "2시간"},
],
},
"doc": {
"kind": "문서 · 리포트",
"icon": "file",
"items": [
{"title": "목차 & 범위 정의", "est": "30분"},
{"title": "핵심 데이터 수집", "est": "2시간"},
{"title": "섹션별 초안 작성", "est": "3시간"},
{"title": "리뷰 & 수정", "est": "1시간"},
{"title": "최종 정리 & 공유", "est": "30분"},
],
},
"generic": {
"kind": "일반 업무",
"icon": "list",
"items": [
{"title": "범위 정의 & 자료 수집", "est": "1시간"},
{"title": "초안 작성", "est": "2시간"},
{"title": "리뷰 & 피드백 반영", "est": "1시간"},
{"title": "최종 정리 & 공유", "est": "30분"},
],
},
}
# 원본 pickScaffold 정규식 (순서 중요: research > dev > doc > generic)
RE_RESEARCH = re.compile(r"(논문|연구|리서치|실험|학회|발표|조사|인터뷰)")
RE_DEV = re.compile(r"(구현|개발|API|로직|컴포넌트|버그|배포|인증|앱)")
RE_DOC = re.compile(r"(리포트|문서|보고|자료|작성|초안|회고|정산)")
def pick_scaffold(title: str, use_llm: bool = False) -> dict:
t = title or ""
if RE_RESEARCH.search(t):
base = SCAFFOLDS["research"]
elif RE_DEV.search(t):
base = SCAFFOLDS["dev"]
elif RE_DOC.search(t):
base = SCAFFOLDS["doc"]
else:
base = SCAFFOLDS["generic"]
if use_llm:
try:
from ..llm.provider import get_provider
enriched = get_provider().generate_json(
_scaffold_prompt(title, base), schema={"items": "list"}
)
items = enriched.get("items")
if isinstance(items, list) and items:
norm = [
{"title": str(i.get("title", "")).strip(), "est": str(i.get("est", "")).strip()}
for i in items
if i.get("title")
]
if norm:
return {"kind": base["kind"], "icon": base["icon"], "items": norm}
except Exception:
pass # 폴백: 정규식 템플릿 그대로
return base
def scaffold_create(s: Session, parent: Task, tpl: dict) -> list[str]:
existing = s.exec(select(Task).where(Task.parent_id == parent.id)).all()
order = max([x.sort_order for x in existing] + [-1]) + 1
ids = []
for i, it in enumerate(tpl["items"]):
tid = "t-" + uuid.uuid4().hex[:8]
s.add(
Task(
id=tid, title=it["title"], project_id=parent.project_id, parent_id=parent.id,
status=TaskStatus.todo, assignee_id=parent.assignee_id, prio=parent.prio,
est=it.get("est", ""), sort_order=order + i,
)
)
ids.append(tid)
s.commit()
return ids
def _scaffold_prompt(title: str, base: dict) -> str:
return (
"다음 작업을 실행 가능한 하위작업으로 4~6개 쪼개세요. "
"각 항목은 {title, est(예상 소요)} 형태의 JSON. 한국어.\n"
f"작업: {title}\n"
f"참고 템플릿({base['kind']}): {[i['title'] for i in base['items']]}\n"
'반드시 {"items":[{"title":"...","est":"..."}]} 형식 JSON만 출력.'
)