graph TD A[소스코드] --> B[자동 문서 추출] B --> C[쿼토 문서] C --> D[다중 출력] E[API 코드] --> F[OpenAPI 스펙] F --> C G[테스트 코드] --> H[예제 추출] H --> C I[GitHub Actions] --> J[자동 배포] D --> J D --> K[HTML 웹사이트] D --> L[PDF 보고서] D --> M[Word 문서]
18 기술 보고서와 문서화
중요코드에서 문서로
API 문서, 기술 보고서, 정부 제출 보고서까지 코드와 함께 자동 생성되는 살아있는 문서를 만들어봅시다.
18.1 Living Documentation의 힘
18.1.1 기존 기술 문서의 문제점
- 낡은 문서: 코드는 업데이트되었지만 문서는 6개월 전 버전
- 분산된 정보: API 명세, 사용 가이드, 예제가 각각 다른 곳에
- 수동 업데이트: 개발자가 기억해서 문서를 수정해야 함
- 형식 불일치: 팀원마다 다른 스타일의 문서
18.1.2 Document as Code 해결책
18.2 API 문서 자동 생성
18.2.1 FastAPI 기반 자동 문서화
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn
= FastAPI(
app ="Text Classification API",
title="Document as Code 패러다임을 적용한 텍스트 분류 API",
description="1.0.0",
version={
contact"name": "이광춘",
"email": "kwangchun@r2bit.com",
"url": "https://r2bit.com"
},={
license_info"name": "MIT License",
"url": "https://opensource.org/licenses/MIT"
}
)
class TextInput(BaseModel):
"""텍스트 분류를 위한 입력 모델
Attributes:
text: 분류할 텍스트 (최대 1000자)
model: 사용할 모델명 (기본값: 'random_forest')
"""
str
text: str] = "random_forest"
model: Optional[
class Config:
= {
schema_extra "example": {
"text": "이것은 분류할 텍스트 예시입니다.",
"model": "random_forest"
}
}
class ClassificationResult(BaseModel):
"""분류 결과 모델
Attributes:
category: 예측된 카테고리
confidence: 예측 신뢰도 (0-1)
model_used: 사용된 모델명
processing_time: 처리 시간 (초)
"""
str
category: float
confidence: str
model_used: float
processing_time:
@app.post(
"/classify",
=ClassificationResult,
response_model="텍스트 분류",
summary="입력된 텍스트를 사전 학습된 모델로 분류합니다."
description
)async def classify_text(input_data: TextInput):
"""
텍스트를 분류하여 카테고리를 예측합니다.
- **text**: 분류할 텍스트 (필수)
- **model**: 사용할 모델명 (선택, 기본값: random_forest)
지원 모델:
- `random_forest`: Random Forest 분류기
- `logistic_regression`: 로지스틱 회귀
- `transformer`: Transformer 기반 모델
Returns:
ClassificationResult: 분류 결과와 메타데이터
"""
# 실제 분류 로직은 여기에 구현
import time
= time.time()
start_time
# 모의 분류 결과
= ClassificationResult(
result ="positive",
category=0.85,
confidence=input_data.model,
model_used=time.time() - start_time
processing_time
)
return result
@app.get(
"/models",
=List[str],
response_model="사용 가능한 모델 목록",
summary="API에서 사용 가능한 모든 분류 모델의 목록을 반환합니다."
description
)async def list_models():
"""사용 가능한 분류 모델 목록을 반환합니다."""
return ["random_forest", "logistic_regression", "transformer"]
18.2.2 자동 생성된 API 문서 통합
library(jsonlite)
library(knitr)
library(gt)
# OpenAPI 스펙 읽기
openapi_spec <- fromJSON("http://localhost:8000/openapi.json")
# API 엔드포인트 정보 추출
endpoints <- openapi_spec$paths
# 엔드포인트 테이블 생성
endpoint_info <- map_dfr(names(endpoints), function(path) {
methods <- names(endpoints[[path]])
map_dfr(methods, function(method) {
info <- endpoints[[path]][[method]]
tibble(
path = path,
method = toupper(method),
summary = info$summary %||% "",
description = str_trunc(info$description %||% "", 100)
)
})
})
endpoint_info %>%
gt() %>%
tab_header(
title = "API 엔드포인트 목록",
subtitle = "자동 생성된 API 문서"
) %>%
cols_label(
path = "경로",
method = "메서드",
summary = "요약",
description = "설명"
) %>%
fmt_markdown(columns = everything()) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
)
18.3 정부 보고서 템플릿
18.3.1 한국 정부 보고서 표준 양식
---
title: "AI 기반 텍스트 분류 시스템 개발"
subtitle: "2024년도 디지털 혁신 과제 최종보고서"
# 정부 보고서 메타데이터
report_meta:
project_name: "AI 기반 텍스트 분류 시스템 개발"
project_code: "2024-DT-001"
period: "2024년 3월 ~ 2024년 12월"
budget: "50,000만원"
# 참여기관
institutions:
lead: "공익법인 한국 R 사용자회"
partners:
- "한국대학교 AI연구소"
- "㈜데이터테크"
# 연구진
researchers:
pm: "이광춘 (공익법인 한국 R 사용자회)"
members:
- "김연구 (한국대학교)"
- "박개발 (㈜데이터테크)"
format:
pdf:
documentclass: article
fontsize: 12pt
geometry:
- left=3cm
- right=2cm
- top=2.5cm
- bottom=2.5cm
number-sections: true
toc: true
toc-depth: 3
fig-pos: 'H'
include-in-header:
- text: |
\usepackage{kotex}
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhead[L]{2024년도 디지털 혁신 과제}
\fancyhead[R]{최종보고서}
\fancyfoot[C]{\thepage}
html:
theme: cosmo
css: styles/gov-report.css
number-sections: true
toc: true
lang: ko-KR
bibliography: references.bib
---
18.3.2 정부 보고서 본문 구조
# 연구개발 과제 개요
## 연구개발 목표
### 최종 목표
AI 기반 텍스트 분류 시스템을 개발하여 정부 문서 처리의 자동화와 효율성을 향상시킨다.
### 세부 목표
1. **기술적 목표**
- 한국어 텍스트 분류 정확도 95% 이상 달성
- 실시간 처리 성능 1,000건/분 이상
- Document as Code 패러다임 적용
2. **활용적 목표**
- 정부 민원 자동 분류 시스템 구축
- 정책 문서 자동 태깅 시스템 개발
- 공공 데이터 품질 향상
## 연구개발 필요성
### 기술적 필요성
현재 정부 기관에서 사용하는 문서 분류 시스템의 한계...
### 경제적 필요성
수작업 문서 분류로 인한 연간 인건비 손실 규모...
### 사회적 필요성
시민 서비스 품질 향상과 행정 효율성 증대...
# 연구개발 내용 및 방법
## 연구개발 추진전략
### 단계별 추진 계획
gantt title 연구개발 추진 일정 dateFormat YYYY-MM-DD section 1단계 요구사항 분석 :done, req, 2024-03-01, 2024-04-30 기술 검토 :done, tech, 2024-04-01, 2024-05-31 section 2단계 시스템 설계 :active, design, 2024-05-01, 2024-07-31 프로토타입 개발 :proto, after design, 60d section 3단계 시스템 구현 :impl, after proto, 90d 성능 최적화 :opt, after impl, 30d section 4단계 시험 운영 :test, after opt, 60d 최종 평가 :eval, after test, 30d
18.3.3 기술 개발 방법론
Document as Code 적용
18.4 기업 기술 문서
18.4.1 소프트웨어 아키텍처 문서
import diagrams
from diagrams import Diagram, Cluster, Edge
from diagrams.aws.compute import ECS, Lambda
from diagrams.aws.database import RDS, ElastiCache
from diagrams.aws.network import ELB, CloudFront
from diagrams.aws.storage import S3
with Diagram("텍스트 분류 시스템 아키텍처", show=False, direction="TB"):
with Cluster("사용자 인터페이스"):
= CloudFront("Web Application")
web_app
with Cluster("API 레이어"):
= ELB("API Gateway")
api_gateway = [
api_servers "API Server 1"),
ECS("API Server 2")
ECS(
]
with Cluster("비즈니스 로직"):
= Lambda("ML Service")
ml_service
with Cluster("데이터 저장소"):
= RDS("PostgreSQL")
database = ElastiCache("Redis Cache")
cache = S3("Model Storage")
storage
# 연결 관계
>> api_gateway >> api_servers
web_app >> ml_service
api_servers >> [database, cache, storage] ml_service
18.4.2 코드 문서화
class TextClassifier:
"""텍스트 분류를 위한 메인 클래스
이 클래스는 Document as Code 패러다임을 적용하여
모델 훈련부터 예측까지의 전체 파이프라인을 관리합니다.
Attributes:
model_type (str): 사용할 모델 타입
is_trained (bool): 모델 훈련 상태
Example:
>>> classifier = TextClassifier(model_type="random_forest")
>>> classifier.train(X_train, y_train)
>>> predictions = classifier.predict(X_test)
"""
def __init__(self, model_type: str = "random_forest"):
"""분류기 초기화
Args:
model_type: 사용할 모델 타입.
'random_forest', 'logistic_regression',
'transformer' 중 선택
"""
self.model_type = model_type
self.is_trained = False
self._model = None
def train(self, X, y, **kwargs):
"""모델 훈련 메서드
Args:
X: 훈련 특성 데이터
y: 훈련 라벨 데이터
**kwargs: 모델별 추가 파라미터
Returns:
self: 메서드 체이닝을 위한 self 반환
Raises:
ValueError: 지원하지 않는 모델 타입인 경우
Example:
>>> classifier.train(X_train, y_train, n_estimators=100)
"""
if self.model_type == "random_forest":
from sklearn.ensemble import RandomForestClassifier
self._model = RandomForestClassifier(**kwargs)
elif self.model_type == "logistic_regression":
from sklearn.linear_model import LogisticRegression
self._model = LogisticRegression(**kwargs)
else:
raise ValueError(f"지원하지 않는 모델 타입: {self.model_type}")
self._model.fit(X, y)
self.is_trained = True
return self
def predict(self, X):
"""텍스트 분류 예측
Args:
X: 예측할 특성 데이터
Returns:
numpy.ndarray: 예측 결과 배열
Raises:
RuntimeError: 모델이 훈련되지 않은 경우
"""
if not self.is_trained:
raise RuntimeError("모델이 훈련되지 않았습니다. train() 메서드를 먼저 실행하세요.")
return self._model.predict(X)
18.4.3 자동 코드 문서 생성
library(reticulate)
# Python 모듈 임포트
py_run_string("
import inspect
import ast
def extract_docstrings(source_file):
'''파이썬 소스 파일에서 독스트링 추출'''
with open(source_file, 'r', encoding='utf-8') as f:
source = f.read()
tree = ast.parse(source)
docstrings = {}
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
docstring = ast.get_docstring(node)
if docstring:
docstrings[node.name] = {
'type': 'class' if isinstance(node, ast.ClassDef) else 'function',
'docstring': docstring,
'lineno': node.lineno
}
return docstrings
# 사용 예
docs = extract_docstrings('text_classifier.py')
")
# R에서 추출된 독스트링 처리
docstrings <- py$docs
# 문서화 테이블 생성
doc_table <- map_dfr(names(docstrings), function(name) {
info <- docstrings[[name]]
tibble(
이름 = name,
타입 = info$type,
설명 = str_trunc(str_split(info$docstring, "\n")[[1]][1], 100),
라인 = info$lineno
)
})
doc_table %>%
gt() %>%
tab_header(
title = "자동 추출된 코드 문서",
subtitle = "Python 독스트링 기반 API 문서"
) %>%
cols_label(
이름 = "함수/클래스명",
타입 = "타입",
설명 = "설명",
라인 = "라인번호"
)
18.5 성능 모니터링 보고서
18.5.1 실시간 대시보드
library(plotly)
library(DT)
# 성능 데이터 시뮬레이션
generate_performance_data <- function(days = 30) {
dates <- seq.Date(Sys.Date() - days, Sys.Date(), by = "day")
tibble(
date = rep(dates, each = 24),
hour = rep(0:23, times = length(dates)),
requests_per_hour = rpois(length(dates) * 24, 100),
avg_response_time = rnorm(length(dates) * 24, 200, 50),
error_rate = pmax(0, rnorm(length(dates) * 24, 0.02, 0.01)),
cpu_usage = pmax(0, pmin(100, rnorm(length(dates) * 24, 65, 15))),
memory_usage = pmax(0, pmin(100, rnorm(length(dates) * 24, 70, 20)))
) %>%
mutate(
datetime = as.POSIXct(paste(date, paste0(hour, ":00:00"))),
avg_response_time = pmax(50, avg_response_time),
error_rate = pmax(0, pmin(0.1, error_rate))
)
}
perf_data <- generate_performance_data()
# 요청량 트렌드
p1 <- perf_data %>%
group_by(date) %>%
summarise(daily_requests = sum(requests_per_hour), .groups = "drop") %>%
plot_ly(x = ~date, y = ~daily_requests, type = 'scatter', mode = 'lines+markers',
name = '일일 요청수', line = list(color = '#1f77b4')) %>%
layout(
title = "일일 API 요청량 추이",
xaxis = list(title = "날짜"),
yaxis = list(title = "요청수")
)
# 응답시간 분포
p2 <- perf_data %>%
plot_ly(y = ~avg_response_time, type = 'box', name = '응답시간') %>%
layout(
title = "API 응답시간 분포",
yaxis = list(title = "응답시간 (ms)")
)
# 에러율 추이
p3 <- perf_data %>%
group_by(date) %>%
summarise(daily_error_rate = mean(error_rate), .groups = "drop") %>%
plot_ly(x = ~date, y = ~daily_error_rate, type = 'scatter', mode = 'lines+markers',
name = '에러율', line = list(color = '#ff7f0e')) %>%
layout(
title = "일일 에러율 추이",
xaxis = list(title = "날짜"),
yaxis = list(title = "에러율 (%)", tickformat = ".1%")
)
# 대시보드 조합
subplot(p1, p2, p3, nrows = 3, shareX = FALSE) %>%
layout(title = "시스템 성능 모니터링 대시보드")
18.5.2 SLA 준수 보고서
library(tibble)
# SLA 기준
sla_targets <- tibble(
metric = c("가용성", "응답시간", "에러율", "처리량"),
target = c("99.9%", "< 300ms", "< 1%", "> 1000 req/min"),
unit = c("percent", "milliseconds", "percent", "requests")
)
# 실제 성능 계산
actual_performance <- perf_data %>%
summarise(
availability = 0.998, # 99.8%
avg_response = mean(avg_response_time),
avg_error_rate = mean(error_rate),
avg_throughput = mean(requests_per_hour) * 60
)
# SLA 비교 테이블
sla_report <- tibble(
지표 = c("가용성", "평균 응답시간", "평균 에러율", "평균 처리량"),
목표 = c("99.9%", "< 300ms", "< 1%", "> 1000 req/min"),
실제값 = c(
paste0(round(actual_performance$availability * 100, 1), "%"),
paste0(round(actual_performance$avg_response, 1), "ms"),
paste0(round(actual_performance$avg_error_rate * 100, 2), "%"),
paste0(round(actual_performance$avg_throughput, 0), " req/min")
),
준수여부 = c(
ifelse(actual_performance$availability >= 0.999, "✅", "❌"),
ifelse(actual_performance$avg_response <= 300, "✅", "❌"),
ifelse(actual_performance$avg_error_rate <= 0.01, "✅", "❌"),
ifelse(actual_performance$avg_throughput >= 1000, "✅", "❌")
)
)
sla_report %>%
gt() %>%
tab_header(
title = "SLA 준수 현황 보고서",
subtitle = paste("기준일:", Sys.Date() - 30, "~", Sys.Date())
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
tab_style(
style = cell_fill(color = "lightgreen"),
locations = cells_body(
columns = 준수여부,
rows = 준수여부 == "✅"
)
) %>%
tab_style(
style = cell_fill(color = "lightcoral"),
locations = cells_body(
columns = 준수여부,
rows = 준수여부 == "❌"
)
)
18.6 CI/CD 파이프라인 문서화
18.6.1 GitHub Actions 워크플로우
# .github/workflows/docs.yml
name: Documentation Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Setup R
uses: r-lib/actions/setup-r@v2
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install R dependencies
run: |
install.packages(c("rmarkdown", "knitr", "gt", "plotly")) shell: Rscript {0}
- name: Install Python dependencies
run: |
pip install -r requirements.txt
- name: Render documentation
run: |
quarto render
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
18.6.2 배포 프로세스 추적
library(gh)
library(lubridate)
# GitHub API를 통한 배포 이력 조회
get_deployment_history <- function(repo, days = 30) {
deployments <- gh(
"GET /repos/{owner}/{repo}/deployments",
owner = str_split(repo, "/")[[1]][1],
repo = str_split(repo, "/")[[1]][2],
per_page = 100
)
map_dfr(deployments, function(deploy) {
statuses <- gh(
"GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses",
owner = str_split(repo, "/")[[1]][1],
repo = str_split(repo, "/")[[1]][2],
deployment_id = deploy$id
)
latest_status <- statuses[[1]]
tibble(
deployment_id = deploy$id,
environment = deploy$environment,
created_at = ymd_hms(deploy$created_at),
status = latest_status$state,
description = latest_status$description %||% ""
)
}) %>%
filter(created_at >= Sys.Date() - days) %>%
arrange(desc(created_at))
}
# 배포 이력 테이블
deployment_history <- get_deployment_history("username/text-classifier-api")
deployment_history %>%
gt() %>%
tab_header(
title = "배포 이력",
subtitle = "최근 30일간 배포 현황"
) %>%
fmt_datetime(
columns = created_at,
date_style = 6,
time_style = 3
)
18.7 다음 단계
다음 장에서는 이러한 기술 문서들을 프레젠테이션과 교육 자료로 변환하는 방법을 배워보겠습니다. 같은 콘텐츠로 다양한 형태의 교육 자료를 만드는 효율적인 방법을 알아보세요.
힌트실습 과제
현재 담당하고 있는 프로젝트나 시스템의 기술 문서를 Document as Code 방식으로 작성해보세요. 코드에서 자동으로 추출되는 정보와 수동으로 작성해야 하는 내용을 구분하여 관리하는 것이 핵심입니다.
경고보안 주의사항
자동 문서 생성 시 코드에서 API 키나 비밀번호 등의 민감한 정보가 노출되지 않도록 주의하세요. 환경변수나 설정 파일을 통해 민감한 정보를 분리하는 것이 중요합니다.