diff --git a/.gitignore b/.gitignore index 8717a66..c6dc98a 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ __pycache__/ # Large local RAG corpus packages/agent/data/corpus/*.jsonl !packages/agent/data/corpus/*fixture*.jsonl +packages/agent/data/corpus/*.summary.json + +# Local browser QA artifacts +.playwright-mcp/ diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-01.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-01.yaml" new file mode 100644 index 0000000..cfb4305 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-01.yaml" @@ -0,0 +1,39 @@ +code: 10공수01-01 +title: 다항식의 연산 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 다항식의 연산의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 다항식의 연산의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-02.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-02.yaml" new file mode 100644 index 0000000..c952b20 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-02.yaml" @@ -0,0 +1,39 @@ +code: 10공수01-02 +title: 나머지정리 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 나머지정리의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 나머지정리의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-03.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-03.yaml" new file mode 100644 index 0000000..544cf10 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-03.yaml" @@ -0,0 +1,39 @@ +code: 10공수01-03 +title: 인수분해 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 인수분해의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 인수분해의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-04.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-04.yaml" new file mode 100644 index 0000000..8fe31f3 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-04.yaml" @@ -0,0 +1,39 @@ +code: 10공수01-04 +title: 복소수와 이차방정식 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 복소수와 이차방정식의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 복소수와 이차방정식의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-05.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-05.yaml" new file mode 100644 index 0000000..01e8ad5 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23001-05.yaml" @@ -0,0 +1,39 @@ +code: 10공수01-05 +title: 이차방정식과 이차함수 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 이차방정식과 이차함수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 이차방정식과 이차함수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-01.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-01.yaml" new file mode 100644 index 0000000..222c3db --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-01.yaml" @@ -0,0 +1,37 @@ +code: 10공수02-01 +title: 직선의 방정식 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 직선의 방정식의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 직선의 방정식의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-02.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-02.yaml" new file mode 100644 index 0000000..c241a55 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23002-02.yaml" @@ -0,0 +1,37 @@ +code: 10공수02-02 +title: 원의 방정식 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 원의 방정식의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 원의 방정식의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-01.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-01.yaml" new file mode 100644 index 0000000..2af381e --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-01.yaml" @@ -0,0 +1,37 @@ +code: 10공수03-01 +title: 집합 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - logical_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 집합의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 집합의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-02.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-02.yaml" new file mode 100644 index 0000000..217227e --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23003-02.yaml" @@ -0,0 +1,37 @@ +code: 10공수03-02 +title: 명제 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - logical_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 명제의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 명제의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-01.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-01.yaml" new file mode 100644 index 0000000..f70a0b3 --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-01.yaml" @@ -0,0 +1,39 @@ +code: 10공수04-01 +title: 함수 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - function_interpretation + forbidden: [] +evaluation_dimensions: + - id: A + description: 함수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 함수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-02.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-02.yaml" new file mode 100644 index 0000000..23c90ad --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23004-02.yaml" @@ -0,0 +1,39 @@ +code: 10공수04-02 +title: 유리함수와 무리함수 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - function_interpretation + forbidden: [] +evaluation_dimensions: + - id: A + description: 유리함수와 무리함수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 유리함수와 무리함수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-01.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-01.yaml" new file mode 100644 index 0000000..23033dd --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-01.yaml" @@ -0,0 +1,39 @@ +code: 10공수05-01 +title: 경우의 수 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - counting_or_data_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 경우의 수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 경우의 수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-02.yaml" "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-02.yaml" new file mode 100644 index 0000000..87160be --- /dev/null +++ "b/packages/agent/data/achievement-standards/10\352\263\265\354\210\23005-02.yaml" @@ -0,0 +1,39 @@ +code: 10공수05-02 +title: 순열과 조합 +school_level: high +grade: null +techniques: + required_at_least_one_of: + - counting_or_data_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 순열과 조합의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 순열과 조합의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23001-02.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23001-02.yaml" new file mode 100644 index 0000000..d1a02ce --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23001-02.yaml" @@ -0,0 +1,37 @@ +code: 9수01-02 +title: 정수와 유리수 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - calculation + forbidden: [] +evaluation_dimensions: + - id: A + description: 정수와 유리수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 정수와 유리수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23001-03.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23001-03.yaml" new file mode 100644 index 0000000..372917b --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23001-03.yaml" @@ -0,0 +1,37 @@ +code: 9수01-03 +title: 유리수의 사칙연산 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - calculation + forbidden: [] +evaluation_dimensions: + - id: A + description: 유리수의 사칙연산의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 유리수의 사칙연산의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23001-04.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23001-04.yaml" new file mode 100644 index 0000000..54c90a5 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23001-04.yaml" @@ -0,0 +1,37 @@ +code: 9수01-04 +title: 유리수와 순환소수 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - calculation + forbidden: [] +evaluation_dimensions: + - id: A + description: 유리수와 순환소수의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 유리수와 순환소수의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23001-06.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23001-06.yaml" new file mode 100644 index 0000000..c616bc6 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23001-06.yaml" @@ -0,0 +1,37 @@ +code: 9수01-06 +title: 근호를 포함한 식의 계산 +school_level: middle +grade: 3 +techniques: + required_at_least_one_of: + - calculation + forbidden: [] +evaluation_dimensions: + - id: A + description: 근호를 포함한 식의 계산의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 근호를 포함한 식의 계산의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23002-02.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23002-02.yaml" new file mode 100644 index 0000000..2038ea0 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23002-02.yaml" @@ -0,0 +1,39 @@ +code: 9수02-02 +title: 일차식의 계산 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 일차식의 계산의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 일차식의 계산의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23002-04.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23002-04.yaml" new file mode 100644 index 0000000..2bebdac --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23002-04.yaml" @@ -0,0 +1,39 @@ +code: 9수02-04 +title: 일차방정식의 활용 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 일차방정식의 활용의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 일차방정식의 활용의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23002-05.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23002-05.yaml" new file mode 100644 index 0000000..55b631d --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23002-05.yaml" @@ -0,0 +1,39 @@ +code: 9수02-05 +title: 식의 계산 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - algebraic_transformation + forbidden: [] +evaluation_dimensions: + - id: A + description: 식의 계산의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 식의 계산의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23003-01.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23003-01.yaml" new file mode 100644 index 0000000..c8772da --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23003-01.yaml" @@ -0,0 +1,39 @@ +code: 9수03-01 +title: 함수의 개념 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - function_interpretation + forbidden: [] +evaluation_dimensions: + - id: A + description: 함수의 개념의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 함수의 개념의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23003-03.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23003-03.yaml" new file mode 100644 index 0000000..8d959dd --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23003-03.yaml" @@ -0,0 +1,39 @@ +code: 9수03-03 +title: 일차함수의 활용 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - function_interpretation + forbidden: [] +evaluation_dimensions: + - id: A + description: 일차함수의 활용의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 일차함수의 활용의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-01.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-01.yaml" new file mode 100644 index 0000000..6ef6c47 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-01.yaml" @@ -0,0 +1,37 @@ +code: 9수04-01 +title: 기본 도형과 작도 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 기본 도형과 작도의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 기본 도형과 작도의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-02.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-02.yaml" new file mode 100644 index 0000000..097b6df --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-02.yaml" @@ -0,0 +1,37 @@ +code: 9수04-02 +title: 삼각형의 성질 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 삼각형의 성질의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 삼각형의 성질의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-03.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-03.yaml" new file mode 100644 index 0000000..69d1b59 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-03.yaml" @@ -0,0 +1,37 @@ +code: 9수04-03 +title: 사각형의 성질 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 사각형의 성질의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 사각형의 성질의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-04.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-04.yaml" new file mode 100644 index 0000000..6d86e84 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-04.yaml" @@ -0,0 +1,37 @@ +code: 9수04-04 +title: 도형의 닮음 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 도형의 닮음의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 도형의 닮음의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-06.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-06.yaml" new file mode 100644 index 0000000..353132b --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-06.yaml" @@ -0,0 +1,37 @@ +code: 9수04-06 +title: 원과 직선의 위치 관계 +school_level: middle +grade: 3 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 원과 직선의 위치 관계의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 원과 직선의 위치 관계의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23004-07.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23004-07.yaml" new file mode 100644 index 0000000..e9e6466 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23004-07.yaml" @@ -0,0 +1,37 @@ +code: 9수04-07 +title: 원주각 +school_level: middle +grade: 3 +techniques: + required_at_least_one_of: + - geometric_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 원주각의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 원주각의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: inverse_question + hint: 조건과 결론의 관계를 바꾸어 묻는다. diff --git "a/packages/agent/data/achievement-standards/9\354\210\23005-01.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23005-01.yaml" new file mode 100644 index 0000000..51185b3 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23005-01.yaml" @@ -0,0 +1,39 @@ +code: 9수05-01 +title: 자료의 정리와 해석 +school_level: middle +grade: 1 +techniques: + required_at_least_one_of: + - counting_or_data_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 자료의 정리와 해석의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 자료의 정리와 해석의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23005-02.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23005-02.yaml" new file mode 100644 index 0000000..c00f6a2 --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23005-02.yaml" @@ -0,0 +1,39 @@ +code: 9수05-02 +title: 경우의 수와 확률 +school_level: middle +grade: 2 +techniques: + required_at_least_one_of: + - counting_or_data_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 경우의 수와 확률의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 경우의 수와 확률의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git "a/packages/agent/data/achievement-standards/9\354\210\23005-03.yaml" "b/packages/agent/data/achievement-standards/9\354\210\23005-03.yaml" new file mode 100644 index 0000000..ce5e74f --- /dev/null +++ "b/packages/agent/data/achievement-standards/9\354\210\23005-03.yaml" @@ -0,0 +1,39 @@ +code: 9수05-03 +title: 대푯값과 산포도 +school_level: middle +grade: 3 +techniques: + required_at_least_one_of: + - counting_or_data_reasoning + forbidden: [] +evaluation_dimensions: + - id: A + description: 대푯값과 산포도의 핵심 개념과 정의를 정확히 적용한다. + must_preserve: true + - id: B + description: 대푯값과 산포도의 표준 풀이 절차를 단계별로 수행한다. + must_preserve: true + - id: C + description: 구한 결과가 원래 조건을 만족하는지 확인한다. + must_preserve: false +difficulty_range: + - easy + - medium +problem_types_supported: + - objective + - short_answer +structural_transforms: + - kind: coefficient_swap + range: [-10, 10] + exclude_zero: true + - kind: variable_rename + allowed: + - x + - y + - a + - b +conceptual_transforms: + - kind: rephrase_as_word_problem + context: + - 수량 관계 + - 조건 해석 diff --git a/packages/agent/scripts/build-expanded-rag-corpus.py b/packages/agent/scripts/build-expanded-rag-corpus.py new file mode 100644 index 0000000..81e4b2e --- /dev/null +++ b/packages/agent/scripts/build-expanded-rag-corpus.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python3 +# pyright: reportAny=false, reportExplicitAny=false, reportUnknownMemberType=false, reportUnknownVariableType=false, reportUnusedCallResult=false +"""Build local-only expanded OpenMath RAG corpora from AI Hub data. + +Inputs are intentionally outside the git working tree by default: +- 66,800 cleaned problem records: ~/Downloads/AI_HUB_data/AI_HUB/rag_problem_generation_dataset +- AI Hub 479 high-school OCR labels: ~/Downloads/AI_HUB_data/downloads_479_high_printed + +Outputs are JSON/JSONL files under packages/agent/data/corpus/. That directory is +git-ignored except for the tiny fixture, so generated corpus data stays local. +""" + +from __future__ import annotations + +import argparse +import json +import re +import zipfile +from collections import Counter +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[3] +AGENT_DIR = ROOT / "packages" / "agent" +DEFAULT_PROBLEM_DATASET = Path.home() / "Downloads/AI_HUB_data/AI_HUB/rag_problem_generation_dataset" +DEFAULT_OCR_DATASET = Path.home() / "Downloads/AI_HUB_data/downloads_479_high_printed" +DEFAULT_OUTPUT = AGENT_DIR / "data/corpus/openmath_rag_records_expanded.jsonl" +DEFAULT_OCR_OUTPUT = AGENT_DIR / "data/corpus/openmath_high_ocr_records.jsonl" +DEFAULT_REPORT = AGENT_DIR / "data/corpus/openmath_rag_records_expanded.summary.json" + + +CHAPTERS_479: dict[str, tuple[str, int | None, str]] = { + "1010": ("도형의 이동", 1, "고등수학(상/하)"), + "1011": ("다항식의 연산", 1, "고등수학(상)"), + "1012": ("나머지정리", 1, "고등수학(상)"), + "1013": ("인수분해", 1, "고등수학(상)"), + "1014": ("복소수와 이차방정식", 1, "고등수학(상)"), + "1015": ("이차방정식과 이차함수", 1, "고등수학(상)"), + "1016": ("여러가지 방정식과 부등식", 1, "고등수학(상)"), + "1017": ("평면좌표", 1, "고등수학(상)"), + "1018": ("직선의 방정식", 1, "고등수학(상)"), + "1019": ("원의 방정식", 1, "고등수학(상)"), + "1021": ("집합", 1, "고등수학(하)"), + "1022": ("명제", 1, "고등수학(하)"), + "1023": ("함수", 1, "고등수학(하)"), + "1024": ("유리함수와 무리함수", 1, "고등수학(하)"), + "1025": ("경우의 수", 1, "고등수학(하)"), + "1026": ("순열과 조합", 1, "고등수학(하)"), + "1111": ("지수와 로그", 2, "수학Ⅰ"), + "1112": ("지수함수와 로그함수", 2, "수학Ⅰ"), + "1113": ("삼각함수", 2, "수학Ⅰ"), + "1114": ("등차수열과 등비수열", 2, "수학Ⅰ"), + "1115": ("수열의 합", 2, "수학Ⅰ"), + "1116": ("수학적 귀납법", 2, "수학Ⅰ"), + "1121": ("함수의 극한", 2, "수학Ⅱ"), + "1122": ("함수의 연속", 2, "수학Ⅱ"), + "1123": ("미분계수", 2, "수학Ⅱ"), + "1124": ("도함수", 2, "수학Ⅱ"), + "1125": ("도함수의 활용", 2, "수학Ⅱ"), + "1126": ("부정적분", 2, "수학Ⅱ"), + "1127": ("정적분", 2, "수학Ⅱ"), + "1128": ("정적분의 활용", 2, "수학Ⅱ"), + "1211": ("수열의 극한", 3, "미적분"), + "1212": ("급수", 3, "미적분"), + "1213": ("여러 가지 함수의 미분", 3, "미적분"), + "1214": ("여러 가지 미분법", 3, "미적분"), + "1215": ("도함수의 활용", 3, "미적분"), + "1216": ("여러 가지 적분법", 3, "미적분"), + "1217": ("정적분의 활용", 3, "미적분"), + "1221": ("순열과 조합, 이항정리", 3, "확률과 통계"), + "1222": ("확률의 뜻과 적용", 3, "확률과 통계"), + "1223": ("조건부확률", 3, "확률과 통계"), + "1224": ("확률분포", 3, "확률과 통계"), + "1225": ("통계적 추정", 3, "확률과 통계"), + "1231": ("이차곡선", 3, "기하"), + "1232": ("벡터의 연산", 3, "기하"), + "1233": ("평면벡터의 성분과 내적", 3, "기하"), + "1234": ("직선과 평면, 정사영", 3, "기하"), + "1235": ("공간좌표", 3, "기하"), + } + + +OPENMATH_TOPICS: dict[str, tuple[str, str, int | None, str]] = { + "9수01-01": ("middle", "소인수분해", 1, "수와 연산"), + "9수01-02": ("middle", "정수와 유리수", 1, "수와 연산"), + "9수01-03": ("middle", "유리수의 사칙연산", 1, "수와 연산"), + "9수02-01": ("middle", "문자의 사용과 식의 값", 1, "문자와 식"), + "9수02-02": ("middle", "일차식의 계산", 1, "문자와 식"), + "9수02-03": ("middle", "일차방정식", 1, "문자와 식"), + "9수02-04": ("middle", "일차방정식의 활용", 1, "문자와 식"), + "9수03-01": ("middle", "함수의 개념", 1, "함수"), + "9수04-01": ("middle", "기본 도형과 작도", 1, "기하"), + "9수05-01": ("middle", "자료의 정리와 해석", 1, "확률과 통계"), + "9수01-04": ("middle", "유리수와 순환소수", 2, "수와 연산"), + "9수02-05": ("middle", "식의 계산", 2, "문자와 식"), + "9수02-06": ("middle", "일차부등식", 2, "문자와 식"), + "9수02-07": ("middle", "연립일차방정식", 2, "문자와 식"), + "9수03-02": ("middle", "일차함수와 그래프", 2, "함수"), + "9수03-03": ("middle", "일차함수의 활용", 2, "함수"), + "9수04-02": ("middle", "삼각형의 성질", 2, "기하"), + "9수04-03": ("middle", "사각형의 성질", 2, "기하"), + "9수04-04": ("middle", "도형의 닮음", 2, "기하"), + "9수05-02": ("middle", "경우의 수와 확률", 2, "확률과 통계"), + "9수01-05": ("middle", "제곱근과 실수", 3, "수와 연산"), + "9수01-06": ("middle", "근호를 포함한 식의 계산", 3, "수와 연산"), + "9수02-08": ("middle", "다항식의 곱셈과 인수분해", 3, "문자와 식"), + "9수02-09": ("middle", "이차방정식", 3, "문자와 식"), + "9수02-10": ("middle", "이차방정식의 활용", 3, "문자와 식"), + "9수03-04": ("middle", "이차함수와 그래프", 3, "함수"), + "9수04-05": ("middle", "삼각비", 3, "기하"), + "9수04-06": ("middle", "원과 직선의 위치 관계", 3, "기하"), + "9수04-07": ("middle", "원주각", 3, "기하"), + "9수05-03": ("middle", "대푯값과 산포도", 3, "확률과 통계"), + "10공수01-01": ("high", "다항식의 연산", None, "공통수학"), + "10공수01-02": ("high", "나머지정리", None, "공통수학"), + "10공수01-03": ("high", "인수분해", None, "공통수학"), + "10공수01-04": ("high", "복소수와 이차방정식", None, "공통수학"), + "10공수01-05": ("high", "이차방정식과 이차함수", None, "공통수학"), + "10공수02-01": ("high", "직선의 방정식", None, "공통수학"), + "10공수02-02": ("high", "원의 방정식", None, "공통수학"), + "10공수03-01": ("high", "집합", None, "공통수학"), + "10공수03-02": ("high", "명제", None, "공통수학"), + "10공수04-01": ("high", "함수", None, "공통수학"), + "10공수04-02": ("high", "유리함수와 무리함수", None, "공통수학"), + "10공수05-01": ("high", "경우의 수", None, "공통수학"), + "10공수05-02": ("high", "순열과 조합", None, "공통수학"), +} + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--problem-dataset", type=Path, default=DEFAULT_PROBLEM_DATASET) + parser.add_argument("--ocr-dataset", type=Path, default=DEFAULT_OCR_DATASET) + parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) + parser.add_argument("--ocr-output", type=Path, default=DEFAULT_OCR_OUTPUT) + parser.add_argument("--report", type=Path, default=DEFAULT_REPORT) + parser.add_argument("--skip-ocr", action="store_true", help="Do not rebuild the separate AI Hub 479 OCR corpus.") + args = parser.parse_args() + + args.output.parent.mkdir(parents=True, exist_ok=True) + problem_summary = build_problem_corpus(args.problem_dataset, args.output) + ocr_summary = None if args.skip_ocr else build_ocr_corpus(args.ocr_dataset, args.ocr_output) + report = {"problem_corpus": problem_summary, "ocr_corpus": ocr_summary} + args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"output": str(args.output), "ocr_output": str(args.ocr_output), "report": str(args.report), **report}, ensure_ascii=False, indent=2)) + + +def build_problem_corpus(dataset_dir: Path, output: Path) -> dict[str, Any]: + metadata_path = dataset_dir / "problem_metadata_image.jsonl" + retrieval_path = dataset_dir / "manifests/retrieval_index.jsonl" + if not metadata_path.exists() or not retrieval_path.exists(): + raise FileNotFoundError(f"Missing cleaned problem dataset files under {dataset_dir}") + + retrieval_by_id = load_retrieval_index(retrieval_path) + summary: dict[str, Any] = {"records": 0, "by_level": Counter(), "by_grade_label": Counter(), "by_dataset": Counter(), "by_topic": Counter()} + with metadata_path.open(encoding="utf-8") as source, output.open("w", encoding="utf-8") as sink: + for line in source: + raw = json.loads(line) + record = to_openmath_problem_record(raw, retrieval_by_id.get(raw["problem_id"])) + sink.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n") + summary["records"] += 1 + summary["by_level"][record["curriculum"]["school_level"]] += 1 + summary["by_grade_label"][grade_label(record)] += 1 + summary["by_dataset"][record["id"]["source_dataset"]] += 1 + summary["by_topic"][f'{record["curriculum"].get("topic_code")} {record["curriculum"]["topic_name"]}'] += 1 + return counter_summary(summary) + + +def load_retrieval_index(path: Path) -> dict[str, dict[str, Any]]: + rows = {} + with path.open(encoding="utf-8") as handle: + for line in handle: + row = json.loads(line) + rows[row["problem_id"]] = row + return rows + + +def to_openmath_problem_record(raw: dict[str, Any], retrieval: dict[str, Any] | None) -> dict[str, Any]: + source = raw["source"] + problem = raw["problem"] + anchor = raw.get("metadata", {}).get("concept_anchor", {}) + filters = retrieval.get("filters", {}) if retrieval else {} + paths = retrieval.get("paths", {}) if retrieval else {} + generation_hints = retrieval.get("generation_hints", {}) if retrieval else {} + school_level = anchor.get("school_level") or infer_school_level(problem.get("grade_label")) + grade = anchor.get("grade") + if grade is None: + grade = infer_grade(problem.get("grade_label")) + raw_topic_code = problem.get("achievement_standard") or problem.get("topic_code") or anchor.get("topic_code") + raw_topic_name = problem.get("topic_name") or anchor.get("topic_name") or "수학" + normalized_topic = normalize_openmath_topic(raw_topic_code, raw_topic_name, school_level) + topic_code = normalized_topic[0] if normalized_topic else raw_topic_code + topic_name = normalized_topic[1] if normalized_topic else raw_topic_name + if normalized_topic and normalized_topic[2] is not None: + grade = normalized_topic[2] + question = problem.get("question_text") or "" + answer = problem.get("answer_text") or "" + explanation = problem.get("explanation_text") + retrieval_text = retrieval.get("retrieval_text") if retrieval else None + embedding_text = retrieval.get("embedding_text") if retrieval else None + if not retrieval_text: + retrieval_text = "\n".join(part for part in [f"학년: {problem.get('grade_label')}", f"단원: {topic_name}", f"문항형식: {problem.get('problem_type')}", f"난이도: {problem.get('difficulty')}", f"문제: {question}", f"정답: {answer}", f"해설: {explanation or ''}"] if part) + if not embedding_text: + embedding_text = "\n".join(part for part in [topic_name, question, answer, explanation or ""] if part) + + return { + "schema_version": "openmath-rag-record-v1", + "id": { + "problem_id": f"real-{source['dataset_id']}-{source['split']}-{source.get('item_id', raw['problem_id'])}", + "source_dataset": str(source["dataset_id"]), + "split": source["split"], + "item_id": source.get("item_id"), + "source_label_type": source.get("source_label_type", "problem_label"), + }, + "curriculum": { + "school_level": school_level, + "grade": grade, + "semester": None, + "topic_code": topic_code, + "topic_name": topic_name, + "achievement_standard": problem.get("achievement_standard"), + "achievement_confidence": 0.95 if problem.get("achievement_standard") else 0.6, + "course": normalized_topic[3] if normalized_topic else None, + }, + "problem": { + "question_text": question, + "choice_blocks": problem.get("choice_blocks") or None, + "answer_text": answer, + "explanation_text": explanation, + "problem_type": normalize_problem_type(problem.get("problem_type")), + "difficulty": normalize_difficulty(problem.get("difficulty")), + }, + "taxonomy": { + "primary_type_id": filters.get("primary_type_id"), + "type_ids": filters.get("type_ids", []), + "primary_subtype_id": filters.get("primary_subtype_id"), + "subtype_ids": filters.get("subtype_ids", []), + }, + "rag": {"retrieval_text": retrieval_text, "embedding_text": embedding_text}, + "media": {"question_image_relpath": paths.get("problem_image"), "answer_image_relpath": paths.get("answer_image")}, + "quality": {"is_usable": bool(question and answer), "source": "local-cleaned-aihub-problem-dataset", "generation_hints": generation_hints}, + "source_trace": {"original_question_json_relpath": paths.get("record_json"), "original_answer_json_relpath": paths.get("record_json")}, +} + + +def normalize_openmath_topic(raw_code: Any, raw_name: Any, school_level: str) -> tuple[str, str, int | None, str] | None: + source = clean_text(f"{raw_code or ''} {raw_name or ''}") + bracket_code = re.search(r"\[(9수|10수학)(\d{2})-(\d{2})\]", source) + if bracket_code: + prefix = "10공수" if bracket_code.group(1) == "10수학" else bracket_code.group(1) + code = f"{prefix}{bracket_code.group(2)}-{bracket_code.group(3)}" + topic = OPENMATH_TOPICS.get(code) + if topic: + return code, topic[1], topic[2], topic[3] + + if school_level == "middle" and any(token in source for token in ["대푯값", "산포도", "평균", "중앙값", "최빈값", "분산", "표준편차"]): + topic = OPENMATH_TOPICS["9수05-03"] + return "9수05-03", topic[1], topic[2], topic[3] + + candidates = [ + (code, topic) + for code, topic in OPENMATH_TOPICS.items() + if topic[0] == school_level and topic[1] in source + ] + if not candidates: + candidates = [ + (code, topic) + for code, topic in OPENMATH_TOPICS.items() + if topic[0] == school_level and topic_name_overlaps(topic[1], source) + ] + if not candidates: + return None + code, topic = max(candidates, key=lambda item: len(item[1][1])) + return code, topic[1], topic[2], topic[3] + + +def topic_name_overlaps(topic_name: str, source: str) -> bool: + tokens = [token for token in re.split(r"[^0-9A-Za-z가-힣]+", topic_name) if len(token) >= 2] + if not tokens: + return False + return sum(1 for token in tokens if token in source) >= max(1, min(2, len(tokens))) + + +def build_ocr_corpus(dataset_dir: Path, output: Path) -> dict[str, Any]: + label_zips = sorted(dataset_dir.rglob("*라벨링데이터/*.zip")) + if not label_zips: + raise FileNotFoundError(f"No AI Hub 479 label zip files under {dataset_dir}") + summary: dict[str, Any] = {"records": 0, "by_grade": Counter(), "by_course": Counter(), "by_chapter": Counter()} + with output.open("w", encoding="utf-8") as sink: + for zip_path in label_zips: + split = "validation" if "Validation" in str(zip_path) or zip_path.name.startswith("VL_") else "train" + with zipfile.ZipFile(zip_path) as archive: + for name in archive.namelist(): + if not name.endswith(".json"): + continue + raw = json.loads(archive.read(name).decode("utf-8")) + record = to_openmath_ocr_record(raw, split, name, zip_path) + if record is None: + continue + sink.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n") + summary["records"] += 1 + summary["by_grade"][str(record["curriculum"]["grade"])] += 1 + summary["by_course"][record["curriculum"]["course"]] += 1 + summary["by_chapter"][f'{record["curriculum"]["topic_code"]} {record["curriculum"]["topic_name"]}'] += 1 + return counter_summary(summary) + + +def to_openmath_ocr_record(raw: dict[str, Any], split: str, entry_name: str, zip_path: Path) -> dict[str, Any] | None: + chapter_id = str(raw.get("chapter_id", "")) + chapter = CHAPTERS_479.get(chapter_id) + if not chapter: + return None + topic_name, grade, course = chapter + segments = raw.get("segments", []) + equations = [clean_text(seg.get("equation", "")) for seg in segments if clean_text(seg.get("equation", ""))] + if not equations: + return None + segment_text = " ".join(equations) + item_id = Path(entry_name).stem + topic_code = f"AIHUB479-{chapter_id}" + retrieval_text = f"학년: 고등학교 {grade}학년\n과목: {course}\n단원: {topic_name}\n자료유형: OCR 수식/텍스트 라벨\n내용: {segment_text}" + return { + "schema_version": "openmath-rag-record-v1", + "id": { + "problem_id": f"ocr-479-{split}-{item_id}", + "source_dataset": "30", + "split": split, + "item_id": item_id, + "source_label_type": "ocr_label_479", + }, + "curriculum": { + "school_level": "high", + "grade": grade, + "semester": None, + "topic_code": topic_code, + "topic_name": topic_name, + "achievement_standard": topic_code, + "achievement_confidence": 0.4, + "course": course, + "chapter_id_479": chapter_id, + }, + "problem": { + "question_text": segment_text, + "choice_blocks": None, + "answer_text": "OCR_LABEL_ONLY", + "explanation_text": None, + "problem_type": "subjective", + "difficulty": "medium", + }, + "taxonomy": {"primary_type_id": "OPT_FORMULA_SYMBOLIC", "type_ids": ["OPT_FORMULA_SYMBOLIC"], "primary_subtype_id": "OPS_SYMBOLIC_EXPRESSION_PROMPT", "subtype_ids": ["OPS_SYMBOLIC_EXPRESSION_PROMPT"]}, + "rag": {"retrieval_text": retrieval_text, "embedding_text": f"{course}\n{topic_name}\n{segment_text}"}, + "media": {"question_image_relpath": image_relpath_for_ocr(entry_name), "answer_image_relpath": None}, + "quality": {"is_usable": True, "source": "aihub-479-high-ocr", "zip": str(zip_path)}, + "source_trace": {"original_question_json_relpath": entry_name, "original_answer_json_relpath": None}, + } + + +def image_relpath_for_ocr(entry_name: str) -> str: + return re.sub(r"\.json$", ".png", entry_name) + + +def clean_text(value: Any) -> str: + return re.sub(r"\s+", " ", str(value)).strip() + + +def infer_school_level(grade_label: Any) -> str: + return "high" if "고등" in str(grade_label) else "middle" + + +def infer_grade(grade_label: Any) -> int | None: + match = re.search(r"([123])학년", str(grade_label)) + return int(match.group(1)) if match else None + + +def normalize_problem_type(value: Any) -> str: + value = str(value or "subjective") + return value if value in {"objective", "essay", "short_answer", "subjective"} else "subjective" + + +def normalize_difficulty(value: Any) -> str: + value = str(value or "medium") + return value if value in {"easy", "medium", "hard"} else "medium" + + +def grade_label(record: dict[str, Any]) -> str: + level = record["curriculum"]["school_level"] + grade = record["curriculum"].get("grade") + if level == "high" and grade is None: + return "고등학교 공통수학" + return f"{'고등학교' if level == 'high' else '중학교'} {grade}학년" + + +def counter_summary(summary: dict[str, Any]) -> dict[str, Any]: + return {key: (value.most_common() if isinstance(value, Counter) else value) for key, value in summary.items()} + + +if __name__ == "__main__": + main() diff --git a/packages/agent/src/schemas/generate-request.schema.ts b/packages/agent/src/schemas/generate-request.schema.ts index 554aa38..c093c50 100644 --- a/packages/agent/src/schemas/generate-request.schema.ts +++ b/packages/agent/src/schemas/generate-request.schema.ts @@ -24,7 +24,7 @@ export const GenerateRequestSchema = z.object({ mode: GenerateModeSchema.default("auto"), school_level: SchoolLevelSchema.default("middle"), - grade: z.union([z.literal(1), z.literal(2), z.literal(3)]), + grade: z.union([z.literal(1), z.literal(2), z.literal(3)]).nullable(), /** FE alias from `packages/web/hooks/use-verification-stream.ts`. */ topic: z.string().optional(), @@ -40,6 +40,13 @@ export const GenerateRequestSchema = z.object({ source_problem_text: z.string().optional(), }).superRefine((request, ctx) => { + if (request.school_level === "middle" && request.grade === null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Middle school requests require grade", + path: ["grade"], + }); + } if (request.topic === undefined && request.topic_code === undefined) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/packages/agent/src/schemas/generation-kind.schema.ts b/packages/agent/src/schemas/generation-kind.schema.ts index 0508d94..3975111 100644 --- a/packages/agent/src/schemas/generation-kind.schema.ts +++ b/packages/agent/src/schemas/generation-kind.schema.ts @@ -44,6 +44,19 @@ const TOPIC_KIND_BY_CODE: Readonly> = { "9수05-01": "statistics", "9수05-02": "probability", "9수05-03": "statistics", + "10공수01-01": "expression", + "10공수01-02": "expression", + "10공수01-03": "expression", + "10공수01-04": "equation", + "10공수01-05": "function", + "10공수02-01": "geometry", + "10공수02-02": "geometry", + "10공수03-01": "probability", + "10공수03-02": "expression", + "10공수04-01": "function", + "10공수04-02": "function", + "10공수05-01": "probability", + "10공수05-02": "probability", }; export function generationKindForTopic(topicCode: string): GenerationKind { diff --git a/packages/agent/src/schemas/strategy.schema.ts b/packages/agent/src/schemas/strategy.schema.ts index 1dbeb54..ddc67ba 100644 --- a/packages/agent/src/schemas/strategy.schema.ts +++ b/packages/agent/src/schemas/strategy.schema.ts @@ -65,7 +65,7 @@ export const StrategySchema = z.object({ code: z.string().regex(OBJECTIVE_CODE_PATTERN), // I-T1 title: z.string().min(1), school_level: SchoolLevelSchema, - grade: z.union([z.literal(1), z.literal(2), z.literal(3)]), + grade: z.union([z.literal(1), z.literal(2), z.literal(3)]).nullable(), techniques: z.object({ required_at_least_one_of: z.array(z.string()), diff --git a/packages/agent/tests/backend-contract.test.ts b/packages/agent/tests/backend-contract.test.ts index 68250fa..6e3d7e8 100644 --- a/packages/agent/tests/backend-contract.test.ts +++ b/packages/agent/tests/backend-contract.test.ts @@ -33,6 +33,27 @@ describe("FE-compatible generate contract", () => { expect(parsed.difficulty).toBe("medium"); }); + it("accepts high-school common-math requests from the web hook", () => { + const parsed = GenerateRequestSchema.parse({ + school_level: "high", + grade: null, + topic: "10공수01-01", + topic_name: "다항식의 연산", + mode: "structural", + dims: ["다항식의 표준 풀이 절차를 단계별로 수행"], + }); + + expect(parsed.school_level).toBe("high"); + expect(parsed.grade).toBeNull(); + expect(parsed.topic).toBe("10공수01-01"); + }); + + it("rejects middle-school requests without grade", () => { + expect(() => + GenerateRequestSchema.parse({ grade: null, topic: "9수02-09", mode: "structural" }), + ).toThrow(/Middle school requests require grade/); + }); + it("rejects requests without topic or topic_code", () => { expect(() => GenerateRequestSchema.parse({ grade: 3, mode: "structural" }), diff --git a/packages/agent/tests/rag-client.test.ts b/packages/agent/tests/rag-client.test.ts index d5ea4ff..c0f5154 100644 --- a/packages/agent/tests/rag-client.test.ts +++ b/packages/agent/tests/rag-client.test.ts @@ -117,6 +117,37 @@ describe("createInMemoryRagClient", () => { expect(results[0]?.item_id).toBe("111:train:linear-equation"); }); + it("matches high-school common math records with nullable grade", async () => { + const jsonlPath = await writeFixture([ + canonicalRecord({ + problem_id: "30:train:high-polynomial", + school_level: "high", + grade: null, + topic_name: "다항식의 연산", + achievement_code: "10공수01-01", + achievement_standard: "다항식의 덧셈과 뺄셈을 계산할 수 있다.", + question_text: "두 다항식의 합을 구하여라.", + answer_text: "x^{2}+3x+1", + problem_type: "short_answer", + difficulty: "medium", + confidence: 1, + }), + ]); + + const rag = createInMemoryRagClient({ jsonlPath }); + const results = await rag.search({ + school_level: "high", + grade: null, + topic_code: "10공수01-01", + topic_name: "다항식의 연산", + k: 3, + }); + + expect(results).toHaveLength(1); + expect(results[0]?.item_id).toBe("30:train:high-polynomial"); + expect(results[0]?.problem.grade).toBeNull(); + }); + it("rejects records whose mapped code matches but topic text contradicts the requested topic", async () => { const jsonlPath = await writeFixture([ canonicalRecord({ diff --git a/packages/agent/tests/strategy-loader.test.ts b/packages/agent/tests/strategy-loader.test.ts index b0f0e94..04abf9d 100644 --- a/packages/agent/tests/strategy-loader.test.ts +++ b/packages/agent/tests/strategy-loader.test.ts @@ -16,8 +16,8 @@ describe("createFsStrategyLoader", () => { const all = await loader.loadAll(); expect(all.map((strategy) => strategy.code)).toEqual( - expect.arrayContaining(["9수02-03", "9수02-07", "9수02-09", "9수01-05"]), + expect.arrayContaining(["9수02-03", "9수02-07", "9수02-09", "9수01-05", "10공수01-01"]), ); - expect(all).toHaveLength(12); + expect(all).toHaveLength(43); }); }); diff --git a/packages/web/app/app/new/export/page.tsx b/packages/web/app/app/new/export/page.tsx index d645a32..df737ca 100644 --- a/packages/web/app/app/new/export/page.tsx +++ b/packages/web/app/app/new/export/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { findTopic, parseGrade, pickFirst } from "../topic/data"; +import { findTopic, parseGrade, parseSchoolLevel, pickFirst } from "../topic/data"; import { generateMockResults, type ResultProblem } from "../result/mock"; import { ExportView } from "./view"; @@ -11,6 +11,7 @@ export const metadata: Metadata = { type Props = { searchParams: { grade?: string | string[]; + school?: string | string[]; topic?: string | string[]; mode?: string | string[]; dims?: string | string[]; @@ -35,6 +36,7 @@ function parseList(raw: string | null): string[] { } export default function ExportPage({ searchParams }: Props) { + const schoolLevel = parseSchoolLevel(searchParams.school); const grade = parseGrade(searchParams.grade); const topic = findTopic(pickFirst(searchParams.topic)); const mode = parseMode(pickFirst(searchParams.mode)); @@ -51,6 +53,7 @@ export default function ExportPage({ searchParams }: Props) { return ( (arr: readonly T[]): T[] { } function storageKey( - grade: Grade, + schoolLevel: SchoolLevel, + grade: Grade | null, topic: Topic, mode: "structural" | "conceptual", dims: string[], @@ -65,6 +68,7 @@ function storageKey( return [ "openmath:verification-result", grade, + schoolLevel, topic.code, topic.name, mode, @@ -74,14 +78,16 @@ function storageKey( } function resultHref( - grade: Grade, + schoolLevel: SchoolLevel, + grade: Grade | null, topic: Topic, mode: "structural" | "conceptual" | null, dims: readonly string[], sourceProblemText: string, ): string { const params = new URLSearchParams({ - grade: String(grade), + grade: grade === null ? "common" : String(grade), + school: schoolLevel, topic: topic.code, }); if (mode !== null) params.set("mode", mode); @@ -178,6 +184,7 @@ function ExamSheet({ } export function ExportView({ + schoolLevel, grade, topic, mode, @@ -188,7 +195,7 @@ export function ExportView({ }: Props) { const [displayProblems, setDisplayProblems] = useState(problems); const [options, setOptions] = useState(() => ({ - title: buildDefaultTitle(grade, topic), + title: buildDefaultTitle(schoolLevel, grade, topic), showDate: true, includeAnswers: true, shuffle: false, @@ -204,10 +211,10 @@ export function ExportView({ useEffect(() => { setDisplayProblems(problems); - if (grade === null || topic === null || mode === null) return; + if ((schoolLevel === "middle" && grade === null) || topic === null || mode === null) return; try { const raw = window.sessionStorage.getItem( - storageKey(grade, topic, mode, dims, sourceProblemText), + storageKey(schoolLevel, grade, topic, mode, dims, sourceProblemText), ); if (raw === null) return; const parsed: unknown = JSON.parse(raw); @@ -238,7 +245,7 @@ export function ExportView({ return shuffleArray(displayProblems); }, [displayProblems, options.shuffle]); - if (grade === null || topic === null) { + if ((schoolLevel === "middle" && grade === null) || topic === null) { return ( <> diff --git a/packages/web/app/app/new/topic/data.ts b/packages/web/app/app/new/topic/data.ts index d6e4793..c44500e 100644 --- a/packages/web/app/app/new/topic/data.ts +++ b/packages/web/app/app/new/topic/data.ts @@ -1,3 +1,4 @@ +export type SchoolLevel = "middle" | "high"; export type Grade = 1 | 2 | 3; export type Category = @@ -5,10 +6,13 @@ export type Category = | "문자와 식" | "함수" | "기하" - | "확률과 통계"; + | "확률과 통계" + | "집합과 명제"; export type Topic = { - grade: Grade; + schoolLevel: SchoolLevel; + grade: Grade | null; + course: string; category: Category; code: string; name: string; @@ -22,12 +26,15 @@ export const categories: readonly ("전체" | Category)[] = [ "함수", "기하", "확률과 통계", + "집합과 명제", ] as const; export const topics: Topic[] = [ /* ───── 중1 ───── */ { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "수와 연산", code: "9수01-01", name: "소인수분해", @@ -35,63 +42,81 @@ export const topics: Topic[] = [ "자연수를 소인수분해 하고, 이를 이용하여 약수와 배수를 구한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "수와 연산", code: "9수01-02", name: "정수와 유리수", achievement: "양수와 음수, 정수와 유리수의 개념을 이해한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "수와 연산", code: "9수01-03", name: "유리수의 사칙연산", achievement: "유리수의 사칙계산의 원리를 이해하고 계산할 수 있다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "문자와 식", code: "9수02-01", name: "문자의 사용과 식의 값", achievement: "문자를 사용하여 수량 관계를 식으로 나타낸다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "문자와 식", code: "9수02-02", name: "일차식의 계산", achievement: "일차식의 덧셈과 뺄셈의 원리를 이해하고 계산한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "문자와 식", code: "9수02-03", name: "일차방정식", achievement: "등식의 성질을 이해하고 일차방정식의 해를 구한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "문자와 식", code: "9수02-04", name: "일차방정식의 활용", achievement: "일차방정식을 활용하여 다양한 문제를 해결한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "함수", code: "9수03-01", name: "함수의 개념", achievement: "함수의 개념을 이해하고 함숫값을 구한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "기하", code: "9수04-01", name: "기본 도형과 작도", achievement: "점 · 선 · 면의 위치 관계를 이해하고 삼각형을 작도한다.", }, { + schoolLevel: "middle", grade: 1, + course: "중학교 수학", category: "확률과 통계", code: "9수05-01", name: "자료의 정리와 해석", @@ -100,70 +125,90 @@ export const topics: Topic[] = [ /* ───── 중2 ───── */ { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "수와 연산", code: "9수01-04", name: "유리수와 순환소수", achievement: "유리수가 유한소수 또는 순환소수로 표현됨을 이해한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "문자와 식", code: "9수02-05", name: "식의 계산", achievement: "지수법칙과 다항식의 곱셈 · 나눗셈을 이해한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "문자와 식", code: "9수02-06", name: "일차부등식", achievement: "부등식의 성질을 이해하고 일차부등식의 해를 구한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "문자와 식", code: "9수02-07", name: "연립일차방정식", achievement: "미지수가 두 개인 연립일차방정식의 해를 구한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "함수", code: "9수03-02", name: "일차함수와 그래프", achievement: "일차함수의 그래프의 성질과 식을 구한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "함수", code: "9수03-03", name: "일차함수의 활용", achievement: "일차함수를 활용하여 실생활 문제를 해결한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "기하", code: "9수04-02", name: "삼각형의 성질", achievement: "이등변삼각형의 성질과 삼각형의 외심 · 내심을 이해한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "기하", code: "9수04-03", name: "사각형의 성질", achievement: "평행사변형 · 직사각형 · 마름모 · 정사각형의 성질을 이해한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "기하", code: "9수04-04", name: "도형의 닮음", achievement: "도형의 닮음을 이해하고 닮음비를 활용한다.", }, { + schoolLevel: "middle", grade: 2, + course: "중학교 수학", category: "확률과 통계", code: "9수05-02", name: "경우의 수와 확률", @@ -172,75 +217,214 @@ export const topics: Topic[] = [ /* ───── 중3 ───── */ { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "수와 연산", code: "9수01-05", name: "제곱근과 실수", achievement: "제곱근의 뜻과 실수의 분류를 이해한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "수와 연산", code: "9수01-06", name: "근호를 포함한 식의 계산", achievement: "근호를 포함한 식의 사칙계산을 한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "문자와 식", code: "9수02-08", name: "다항식의 곱셈과 인수분해", achievement: "다항식의 곱셈공식과 인수분해 공식을 이해한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "문자와 식", code: "9수02-09", name: "이차방정식", achievement: "이차방정식의 해를 인수분해 · 근의 공식으로 구한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "문자와 식", code: "9수02-10", name: "이차방정식의 활용", achievement: "이차방정식을 활용하여 다양한 문제를 해결한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "함수", code: "9수03-04", name: "이차함수와 그래프", achievement: "이차함수의 그래프의 성질과 식을 구한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "기하", code: "9수04-05", name: "삼각비", achievement: "직각삼각형에서 삼각비의 값을 구하고 활용한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "기하", code: "9수04-06", name: "원과 직선의 위치 관계", achievement: "원과 직선의 위치 관계를 이해하고 접선의 성질을 활용한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "기하", code: "9수04-07", name: "원주각", achievement: "원주각의 성질을 이해하고 활용한다.", }, { + schoolLevel: "middle", grade: 3, + course: "중학교 수학", category: "확률과 통계", code: "9수05-03", name: "대푯값과 산포도", achievement: "평균 · 중앙값 · 최빈값 · 분산 · 표준편차를 이해한다.", }, + + /* ───── 고등 공통수학 ───── */ + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "문자와 식", + code: "10공수01-01", + name: "다항식의 연산", + achievement: "다항식의 덧셈 · 뺄셈 · 곱셈을 계산하고 식을 변형한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "문자와 식", + code: "10공수01-02", + name: "나머지정리", + achievement: "항등식과 나머지정리를 이용하여 다항식의 값을 구한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "문자와 식", + code: "10공수01-03", + name: "인수분해", + achievement: "여러 가지 인수분해 공식을 이용하여 다항식을 인수분해한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "문자와 식", + code: "10공수01-04", + name: "복소수와 이차방정식", + achievement: "복소수의 뜻과 성질을 이해하고 이차방정식의 해를 해석한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "문자와 식", + code: "10공수01-05", + name: "이차방정식과 이차함수", + achievement: "이차방정식과 이차함수의 관계를 그래프와 식으로 해석한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "기하", + code: "10공수02-01", + name: "직선의 방정식", + achievement: "좌표평면에서 직선의 방정식을 구하고 위치 관계를 해석한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "기하", + code: "10공수02-02", + name: "원의 방정식", + achievement: "원의 방정식과 접선의 방정식을 구하고 도형 조건을 해석한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "집합과 명제", + code: "10공수03-01", + name: "집합", + achievement: "집합의 연산과 포함 관계를 이용하여 원소의 개수를 구한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "집합과 명제", + code: "10공수03-02", + name: "명제", + achievement: "명제의 참거짓과 충분조건 · 필요조건 · 필요충분조건을 판단한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "함수", + code: "10공수04-01", + name: "함수", + achievement: "함수의 뜻과 그래프를 이해하고 합성함수와 역함수를 다룬다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "함수", + code: "10공수04-02", + name: "유리함수와 무리함수", + achievement: "유리함수와 무리함수의 그래프와 성질을 해석한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "확률과 통계", + code: "10공수05-01", + name: "경우의 수", + achievement: "합의 법칙과 곱의 법칙을 이용하여 경우의 수를 구한다.", + }, + { + schoolLevel: "high", + grade: null, + course: "공통수학", + category: "확률과 통계", + code: "10공수05-02", + name: "순열과 조합", + achievement: "순열과 조합의 뜻을 이해하고 경우의 수를 계산한다.", + }, ]; export function parseGrade( @@ -253,8 +437,18 @@ export function parseGrade( return null; } -export function gradeLabel(g: Grade): string { - return `중${g}`; +export function parseSchoolLevel(raw: string | string[] | undefined): SchoolLevel { + const s = Array.isArray(raw) ? raw[0] : raw; + return s === "high" ? "high" : "middle"; +} + +export function gradeLabel(g: Grade | null, schoolLevel: SchoolLevel = "middle"): string { + if (schoolLevel === "high") return g === null ? "고등 공통수학" : `고${g}`; + return g === null ? "중등" : `중${g}`; +} + +export function topicScopeLabel(topic: Topic): string { + return `${gradeLabel(topic.grade, topic.schoolLevel)} · ${topic.course}`; } export function pickFirst( @@ -269,6 +463,10 @@ export function findTopic(code: string | null): Topic | null { return topics.find((t) => t.code === code) ?? null; } +export function topicsForScope(schoolLevel: SchoolLevel, grade: Grade | null): Topic[] { + return topics.filter((topic) => topic.schoolLevel === schoolLevel && topic.grade === grade); +} + /* ───── 평가 차원 후보 (S3-B) ────────────────────────────── * 단원별 명시적 후보가 있으면 사용, 없으면 fallback. * `default` 가 true 인 항목은 페이지 진입 시 자동 체크. diff --git a/packages/web/app/app/new/topic/examples.json b/packages/web/app/app/new/topic/examples.json index 209bcb0..a0f4b96 100644 --- a/packages/web/app/app/new/topic/examples.json +++ b/packages/web/app/app/new/topic/examples.json @@ -1,50 +1,52 @@ { - "generated_at": "2026-06-06T15:47:54.774Z", - "corpus_source": "openmath_rag_records.jsonl", - "note": "임시 mini-매핑 기반. D-13 도입 후 mapping-2022-to-2015.yaml 로 이관.", + "schema_version": "openmath-topic-examples-v2", "units": { "9수01-01": { "ui_name": "소인수분해", + "school_level": "middle", "grade": 1, - "candidate_pool": 1317, + "course": "중학교 수학", + "candidate_pool": 319, "examples": [ { - "id": "110:train:21709_51439", + "id": "real-110-train-21721_51492", "achievement_code_2015": "9수01-01", - "topic_name_2015": "소수와 합성수", - "difficulty": "easy", + "topic_name_2015": "소인수분해", + "difficulty": "medium", "problem_type": "essay", - "question_text": "자연수 $a$보다 작은 소수가 $4$ 개일 때, $a$가 될 수 있는 수의 개수를 구하여라.", - "answer_text": "$4$ 개" + "question_text": "$48=2^a\\times3$, $18=2\\times3^b$일 때, $a+b$의 값을 구하여라. (단, $a$, $b$는 자연수)", + "answer_text": "$6$" }, { - "id": "110:train:21726_51553", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "소인수 구하기", - "difficulty": "easy", + "id": "real-110-train-21721_51493", + "achievement_code_2015": "9수01-01", + "topic_name_2015": "소인수분해", + "difficulty": "medium", "problem_type": "essay", - "question_text": "$N(x)$는 자연수 $x$를 소인수분해하였을 때, 소인수들의 합을 나타낸다. 예를 들어 $N(2)=2$, $N(10)=7$이다. $N(84)=a$일 때, $N(a)$의 값을 구하여라.", - "answer_text": "$N(a)=5$" + "question_text": "$252=2^a\\times3^2\\times7$, $270=2\\times3^b\\times5$일 때, $b-a$의 값을 구하여라. (단, $a$, $b$는 자연수)", + "answer_text": "$1$" }, { - "id": "110:train:21722_51626", + "id": "real-110-train-21721_51654", "achievement_code_2015": "9수01-01", - "topic_name_2015": "소인수분해한 결과에서 밑과 지수구하기", - "difficulty": "easy", + "topic_name_2015": "소인수분해", + "difficulty": "medium", "problem_type": "essay", - "question_text": "$225$를 $a^x\\times b^y$으로 소인수분해하였을 때, 자연수 $a$, $b$, $x$, $y$에 대하여 $a+b-x+y$의 값을 구하여라. (단, $a$, $b$는 서로 다른 소수, $a 중 선분의 수직이등분선 작도를 이용하여 할 수 있는 작도를 모두 골라라. <보기>㉠ 선분 $AB$를 지름으로 하는 원의 작도㉡ $∠AOB=45˚$인 각의 작도㉢ 주어진 선분을 $4$등분하는 작도 ㉣평행선의 작도", - "answer_text": "㉠, ㉡, ㉢" + "problem_type": "subjective", + "question_text": "오른쪽 그림과 같은 직육면체를 보고 다음을 구하시오.\n① 모서리 $\\overline{\\mathrm{AB}}$와 모서리 $\\overline{\\mathrm{BF}}$의 교점.\n② 면 $ABCD$와 면 $CGHD$의 교선 $. $\n직선 $BC$를 교선으로 가지는 두 면입니다.\n이 이미지는 직육면체(또는 상자)를 나타내며, 각 꼭짓점에 $A$, $B$, $C$, $D$, $E$, $F$, $G$, $H$라는 라벨이 붙어 있습니다.", + "answer_text": "① 점 $B$ ② 모서리 $CD$ ③ 면 $ABCD$, 면 $BFGC$" }, { - "id": "111:train:00041_44058", - "achievement_code_2015": "9수03-03", - "topic_name_2015": "작도 가능한 각", + "id": "real-30-train-S3_중등_1_000521", + "achievement_code_2015": "9수04-01", + "topic_name_2015": "기본 도형과 작도", "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "다음 보기 중 작도할 수 있는 각은 모두 몇 개인지 구하여라. $ 7.5^{\\circ} , 10^{\\circ} , 30^{\\circ} , 50^{\\circ} $ $ 75^{\\circ} , 120^{\\circ} , 135^{\\circ} , 150^{\\circ} $", - "answer_text": "$6$개" + "problem_type": "subjective", + "question_text": "다음 도형을 기호로 나타내시오.\n① 양쪽 화살표가 있는 직선\n② 점선, 직선, 점선으로 있는 직선\n③ 오른쪽으로만 화살표가 있는 직선 (반직선)\n④ 왼쪽으로만 화살표가 있는 직선 (반직선)", + "answer_text": "$(1) \\overleftrightarrow{P Q}$\n② $\\overline{\\mathrm{PQ}}$\n③ $\\overrightarrow{\\mathrm{PQ}}$\n④ $\\overrightarrow{\\mathrm{QP}}$" } ] }, "9수05-01": { "ui_name": "자료의 정리와 해석", + "school_level": "middle", "grade": 1, - "candidate_pool": 425, + "course": "중학교 수학", + "candidate_pool": 460, "examples": [ { - "id": "110:train:34931_140059", - "achievement_code_2015": "9수04-02", - "topic_name_2015": "줄기와 잎 그림의 이해(2)-두 자료", + "id": "real-111-train-00044_23188", + "achievement_code_2015": "9수05-01", + "topic_name_2015": "자료의 정리와 해석", "difficulty": "easy", - "problem_type": "essay", - "question_text": "다음은 정재네 반 남학생과 여학생의 지난 체육 시간의 줄넘기 횟수를 조사하여 나타낸 줄기와 잎 그림이다. 남학생과 여학생 중에서 줄넘기 횟수가 각각 $5$ 번째로 많은 학생의 횟수의 차를 구하여라.", - "answer_text": "$7$ 회" + "problem_type": "objective", + "question_text": "다음 중 옳은 것을 모두 고르면? (정답 $2$개) ①변량과 평균이 같을 때 편차는 $0$이다. ②각 변량의 편차의 합은 항상 $1$이다. ③평균이 작을수록 산포도의 값이 크다. ④편차의 절댓값이 작을수록 변량은 평균에 가까이 있다. ⑤분산이 작을수록 변량은 평균에서 멀리 떨어져 있다.", + "answer_text": "①, ④" }, { - "id": "110:train:39532_155189", - "achievement_code_2015": "9수04-02", - "topic_name_2015": "줄기와 잎 그림", - "difficulty": "easy", - "problem_type": "essay", - "question_text": "다음은 소희네 반 학생들의 키를 조사하여 나타낸 줄기와 잎 그림인데, 일부가 가려져 보이지 않는다. 키가 $140 cm $이상 $160 cm $미만인 학생이 전체의 $40 \\%$일 때, 전체 학생 수를 구하여라.", - "answer_text": "$25$ 명" + "id": "real-111-train-00044_23196", + "achievement_code_2015": "9수05-01", + "topic_name_2015": "자료의 정리와 해석", + "difficulty": "medium", + "problem_type": "short_answer", + "question_text": "정환이와 희정이가 $20$점 만점의 사격 경기를 $10$차례씩 하여 아래 표와 같은 결과를 얻었다. 다음 물음에 답하여라.
학생 평균(점) 표준편차(점)
정환 $10$ $2$
희정 $10$ $6$
(1) 정환이와 희정이 중 누가 더 고른 점수를 얻었다고 할 수 있는가?(2) $15$점 이상을 얻은 경기의 수가 더 많은 학생이 상을 받는다고 할 때, 상을 받을 가능성이 높은 학생은 누구인가?", + "answer_text": "(1) 정환(2) 희정" }, { - "id": "110:train:9099_73871", - "achievement_code_2015": "9수04-02", - "topic_name_2015": "도수분포표의 이해(3)-비율", + "id": "real-111-train-00044_23200", + "achievement_code_2015": "9수05-01", + "topic_name_2015": "자료의 정리와 해석", "difficulty": "easy", - "problem_type": "essay", - "question_text": "다음은 학생 $30$ 명의 수학 경시대회 점수를 조사하여 나타낸 도수분포표이다. 수학 경시대회 점수가 $40$ 점 미만인 학생은 전체의 몇 %인지 구하여라.", - "answer_text": "$20\\%$" + "problem_type": "short_answer", + "question_text": "한 반을 A, B, C, D 네 모둠으로 나누어 다트 던지기 시합을 했다. 각 모둠에서 $10$회씩 다트를 던지며, 시합에서 이기는 기준은 다음과 같다. · 점수의 합계가 큰 모둠이 이긴다. · 점수의 합계가 같은 경우, 점수가 고른 모둠이 이긴다. 다음은 각 모둠별 점수이다. 어느 모둠이 이겼는지 구하여라. (단위: 점)
모둠 $1$ $2$ $3$ $4$ $5$ $6$ $7$ $8$ $9$ $10$ 합계
A $4$ $6$ $6$ $4$ $8$ $0$ $2$ $4$ $8$ $10$ $52$
B $4$ $4$ $6$ $6$ $8$ $4$ $10$ $6$ $10$ $8$ $66$
C $6$ $4$ $8$ $4$ $4$ $6$ $8$ $10$ $10$ $10$ $70$
D $8$ $6$ $6$ $6$ $8$ $8$ $6$ $6$ $8$ $8$ $70$
", + "answer_text": "D 모둠" } ] }, "9수01-04": { "ui_name": "유리수와 순환소수", + "school_level": "middle", "grade": 2, - "candidate_pool": 386, + "course": "중학교 수학", + "candidate_pool": 51, "examples": [ { - "id": "110:train:25058_77282", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "유한소수와 무한소수 구분하기", + "id": "real-110-train-34453_137383", + "achievement_code_2015": "9수01-04", + "topic_name_2015": "유리수와 순환소수", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음은 분수 $\\frac{3}{8}$을 유한소수로 나타내는 과정이다.$\\square$에 들어갈 수로 옳지 $\\underline{않은}$ 것은? $\\cfrac{3}{8}=\\cfrac{3}{2^{\\boxed{①}}}=\\cfrac{3\\times \\boxed{②}}{2^3 \\times \\boxed{③}}=\\cfrac{375}{\\boxed{④}}=\\boxed{⑤}$ ①$3$ ②$5$ ③$5^3$ ④$1000$ ⑤$0.375$", - "answer_text": "$②$" + "question_text": "한 자리의 자연수 $a$, $b$에 대하여 순환소수 $0.\\dot{a}\\dot{b}$를 기약분수로 나타내면 $\\frac{7}{33}$일 때, 순환소수 $0.b\\dot{a}$를 기약분수로 나타내어라.", + "answer_text": "$\\frac{11}{90}$" }, { - "id": "110:train:25067_77257", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "유한소수로 나타낼수 있는 분수 찾기", + "id": "real-110-train-34453_137384", + "achievement_code_2015": "9수01-04", + "topic_name_2015": "유리수와 순환소수", "difficulty": "easy", "problem_type": "essay", - "question_text": "두 자연수 $a$, $b$에 대하여 $\\frac{a}{b}$를 소수로 나타낼 때, 유한소수이면 $a △ b$$=-1$이고 순환소수이면 $a △ b$$=1$이다. 이때 $(6 △ 12)+(9 △ 20)$의 값을 구하여라.", - "answer_text": "$-2$" + "question_text": "한 자리의 자연수 $a$, $b$에 대하여 순환소수 $0.\\dot{a}\\dot{b}$를 기약분수로 나타내면 $\\frac{8}{33}$일 때, 순환소수 $0.b\\dot{a}$를 기약분수로 나타내어라.", + "answer_text": "$\\frac{19}{45}$" }, { - "id": "110:train:9505_85262", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "유한소수가 되도록 하는 미지수의 값 구하기(1)", - "difficulty": "easy", + "id": "real-110-train-34453_137386", + "achievement_code_2015": "9수01-04", + "topic_name_2015": "유리수와 순환소수", + "difficulty": "medium", "problem_type": "essay", - "question_text": "$\\frac{9}{168}\\times n$을 소수로 나타내면 유한소수가 된다. 이때 $n$의 값이 될 수 있는 가장 작은 두 자리 자연수를 구하여라.", - "answer_text": "$14$" + "question_text": "한 자리의 자연수 $a$, $b$에 대하여 순환소수 $0.\\dot{a}\\dot{b}$를 기약분수로 나타내면 $\\frac{5}{11}$일 때, 순환소수 $0.b\\dot{a}$를 기약분수로 나타내어라.", + "answer_text": "$\\frac{49}{90}$" } ] }, "9수02-05": { "ui_name": "식의 계산", + "school_level": "middle", "grade": 2, - "candidate_pool": 686, + "course": "중학교 수학", + "candidate_pool": 499, "examples": [ { - "id": "110:train:9685_85505", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "지수법칙(1)- a^m×a^n=a^m+n", - "difficulty": "easy", + "id": "real-110-train-9560_7673", + "achievement_code_2015": "9수02-05", + "topic_name_2015": "식의 계산", + "difficulty": "medium", "problem_type": "essay", - "question_text": "$4\\times5\\times9\\times14=2^a\\times3^b\\times5^c\\times7^d$일 때, $ab+cd$의 값을 구하여라. (단, $a$,$b$,$c $,$d$는 자연수)", - "answer_text": "$7$" + "question_text": "$1.\\dot{2}+2.\\dot{4}+3.\\dot{6}$을 계산한 값을 순환소수로 바르게 나타낸 것은? ①$7.\\dot{3}\\dot{3}$ ②$7.3\\dot{3}$ ③$7.\\dot{7}\\dot{3}$ ④$\\dot7.\\dot{3}$ ⑤$7.\\dot{3}$", + "answer_text": "⑤" }, { - "id": "110:train:12599_85516", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "지수법칙(2)- (am)^n =a^mn", + "id": "real-110-train-9560_7674", + "achievement_code_2015": "9수02-05", + "topic_name_2015": "식의 계산", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음 $\\square$안에 알맞은 수를 써넣어라. $a^5\\times b^3\\times(b^4)^3\\times(a^2)^2\\times b$ = $a^{\\square}b^{\\square}$", - "answer_text": "$9$, $16$" + "question_text": "$2.1\\dot{5}+3.\\dot{5}-0.3\\dot{7}$을 계산한 값을 순환소수로 바르게 나타낸 것은? $①$ $5.\\dot{5}\\dot{3}$ $②$ $5.3\\dot{3}$ $③$ $5.\\dot{3}$ $④$ $5.\\dot{3}\\dot{3}$ $⑤$ $\\dot{5}.\\dot{3}$", + "answer_text": "③" }, { - "id": "110:train:25085_85531", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "지수법칙(3)- a^m÷a^n=a^m-n", - "difficulty": "easy", + "id": "real-110-train-9560_77065", + "achievement_code_2015": "9수02-05", + "topic_name_2015": "식의 계산", + "difficulty": "medium", "problem_type": "essay", - "question_text": "다음 중 옳지 않은 것은? $\\\\㉠2^4\\div2^7>0$$\\\\㉡2^5\\div2^3>0$$\\\\㉢2^4\\div2^4=0$", - "answer_text": "㉢" + "question_text": "$3.\\dot{5}+0.\\dot{8}\\dot{3}-1.\\dot{2}\\dot{6}$을 계산한 값을 순환소수로 바르게 나타낸 것은? ①$3.0\\dot{1}\\dot{3}$ ② $3.1\\dot{3}$ ③$\\dot{3}.\\dot{1}3$ ④$\\dot{3}.1\\dot{3}$ ⑤$3.\\dot{1}\\dot{3}$", + "answer_text": "⑤" } ] }, "9수02-06": { "ui_name": "일차부등식", + "school_level": "middle", "grade": 2, - "candidate_pool": 1069, + "course": "중학교 수학", + "candidate_pool": 966, "examples": [ { - "id": "110:train:10670_12625", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "문장을 부등식으로 나타내기", + "id": "real-110-train-10701_20109", + "achievement_code_2015": "9수02-06", + "topic_name_2015": "일차부등식", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음 문장을 부등식으로 나타내어라. $x$의 $2$ 배에서 $1$을 뺀 수는 $x$에 $6$을 더한 수의 $3$ 배보다 작다.", - "answer_text": "$2x-1<3(x+6)$" + "question_text": "다음 중 일차부등식이 아닌 것은?$\\\\$ ① 무게가 $25 g$인 상자에 무게가 $8 g$인 구슬 $x$ 개를 넣으면 전체 무게는 $50 g$ 이하이다. $\\\\$ ② 한 변의 길이가 $2x cm$인 정사각형의 넓이는 $500 cm^2$보다 크다. $\\\\$ ③ $x km$의 거리를 시속 $4 km$로 걸을 때 걸린 시간은 $3$ 시간 이하이다. $\\\\$ ④ 한 개에 $600$ 원인 음료수 $x$ 개와 한 개에 $1000$ 원인 과자 $4$ 개의 총금액은 $9500$ 원 초과이다. $\\\\$ ⑤ 어느 학급 $50$ 명 중 남학생이 $x$ 명일 때, 여학생 $23$명 보다적다.", + "answer_text": "②" }, { - "id": "110:train:10679_81342", - "achievement_code_2015": "9수02-11", - "topic_name_2015": "부등식의 해", + "id": "real-110-train-10703_81014", + "achievement_code_2015": "9수02-06", + "topic_name_2015": "일차부등식", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음 부등식 중 $x=3$이 해가 되는 것은? ① $x+1>4$$ $$ $ ② $-x<0$ $ $$ $ ③ $2x+3<9$ ④ $\\frac{3x+3}{4}<2$$ $$ $ ⑤ $11-3x<0$", - "answer_text": "$②$" + "question_text": "부등식 $5(1-x)\\le ax+5$가 일차부등식이 되도록 하는 수 $a$의 값이 아닌 것은? ① $-5$ ② $-2$ ③ $0$ ④ $1$ ⑤ $3$", + "answer_text": "①" }, { - "id": "110:train:41450_161453", - "achievement_code_2015": "9수02-11", - "topic_name_2015": "부등식의 성질(2)-a, b에 대한 식의 대소 관계가 주어진 경우", + "id": "real-110-train-10703_86909", + "achievement_code_2015": "9수02-06", + "topic_name_2015": "일차부등식", "difficulty": "easy", "problem_type": "essay", - "question_text": "$-2a+3<6b+11$일 때, $\\square$안에 알맞은 기호를 끌어 놓아라. $3a\\square-9b-12$ $<$ $>$", - "answer_text": "$>$" + "question_text": "부등식 $-ax+4\\le x+1-2x$가 일차부등식이 되도록 하는 수 $a$의 값이 아닌 것은? ① $-2$ ② $-1$ ③ $0$ ④ $1$ ⑤ $2$", + "answer_text": "④" } ] }, "9수02-07": { "ui_name": "연립일차방정식", + "school_level": "middle", "grade": 2, - "candidate_pool": 675, + "course": "중학교 수학", + "candidate_pool": 345, "examples": [ { - "id": "110:train:34547_137981", - "achievement_code_2015": "9수02-13", + "id": "real-110-train-34547_137981", + "achievement_code_2015": "9수02-07", "topic_name_2015": "연립일차방정식", "difficulty": "easy", "problem_type": "essay", @@ -460,566 +486,1066 @@ "answer_text": "$1$개" }, { - "id": "110:train:10229_11599", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "연립방정식의 해 또는 계수가 문자로 주어질 때", + "id": "real-110-train-34547_137982", + "achievement_code_2015": "9수02-07", + "topic_name_2015": "연립일차방정식", "difficulty": "easy", "problem_type": "essay", - "question_text": "연립방정식 의 해가 $ \\begin{cases} 5(x-2)-y=x+16+ay \\\\ b(x-2)=3(y+5)-11 \\\\ \\end{cases}$의 해가 $(3, -2)$일 때, 수 $a$, $b$에 대하여 $2a+5b$의 값을 구하여라.", - "answer_text": "$2$" + "question_text": "$x$, $y$가 자연수일 때, 일차방정식 $5x+4y=19$의 해는 $a$ 개, 일차방정식 $4x+3y=31$의 해는 $b$ 개이다. 다음 물음에 답하여라. (1) 일차방정식 $5x+4y=19$의 해의 개수를 구하여라. (2) 일차방정식 $4x+3y=31$의 해의 개수를 구하여라. (3) $a+b$의 값을 구하여라.", + "answer_text": "$1$개" }, { - "id": "110:train:10342_80010", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "연립방정식의 풀이-대입법", - "difficulty": "easy", + "id": "real-110-train-34547_137983", + "achievement_code_2015": "9수02-07", + "topic_name_2015": "연립일차방정식", + "difficulty": "medium", "problem_type": "essay", - "question_text": "다음은 연립방정식 $\\begin{cases} y=5-x \\dots ㉠ \\\\ x+3y=17 \\dots ㉡ \\end{cases}$을 푸는 과정이다. $\\square$ 안에 알맞은 수를 써넣어라. $㉠$을 $㉡$에 대입하면 $\\\\$ $x+3(5-x)=17$ $\\\\$ $-2x = \\square \\\\ \\therefore x=\\square$ 을 $㉠$에 대입하면 $y=\\square$", - "answer_text": "$2$, $-1$, $-1$, $6$" + "question_text": "$x$, $y$가 자연수일 때, 일차방정식 $x+2y=9$의 해는 $a$ 개, 일차방정식 $2x+3y=10$의 해는 $b$ 개이다. 다음 물음에 답하여라. (1) 일차방정식 $x+2y=9$의 해의 개수를 구하여라. (2) 일차방정식 $2x+3y=10$의 해의 개수를 구하여라. (3) $a+b$의 값을 구하여라.", + "answer_text": "답: $4$ 개" } ] }, "9수03-02": { "ui_name": "일차함수와 그래프", + "school_level": "middle", "grade": 2, - "candidate_pool": 941, + "course": "중학교 수학", + "candidate_pool": 65, "examples": [ { - "id": "110:train:11233_12404", - "achievement_code_2015": "9수02-15", - "topic_name_2015": "일차함수의 뜻", + "id": "real-110-train-34635_138527", + "achievement_code_2015": "9수03-02", + "topic_name_2015": "일차함수와 그래프", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음 문장에서 $y$를 $x$에 대한 식으로 나타내어라. 문구점에서 $800$ 원짜리 연필 $x$ 자루를 사고 $5000$ 원을 냈을 때의 거스름돈은 $y$ 원이다.", - "answer_text": "$y=5000-800x$" + "question_text": "일차함수 $y=-3x+2$의 그래프는 $x$절편이 $a$, $y$절편이 $b$이다. 일차함수 $y=bx+a$의 그래프의 $x$절편과 $y$절편의 합을 구하려고 한다. (1) 일차함수 $y=-3x+2$의 그래프의 $x$절편과 $y$절편을 각각 구하여라. (2)일차함수 $y=bx+a$의 그래프의 $x$절편과 $y$절편의 합을 구하여라.", + "answer_text": "$x$절편 : $\\frac{2}{3}$, $y$절편 : $2$" }, { - "id": "110:train:34631_138507", - "achievement_code_2015": "9수02-15", - "topic_name_2015": "일차함수의 그래프", + "id": "real-110-train-34635_138528", + "achievement_code_2015": "9수03-02", + "topic_name_2015": "일차함수와 그래프", "difficulty": "easy", "problem_type": "essay", - "question_text": "일차함수 $f(x)=ax+b$에 대하여 $f(-2)=-3$, $f(2)=5$일 때, 다음 물음에 답하여라. (단, $a$, $b$는 수) (1) $a$와 $b$의 값을 각각 구하여라. (2) $f(c)=f(5)-2f(1)$일 때, $c$의 값을 구하여라.", - "answer_text": "$a=2$, $b=1$" + "question_text": "일차함수 $y=-2x+5$의 그래프는 $x$절편이 $a$, $y$절편이 $b$이다. 일차함수 $y=bx+a$의 그래프의 $x$절편과 $y$절편의 합을 구하려고 한다. (1)일차함수 $y=-2x+5$의 그래프의 $x$절편과 $y$절편을 각각 구하여라. (2)일차함수 $y=bx+a$의 그래프의 $x$절편과 $y$절편의 합을 구하여라.", + "answer_text": "$x$절편 : $\\frac{5}{2}$, $y$절편 : $5$" }, { - "id": "110:train:11261_12542", - "achievement_code_2015": "9수02-15", - "topic_name_2015": "일차함수 y=ax+b의 그래프 위의 점(1)", + "id": "real-110-train-34635_138529", + "achievement_code_2015": "9수03-02", + "topic_name_2015": "일차함수와 그래프", "difficulty": "easy", "problem_type": "essay", - "question_text": "일차함수 $y=\\frac{3}{4}x+3$의 그래프가 점 $(p, -6)$을 지날 때, $p$의 값을 구하여라.", - "answer_text": "$p=-12$" + "question_text": "일차함수 $y=-4x+6$의 그래프는 $x$절편이 $a$, $y$절편이 $b$이다. 일차함수 $y=bx+a$의 그래프의 $x절편$과 $y절편$의 합을 구하려고 한다. (1) 일차함수 $y=-4x+6$의 그래프의 $x$절편과 $y$절편을 각각 구하여라. (2) 일차함수 $y=bx+a$의 그래프의 $x$절편과 $y$절편의 합을 구하여라.", + "answer_text": "$x$절편 : $\\frac{3}{2}$, $y$절편 : $6$" } ] }, "9수03-03": { "ui_name": "일차함수의 활용", + "school_level": "middle", "grade": 2, - "candidate_pool": 652, + "course": "중학교 수학", + "candidate_pool": 727, "examples": [ { - "id": "110:train:7203_2474", - "achievement_code_2015": "9수03-11", - "topic_name_2015": "좌표의 활용", + "id": "real-110-train-11439_83977", + "achievement_code_2015": "9수03-03", + "topic_name_2015": "일차함수의 활용", "difficulty": "easy", "problem_type": "essay", - "question_text": "네 점 $A(0, 6)$, $B(-4, 1)$, $C(5, 1)$, $D(9, 6)$을 꼭짓점으로 하는 사각형 $ABCD$의 넓이를 구하여라.", - "answer_text": "$45$" + "question_text": "다음 그림과 같은 일차함수의 그래프의 기울기를 $a$, $x$절편을 $b$, $y$절편을 $c$라 할 때, $a-b+c$의 값을 구하여라.", + "answer_text": "$-\\frac{11}{2}$" }, { - "id": "110:train:9852_85693", - "achievement_code_2015": "9수02-09", - "topic_name_2015": "도형에서 단항식의 나눗셈의 활용", + "id": "real-110-train-11439_83978", + "achievement_code_2015": "9수03-03", + "topic_name_2015": "일차함수의 활용", "difficulty": "easy", "problem_type": "essay", - "question_text": "한 대각선의 길이가 $3a^3b^7$인 마름모의 넓이가 $(3a^2b^5)^2$일 때, 이 마름모의 다른 대각선의 길이를 구하여라.", - "answer_text": "$6ab^3$" + "question_text": "다음 그림과 같은 일차함수의 그래프의 기울기를 $a$, $x$절편을 $b$, $y$절편을 $c$라 할 때, $abc$의 값을 구하여라.", + "answer_text": "$-25$" }, { - "id": "110:train:10561_12284", - "achievement_code_2015": "9수03-14", - "topic_name_2015": "도형의 활용 문제", + "id": "real-110-train-11441_83979", + "achievement_code_2015": "9수03-03", + "topic_name_2015": "일차함수의 활용", "difficulty": "easy", "problem_type": "essay", - "question_text": "가로의 길이와 세로의 길이의 합이 $25cm$인 직사각형이 있다. 가로의 길이가 세로의 길이보다 $3cm$ 더 짧을 때, 이 직사각형의 세로의 길이를 구하여라.", - "answer_text": "$14 cm$" + "question_text": "일차함수 $y=px-1$의 그래프를 $y$축의 방향으로 $5$만큼 평행이동한 그래프의 기울기가 $-2$일 때, 이 그래프의 $x$ 절편을 $q$, $y$ 절편을 $r$라 하자. 이때 수 $p, q, r$에 대하여 $pqr$의 값을 구하여라.", + "answer_text": "$-16$" } ] }, "9수04-02": { "ui_name": "삼각형의 성질", + "school_level": "middle", "grade": 2, - "candidate_pool": 143, + "course": "중학교 수학", + "candidate_pool": 1271, "examples": [ { - "id": "110:train:18141_38983", - "achievement_code_2015": "9수03-10", - "topic_name_2015": "삼각형의 외심의 뜻과 위치", - "difficulty": "easy", + "id": "real-110-train-15959_29425", + "achievement_code_2015": "9수04-02", + "topic_name_2015": "삼각형의 성질", + "difficulty": "medium", "problem_type": "essay", - "question_text": "다음은 삼각형의 세 변의 수직이등분선은 한 점에서 만남을 설명하는 과정이다. 안에 알맞은 것을 써넣어라.", - "answer_text": "풀이 참조" + "question_text": "다음 그림에서 $l // m$일 때, $\\angle x$의 크기를 구하여라.", + "answer_text": "$\\angle x=74\\degree$" }, { - "id": "111:train:00043_43182", - "achievement_code_2015": "9수03-09", - "topic_name_2015": "이등변삼각형의 성질(1)-두 밑각의 크기", - "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "다음 삼각형 $ ABC $ 에서 $ \\overline{AB}=\\overline{AC} $ 일 때, $ \\angle x $ 의 크기를 구하여라.", - "answer_text": "(1) $ 30^{\\circ} $ (2) $ 81^{\\circ} $" + "id": "real-110-train-15959_29426", + "achievement_code_2015": "9수04-02", + "topic_name_2015": "삼각형의 성질", + "difficulty": "medium", + "problem_type": "essay", + "question_text": "다음 그림에서 $l // m$일 때, $\\angle x$의 크기를 구하여라. ", + "answer_text": "$\\angle x=42\\degree$" }, { - "id": "111:train:00043_43276", - "achievement_code_2015": "9수03-09", - "topic_name_2015": "이등변삼각형의 성질(2)-수직이등분선", - "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "$ \\overline{AB}=\\overline{AC} $ 인 $ \\triangle ABC $ 에서 $ \\overline{BC} $를 점 B에서부터 $ n: 2 m, n: m, m: n, m^{2}: n^{2} (m>0, n>0) $으로 나누는 점을 각각 $P, Q, R, S$라 한다. $ \\angle \\mathrm{P A R}=\\angle \\mathrm{Q A S}=90^{\\degree} $일 때, $ \\frac{m}{n} $의 값을 구하여라.", - "answer_text": "$ 2 $" + "id": "real-110-train-15959_32404", + "achievement_code_2015": "9수04-02", + "topic_name_2015": "삼각형의 성질", + "difficulty": "medium", + "problem_type": "essay", + "question_text": "다음 그림에서 $l // m$일 때, $\\angle x$의 크기를 구하여라.", + "answer_text": "$\\angle x=85\\degree$" } ] }, "9수04-03": { "ui_name": "사각형의 성질", + "school_level": "middle", "grade": 2, - "candidate_pool": 201, + "course": "중학교 수학", + "candidate_pool": 934, "examples": [ { - "id": "110:train:18734_115455", - "achievement_code_2015": "9수01-06", - "topic_name_2015": "평행사변형이 되는 조건의 응용(2)-각의 크기, 변의 길이", - "difficulty": "easy", + "id": "real-110-train-18780_105396", + "achievement_code_2015": "9수04-03", + "topic_name_2015": "사각형의 성질", + "difficulty": "medium", "problem_type": "essay", - "question_text": "다음은 평행사변형 $ABCD$에서 $\\angle B$와 $\\angle D$의 이등분선이 $\\overline{AD}$, $\\overline{BC}$와 만나는 점을 각각 $E$, $F$라 할 때, $\\square BFDE$가 어떤 사각형인지 구하는 과정이다. 빈칸에 알맞은 것을 채워라.", - "answer_text": "풀이 참조" + "question_text": "다음 그림에서 $\\square ABCD$가 직사각형일 때, $\\angle x$의 크기를 구하여라.", + "answer_text": "$\\angle x=50\\degree$" }, { - "id": "111:train:00043_43841", - "achievement_code_2015": "9수03-11", - "topic_name_2015": "평행사변형이 되는 조건", - "difficulty": "easy", - "problem_type": "objective", - "question_text": "다음 사각형 중에서 평행사변형이라고 할 수 없는 것을 모두 고르면? (정답 $2$개)", - "answer_text": "③, ⑤" + "id": "real-110-train-18780_41075", + "achievement_code_2015": "9수04-03", + "topic_name_2015": "사각형의 성질", + "difficulty": "medium", + "problem_type": "essay", + "question_text": "다음 그림에서 $\\square$$ABCD$가 직사각형일 때, $\\angle ACD$의 크기를 구하여라.", + "answer_text": "$\\angle ACD=29\\degree$" }, { - "id": "111:train:00043_43880", - "achievement_code_2015": "9수03-13", - "topic_name_2015": "평행사변형이 되는 조건의 응용(1)-설명하기", + "id": "real-110-train-18780_41076", + "achievement_code_2015": "9수04-03", + "topic_name_2015": "사각형의 성질", "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "다음 $\\square ABCD$가 평행사변형일 때, 색칠한 부분의 사각형이 평행사변형이 되는 조건을 말하여라.", - "answer_text": "(1) 한 쌍의 대변이 평행하고, 그 길이가 같다." + "problem_type": "essay", + "question_text": "다음 그림에서 $\\square ABCD$가 직사각형일 때, $\\angle x$의 크기를 구하여라.", + "answer_text": "$\\angle x=47\\degree$" } ] }, "9수04-04": { "ui_name": "도형의 닮음", + "school_level": "middle", "grade": 2, - "candidate_pool": 254, + "course": "중학교 수학", + "candidate_pool": 249, "examples": [ { - "id": "111:train:00041_36222", - "achievement_code_2015": "9수03-13", - "topic_name_2015": "SAS닮음을 이용하여 변의 길이 구하기", + "id": "real-110-train-19320_108330", + "achievement_code_2015": "9수04-04", + "topic_name_2015": "도형의 닮음", "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "두 선분 $AB$와 $CD$가 점 $O$에서 만나고,$ 3 \\overline{AO}=\\overline{DO}, \\overline{CO}=\\frac{1}{3} \\overline{BO} $일 때, $ \\frac{\\overline{AC}}{\\overline{BD}} $의 값을 구하여라.", - "answer_text": "$ \\frac{1}{3} $" + "problem_type": "essay", + "question_text": "다음 그림에서 $\\triangle ABC \\backsim\\triangle DFE$이고 $\\triangle ABC$와 $\\triangle DFE$의 닮음비가 $8:5$일 때, $\\triangle DFE$의 둘레의 길이를 구하여라.", + "answer_text": "$60 cm$" }, { - "id": "111:train:00041_36233", - "achievement_code_2015": "9수03-13", - "topic_name_2015": "AA닮음을 이용하여 변의 길이 구하기(2)", - "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "다음 평행사변형 $ABCD$에서 $x$의 값을 구하여라.", - "answer_text": "$3$" + "id": "real-110-train-19321_46989", + "achievement_code_2015": "9수04-04", + "topic_name_2015": "도형의 닮음", + "difficulty": "medium", + "problem_type": "essay", + "question_text": "다음 그림의 두 평행사변형 $ABCD$, $EFGH$에 대하여 $\\square ABCD\\backsim\\square EFGH$이고 닮음비가 $3 : 4$일 때, $\\square EFGH$의 둘레의 길이를 구하여라.", + "answer_text": "$88 cm$" }, { - "id": "111:train:00043_47737", - "achievement_code_2015": "9수03-13", - "topic_name_2015": "직각삼각형의 닮음", + "id": "real-110-train-19321_46990", + "achievement_code_2015": "9수04-04", + "topic_name_2015": "도형의 닮음", "difficulty": "easy", - "problem_type": "short_answer", - "question_text": "$ \\overline{AB}=8 \\mathrm{~cm}, \\overline{BC}=6 \\mathrm{~cm} $ 이고 $ \\angle B=90^{\\circ} $ 인 직각삼각형 $ ABC $ 가 있다. $ \\triangle ABC $ 를 $ \\overline{AB} $를 축으로 하여 $1$회전 시켜 생기는 원뿔 안에 밑면의 반지름의 길이가 $2cm$인 원기둥을 다음 그림과 같이 그릴 때, 이 원기둥의 높이를 구하여라.", - "answer_text": "$ \\frac{16}{3} \\mathrm{~cm} $" + "problem_type": "essay", + "question_text": "다음 그림의 두 평행사변형 $ABCD$, $HGFE$에 대하여 $\\square ABCD$~$\\square HGFE$이고 닮음비가 $5 : 4$일 때, $\\square ABCD$의 둘레의 길이를 구하여라.", + "answer_text": "$90 cm$" } ] }, "9수05-02": { "ui_name": "경우의 수와 확률", + "school_level": "middle", "grade": 2, - "candidate_pool": 643, + "course": "중학교 수학", + "candidate_pool": 98, "examples": [ { - "id": "110:train:7907_4113", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "수를 뽑는 경우의 수", + "id": "real-110-train-8714_102048", + "achievement_code_2015": "9수05-02", + "topic_name_2015": "경우의 수와 확률", "difficulty": "easy", "problem_type": "essay", - "question_text": "$4$부터 $8$까지의 자연수가 각각 적힌 카드 $5$ 장 중에서 한 장의 카드를 뽑아서 나온 수를 $x$라 한다. $\\frac{1}{x}$을 소수로 나타내었을 때, 순환소수가 되는 경우의 수를 구하여라.", - "answer_text": "$2$" + "question_text": "주머니 속에 흰 바둑돌 $5$ 개와 검은 바둑돌 $3$ 개가 들어 있다. 이 주머니에서 바둑돌 $1$ 개를 꺼내 확인하고 다시 넣은 후 또 $1$ 개를 꺼낼 때, 두 개 모두 검은 바둑돌일 확률을 구하여라.", + "answer_text": "$\\frac{9}{64}$" }, { - "id": "110:train:7911_4097", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "주사위, 동전을 던지는 경우의 수", - "difficulty": "easy", + "id": "real-110-train-8714_102049", + "achievement_code_2015": "9수05-02", + "topic_name_2015": "경우의 수와 확률", + "difficulty": "medium", "problem_type": "essay", - "question_text": "서로 다른 두 개의 주사위를 동시에 던질 때, 나오는 눈의 수의 합이 $10$이 되는 경우의 수를 구하여라.", - "answer_text": "$3$" + "question_text": "주머니 속에 빨간 구슬 $3$ 개와 파란 구슬 $7$ 개가 들어 있다. 이 주머니에서 구슬 $1\\text{ }\\text{개}$를 꺼내 확인하고 다시 넣은 후 또 $1$ 개를 꺼낼 때, 첫 번째는 빨간 구슬, 두 번째는 파란 구슬일 확률을 구하여라.", + "answer_text": "$\\frac{21}{100}$" }, { - "id": "110:train:34903_140204", - "achievement_code_2015": "9수04-05", - "topic_name_2015": "여러 가지 경우의 수", + "id": "real-110-train-8714_102051", + "achievement_code_2015": "9수05-02", + "topic_name_2015": "경우의 수와 확률", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음과 같이 $ㄱ$~$ㅂ$ 중에서 하나를 선택하여 사다리를 타면 상품을 받는 사다리 타기 그림을 그리는 중이다. 상품은 시계, 가방, 게임기가 있고, 시계는 가방보다 $2$ 개 더 많이 있을 때, 나머지 자리에 상품을 배치하여 그림을 완성하는 경우의 수를 구하여라. (단, 어느 것을 선택해도 상품은 $1$ 개 받는다.)", - "answer_text": "$3$" + "question_text": "상자 속에 노란 공 $2$ 개와 빨간 공 $6$ 개가 들어 있다. 이 상자에서 공 $1$개를 꺼내 확인하고 다시 넣은 후 또 $1$ 개를 꺼낼 때, 첫 번째는 노란 공, 두 번째는 빨간 공일 확률을 구하여라.", + "answer_text": "$\\frac{3}{16}$" } ] }, "9수01-05": { "ui_name": "제곱근과 실수", + "school_level": "middle", "grade": 3, - "candidate_pool": 1444, + "course": "중학교 수학", + "candidate_pool": 450, "examples": [ { - "id": "110:train:34441_137463", - "achievement_code_2015": "9수01-07", - "topic_name_2015": "제곱근의 뜻과 성질", - "difficulty": "easy", - "problem_type": "essay", - "question_text": "$25$의 두 제곱근을 $a$, $b$라 할 때, 제곱근 $\\sqrt{2a+b+4}$의 값을 구하려고 한다. (단, $a>b$) (1) $a$, $b$의 값을 각각 구하여라. (2) 제곱근 $\\sqrt{2a+b+4}$의 값을 구하여라.", - "answer_text": "$a=5$$,$ $b=-5$" + "id": "real-30-train-S3_중등_1_000072", + "achievement_code_2015": "9수01-05", + "topic_name_2015": "제곱근과 실수", + "difficulty": "medium", + "problem_type": "objective", + "question_text": "다음 계산 과정에서 사용된 법칙을 차례로 나열한 것은?\n$\\begin{array}{l}1+\\left\\{\\left(\\frac{3}{2}+4\\right)+\\frac{1}{2}\\right\\} \\times 4 \\\\ =1+\\left\\{\\frac{3}{2}+\\left(4+\\frac{1}{2}\\right)\\right\\} \\times 4 \\\\ =1+\\left\\{\\frac{3}{2}+\\left(\\frac{1}{2}+4\\right)\\right\\} \\times 4\\end{array}$ $\\square$ $\\square$", + "answer_text": "⑤ 결합법칙, 교환법칙" }, { - "id": "110:train:11954_116148", - "achievement_code_2015": "9수01-07", - "topic_name_2015": "제곱근의 성질을 이용하여 제곱근 구하기", - "difficulty": "easy", - "problem_type": "essay", - "question_text": "$\\sqrt{16}$의 음의 제곱근을 $A$, $(-3)^2$의 양의 제곱근을 $B$라 할 때, $A+B$의 값을 구하여라.", - "answer_text": "$1$" + "id": "real-30-train-S3_중등_1_000073", + "achievement_code_2015": "9수01-05", + "topic_name_2015": "제곱근과 실수", + "difficulty": "medium", + "problem_type": "subjective", + "question_text": "오른쪽 그림의 정육면체 모양의 주사위에서 마주 보는 면에 적힌 두 수의 합이 0이라고 할 때, 보이지 않는 세 면에 적힌 수들의 합을 구하고 그 과정을 서술하시오.\n-3, -8, +9가 적혀있는 정육면체 모양의 주사위.", + "answer_text": "$(-8)$과 마주 보는 면에 적힌 수는 $0-(-8)=0+(+8)=+8$ $(-3)$과 마주 보는 면에 적힌 수는 $0-(-3)=0+(+3)=+3$ $(+9)$와 마주 보는 면에 적힌 수는 $0-(+9)=0+(-9)=-9$ 따라서 보이지 않는 세 면에 적힌 수들의 합은 \\[ (+8)+(+3)+(-9)=(+11)+(-9)=+2 \\]" }, { - "id": "110:train:11979_116172", - "achievement_code_2015": "9수01-07", - "topic_name_2015": "제곱근의 성질을 이용하여 계산하기", + "id": "real-30-train-S3_중등_1_000074", + "achievement_code_2015": "9수01-05", + "topic_name_2015": "제곱근과 실수", "difficulty": "easy", - "problem_type": "essay", - "question_text": "$A$, $B$가 다음과 같을 때, $A+B$의 값을 구하여라. $A=\\sqrt{16}+\\sqrt{(-2)^2}-(\\sqrt{5})^2$ $B=\\sqrt{100}\\times\\sqrt{(-\\frac{1}{5})^2}$", - "answer_text": "$3$" + "problem_type": "objective", + "question_text": "다음 중 네 번째로 계산해야 할 곳을 말하시오.\n\\[ \\begin{array}{l} &4&-&[&5&+&3 &\\times&\\{(-2) &\\times& 4&+&(-16) &\\div& 8\\}] \\\\ &&\\uparrow&&& \\uparrow&& \\uparrow&&\\uparrow &&\\uparrow &&\\uparrow\\\\ &&㉠&&&㉡&& ㉢&&㉣&& ㉤&&㉥ \\end{array} \\]", + "answer_text": "㉢" } ] }, "9수01-06": { "ui_name": "근호를 포함한 식의 계산", + "school_level": "middle", "grade": 3, - "candidate_pool": 57, + "course": "중학교 수학", + "candidate_pool": 369, "examples": [ { - "id": "110:train:12231_116781", - "achievement_code_2015": "9수03-16", - "topic_name_2015": "분배법칙을 이용한 분모의 유리화", + "id": "real-110-train-25095_77845", + "achievement_code_2015": "9수01-06", + "topic_name_2015": "근호를 포함한 식의 계산", "difficulty": "easy", "problem_type": "essay", - "question_text": "$\\frac{\\sqrt{12}+4}{\\sqrt{2}}$의 분모를 유리화하였더니 $a\\sqrt{2}+b\\sqrt{6}$이 되었다. 유리수 $a$, $b$에 대하여 $a+b$의 값을 구하여라.", - "answer_text": "$3$" + "question_text": "다음을 각각 만족시키는 세 수 $p$, $q$, $r$의 크기를 비교하여라. $\\bullet(2x^2y^2)^2\\div(xy)^2=4x^py^2$ $\\bullet(2xy^2)^3\\times2xy=16x^4y^q$ $\\bullet\\frac{x^2}{3}\\times36x^2y^3\\div xy^4=\\frac{rx^3}{y}$", + "answer_text": "$p0$)", - "answer_text": "$a=6$" + "id": "real-30-train-S3_중등_2_001594", + "achievement_code_2015": "9수03-04", + "topic_name_2015": "이차함수와 그래프", + "difficulty": "medium", + "problem_type": "subjective", + "question_text": "$x$와 $y$ 사이의 관계가 다음과 같을 때, 주어진 표를 완성하고, $y$가 $x$의 함수인지 아닌지 말하시오.\n① 사탕 20 개가 들어 있는 사탕 한 봉지에서 사탕 $x$ 개를 먹었을 때 남은 사탕 $y$ 개\n② 자연수 $x$보다 작은 자연수 $y$\n
$x$ (개) 1 2 3 4
$y$ (개)
\n
$x$ 1 2 3 4
$y$
", + "answer_text": "① 함수이다. ② 함수가 아니다." }, { - "id": "110:train:12837_121265", - "achievement_code_2015": "9수02-21", - "topic_name_2015": "이차함수 y=ax²의 그래프의 활용", + "id": "real-30-train-S3_중등_2_001711", + "achievement_code_2015": "9수03-04", + "topic_name_2015": "이차함수와 그래프", "difficulty": "easy", - "problem_type": "essay", - "question_text": "이차함수 $y=-4x^2$의 그래프가 두 점 $(a, -4)$, $(2, b)$를 지날 때, $ab$의 값을 구하여라. (단, $a<0$)", - "answer_text": "$16$" + "problem_type": "objective", + "question_text": "다음 보기 중 $y$가 $x$의 함수인 것을 모두 고른 것은?\n보기 상자에 다음과 같은 내용이 들어 있다. ᄀ. $x$와 $y$의 합이 8 ㄴ. 자연수 $x$의 배수 $y$ ㄷ. 재연수 $x$와 서로소인 수 $y$ ㄹ. 자연수 $x$보다 작은 자연수 $y$ ㅁ. 농도가 $x \\%$ 인 소금물 200g 속에 들어 있는 소금의 양이 $y \\mathrm{~g}$", + "answer_text": "③ ㄱ,ㅁ" }, { - "id": "110:train:34879_165828", - "achievement_code_2015": "9수02-22", - "topic_name_2015": "이차함수 y=ax²의 그래프", - "difficulty": "easy", - "problem_type": "essay", - "question_text": "다음 조건을 만족시키는 음의 정수 $a$는 모두 몇 개인지 구하여라. 이차함수 $y=ax^2$의 그래프는 $y=-\\frac{4}{3}x^2$의 그래프보다 폭이 더 좁고, $y=-5x^2$의 그래프보다 폭이 더 넓다.", - "answer_text": "$3$ 개" + "id": "real-30-train-S3_중등_2_001778", + "achievement_code_2015": "9수03-04", + "topic_name_2015": "이차함수와 그래프", + "difficulty": "medium", + "problem_type": "objective", + "question_text": "다음 보기 중 $y$가 $x$의 일차함수인 것을 모두 고르시오.\n보기라고 쓰여져 있는 네모상자에 다음과 같은 내용이 들어 있다. ㄱ. 한 사람의 버스 요금이 $900$원일 때, $x$명의 버스 요금은 $y$원이다. ㄴ. 한 변의 길이가 $x \\mathrm{~cm}$인 정사각형의 넓이는 $y \\mathrm{~cm}^{2}$이다. ㄷ. $500$원짜리 연필 한 자루와 $x$원짜리 지우개 $3$ 개의 값의 합은 $y$원이다. ㄹ. $100$개의 영양제를 하루에 $x$0개씩 $x$일 동안 먹고 남은 양은 $y$개이다. ㅁ. $x$3km의 거리를 시속 $x$4로 달린 시간은 $y$시간이다.", + "answer_text": "ㄱ, ㄷ, ㄹ" } ] }, "9수04-05": { "ui_name": "삼각비", + "school_level": "middle", "grade": 3, - "candidate_pool": 375, + "course": "중학교 수학", + "candidate_pool": 1782, "examples": [ { - "id": "110:train:33053_127747", - "achievement_code_2015": "9수03-16", - "topic_name_2015": "직선의 기울기와 삼각비", - "difficulty": "easy", + "id": "real-110-train-14432_125221", + "achievement_code_2015": "9수04-05", + "topic_name_2015": "삼각비", + "difficulty": "medium", "problem_type": "essay", - "question_text": "점 $(2\\sqrt{3}, 5)$를 지나고 $x$축의 양의 방향과 이루는 각의 크기가 $60\\degree$인 직선의 방정식을 구하여라.", - "answer_text": "$y=\\sqrt{3}x-1$" + "question_text": "다음 그림과 같은 직각삼각형 $ABC$에서 $\\overline{AC}=9$이고 $\\tan B=\\frac{3}{8}$일 때, $\\triangle ABC$의 넓이를 구하여라.", + "answer_text": "$108$" }, { - "id": "110:train:34260_135247", - "achievement_code_2015": "9수03-16", - "topic_name_2015": "사분원을 이용하여 삼각비의 값 구하기", - "difficulty": "easy", + "id": "real-110-train-14432_134099", + "achievement_code_2015": "9수04-05", + "topic_name_2015": "삼각비", + "difficulty": "medium", "problem_type": "essay", - "question_text": "이차방정식 $4x^2-4x+1=0$의 한 근을 $\\sin A$라 할 때, $\\cos3A-2\\sin2A+3\\tan A$의 값을 구하여라. (단, $0\\degree0$)", - "answer_text": "$a=6$" + "question_text": "$5$ 개의 문자 A, B, C, D, E를 모두 한 번씩 사용하여 사전식으로 배열할 때, BDCEA는 몇 번째에 오는지 구하시오.", + "answer_text": "$40$ 번째" }, { - "id": "110:train:13562_134811", - "achievement_code_2015": "9수03-16", - "topic_name_2015": "평균과 분산 또는 표준편차를 알 때, 변량 구하기", + "id": "real-110-train-28634_101432", + "achievement_code_2015": "10공수05-01", + "topic_name_2015": "경우의 수", "difficulty": "easy", "problem_type": "essay", - "question_text": "다음은 평균이 $47$인 변량 $a$, $b$, $c$, $d$에 대한 편차를 나타낸 표이다. $2c+x$의 값을 구하여라.
변량 $a$$b$$c$$d$
편차 $5$ $-2$$x$$-9$
", - "answer_text": "$112$" + "question_text": "$5$ 개의 문자 $A, B, C, D, E$를 모두 한 번씩 사용하여 사전식으로 배열할 때, $CDBEA$는 몇 번째에 오는지 구하시오.", + "answer_text": "$64$ $번째$" + } + ] + }, + "10공수05-02": { + "ui_name": "순열과 조합", + "school_level": "high", + "grade": null, + "course": "공통수학", + "candidate_pool": 10, + "examples": [ + { + "id": "real-30-train-S3_고등_1_007208", + "achievement_code_2015": "10공수05-02", + "topic_name_2015": "순열과 조합", + "difficulty": "medium", + "problem_type": "objective", + "question_text": "부모를 포함하여 5 명의 가족이 가족 사진을 찍으려고 한다. 앞줄에는 2 명이 앉고 뒷줄에는 3 명이 선다고 할 때, 부모가 같은 줄에 있게 되는 방법의 수는?", + "answer_text": "④ 48" + }, + { + "id": "real-30-train-S3_고등_1_007210", + "achievement_code_2015": "10공수05-02", + "topic_name_2015": "순열과 조합", + "difficulty": "medium", + "problem_type": "objective", + "question_text": "사진을 찍기 위해 남자 5 명과 여자 3 명이 한 줄로 설 때, 양 끝에는 남자가 오고 여자들끼리는 서로 이웃하 지 않게 서는 방법의 수는?", + "answer_text": "⑤ 2880" + }, + { + "id": "real-30-train-S3_고등_1_007213", + "achievement_code_2015": "10공수05-02", + "topic_name_2015": "순열과 조합", + "difficulty": "hard", + "problem_type": "objective", + "question_text": "동아리 발표 대회의 심사 위 원석은 남자 3 명과 여자 3 명으로 구성되어 있는데, 심 사 위원석은 오른쪽 그림과 같이 배석되어 있다. 붙어\n있는 의자에는 반드시 남녀가 1 명씩 앉도록 할 때, 심 사위원 6명이 앉을 수 있는 모든 경우의 수는?\n심사위원이 앉을 수 있는 의자 6개와 직사각형 모양의 책상이 있다.", + "answer_text": "② 288" } ] } diff --git a/packages/web/app/app/new/topic/page.tsx b/packages/web/app/app/new/topic/page.tsx index ae119fb..f1b9a67 100644 --- a/packages/web/app/app/new/topic/page.tsx +++ b/packages/web/app/app/new/topic/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { parseGrade } from "./data"; +import { parseGrade, parseSchoolLevel } from "./data"; import { TopicPicker } from "./picker"; export const metadata: Metadata = { @@ -9,10 +9,11 @@ export const metadata: Metadata = { }; type Props = { - searchParams: { grade?: string | string[] }; + searchParams: { school?: string | string[]; grade?: string | string[] }; }; export default function TopicPage({ searchParams }: Props) { + const schoolLevel = parseSchoolLevel(searchParams.school); const grade = parseGrade(searchParams.grade); - return ; + return ; } diff --git a/packages/web/app/app/new/topic/picker.tsx b/packages/web/app/app/new/topic/picker.tsx index e278028..4985a1e 100644 --- a/packages/web/app/app/new/topic/picker.tsx +++ b/packages/web/app/app/new/topic/picker.tsx @@ -6,16 +6,19 @@ import { LatexMixed } from "@/components/math/latex-renderer"; import { type Category, type Grade, + type SchoolLevel, type Topic, categories, gradeLabel, - topics, + topicScopeLabel, + topicsForScope, } from "./data"; import examplesData from "./examples.json"; type Filter = "전체" | Category; type Props = { + schoolLevel: SchoolLevel; grade: Grade | null; }; @@ -31,7 +34,7 @@ type ExampleEntry = { type ExamplesUnit = { ui_name: string; - grade: number; + grade: number | null; candidate_pool: number; examples: ExampleEntry[]; }; @@ -56,20 +59,17 @@ function problemTypeLabel(t: ExampleEntry["problem_type"]): string { } } -export function TopicPicker({ grade }: Props) { +export function TopicPicker({ schoolLevel, grade }: Props) { const [filter, setFilter] = useState("전체"); const [selectedCode, setSelectedCode] = useState(null); const visible = useMemo(() => { - if (grade === null) return []; - return topics.filter( - (t) => - t.grade === grade && - (filter === "전체" || t.category === filter), + return topicsForScope(schoolLevel, grade).filter( + (t) => filter === "전체" || t.category === filter, ); - }, [grade, filter]); + }, [schoolLevel, grade, filter]); - if (grade === null) { + if (schoolLevel === "middle" && grade === null) { return ( <>