diff --git a/docs/api-spec.md b/docs/api-spec.md
index cc0ae27..4b69256 100644
--- a/docs/api-spec.md
+++ b/docs/api-spec.md
@@ -424,7 +424,7 @@ OAuth 인가 코드로 로그인을 완료하고 사용자 정보와 토큰을
### 응답 필드
-`data`는 비교 대상 정책 배열이다. 각 원소는 `Policy` 엔티티 전체가 아니라 비교에 필요한 필드만 담은 값이며, **정책 상세 조회 응답의 부분집합으로 유지한다** — 같은 정책을 상세로 볼 때 감춰지는 값이 비교로 볼 때만 드러나면 안 되기 때문이다. 따라서 자격 판정용 내부 코드(`jobCodes`, `schoolCodes`, `majorCodes`, `specializationCodes`, `maritalStatusCode`)와 보류 필드, raw payload는 상세와 동일하게 노출하지 않는다.
+`data`는 비교 대상 정책 배열이다. 각 원소는 `Policy` 엔티티 전체가 아니라 비교에 필요한 필드만 담은 값이며, **정책 상세 조회 응답의 부분집합으로 유지한다** — 같은 정책을 상세로 볼 때 감춰지는 값이 비교로 볼 때만 드러나면 안 되기 때문이다. 비교표는 자격 원문(추가 자격조건·참여 제한사항 등)만 나란히 보여주면 되므로, 상세 응답이 노출하는 자격 판정용 내부 코드(`jobCodes`, `schoolCodes`, `majorCodes`, `specializationCodes`, `maritalStatusCode` — #92부터 정책 목록/상세 응답에 노출)와 보류 필드, raw payload는 비교 응답에는 포함하지 않는다.
| 필드 | 타입 | 내용 |
|---|---|---|
diff --git a/src/main/java/com/bop/youthpick/policy/dto/PolicyCardResponse.java b/src/main/java/com/bop/youthpick/policy/dto/PolicyCardResponse.java
index ff733a2..6c3dac0 100644
--- a/src/main/java/com/bop/youthpick/policy/dto/PolicyCardResponse.java
+++ b/src/main/java/com/bop/youthpick/policy/dto/PolicyCardResponse.java
@@ -9,6 +9,9 @@
*
*
{@code provinces}는 정책이 적용되는 시도명 목록(중복 제거·정렬)이다. 지역 매핑이 없으면 빈 리스트 — "전국/OO 외 N" 같은 표시 문구 조립은
* 프론트 책임이며, 상세({@link PolicyDetailResponse#regions()})와 같은 규칙을 쓴다.
+ *
+ *
자격 판정용 내부 코드(jobCodes/schoolCodes/majorCodes/specializationCodes/maritalStatusCode)는 목록 화면에서도
+ * 프론트가 추천 하드필터(결혼·전공·신분 등 자격조건으로 부적격 정책 제외)를 적용할 수 있도록 상세와 동일하게 노출한다(#92).
*/
public record PolicyCardResponse(
Long id,
@@ -17,6 +20,11 @@ public record PolicyCardResponse(
String description,
Integer minAge,
Integer maxAge,
+ String jobCodes,
+ String schoolCodes,
+ String maritalStatusCode,
+ String majorCodes,
+ String specializationCodes,
LocalDate applicationEndDate,
List provinces) {
@@ -28,6 +36,11 @@ public static PolicyCardResponse from(Policy policy, List provinces) {
policy.getDescription(),
policy.getMinAge(),
policy.getMaxAge(),
+ policy.getJobCodes(),
+ policy.getSchoolCodes(),
+ policy.getMaritalStatusCode(),
+ policy.getMajorCodes(),
+ policy.getSpecializationCodes(),
policy.getApplicationEndDate(),
provinces);
}
diff --git a/src/main/java/com/bop/youthpick/policy/dto/PolicyComparisonItemResponse.java b/src/main/java/com/bop/youthpick/policy/dto/PolicyComparisonItemResponse.java
index 2f0bf69..1488c6d 100644
--- a/src/main/java/com/bop/youthpick/policy/dto/PolicyComparisonItemResponse.java
+++ b/src/main/java/com/bop/youthpick/policy/dto/PolicyComparisonItemResponse.java
@@ -8,8 +8,9 @@
* 비교표 한 열(정책 한 건)의 응답. 자격요건 원문 노출용 필드를 나열해 프론트가 항목별로 나란히 비교할 수 있게 한다.
*
* 필드 구성은 {@link PolicyDetailResponse}의 부분집합으로 유지한다 — 같은 정책을 상세로 볼 때는 감춰지는 값이 비교로 볼 때만 드러나면 안 되기
- * 때문이다. 따라서 자격 판정용 내부 코드(jobCodes/schoolCodes/majorCodes/specializationCodes/maritalStatusCode)와 보류
- * 필드, raw payload는 상세와 동일하게 노출하지 않는다.
+ * 때문이다. 비교표는 자격 원문(추가 자격조건·참여 제한사항 등)만 나란히 보여주면 되므로, 상세가 노출하는 자격 판정용 내부 코드
+ * (jobCodes/schoolCodes/majorCodes/specializationCodes/maritalStatusCode, #92부터 상세에 노출)와 보류 필드, raw
+ * payload는 비교 응답에는 포함하지 않는다.
*/
public record PolicyComparisonItemResponse(
Long policyId,
diff --git a/src/main/java/com/bop/youthpick/policy/dto/PolicyDetailResponse.java b/src/main/java/com/bop/youthpick/policy/dto/PolicyDetailResponse.java
index 310b788..10f0437 100644
--- a/src/main/java/com/bop/youthpick/policy/dto/PolicyDetailResponse.java
+++ b/src/main/java/com/bop/youthpick/policy/dto/PolicyDetailResponse.java
@@ -4,7 +4,10 @@
import java.time.LocalDate;
import java.util.List;
-/** 정책 상세 조회 응답 (유저 대상). 자격 판정용 내부 코드·보류 필드·raw payload는 노출하지 않는다. */
+/**
+ * 정책 상세 조회 응답 (유저 대상). 자격 판정용 내부 코드(jobCodes/schoolCodes/incomeConditionCode 등)는 프론트가 추천
+ * 하드필터(결혼·전공·신분 등 자격조건으로 부적격 정책 제외)를 복구할 수 있도록 노출한다(#92). 보류 필드·raw payload는 계속 노출하지 않는다.
+ */
public record PolicyDetailResponse(
Long id,
String policyNo,
@@ -17,9 +20,14 @@ public record PolicyDetailResponse(
String organizationName,
Integer minAge,
Integer maxAge,
+ String jobCodes,
+ String schoolCodes,
String incomeConditionCode,
Integer incomeMaxAmount,
String incomeEtcContent,
+ String maritalStatusCode,
+ String majorCodes,
+ String specializationCodes,
String additionalQualification,
String participationRestriction,
String applicationPeriodType,
@@ -52,9 +60,14 @@ public static PolicyDetailResponse from(Policy policy, List regi
policy.getOrganizationName(),
policy.getMinAge(),
policy.getMaxAge(),
+ policy.getJobCodes(),
+ policy.getSchoolCodes(),
policy.getIncomeConditionCode(),
policy.getIncomeMaxAmount(),
policy.getIncomeEtcContent(),
+ policy.getMaritalStatusCode(),
+ policy.getMajorCodes(),
+ policy.getSpecializationCodes(),
policy.getAdditionalQualification(),
policy.getParticipationRestriction(),
policy.getApplicationPeriodType(),
diff --git a/src/test/java/com/bop/youthpick/policy/controller/PolicyControllerTest.java b/src/test/java/com/bop/youthpick/policy/controller/PolicyControllerTest.java
index 042ff5c..f0ba22b 100644
--- a/src/test/java/com/bop/youthpick/policy/controller/PolicyControllerTest.java
+++ b/src/test/java/com/bop/youthpick/policy/controller/PolicyControllerTest.java
@@ -48,9 +48,14 @@ class PolicyControllerTest {
"국토교통부",
19,
34,
+ "0013010",
+ "0043010",
null,
null,
null,
+ "0055010",
+ "0066010",
+ "0077010",
null,
null,
null,
@@ -83,6 +88,11 @@ void clearContext() {
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(1))
.andExpect(jsonPath("$.data.title").value("청년 월세 지원"))
+ .andExpect(jsonPath("$.data.jobCodes").value("0013010"))
+ .andExpect(jsonPath("$.data.schoolCodes").value("0043010"))
+ .andExpect(jsonPath("$.data.maritalStatusCode").value("0055010"))
+ .andExpect(jsonPath("$.data.majorCodes").value("0066010"))
+ .andExpect(jsonPath("$.data.specializationCodes").value("0077010"))
.andExpect(jsonPath("$.data.regions[0].regionCode").value("11680"))
.andExpect(jsonPath("$.data.regions[0].provinceName").value("서울특별시"))
.andExpect(jsonPath("$.data.regions[0].districtName").value("강남구"));
diff --git a/src/test/java/com/bop/youthpick/policy/service/PolicyServiceTest.java b/src/test/java/com/bop/youthpick/policy/service/PolicyServiceTest.java
index 4dfda4e..4f7cba4 100644
--- a/src/test/java/com/bop/youthpick/policy/service/PolicyServiceTest.java
+++ b/src/test/java/com/bop/youthpick/policy/service/PolicyServiceTest.java
@@ -75,6 +75,12 @@ void setUp() {
assertThat(response.id()).isEqualTo(1L);
assertThat(response.title()).isEqualTo("청년 월세 지원");
assertThat(response.regions()).containsExactly(new RegionResponse("11680", "서울특별시", "강남구"));
+ // #92: 자격 판정용 내부 코드도 상세 응답에 노출해 프론트 추천 하드필터를 복구할 수 있게 한다.
+ assertThat(response.jobCodes()).isEqualTo("0013010");
+ assertThat(response.schoolCodes()).isEqualTo("0043010");
+ assertThat(response.maritalStatusCode()).isEqualTo("0055010");
+ assertThat(response.majorCodes()).isEqualTo("0066010");
+ assertThat(response.specializationCodes()).isEqualTo("0077010");
}
@Test
@@ -175,6 +181,12 @@ void setUp() {
assertThat(card.id()).isEqualTo(1L);
assertThat(card.title()).isEqualTo("한 시도 정책");
assertThat(card.category()).isEqualTo("주거");
+ // #92: 목록(카드) 응답도 상세와 동일하게 자격 판정용 내부 코드를 노출해 프론트 추천 하드필터를 복구할 수 있게 한다.
+ assertThat(card.jobCodes()).isEqualTo("0013010");
+ assertThat(card.schoolCodes()).isEqualTo("0043010");
+ assertThat(card.maritalStatusCode()).isEqualTo("0055010");
+ assertThat(card.majorCodes()).isEqualTo("0066010");
+ assertThat(card.specializationCodes()).isEqualTo("0077010");
assertThat(page.getContent())
.extracting(PolicyCardResponse::provinces)
.containsExactly(
@@ -312,6 +324,11 @@ private Policy newPolicy(Long id, String title) {
ReflectionTestUtils.setField(policy, "id", id);
ReflectionTestUtils.setField(policy, "policyNo", "R2026" + id);
ReflectionTestUtils.setField(policy, "title", title);
+ ReflectionTestUtils.setField(policy, "jobCodes", "0013010");
+ ReflectionTestUtils.setField(policy, "schoolCodes", "0043010");
+ ReflectionTestUtils.setField(policy, "maritalStatusCode", "0055010");
+ ReflectionTestUtils.setField(policy, "majorCodes", "0066010");
+ ReflectionTestUtils.setField(policy, "specializationCodes", "0077010");
return policy;
}