Skip to content

feat(search): 가격 분포 facets(searchProductFacets) - #254

Merged
chanwoo7 merged 2 commits into
developfrom
feat/search-product-facets
Aug 30, 2026
Merged

feat(search): 가격 분포 facets(searchProductFacets)#254
chanwoo7 merged 2 commits into
developfrom
feat/search-product-facets

Conversation

@chanwoo7

Copy link
Copy Markdown
Member

배경

검색 결과 가격대 필터 시트(figma search/03)의 가격 분포 히스토그램 + 최저/최고가 대응(검색 화면 API 4/4, 마지막). #253 후속.

변경

  • searchProductFacets(input: { keyword!, eventCategoryIds, styleCategoryIds, regionIds }): 가격 조건을 제외한 검색 조건으로 표시가(sale ?? regular)를 모아 5,000원 고정 폭 버킷(0~70,000) + '70,000원 이상' 버킷으로 카운트. 빈 구간도 0으로 반환(FE 막대 자리 고정). minPrice/maxPrice(결과 없으면 null)·totalCount 동반.
  • 검색 where 빌더(buildProductSearchWhere) 재사용으로 목록·분포 조건 단일 소스.
  • 'N개 상품보기' 카운트는 searchProducts.totalCount 재사용(별도 쿼리 없음 — 확정 정책).

자체 판단(시안 외)

버킷 폭 5,000원·상한 70,000원(시안 슬라이더 눈금 기반, 상수로 분리). 메모리 집계(소규모 전제, 주석에 전환 여지 명시).

테스트

7건 추가. yarn validate 통과(207 suites).

검색 결과 가격대 필터 시트(figma search 03)의 분포 히스토그램·최저/최고가 대응
(검색 화면 API 4/4). 정책은 문답으로 확정.

- searchProductFacets(keyword, eventCategoryIds, styleCategoryIds, regionIds):
  가격 조건을 뺀 나머지 검색 조건으로 표시가(sale ?? regular)를 모아
  5,000원 고정 폭 버킷(0~70,000) + '70,000원 이상' 버킷으로 센다.
  빈 구간도 count 0으로 반환해 FE 막대 자리를 고정. min/max 표시가 동반(없으면 null).
- 버킷 폭 5,000원·상한 70,000원은 시안 슬라이더 눈금(2만~7만 이상) 기반 자체 판단(상수).
- 'N개 상품보기' 카운트는 searchProducts.totalCount 재사용(별도 API 없음 — 사용자 확정).
- 검색 where 빌더를 그대로 재사용해 목록·분포 조건 불일치를 차단. 메모리 집계는
  상품 수 소규모 전제(규모 증가 시 SQL FLOOR 그룹핑 전환 여지 주석).

테스트 7건: 버킷 경계(5,000 정확히 상위 버킷·상한 이상), 빈 구간 0, 폭/상한 파라미터,
분포·min/max·건수, 카테고리·지역 반영, 빈 결과, resolver 통합.
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f4e1fbb-11e4-42e9-9f6f-aef9978b74cd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🩺 NestJS Doctor — 90/100 (Excellent)

진단 288건 (error 0).

Category error warning info
architecture 0 0 9
correctness 0 140 0
performance 0 24 16
schema 0 0 86
security 0 13 0
architecture / security 상위 항목
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'IAuditLogRepository'.
  • warning security/security/no-exposed-env-vars: Direct 'process.env.NODE_ENV' access in 'AuthController'. Use ConfigService instead.
  • warning security/security/require-guards-on-endpoints: Endpoint 'start' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'callback' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'refresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'logout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogin' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerRefresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'devIssueToken' has no @UseGuards() at class or method level.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/conversation/repositories/conversation.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'ConversationRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/order/repositories/order.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'OrderRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/product/repositories/product.repository'.

오탐 포함 가능 · 기준 docs/guide/architecture-conventions.md

@github-actions

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

Unused exported types (2)
Duplicate exports (1)
전체 리포트
Unused exported types (2)
ProductSearchScope  type  src/features/product/index.ts:20:8
StoreSearchScope    type  src/features/store/index.ts:17:8  
Duplicate exports (1)
KEYWORD_RANK_SNAPSHOT_SIZE|MAX_POPULAR_KEYWORDS_LIMIT  src/features/search/constants/search.constants.ts

청소 후보(오탐 가능) · 기준 docs/guide/architecture-conventions.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85021e69bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +114
minPrice: prices.length > 0 ? Math.min(...prices) : null,
maxPrice: prices.length > 0 ? Math.max(...prices) : null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid spreading every matched price into Math.min/max

When a broad facet query matches roughly 125,000 or more products under the deployed Node 24 runtime, Math.min(...prices) and Math.max(...prices) exceed V8's function-argument limit and throw RangeError: Maximum call stack size exceeded, turning this public query into a 500 response. Compute both extrema during the existing iteration/reduction, or aggregate them in the repository, so result cardinality does not determine whether the query succeeds.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영: Math.min/max spread를 순회 집계로 교체(V8 인자 한도 방어).

@codecov

codecov Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.96970% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...eatures/product/services/product-search.service.ts 88.88% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.88% 5482/5601
🟢 Branches 94.08% 2162/2298
🟢 Functions 96.59% 1077/1115
🟢 Lines 98.46% 4978/5056

Test suite run success

1772 tests passing in 207 suites.

Report generated by 🧪jest coverage report action from 9df3c95

Math.min/max(...prices)는 V8 인자 한도(약 12만+)에서 RangeError — 순회 집계로 방어.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df3c95336

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return counts.map((count, i) => ({
minPrice: i === bucketCount ? max : i * size,
maxPrice: i === bucketCount ? null : Math.min((i + 1) * size, max),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make bucket bounds match inclusive search filters

When a client uses a bucket's returned maxPrice in SearchProductsInput, the histogram and result count disagree at every boundary: this code defines the bucket as [minPrice, maxPrice) and assigns a 10,000원 product to the 10,000–15,000 bucket, while buildProductSearchWhere applies the search maximum with lte, so the same product is also returned for the 5,000–10,000 selection. Return bounds compatible with the inclusive filter (or change the filter/API contract consistently) so bucket counts match the products shown.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

미반영: 버킷은 가격 슬라이더 배경의 분포 시각화용이지 클릭 필터 계약이 아님(figma 03 — 슬라이더가 임의 min/max를 보내고, 필터바 프리셋 경계는 FE 정의). half-open [min, max)는 히스토그램 표준 규약이고 SDL 주석에 명시됨. 경계 1건의 시각적 오차는 수용, 버킷을 필터 프리셋으로 쓰는 요구가 생기면 그때 계약을 맞춘다.

@chanwoo7
chanwoo7 merged commit 66aa80a into develop Aug 30, 2026
11 checks passed
@chanwoo7
chanwoo7 deleted the feat/search-product-facets branch August 30, 2026 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant