Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ jobs:
- name: Run @repo/crawlers Open-Core Intelligence Tests
run: pnpm --filter @repo/crawlers test

- name: Verify @repo/crawlers dist entry points (consumed by DOS/Rate-Services)
run: pnpm --filter @repo/crawlers test:dist

- name: Build & Typecheck @repo/extension (MV3)
run: pnpm --filter @repo/extension build

Expand Down
12 changes: 9 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ Hệ thống Rate được tách thành **2 repositories riêng biệt** (và d

> ⚠️ **CẤM TUYỆT ĐỐI:** Không bao giờ commit code giải Captcha, xoay Proxy, sniff API nội bộ của sàn, hoặc khoá bí mật (`SUPABASE_SERVICE_ROLE_KEY`, `AI_API_KEY`, `MPROXY_*`) vào `DOS/Rate`.

> 🔗 **Một nguồn sự thật cho Engine:** `DOS/Rate-Services` dùng `@repo/crawlers` (`apps/importer`) và `@repo/shared-data` (`packages/shared-data`) qua git submodule `vendor/rate` trỏ về `DOS/Rate` (pnpm workspace, import theo tên package, ví dụ `@repo/crawlers` hoặc `@repo/crawlers/shopee/arena-spec-syncer`). **KHÔNG** copy file engine sang Rate-Services. Cần dùng thêm module nào thì khai báo subpath tường minh trong `exports` của `apps/importer/package.json` (không dùng wildcard `./shopee/*` vì các script test chạy ngay khi import) và cập nhật `test:dist`. Mọi thay đổi engine phải merge vào `DOS/Rate` trước, sau đó bump submodule + chạy `pnpm install` để cập nhật `pnpm-lock.yaml` bên Rate-Services.

---

## 3. Kiến Trúc Kỹ Thuật Cốt Lõi & 4-Layer Backend (Zero-Strapi 2026)
Expand Down Expand Up @@ -151,7 +153,11 @@ pnpm --filter @repo/crawlers exec tsx src/shopee/test-review-analyzer.ts
pnpm --filter @repo/web typecheck
pnpm --filter @repo/web build

# Tại D:\Projects\Rate-Services
npm run build
npm run test:enricher
pnpm --filter @repo/crawlers test:dist

# Tại D:\Projects\Rate-Services (pnpm workspace + submodule vendor/rate)
git submodule update --init
pnpm install
pnpm build
pnpm test:enricher
```
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Hệ thống **Rate Platform (`rate.vn`)** đã loại bỏ hoàn toàn Strapi C
2. **🔒 `DOS/Rate-Services` (Private Enterprise - `D:\Projects\Rate-Services`):**
- Chứa toàn bộ phần nhạy cảm: Playwright Stealth Scrapers (`shopee-scraper.ts`, `shopee-search-scraper.ts`, `sniff-search-api.ts`), Giải Captcha (`shopee-captcha-solver.ts`), Xoay Proxy (`proxy-manager.ts`), và Queue Workers (`queue-worker.ts`).
- **TUYỆT ĐỐI KHÔNG** đưa code bypass anti-bot/captcha/proxy vào repo Public `DOS/Rate`.
- Engine Open-Core (`@repo/crawlers`, `@repo/shared-data`) được dùng qua git submodule `vendor/rate` (pnpm workspace). **KHÔNG** copy file engine sang Rate-Services; sửa engine tại `DOS/Rate` rồi bump submodule.

---

Expand Down
4 changes: 4 additions & 0 deletions Docs/Resources/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,7 @@ Hệ thống **Rate Platform (`rate.vn`)** vận hành trên kiến trúc **4 T
- [x] **5.18. Bộ Kiểm Toán Độ Lệch Giá Đa Sàn (`PriceSpreadAuditor`) & Bảng Xếp Hạng Chênh Lệch Giá 10 Sàn (`priceSpreadLeaders` - Issue #13 bên `DOS/Rate-Services` & Issue #92 bên `DOS/Rate`):**
- Xây dựng `PriceSpreadAuditor` (`src/scrapers/price-spread-auditor.ts`) tích hợp vào Step 6 của `src/index.ts`, tự động tính toán độ lệch giá tuyệt đối (`spreadVnd = maxPrice - minPrice`), tỷ lệ chênh lệch (`spreadPct`), sàn rẻ nhất và sàn đắt nhất cho toàn bộ `36/36` Master Products trên 10 sàn, đồng bộ vào `key_specs.price_spread_audit` trên `rate.master_products`.
- Mở rộng `getCrossPlatformDealRadar` (`apps/web/src/server/rate/deals.ts`) và giao diện Chrome Side Panel (`apps/extension/src/sidepanel/sidepanel.ts`) hiển thị bảng **📊 Chênh Lệch Giá Đa Sàn Lớn Nhất (`priceSpreadLeaders`)**.
- [x] **5.19. Một Nguồn Sự Thật Cho Engine Open-Core: Rate-Services Dùng `@repo/crawlers` & `@repo/shared-data` Qua Git Submodule `vendor/rate` (Issue #103, PR #105 & PR #107 bên `DOS/Rate`, Issue #14 & PR #15 bên `DOS/Rate-Services`):**
- Kiểm toán độ lệch giữa 2 bản copy engine: sau khi chuẩn hóa format bằng prettier, bản copy trong `DOS/Rate-Services` không còn dòng logic nào mà `DOS/Rate` thiếu (`DOS/Rate` là superset), nên xóa bản copy không làm mất logic.
- Chuẩn hóa entry point build cho `@repo/crawlers` (`rootDir: ./src`, `declaration: true`, `exports` tường minh cho `.`, `./shopee/arena-spec-syncer`, `./shopee/flagship-seed-data`; các script test chạy ngay khi import được giữ private, PR #105) và thêm smoke test `pnpm --filter @repo/crawlers test:dist` vào CI để chặn hồi quy (PR #107).
- Sửa lỗi build `@repo/crawlers` làm CI `dev` đỏ từ 5.10: 8 seed đa ngành hàng chưa có `expert_review_summary` / `expert_criteria_scores` nên 2 field này chuyển thành optional trong `FlagshipDeviceSeed` (web đã có fallback từ thông số).
3 changes: 2 additions & 1 deletion apps/importer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"dev": "tsx watch src/index.ts",
"lint": "eslint . --ext .ts",
"format": "prettier --write \"src/**/*.ts\"",
"test": "tsx src/shopee/__tests__/open-core-intelligence.test.ts && tsx src/shopee/test-review-analyzer.ts"
"test": "tsx src/shopee/__tests__/open-core-intelligence.test.ts && tsx src/shopee/test-review-analyzer.ts",
"test:dist": "node src/shopee/__tests__/dist-exports.smoke.mjs"
},
"keywords": [
"product-normalizer",
Expand Down
81 changes: 81 additions & 0 deletions apps/importer/src/shopee/__tests__/dist-exports.smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Smoke test for the built @repo/crawlers & @repo/shared-data entry points.
* Imports by package name (Node self-reference via "exports"), the same way
* DOS/Rate-Services consumes the engine from its vendor/rate checkout.
* Run after `pnpm --filter @repo/shared-data build && pnpm --filter @repo/crawlers build`.
*/
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { createRequire } from 'node:module';

const crawlers = await import('@repo/crawlers');
for (const name of [
'ProductNormalizer',
'detectCategorySlug',
'EntityMatcher',
'AiSpecEnricher',
'sanitizeSpecifications',
'AiReviewAnalyzer',
'computeReviewWeight',
'ShopeeIngestionPipeline',
]) {
assert.equal(
typeof crawlers[name],
'function',
`@repo/crawlers is missing export ${name}`
);
}

const { ArenaSpecSyncer } =
await import('@repo/crawlers/shopee/arena-spec-syncer');
assert.equal(
typeof ArenaSpecSyncer,
'function',
'ArenaSpecSyncer subpath export is broken'
);

const { FLAGSHIP_DEVICES_TO_SEED } =
await import('@repo/crawlers/shopee/flagship-seed-data');
assert.ok(
FLAGSHIP_DEVICES_TO_SEED.length > 0,
'flagship-seed-data subpath export is empty'
);

const { ProductSpecificationsSchema } = await import('@repo/shared-data');
assert.equal(
typeof ProductSpecificationsSchema?.safeParse,
'function',
'@repo/shared-data is broken'
);

// Scripts that execute on import (test-ai-enricher, test-review-analyzer,
// run-ingestion-test) must stay private. Resolve only, never import them.
for (const script of [
'test-ai-enricher',
'test-review-analyzer',
'run-ingestion-test',
]) {
assert.throws(
() => import.meta.resolve(`@repo/crawlers/shopee/${script}`),
{ code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' },
`@repo/crawlers/shopee/${script} must not be exported`
);
}

// Type declarations must ship with the entry points.
const require = createRequire(import.meta.url);
const pkgDir = require
.resolve('@repo/crawlers/package.json')
.replace(/package\.json$/, '');
for (const dts of [
'dist/index.d.ts',
'dist/shopee/types.d.ts',
'dist/shopee/arena-spec-syncer.d.ts',
'dist/shopee/flagship-seed-data.d.ts',
]) {
assert.ok(fs.existsSync(pkgDir + dts), `Missing type declarations: ${dts}`);
}

console.log(
'✅ @repo/crawlers & @repo/shared-data dist entry points resolve by package name.'
);
Loading