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.
124 lines
4.3 KiB
Python
124 lines
4.3 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만 출력.'
|
|
)
|