零售領域¶
本導覽說明示範所使用的零售分析資料模型、SQLite 設定、種子資料與 REST 端點,也會指出為程式碼審查示範刻意保留的程式碼異味。
領域模型¶
API 模型位於
AgentHQDemo.Api/Models。
Transaction
代表一筆零售交易,包含整數型別的 Id、 CustomerId、 Amount、
ProductCategory、 StoreId、 Timestamp,以及 IsFlagged。模型包含下列資料註解: Required、 StringLength,以及 Range,ASP.NET Core 模型繫結可在控制器檢查 ModelState。
CustomerSegment
代表一個分析區隔,並儲存區隔名稱、說明、客戶數、平均每月消費及留存率。
SegmentPrediction
是預測呼叫所回傳的不可變記錄。它包含客戶 ID、預測區隔、信賴度分數,以及主要特徵名稱。
資料庫與啟動植入資料¶
RetailDbContext
是一個小型 EF Core 資料庫內容,包含兩個集合:
public DbSet<Transaction> Transactions => Set<Transaction>();
public DbSet<CustomerSegment> Segments => Set<CustomerSegment>();
Program.cs
會使用下列項目設定 SQLite: Data Source=retail.db,註冊
RetailAnalyticsService,並在應用程式啟動期間植入資料:
RetailAnalyticsService.SeedDataAsync 對一般示範重新啟動而言具等冪性:只要已有任何交易,它便會立即回傳。在全新的資料庫中,它會插入範例交易與區隔,接著儲存變更。
種子資料¶
種子資料包含 10 筆交易,涵蓋客戶 C001 到 C005:
| 客戶 | 植入的模式 |
|---|---|
C001 |
雜貨與電子產品類別的購買總額為 335.49 |
C002 |
雜貨與保健類別的購買總額為 47.50 |
C003 |
電子產品與時尚類別的購買總額為 1,700.00 |
C004 |
兩筆低消費雜貨購買,總額為 21.49 |
C005 |
電子產品與時尚類別的購買總額為 995.00 |
它也會建立四個客戶區隔:
| 區隔 | 客戶數 | 平均每月消費 | 留存率 | 說明 |
|---|---|---|---|---|
| 高價值 | 150 | $850 | 92% | 消費金額排名前 10% 且具高度忠誠度指標的顧客 |
| 一般 | 3,200 | $180 | 78% | 跨類別穩定進行每月消費的顧客 |
| 流失風險 | 890 | $95 | 45% | 過去 90 天的購買頻率下降 |
| 新客 | 420 | $120 | 65% | 最近 90 天內加入 |
REST 端點¶
TransactionsController
會公開交易讀取、建立與刪除端點:
| 端點 | 回傳 |
|---|---|
GET /api/transactions |
所有 Transaction 記錄。 |
GET /api/transactions/{id} |
一筆 Transaction;若控制器收到 null,則回傳 404。 |
POST /api/transactions |
建立交易並回傳 201 Created ,內容為已儲存的記錄。 |
DELETE /api/transactions/{id} |
204 No Content (刪除成功時),或 404 (找不到時)。 |
SegmentsController
會公開區隔與預測端點:
| 端點 | 回傳 |
|---|---|
GET /api/segments |
所有 CustomerSegment 記錄。 |
GET /api/segments/{id} |
一個 CustomerSegment,或 404 (找不到時)。 |
GET /api/segments/predict/{customerId} |
一個用於該客戶的 SegmentPrediction。 |
聊天端點的說明請參閱 透過 SSE 串流傳送回應,因為它們屬於 Copilot 串流路徑,而非零售資料 API。
區隔預測邏輯¶
RetailAnalyticsService.PredictSegmentAsync
會載入客戶的所有交易,並衍生總消費、平均消費與購買頻率,接著依序套用下列規則:
- 沒有交易:回傳
New,信賴度為0.5以及no_history。 - 總消費高於
1000:回傳High Value,信賴度為0.89。 - 頻率為三次以上:回傳
Regular,信賴度為0.75。 - 平均消費低於
50:回傳At Risk,信賴度為0.62。 - 否則:回傳
Regular,信賴度為0.55。
回傳的 TopFeatures 陣列會說明規則的輸入,例如總消費、頻率或平均消費。
四個刻意保留的程式碼異味¶
這些是為程式碼審查工作階段刻意保留的示範素材。請勿在此示範中將它們當成意外錯誤來修正;應將其用作審查者應注意並說明的範例。
1. N+1 查詢位於 RetailAnalyticsService.GetTransactionsWithSegmentsAsync¶
問題內容:此方法先載入所有交易,再逐筆循環處理交易並呼叫 PredictSegmentAsync,這會再為該客戶執行一次資料庫查詢。
問題所在:查詢數量會隨交易數量增加。對少量種子資料而言尚可接受,但面對實際零售資料量時可能變得緩慢且成本高昂。
審查者應指出:「這是 N+1 查詢模式。請考慮批次取得客戶交易資料,或使用要求中已載入的資料計算區隔預測。」
2. 缺少 null 檢查: RetailAnalyticsService.GetTransactionAsync¶
問題內容:服務使用 FindAsync(id) ,並使用下列方式抑制可為 Null 的流程警告:
!,並回傳 Task<Transaction> ,即使資料庫可能不會回傳任何資料列。
問題所在:呼叫端無法從簽章得知 null 是可能的,未來的程式碼可能會在檢查前解除參考該結果。
審查者應指出:「服務合約應反映找不到資料的情況,例如回傳 Transaction? 或結果型別,且呼叫端應明確處理此情況。」
3. 缺少輸入驗證: RetailAnalyticsService.AddTransactionAsync¶
問題內容:服務接受傳入的 Transaction,設定
Timestamp = DateTime.UtcNow,接著在未檢查金額、客戶 ID、類別或門市值的情況下儲存。
問題所在: TransactionsController.Create 會檢查 ModelState,但測試、其他服務或未來的端點仍可直接呼叫服務本身。無效的領域資料可能繞過控制器驗證。
審查者應指出:「領域不變條件不可只依賴控制器驗證。請在服務中驗證交易,或集中管理規則,讓每個呼叫端都受到相同保護。」
4. 硬式編碼的門檻值位於 RetailAnalyticsService.PredictSegmentAsync¶
問題內容:高價值規則使用常值門檻 1000 (在程式碼中)。
問題所在:業務門檻會隨市場、季節及零售商而變動。隱藏在程式碼中的魔術數字難以稽核、調整,也難以向業務關係人說明。
審查者應指出:「請將高價值門檻移至設定或原則物件、給予清楚名稱,並在測試中涵蓋邊界行為。」