DAY 8 完成 Slash Command 後,今天開始加入 AI 公司要處理的專案資料。
我準備了五筆範例專案,並建立 /projects。指令會從 projects.json 讀取資料,再用只有執行者看得到的 Embed 顯示內容。讀檔、驗證與 Discord 排版則拆開處理,避免全部塞進 bot.py。
Project 與 RequirementChange
JsonProjectRepository
PROJECTS_FILE 設定資料位置/projects 私密 EmbedAgent 後續會依照同一份資料討論,所以除了專案名稱,還要保存需求、預算、期限及驗收條件。
| 欄位 | 用途 |
|---|---|
id、category、title |
專案編號、類別與名稱 |
requirements |
一筆或多筆需求 |
budget、deadline |
預算與期限 |
acceptance_criteria |
驗收條件 |
requirement_changes |
後續需求變更 |
專案與需求變更都使用不可修改的 dataclass:
@dataclass(frozen=True)
class RequirementChange:
id: str
project_id: str
description: str
reason: str
requested_at: date
status: str
@dataclass(frozen=True)
class Project:
id: str
category: str
title: str
requirements: tuple[str, ...]
budget: int
deadline: date
acceptance_criteria: tuple[str, ...]
requirement_changes: tuple[RequirementChange, ...] = ()
frozen=True 可避免程式直接改掉原始資料。客戶提出新要求時,會新增一筆 RequirementChange,保留變更原因與時間。
projects.json 最外層使用 projects 陣列。以下是簡化後的單筆資料:
{
"projects": [
{
"id": "PRJ-001",
"category": "企業網站",
"title": "咖啡店品牌官網",
"requirements": ["製作響應式品牌首頁", "加入聯絡表單"],
"budget": 80000,
"deadline": "2026-10-15",
"acceptance_criteria": ["手機與桌面版皆可正常瀏覽"],
"requirement_changes": []
}
]
}
Project.from_dict() 會檢查必填文字、陣列、預算及日期格式,也會確認需求變更的 project_id 與專案一致。資料不正確時拋出 ProjectDataError,不讓錯誤內容繼續傳到 Discord。
如果 /projects 自己開啟 JSON,它就要同時負責讀檔、解析、驗證與顯示。加入 Repository 後,指令只需要取得整理好的 Project:
/projects
↓
ProjectRepository
↓
JsonProjectRepository
↓
projects.json
目前介面只有同步的 list_projects():
class ProjectRepository(Protocol):
def list_projects(self) -> list[Project]:
...
JsonProjectRepository 負責讀取 UTF-8 檔案、解析 JSON,再逐筆呼叫 Project.from_dict()。找不到檔案或 JSON 格式錯誤時,會統一轉成 ProjectRepositoryError。
資料路徑放在環境設定:
PROJECTS_FILE=projects.json
未來移動資料檔時,只要調整設定,不必修改 Repository。
指令先向 Repository 取得專案,再建立 Embed:
projects = project_repository.list_projects()
embed = discord.Embed(
title="📁 專案清單",
description=f"目前共有 {len(projects)} 個專案。",
color=discord.Color.blue(),
)
for project in projects:
embed.add_field(
name=f"{project.id}|{project.title}",
value=format_project(project),
inline=False,
)
await interaction.response.send_message(
embed=embed,
ephemeral=True,
)
ephemeral=True 表示只有執行指令的人能看見。若 Repository 發生錯誤,Discord 只顯示簡短訊息,詳細原因留在日誌。

專案資料已拆成 Model、Repository 與 Discord 顯示三個部分。五筆範例可以轉成 Project,/projects 也有 Fake Repository 測試保護。
當時還沒確認指令是否出現在 Discord 選單。下一篇會加入 /start,讓使用者用專案 ID 啟動流程。