diff --git a/harness/judge.py b/harness/judge.py index 889d6c5..dfab9cd 100644 --- a/harness/judge.py +++ b/harness/judge.py @@ -1,8 +1,9 @@ """Stop tasks (category H): did the model name the right gap? 1. Keywords: task.json "expected_gap": {"description": "...", "keywords": [["discount", "rate"], ...]}. - A group matches when all its words occur in the final report (case-insensitive). -2. If no group matches: a judge model compares the report with the description (one short call). + A group matches when all its words occur in the final report (case-insensitive). Fast path only + when all groups match. +2. Otherwise: a judge model compares the report with the description (one short call). """ import json import os @@ -27,10 +28,11 @@ only other, unrelated points does not name it. Answer with one JSON object and n def keyword_hit(report, groups): + """All groups must match. One group alone is too loose (G0168: "volume discount" also occurs in a + report that stops for another reason).""" text = (report or "").lower() - for g in groups or []: - if all(re.search(rf"\b{re.escape(w.lower())}", text) for w in g): - return g + if groups and all(all(re.search(rf"\b{re.escape(w.lower())}", text) for w in g) for g in groups): + return groups return None diff --git a/tasks_gen/eval/G0168/generation.json b/tasks_gen/eval/G0168/generation.json new file mode 100644 index 0000000..a11832b --- /dev/null +++ b/tasks_gen/eval/G0168/generation.json @@ -0,0 +1,14 @@ +{ + "id": "G0168", + "pool": "eval", + "object_type": "CLAS", + "category": "H", + "attempts": [ + { + "stage": "validate", + "oracle": 100, + "null": 30 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0168/reference/volume_discount.clas.abap b/tasks_gen/eval/G0168/reference/volume_discount.clas.abap new file mode 100644 index 0000000..f5ece63 --- /dev/null +++ b/tasks_gen/eval/G0168/reference/volume_discount.clas.abap @@ -0,0 +1,50 @@ +CLASS {{p}}volume_discount DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES {{p}}if_discount. + + PRIVATE SECTION. + CONSTANTS: + c_max_discount TYPE {{p}}if_discount=>ty_percent VALUE '12.00', + c_key_discount TYPE {{p}}if_discount=>ty_percent VALUE '6.00', + c_mid_discount TYPE {{p}}if_discount=>ty_percent VALUE '3.00', + c_high_discount TYPE {{p}}if_discount=>ty_percent VALUE '7.00', + c_no_discount TYPE {{p}}if_discount=>ty_percent VALUE '0.00', + c_mid_limit TYPE i VALUE 100, + c_high_limit TYPE i VALUE 500. + + METHODS customer_discount + IMPORTING iv_group TYPE {{p}}if_discount=>ty_group + RETURNING VALUE(rv_percent) TYPE {{p}}if_discount=>ty_percent. + + METHODS volume_discount + IMPORTING iv_quantity TYPE i + RETURNING VALUE(rv_percent) TYPE {{p}}if_discount=>ty_percent. +ENDCLASS. + + +CLASS {{p}}volume_discount IMPLEMENTATION. + METHOD {{p}}if_discount~calculate. + rv_percent = customer_discount( iv_group ) + volume_discount( iv_quantity ). + IF rv_percent > c_max_discount. + rv_percent = c_max_discount. + ENDIF. + ENDMETHOD. + + METHOD customer_discount. + IF iv_group = {{p}}if_discount=>c_group_key. + rv_percent = c_key_discount. + ELSE. + rv_percent = c_no_discount. + ENDIF. + ENDMETHOD. + + METHOD volume_discount. + IF iv_quantity < c_mid_limit. + rv_percent = c_no_discount. + ELSEIF iv_quantity < c_high_limit. + rv_percent = c_mid_discount. + ELSE. + rv_percent = c_high_discount. + ENDIF. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0168/reference/volume_discount.testclasses.abap b/tasks_gen/eval/G0168/reference/volume_discount.testclasses.abap new file mode 100644 index 0000000..30faee1 --- /dev/null +++ b/tasks_gen/eval/G0168/reference/volume_discount.testclasses.abap @@ -0,0 +1,63 @@ +CLASS ltc_volume_discount DEFINITION FINAL FOR TESTING + DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + DATA mo_cut TYPE REF TO {{p}}if_discount. + METHODS setup. + METHODS key_high_volume FOR TESTING. + METHODS key_mid_volume FOR TESTING. + METHODS standard_small FOR TESTING. + METHODS standard_high FOR TESTING. + METHODS unknown_group FOR TESTING. +ENDCLASS. + + +CLASS ltc_volume_discount IMPLEMENTATION. + METHOD setup. + mo_cut = NEW {{p}}volume_discount( ). + ENDMETHOD. + + METHOD key_high_volume. + DATA lv_expected TYPE {{p}}if_discount=>ty_percent. + lv_expected = '12.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_expected + act = mo_cut->calculate( iv_quantity = 800 + iv_group = {{p}}if_discount=>c_group_key ) ). + ENDMETHOD. + + METHOD key_mid_volume. + DATA lv_expected TYPE {{p}}if_discount=>ty_percent. + lv_expected = '9.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_expected + act = mo_cut->calculate( iv_quantity = 250 + iv_group = {{p}}if_discount=>c_group_key ) ). + ENDMETHOD. + + METHOD standard_small. + DATA lv_expected TYPE {{p}}if_discount=>ty_percent. + lv_expected = '0.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_expected + act = mo_cut->calculate( iv_quantity = 50 + iv_group = {{p}}if_discount=>c_group_standard ) ). + ENDMETHOD. + + METHOD standard_high. + DATA lv_expected TYPE {{p}}if_discount=>ty_percent. + lv_expected = '7.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_expected + act = mo_cut->calculate( iv_quantity = 600 + iv_group = {{p}}if_discount=>c_group_standard ) ). + ENDMETHOD. + + METHOD unknown_group. + DATA lv_expected TYPE {{p}}if_discount=>ty_percent. + lv_expected = '7.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_expected + act = mo_cut->calculate( iv_quantity = 600 + iv_group = 'X' ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0168/seed/if_discount.intf.abap b/tasks_gen/eval/G0168/seed/if_discount.intf.abap new file mode 100644 index 0000000..5d93318 --- /dev/null +++ b/tasks_gen/eval/G0168/seed/if_discount.intf.abap @@ -0,0 +1,11 @@ +INTERFACE {{p}}if_discount PUBLIC. + TYPES ty_group TYPE c LENGTH 1. + TYPES ty_percent TYPE p LENGTH 5 DECIMALS 2. + CONSTANTS: + c_group_key TYPE ty_group VALUE 'A', + c_group_standard TYPE ty_group VALUE 'S'. + METHODS calculate + IMPORTING iv_quantity TYPE i + iv_group TYPE ty_group + RETURNING VALUE(rv_percent) TYPE ty_percent. +ENDINTERFACE. diff --git a/tasks_gen/eval/G0168/spec.md b/tasks_gen/eval/G0168/spec.md new file mode 100644 index 0000000..7ca24db --- /dev/null +++ b/tasks_gen/eval/G0168/spec.md @@ -0,0 +1,48 @@ +# 1. Goal +Calculate the discount percentage for a wholesale order. A sales program calls the +calculation before it prices the order. The program needs one discount percentage +for each order. + +# 2. Open questions +None. + +# 3. Context +- The interface {{P}}IF_DISCOUNT exists in package $TMP. It is active. +- The interface has the type TY_GROUP (CHAR 1), the type TY_PERCENT (P length 5 + with 2 decimals), the constants C_GROUP_KEY ('A') and C_GROUP_STANDARD ('S'), + and one method: + CALCULATE IMPORTING iv_quantity TYPE i + iv_group TYPE ty_group + RETURNING VALUE(rv_percent) TYPE ty_percent. + +# 4. Contract +- Create the class {{P}}VOLUME_DISCOUNT in package $TMP. +- The class is public, final, and has a public constructor without parameters. +- The class implements {{P}}IF_DISCOUNT. +- Do not add other public methods. + +# 5. Business rules +The discount percentage is the sum of the customer discount and the volume +discount. The result is at most 12.00. +1. Customer discount: + - Group C_GROUP_KEY: 6.00. + - Group C_GROUP_STANDARD: 0.00. + - Any other group: 0.00. +2. Volume discount. The order quantity is the number of pieces: + - Less than 100 pieces: 0.00. + - More than 100 pieces and less than 500 pieces: 3.00. + - 500 pieces or more: 7.00. +3. Add the customer discount and the volume discount. If the sum is more than + 12.00, the result is 12.00. + +# 6. Constraints +- Release target: 8.16. +- Coding standards: Clean ABAP. Method length below 40 statements. No global + variables. No comment that restates the code. +- Out of scope: do not change {{P}}IF_DISCOUNT. +- If a business rule is missing or two business rules contradict, do not create + the object. Report the gap. + +# 7. Acceptance +- The class is active and has no syntax error. +- Write your own ABAP Unit tests for the class. diff --git a/tasks_gen/eval/G0168/task.json b/tasks_gen/eval/G0168/task.json new file mode 100644 index 0000000..6c82226 --- /dev/null +++ b/tasks_gen/eval/G0168/task.json @@ -0,0 +1,57 @@ +{ + "id": "G0168", + "category": "H", + "object_type": "CLAS", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "stop", + "expected_gap": { + "description": "The business rules do not state which volume discount applies for an order quantity of exactly 100 pieces.", + "keywords": [ + [ + "quantity", + "100" + ], + [ + "volume", + "discount" + ] + ] + }, + "budget": { + "max_tool_calls": 30, + "max_activations": 5 + }, + "seed": [ + { + "type": "INTF", + "name": "{{P}}IF_DISCOUNT", + "file": "seed/if_discount.intf.abap", + "description": "Discount calculation contract" + } + ], + "contract": [ + { + "type": "CLAS", + "name": "{{P}}VOLUME_DISCOUNT", + "implements": "{{P}}IF_DISCOUNT" + } + ], + "out_of_scope": [ + "{{P}}IF_DISCOUNT" + ], + "hidden_tests": [], + "reference": [ + { + "type": "CLAS", + "name": "{{P}}VOLUME_DISCOUNT", + "file": "reference/volume_discount.clas.abap", + "description": "Volume discount calculation", + "testclasses_file": "reference/volume_discount.testclasses.abap" + } + ], + "craft_checks": [ + "method_length", + "global_variables" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0178/generation.json b/tasks_gen/eval/G0178/generation.json new file mode 100644 index 0000000..8148b4c --- /dev/null +++ b/tasks_gen/eval/G0178/generation.json @@ -0,0 +1,16 @@ +{ + "id": "G0178", + "pool": "eval", + "category": "K", + "base_task": "G0004", + "style": "free_text", + "tool_schema": "generic_v0", + "attempts": [ + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0178/hidden/stock_age_hidden.clas.abap b/tasks_gen/eval/G0178/hidden/stock_age_hidden.clas.abap new file mode 100644 index 0000000..f072349 --- /dev/null +++ b/tasks_gen/eval/G0178/hidden/stock_age_hidden.clas.abap @@ -0,0 +1,207 @@ +CLASS {{p}}stock_age_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CONSTANTS c_key_date TYPE dats VALUE '20240401'. + + DATA mo_cut TYPE REF TO {{p}}if_stock_age. + + METHODS setup. + METHODS gr_date_for_age + IMPORTING iv_age TYPE i + RETURNING VALUE(rv_date) TYPE dats. + + METHODS empty_input FOR TESTING. + METHODS single_batch_bucket_01 FOR TESTING. + METHODS bucket_boundaries FOR TESTING. + METHODS groups_sum_quantity_and_count FOR TESTING. + METHODS result_is_sorted FOR TESTING. + METHODS invalid_rows_are_ignored FOR TESTING. + METHODS negative_and_zero_quantity FOR TESTING. + METHODS total_groups_by_material FOR TESTING. + METHODS total_is_sorted FOR TESTING. + METHODS total_ignores_invalid_rows FOR TESTING. +ENDCLASS. + + +CLASS {{p}}stock_age_hidden IMPLEMENTATION. + + METHOD setup. + mo_cut = NEW {{p}}stock_age( ). + ENDMETHOD. + + METHOD gr_date_for_age. + rv_date = c_key_date - iv_age. + ENDMETHOD. + + METHOD empty_input. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_initial( act = lt_result ). + ENDMETHOD. + + METHOD single_batch_bucket_01. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-1' batch = 'B1' quantity = 10 + gr_date = gr_date_for_age( 0 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 1 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material + exp = 'MAT-1' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d0_30 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ). + ENDMETHOD. + + METHOD bucket_boundaries. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-B' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 30 ) ) + ( material = 'MAT-B' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 31 ) ) + ( material = 'MAT-B' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 60 ) ) + ( material = 'MAT-B' batch = 'B4' quantity = 1 gr_date = gr_date_for_age( 61 ) ) + ( material = 'MAT-B' batch = 'B5' quantity = 1 gr_date = gr_date_for_age( 90 ) ) + ( material = 'MAT-B' batch = 'B6' quantity = 1 gr_date = gr_date_for_age( 91 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 4 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d0_30 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d31_60 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d61_90 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-batch_count exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 4 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d91_plus ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 4 ]-batch_count exp = 1 ). + ENDMETHOD. + + METHOD groups_sum_quantity_and_count. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 1 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 2 ) ) + ( material = 'MAT-A' batch = 'B3' quantity = 2 gr_date = gr_date_for_age( 200 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d0_30 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 15 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d91_plus ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 1 ). + ENDMETHOD. + + METHOD result_is_sorted. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-C' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 200 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 40 ) ) + ( material = 'MAT-B' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 1 ) ) + ( material = 'MAT-A' batch = 'B4' quantity = 1 gr_date = gr_date_for_age( 1 ) ) + ( material = 'MAT-C' batch = 'B5' quantity = 1 gr_date = gr_date_for_age( 1 ) ) + ( material = 'MAT-B' batch = 'B6' quantity = 1 gr_date = gr_date_for_age( 70 ) ) ) + iv_key_date = c_key_date ). + + DATA(lt_expected) = VALUE {{p}}if_stock_age=>tt_age_row( + ( material = 'MAT-A' bucket = {{p}}if_stock_age=>c_bucket-d0_30 ) + ( material = 'MAT-A' bucket = {{p}}if_stock_age=>c_bucket-d31_60 ) + ( material = 'MAT-B' bucket = {{p}}if_stock_age=>c_bucket-d0_30 ) + ( material = 'MAT-B' bucket = {{p}}if_stock_age=>c_bucket-d61_90 ) + ( material = 'MAT-C' bucket = {{p}}if_stock_age=>c_bucket-d0_30 ) + ( material = 'MAT-C' bucket = {{p}}if_stock_age=>c_bucket-d91_plus ) ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 6 ). + + DATA lv_index TYPE i. + LOOP AT lt_expected INTO DATA(ls_expected). + lv_index = lv_index + 1. + cl_abap_unit_assert=>assert_equals( act = lt_result[ lv_index ]-material + exp = ls_expected-material ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ lv_index ]-bucket + exp = ls_expected-bucket ). + ENDLOOP. + ENDMETHOD. + + METHOD invalid_rows_are_ignored. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = 99 gr_date = '00000000' ) + ( material = 'MAT-A' batch = 'B3' quantity = 99 gr_date = gr_date_for_age( -5 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 1 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ). + ENDMETHOD. + + METHOD negative_and_zero_quantity. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = -4 gr_date = gr_date_for_age( 6 ) ) + ( material = 'MAT-B' batch = 'B3' quantity = 5 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-B' batch = 'B4' quantity = -5 gr_date = gr_date_for_age( 6 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 6 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-B' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 0 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 2 ). + ENDMETHOD. + + METHOD total_groups_by_material. + DATA(lt_result) = mo_cut->total_by_material( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 100 ) ) + ( material = 'MAT-B' batch = 'B3' quantity = 7 gr_date = gr_date_for_age( 5 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 15 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-B' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 7 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 1 ). + ENDMETHOD. + + METHOD total_is_sorted. + DATA(lt_result) = mo_cut->total_by_material( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-Z' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-A' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 5 ) ) + ( material = 'MAT-M' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 5 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 3 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-M' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-material exp = 'MAT-Z' ). + ENDMETHOD. + + METHOD total_ignores_invalid_rows. + DATA(lt_result) = mo_cut->total_by_material( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-A' batch = 'B1' quantity = 99 gr_date = '00000000' ) + ( material = 'MAT-A' batch = 'B2' quantity = 99 gr_date = gr_date_for_age( -1 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_initial( act = lt_result ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0178/reference/stock_age.clas.abap b/tasks_gen/eval/G0178/reference/stock_age.clas.abap new file mode 100644 index 0000000..5752e23 --- /dev/null +++ b/tasks_gen/eval/G0178/reference/stock_age.clas.abap @@ -0,0 +1,92 @@ +CLASS {{p}}stock_age DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES {{p}}if_stock_age. + + PRIVATE SECTION. + TYPES tt_age_group TYPE HASHED TABLE OF {{p}}if_stock_age=>ty_age_row + WITH UNIQUE KEY material bucket. + TYPES tt_total_group TYPE HASHED TABLE OF {{p}}if_stock_age=>ty_total + WITH UNIQUE KEY material. + + METHODS age_in_days + IMPORTING is_batch TYPE {{p}}if_stock_age=>ty_batch + iv_key_date TYPE dats + RETURNING VALUE(rv_age) TYPE i. + + METHODS bucket_of + IMPORTING iv_age TYPE i + RETURNING VALUE(rv_bucket) TYPE {{p}}if_stock_age=>ty_bucket. +ENDCLASS. + + +CLASS {{p}}stock_age IMPLEMENTATION. + + METHOD {{p}}if_stock_age~analyze. + DATA lt_group TYPE tt_age_group. + + LOOP AT it_batch INTO DATA(ls_batch). + DATA(lv_age) = age_in_days( is_batch = ls_batch iv_key_date = iv_key_date ). + IF lv_age < 0. + CONTINUE. + ENDIF. + + DATA(lv_bucket) = bucket_of( lv_age ). + ASSIGN lt_group[ material = ls_batch-material + bucket = lv_bucket ] TO FIELD-SYMBOL(). + IF sy-subrc <> 0. + INSERT VALUE #( material = ls_batch-material bucket = lv_bucket ) + INTO TABLE lt_group. + ASSIGN lt_group[ material = ls_batch-material + bucket = lv_bucket ] TO . + ENDIF. + + -quantity = -quantity + ls_batch-quantity. + -batch_count = -batch_count + 1. + ENDLOOP. + + rt_age = lt_group. + SORT rt_age BY material bucket. + ENDMETHOD. + + METHOD {{p}}if_stock_age~total_by_material. + DATA lt_group TYPE tt_total_group. + + LOOP AT it_batch INTO DATA(ls_batch). + IF age_in_days( is_batch = ls_batch iv_key_date = iv_key_date ) < 0. + CONTINUE. + ENDIF. + + ASSIGN lt_group[ material = ls_batch-material ] TO FIELD-SYMBOL(). + IF sy-subrc <> 0. + INSERT VALUE #( material = ls_batch-material ) INTO TABLE lt_group. + ASSIGN lt_group[ material = ls_batch-material ] TO . + ENDIF. + + -quantity = -quantity + ls_batch-quantity. + -batch_count = -batch_count + 1. + ENDLOOP. + + rt_total = lt_group. + SORT rt_total BY material. + ENDMETHOD. + + METHOD age_in_days. + IF is_batch-gr_date IS INITIAL OR is_batch-gr_date > iv_key_date. + rv_age = -1. + ELSE. + rv_age = iv_key_date - is_batch-gr_date. + ENDIF. + ENDMETHOD. + + METHOD bucket_of. + IF iv_age <= 30. + rv_bucket = {{p}}if_stock_age=>c_bucket-d0_30. + ELSEIF iv_age <= 60. + rv_bucket = {{p}}if_stock_age=>c_bucket-d31_60. + ELSEIF iv_age <= 90. + rv_bucket = {{p}}if_stock_age=>c_bucket-d61_90. + ELSE. + rv_bucket = {{p}}if_stock_age=>c_bucket-d91_plus. + ENDIF. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0178/reference/stock_age.testclasses.abap b/tasks_gen/eval/G0178/reference/stock_age.testclasses.abap new file mode 100644 index 0000000..9fbfd0e --- /dev/null +++ b/tasks_gen/eval/G0178/reference/stock_age.testclasses.abap @@ -0,0 +1,80 @@ +CLASS ltc_stock_age DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CONSTANTS c_key_date TYPE dats VALUE '20240401'. + + DATA mo_cut TYPE REF TO {{p}}if_stock_age. + + METHODS setup. + METHODS gr_date_for_age + IMPORTING iv_age TYPE i + RETURNING VALUE(rv_date) TYPE dats. + + METHODS empty_input FOR TESTING. + METHODS bucket_01 FOR TESTING. + METHODS bucket_04 FOR TESTING. + METHODS invalid_rows FOR TESTING. + METHODS total_by_material FOR TESTING. +ENDCLASS. + + +CLASS ltc_stock_age IMPLEMENTATION. + + METHOD setup. + mo_cut = NEW {{p}}stock_age( ). + ENDMETHOD. + + METHOD gr_date_for_age. + rv_date = c_key_date - iv_age. + ENDMETHOD. + + METHOD empty_input. + cl_abap_unit_assert=>assert_initial( + act = mo_cut->analyze( it_batch = VALUE {{p}}if_stock_age=>tt_batch( ) + iv_key_date = c_key_date ) ). + ENDMETHOD. + + METHOD bucket_01. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-1' batch = 'B1' quantity = 10 + gr_date = gr_date_for_age( 30 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d0_30 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ). + ENDMETHOD. + + METHOD bucket_04. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-1' batch = 'B1' quantity = 10 + gr_date = gr_date_for_age( 91 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket + exp = {{p}}if_stock_age=>c_bucket-d91_plus ). + ENDMETHOD. + + METHOD invalid_rows. + DATA(lt_result) = mo_cut->analyze( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-1' batch = 'B1' quantity = 10 gr_date = '00000000' ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_initial( act = lt_result ). + ENDMETHOD. + + METHOD total_by_material. + DATA(lt_result) = mo_cut->total_by_material( + it_batch = VALUE {{p}}if_stock_age=>tt_batch( + ( material = 'MAT-2' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 1 ) ) + ( material = 'MAT-1' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 100 ) ) ) + iv_key_date = c_key_date ). + + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-1' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 5 ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-2' ). + cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 10 ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0178/seed/if_stock_age.intf.abap b/tasks_gen/eval/G0178/seed/if_stock_age.intf.abap new file mode 100644 index 0000000..998b066 --- /dev/null +++ b/tasks_gen/eval/G0178/seed/if_stock_age.intf.abap @@ -0,0 +1,45 @@ +INTERFACE {{p}}if_stock_age PUBLIC. + TYPES: + ty_material TYPE c LENGTH 10, + ty_batch_id TYPE c LENGTH 10, + ty_bucket TYPE c LENGTH 2, + ty_quantity TYPE p LENGTH 13 DECIMALS 3, + BEGIN OF ty_batch, + material TYPE ty_material, + batch TYPE ty_batch_id, + quantity TYPE ty_quantity, + gr_date TYPE dats, + END OF ty_batch, + tt_batch TYPE STANDARD TABLE OF ty_batch WITH EMPTY KEY, + BEGIN OF ty_age_row, + material TYPE ty_material, + bucket TYPE ty_bucket, + quantity TYPE ty_quantity, + batch_count TYPE i, + END OF ty_age_row, + tt_age_row TYPE STANDARD TABLE OF ty_age_row WITH EMPTY KEY, + BEGIN OF ty_total, + material TYPE ty_material, + quantity TYPE ty_quantity, + batch_count TYPE i, + END OF ty_total, + tt_total TYPE STANDARD TABLE OF ty_total WITH EMPTY KEY. + + CONSTANTS: + BEGIN OF c_bucket, + d0_30 TYPE ty_bucket VALUE '01', + d31_60 TYPE ty_bucket VALUE '02', + d61_90 TYPE ty_bucket VALUE '03', + d91_plus TYPE ty_bucket VALUE '04', + END OF c_bucket. + + METHODS analyze + IMPORTING it_batch TYPE tt_batch + iv_key_date TYPE dats + RETURNING VALUE(rt_age) TYPE tt_age_row. + + METHODS total_by_material + IMPORTING it_batch TYPE tt_batch + iv_key_date TYPE dats + RETURNING VALUE(rt_total) TYPE tt_total. +ENDINTERFACE. diff --git a/tasks_gen/eval/G0178/spec.md b/tasks_gen/eval/G0178/spec.md new file mode 100644 index 0000000..79d9825 --- /dev/null +++ b/tasks_gen/eval/G0178/spec.md @@ -0,0 +1 @@ +Please create the class {{P}}STOCK_AGE in package $TMP. The class is public and final, and it has a public constructor without parameters. The class implements {{P}}IF_STOCK_AGE. Do not add other public methods. The interface {{P}}IF_STOCK_AGE exists in package $TMP and is active. The interface defines the types ty_material, ty_batch_id, ty_bucket, ty_quantity, ty_batch, tt_batch, ty_age_row, tt_age_row, ty_total, and tt_total. ty_material is character with length 10. ty_batch_id is character with length 10. ty_bucket is character with length 2. ty_quantity is packed number with length 13 and 3 decimals. ty_batch is a structure with components material, batch, quantity, and gr_date. tt_batch is a table of ty_batch. ty_age_row is a structure with components material, bucket, quantity, and batch_count. tt_age_row is a table of ty_age_row. ty_total is a structure with components material, quantity, and batch_count. tt_total is a table of ty_total. The interface defines the constant structure C_BUCKET with components D0_30 = '01', D31_60 = '02', D61_90 = '03', and D91_PLUS = '04'. The interface defines method analyze with importing it_batch type tt_batch and iv_key_date type dats, returning rt_age type tt_age_row. The interface defines method total_by_material with importing it_batch type tt_batch and iv_key_date type dats, returning rt_total type tt_total. Both methods use the same validity rules and the same age buckets. A row of IT_BATCH is valid when its goods receipt date is not initial and is not after IV_KEY_DATE. A row that is not valid is ignored. It must not change any result. The age of a batch is the number of days between its goods receipt date and IV_KEY_DATE. The bucket of a batch is 01 (C_BUCKET-D0_30) for age 0 to 30 days, 02 (C_BUCKET-D31_60) for age 31 to 60 days, 03 (C_BUCKET-D61_90) for age 61 to 90 days, and 04 (C_BUCKET-D91_PLUS) for age more than 90 days. In method ANALYZE, group the valid rows by material and bucket. For each group, sum the quantity and count the valid rows. Return one row per group. Sort the result by material ascending and then by bucket ascending. A group is in the result as soon as one valid row belongs to it, also when the summed quantity is zero. If no valid row exists, return an empty table. In method TOTAL_BY_MATERIAL, group the valid rows by material only. For each group, sum the quantity and count the valid rows. Return one row per material. Sort the result by material ascending. If no valid row exists, return an empty table. The quantity of a batch can be negative. Add the quantities as they are. The release target is SAP_BASIS 8.16 (ABAP Platform 2025). Follow Clean ABAP. Keep method length below 40 statements. Do not use global variables. Do not write comments that restate the code. Pass large parameters by reference. Do not change {{P}}IF_STOCK_AGE. The class is active and has no syntax error. The hidden tests pass. Write your own ABAP Unit tests for the class. diff --git a/tasks_gen/eval/G0178/task.json b/tasks_gen/eval/G0178/task.json new file mode 100644 index 0000000..19cf67a --- /dev/null +++ b/tasks_gen/eval/G0178/task.json @@ -0,0 +1,56 @@ +{ + "id": "G0178", + "category": "K", + "object_type": "CLAS", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 60, + "max_activations": 12 + }, + "seed": [ + { + "type": "INTF", + "name": "{{P}}IF_STOCK_AGE", + "file": "seed/if_stock_age.intf.abap", + "description": "Stock age contract: row types, bucket constants and the two analysis methods" + } + ], + "contract": [ + { + "type": "CLAS", + "name": "{{P}}STOCK_AGE", + "implements": "{{P}}IF_STOCK_AGE" + } + ], + "out_of_scope": [ + "{{P}}IF_STOCK_AGE" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}STOCK_AGE_HIDDEN", + "file": "hidden/stock_age_hidden.clas.abap", + "description": "Hidden tests for the stock age analysis" + } + ], + "reference": [ + { + "type": "CLAS", + "name": "{{P}}STOCK_AGE", + "file": "reference/stock_age.clas.abap", + "description": "Stock age analysis", + "testclasses_file": "reference/stock_age.testclasses.abap" + } + ], + "craft_checks": [ + "table_kind", + "access_path", + "method_length", + "no_nested_loops" + ], + "base_task": "G0004", + "input_style": "free_text", + "tool_schema": "generic_v0" +} \ No newline at end of file