跳至內容

零售領域

本導覽說明示範所使用的零售分析資料模型、SQLite 設定、種子資料與 REST 端點,也會指出為程式碼審查示範刻意保留的程式碼異味。

領域模型

API 模型位於 AgentHQDemo.Api/Models

Transaction 代表一筆零售交易,包含整數型別的 IdCustomerIdAmountProductCategoryStoreIdTimestamp,以及 IsFlagged。模型包含下列資料註解: RequiredStringLength,以及 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,並在應用程式啟動期間植入資料:

await db.Database.EnsureCreatedAsync();
await service.SeedDataAsync();

RetailAnalyticsService.SeedDataAsync 對一般示範重新啟動而言具等冪性:只要已有任何交易,它便會立即回傳。在全新的資料庫中,它會插入範例交易與區隔,接著儲存變更。

種子資料

種子資料包含 10 筆交易,涵蓋客戶 C001C005

客戶 植入的模式
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 會載入客戶的所有交易,並衍生總消費、平均消費與購買頻率,接著依序套用下列規則:

  1. 沒有交易:回傳 New ,信賴度為 0.5 以及 no_history
  2. 總消費高於 1000:回傳 High Value ,信賴度為 0.89
  3. 頻率為三次以上:回傳 Regular ,信賴度為 0.75
  4. 平均消費低於 50:回傳 At Risk ,信賴度為 0.62
  5. 否則:回傳 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 (在程式碼中)。

問題所在:業務門檻會隨市場、季節及零售商而變動。隱藏在程式碼中的魔術數字難以稽核、調整,也難以向業務關係人說明。

審查者應指出:「請將高價值門檻移至設定或原則物件、給予清楚名稱,並在測試中涵蓋邊界行為。」