Pipeline controller: plan 2 (hard +, error +50 %), backlog throttle, 2 trajectory workers, stop rules, summaries every 50; budget reserve

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Kral
2026-10-05 13:35:54 +02:00
parent 1c4667a0fc
commit 13360292dd
781 changed files with 50211 additions and 22 deletions

View File

@@ -132,6 +132,8 @@ Bitti sayılır: tek baz model seçildi, gerekçesi yazıldı.
Durum (2026-10-05): 3.1 eğitim modu hazır (`harness/trainset.py`, havuz `tasks_gen/train`, eval ile çakışma kontrolü `harness/overlap.py`, 30 hata-odaklı görev dahil), üretim 12:00'de başladı (hedef 200 kabul, ~10 $ usage). 3.2 için proxy (yerel abaplint ile "save failed" ayrıntısı), yörünge kaydı (`harness/record.py`), Qwen çevirici (`train/to_qwen.py`) ve kabul süzgeci (`train/accept.py`) hazır; yörünge koşuları (`harness/trajectories.py`, 6 worker) üretim bitince.
<!-- stage2-summaries -->
| Alt adım | İş |
|---|---|
| 3.1 | Görev üreteci: eval görevleriyle aynı şemada binlerce sentetik görev. Eval görevleriyle çakışma yok. Girdilerin bir kısmı serbest metin; yörüngelerin az bir kısmı farklı tool isim/şemalarıyla. |

View File

@@ -35,7 +35,8 @@ def spent(since=None):
def check_budget():
limit = float(os.environ.get("BUDGET_LIMIT_USD", "45"))
# BUDGET_RESERVE_USD: ledger USD kept back (second teacher test later); the guard stops at limit - reserve
limit = float(os.environ.get("BUDGET_LIMIT_USD", "45")) - float(os.environ.get("BUDGET_RESERVE_USD", "0"))
s = spent()
if s >= limit:
raise BudgetExceeded(f"cycle spend {s} USD >= limit {limit} USD")

353
harness/pipeline.py Normal file
View File

@@ -0,0 +1,353 @@
"""Stage 2 pipeline controller (rules of 2026-10-05, Opus 5.5 + Kral).
Runs in the background; nobody polls it. It
- starts plan 2 generation (3 workers, hard and error share raised, no fixed target) when the first plan has
ended, until --gen-deadline or the budget guard; adds K (free-text) variants at about 10 % of the other tasks;
- runs trajectories with 2 workers (1 after any A4H error), at most 2 attempts per task: a second attempt only
when the first one failed or was accepted without a repair;
- writes a summary to train/STATE.md and docs/yol-haritasi.md every 50 accepted trajectories, and commits;
- stops and reports (runs/pipeline/STOPPED.txt, STOP flag for the generators) when: acceptance over the last
30 runs is below 50 %, one harness error pattern appears 3 times, the budget guard stops, or the deadline.
python3 -m harness.pipeline [--wait-pid PID] [--workers 2] [--gen-deadline 2026-10-10T18:00]
[--traj-deadline 2026-10-11T23:30]
Budget: BUDGET_LIMIT_USD and BUDGET_RESERVE_USD in .env (the reserve stays for a second teacher test).
"""
import argparse
import glob
import json
import os
import re
import statistics
import subprocess
import sys
import threading
import time
from .adt_client import load_env
from . import trainset, trajectories
from .ledger import spent
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, "train"))
import accept as acc # noqa: E402 (train/accept.py)
OUT = trajectories.OUT
PIPE = os.path.join(ROOT, "runs", "pipeline")
BASE_LEDGER = 66.9665 # ledger when Part B started (2026-10-05 morning)
LEDGER_TO_USAGE = 2.7
ALLOWANCE_USAGE = 35.0 # until 11 October, without asking (Kral)
MARKER = "<!-- stage2-summaries -->"
GIT_ENV = dict(os.environ, GIT_AUTHOR_NAME="Kral", GIT_AUTHOR_EMAIL="kral@local",
GIT_COMMITTER_NAME="Kral", GIT_COMMITTER_EMAIL="kral@local")
HARNESS_MATCH = acc.HARNESS_ERRORS
def log(*a):
print(time.strftime("%F %T"), *a, flush=True)
def ts(text):
return time.mktime(time.strptime(text, "%Y-%m-%dT%H:%M"))
class Pipeline:
def __init__(self, a):
self.a = a
self.lock = threading.Lock()
self.stop = threading.Event()
self.reason = None
self.max_workers = a.workers
self.inflight = set()
self.cache = {} # run_dir -> evaluation
self.events = {} # harness error key -> count
self.task_meta = {}
self.gen_deadline, self.traj_deadline = ts(a.gen_deadline), ts(a.traj_deadline)
self.children = []
rows = self.rows()
for r in rows:
self.evaluate(r)
self.milestone = self.accepted_total() // 50
# ---- data -----------------------------------------------------------------------------------------------
def rows(self):
p = os.path.join(OUT, "summary.jsonl")
return [json.loads(l) for l in open(p)] if os.path.exists(p) else []
def meta(self, task_id):
if task_id not in self.task_meta:
try:
t = json.load(open(os.path.join(trajectories.POOL, task_id, "task.json")))
except OSError:
t = {}
self.task_meta[task_id] = {"category": t.get("category"), "object_type": t.get("object_type")}
return self.task_meta[task_id]
def evaluate(self, row):
"""{accepted, repair, reasons, event_keys, hints, tokens-free} of one summary row (cached)."""
key = row.get("run_dir") or f"{row['task']}_{row['attempt']}"
if key in self.cache:
return self.cache[key]
ev = {"task": row["task"], "attempt": row["attempt"], "accepted": False, "repair": False,
"reasons": [], "events": [], "hints": 0}
path = os.path.join(OUT, row.get("run_dir") or "-", "record.json")
if row.get("harness_error"):
ev["reasons"].append("harness_error")
ev["events"].append("harness:" + str(row.get("error"))[:60])
if row.get("setup_failed"):
ev["reasons"].append("setup_failed")
ev["events"].append("setup_failed")
if os.path.exists(path):
rec = json.load(open(path))
ok, why = acc.judge(rec, row, self.a.min_score)
ev["accepted"], ev["reasons"] = ok, why or ev["reasons"]
ev["hints"] = rec["metadata"].get("syntax_hints") or 0
if ok:
ev["repair"] = acc.has_repair(acc.trim_loops(rec["messages"])[0])
for r in rec["tool_results_raw"]:
m = HARNESS_MATCH.search(r.get("result") or "")
if m and not r.get("tool", "").startswith("sap_run_unit"):
ev["events"].append("text:" + m.group(0)[:40])
break
if row.get("teardown_ok") is False:
ev["events"].append("teardown")
elif not ev["reasons"]:
ev["reasons"].append("no_record")
self.cache[key] = ev
return ev
def evaluated(self):
return [self.evaluate(r) for r in self.rows()]
def accepted_total(self):
return sum(e["accepted"] for e in self.evaluated())
# ---- jobs -----------------------------------------------------------------------------------------------
def next_job(self):
with self.lock:
rows = self.rows()
done = {(r["task"], r["attempt"]) for r in rows}
ev0 = {r["task"]: self.evaluate(r) for r in rows if r["attempt"] == 0}
tasks = trajectories.accepted_tasks()
for t in tasks: # first attempt for every task first
if (t, 0) not in done and (t, 0) not in self.inflight:
self.inflight.add((t, 0))
return t, 0
for t in tasks: # second attempt: first failed, or accepted without a repair
e = ev0.get(t)
if e and (t, 1) not in done and (t, 1) not in self.inflight and (not e["accepted"] or not e["repair"]):
self.inflight.add((t, 1))
return t, 1
return None
def worker(self, i):
while not self.stop.is_set():
if time.time() > self.traj_deadline:
self.halt("trajectory deadline reached")
return
if i >= self.max_workers:
time.sleep(30)
continue
job = self.next_job()
if not job:
if self.generation_alive() or self.inflight:
time.sleep(60)
continue
self.halt("no job left and generation ended")
return
trajectories.one(job[0], job[1], self.stop)
with self.lock:
self.inflight.discard(job)
self.after_run(job)
if self.stop.is_set() and not self.reason:
self.halt("budget guard stopped a run")
# ---- rules ----------------------------------------------------------------------------------------------
def after_run(self, job):
rows = self.rows()
row = next((r for r in reversed(rows) if (r["task"], r["attempt"]) == job), None)
if row is None:
return
ev = self.evaluate(row)
for k in ev["events"]:
self.events[k] = self.events.get(k, 0) + 1
log("A4H/harness event:", k, "x", self.events[k], "->", row.get("run_dir"))
if self.max_workers > 1:
self.max_workers = 1
log("harness error seen: trajectory workers 2 -> 1")
if self.events[k] >= 3:
self.halt(f"harness error pattern repeated 3 times: {k}")
evs = self.evaluated()
if len(evs) >= 30:
rate = sum(e["accepted"] for e in evs[-30:]) / 30
if rate < 0.5:
self.halt(f"acceptance {rate:.0%} over the last 30 runs (below 50 %)")
try:
from .ledger import check_budget
check_budget()
except Exception as e: # noqa: BLE001 BudgetExceeded
self.halt(f"budget guard: {e}")
total = sum(e["accepted"] for e in evs)
while total // 50 > self.milestone and not self.reason:
self.milestone += 1
self.summary(self.milestone * 50)
def halt(self, reason):
if self.stop.is_set() and self.reason:
return
self.reason = reason
self.stop.set()
os.makedirs(PIPE, exist_ok=True)
open(trainset.STOP_FLAG, "w").write(reason + "\n") # generators end after their current slot
log("STOP:", reason)
# ---- generation side --------------------------------------------------------------------------------------
def generation_alive(self):
if self.reason:
return False
out = subprocess.run(["pgrep", "-f", r"harness\.trainset (run|k)"], capture_output=True, text=True).stdout
return bool(out.split()) or time.time() < self.gen_deadline
def old_generators(self):
out = subprocess.run(["pgrep", "-fl", r"harness\.trainset run --part"], capture_output=True, text=True).stdout
return [l for l in out.splitlines() if "--plan plan2" not in l]
def supervisor(self):
launched = False
k_proc = None
crashes = 0
while not self.stop.is_set():
time.sleep(60)
if time.time() > self.gen_deadline or os.path.exists(trainset.STOP_FLAG):
continue
if not launched and not self.old_generators():
trainset.ensure_plan("plan2")
dl = self.a.gen_deadline
for i in range(3):
f = open(os.path.join(ROOT, "runs", "gen_train", f"p2_w{i}.log"), "a")
self.children.append(subprocess.Popen(
[sys.executable, "-m", "harness.trainset", "run", "--plan", "plan2", "--part", str(i),
"--parts", "3", "--deadline", dl], cwd=ROOT, stdout=f, stderr=f))
launched = True
log("plan 2 generators started (3 workers, deadline", dl + ")")
elif launched:
for i, c in enumerate(self.children):
if c.poll() not in (None, 0) and crashes < 3:
crashes += 1
log("generator", i, "exited with", c.returncode, "- restarted")
f = open(os.path.join(ROOT, "runs", "gen_train", f"p2_w{i}.log"), "a")
self.children[i] = subprocess.Popen(
[sys.executable, "-m", "harness.trainset", "run", "--plan", "plan2", "--part", str(i),
"--parts", "3", "--deadline", self.a.gen_deadline], cwd=ROOT, stdout=f, stderr=f)
# K variants: free text, about 10 % of the other accepted training tasks
if k_proc is None or k_proc.poll() is not None:
logs = [json.load(open(f)) for f in glob.glob(os.path.join(trainset.POOL, "_logs", "G*.json"))]
base = sum(1 for l in logs if l.get("accepted") and l.get("category") != "K")
have = sum(1 for l in logs if l.get("category") == "K")
want = min(99, int(0.1 * base))
if want > have:
f = open(os.path.join(ROOT, "runs", "gen_train", "k.log"), "a")
k_proc = subprocess.Popen([sys.executable, "-m", "harness.trainset", "k", "--k-count", str(want)],
cwd=ROOT, stdout=f, stderr=f)
# ---- summary ----------------------------------------------------------------------------------------------
def summary(self, n):
log("summary at", n, "accepted trajectories")
subprocess.run([sys.executable, "train/accept.py", "--min-score", str(self.a.min_score)], cwd=ROOT,
capture_output=True)
vp = os.path.join(ROOT, "train", ".venv", "bin", "python")
subprocess.run([vp, "train/to_qwen.py", os.path.join(OUT, "accepted.jsonl"), os.path.join(OUT, "qwen.jsonl")],
cwd=ROOT, capture_output=True)
toks = sorted(json.loads(l)["n_tokens"] for l in open(os.path.join(OUT, "qwen.jsonl")))
pct = lambda p: toks[min(len(toks) - 1, int(p * len(toks)))] if toks else None # noqa: E731
rows, evs = self.rows(), self.evaluated()
logs = [json.load(open(f)) for f in glob.glob(os.path.join(trainset.POOL, "_logs", "G*.json"))]
acc_t = sum(1 for l in logs if l.get("accepted"))
accepted = [e for e in evs if e["accepted"]]
repairs = sum(e["repair"] for e in accepted)
def table(keyname):
agg = {}
for e in evs:
k = self.meta(e["task"])[keyname] or "?"
a = agg.setdefault(k, [0, 0, 0])
a[0] += 1
a[1] += e["accepted"]
a[2] += e["accepted"] and e["repair"]
return "; ".join(f"{k} {v[1]}/{v[0]} (repair {v[2]})" for k, v in sorted(agg.items()))
s = spent()
used = (s - BASE_LEDGER) / LEDGER_TO_USAGE
guard = float(os.environ.get("BUDGET_LIMIT_USD", "0")) - float(os.environ.get("BUDGET_RESERVE_USD", "0"))
text = f"""### Stage 2 summary at {n} accepted trajectories ({time.strftime('%Y-%m-%d %H:%M')})
- Tasks: {acc_t} accepted of {len(logs)} generated (K variants {sum(1 for l in logs if l.get('category') == 'K')}); \
trajectory runs {len(rows)}, accepted trajectories {len(accepted)} (acceptance {len(accepted) / max(len(rows), 1):.0%}).
- Budget: used {used:.1f} usage (ledger {s - BASE_LEDGER:.1f}) since 2026-10-05; left to the guard {(guard - s) / LEDGER_TO_USAGE:.1f} usage \
(ledger {guard - s:.1f}; reserve {os.environ.get('BUDGET_RESERVE_USD', '0')} ledger kept); allowance until 11 October {ALLOWANCE_USAGE - used:.1f} usage.
- Repair share (accepted trajectories with an error followed by a fix): {repairs}/{len(accepted)} = {repairs / max(len(accepted), 1):.0%}.
- By category, accepted/runs: {table('category')}.
- By object type, accepted/runs: {table('object_type')}.
- Tokens of accepted samples (20 tool schemas kept): p50 {pct(0.5)}, p90 {pct(0.9)}, p95 {pct(0.95)}, max {toks[-1] if toks else None}, n {len(toks)}.
- Syntax hints (proxy syntaxCheck added): {sum(e['hints'] for e in evs)} in {sum(1 for e in evs if e['hints'])} runs.
- Harness events: {sum(self.events.values())} ({self.events or 'none'}); trajectory workers now {self.max_workers}.
"""
if getattr(self.a, "dry_summary", False):
print(text)
return
with open(os.path.join(ROOT, "train", "STATE.md"), "a") as f:
f.write("\n" + text)
p = os.path.join(ROOT, "docs", "yol-haritasi.md")
d = open(p).read()
d = d.replace(MARKER, MARKER + "\n\n" + text, 1) if MARKER in d else d + "\n" + text
open(p, "w").write(d)
subprocess.run(["git", "add", "train/STATE.md", "docs/yol-haritasi.md", "tasks_gen/train", "harness", "train"],
cwd=ROOT, env=GIT_ENV, capture_output=True)
subprocess.run(["git", "commit", "-q", "-m", f"Stage 2 summary at {n} accepted trajectories\n\n"
"Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>"],
cwd=ROOT, env=GIT_ENV, capture_output=True)
# ---- main -------------------------------------------------------------------------------------------------
def run(self):
os.makedirs(PIPE, exist_ok=True)
if os.path.exists(trainset.STOP_FLAG):
os.remove(trainset.STOP_FLAG)
log("pipeline start: workers", self.max_workers, "accepted", self.accepted_total(), "spent", spent())
threads = [threading.Thread(target=self.worker, args=(i,)) for i in range(self.a.workers)]
threading.Thread(target=self.supervisor, daemon=True).start()
for t in threads:
t.start()
for t in threads:
t.join()
evs = self.evaluated()
text = (f"reason: {self.reason}\ntime: {time.strftime('%F %T')}\nruns: {len(evs)}, accepted "
f"{sum(e['accepted'] for e in evs)}\nspent ledger: {spent()}\nevents: {self.events}\n")
open(os.path.join(PIPE, "STOPPED.txt"), "w").write(text)
log("pipeline ended:", text.replace("\n", " | "))
def main():
load_env(os.path.join(ROOT, ".env"))
ap = argparse.ArgumentParser()
ap.add_argument("--wait-pid", type=int, help="start when this process has ended (the first trajectory batch)")
ap.add_argument("--workers", type=int, default=2)
ap.add_argument("--min-score", type=float, default=80)
ap.add_argument("--gen-deadline", default="2026-10-10T18:00")
ap.add_argument("--traj-deadline", default="2026-10-11T23:30")
ap.add_argument("--dry-summary", action="store_true", help="print the summary text and exit (no file, no commit)")
a = ap.parse_args()
if a.dry_summary:
Pipeline(a).summary(0)
return
if a.wait_pid:
while True:
try:
os.kill(a.wait_pid, 0)
except OSError:
break
time.sleep(30)
os.makedirs(OUT, exist_ok=True)
Pipeline(a).run()
if __name__ == "__main__":
main()

View File

@@ -46,13 +46,16 @@ CATEGORY_SHARE = {"A": 10, "B": 15, "C": 15, "D": 10, "E": 15, "F": 10, "G": 10,
ERROR_CATEGORY = {"named-type": "A", "reserved-word": "D", "long-names": "C"}
def build_plan():
"""Category shares as in the eval plan; object types inside a category as in evalset.SLOTS."""
def build_plan(n_slots=N_SLOTS, n_error=N_ERROR, hard_share=0.0, first_id=FIRST_ID, run_base=RUN_BASE):
"""Category shares as in the eval plan; object types inside a category as in evalset.SLOTS.
n_error slots target the common errors (named-type, reserved-word, long-names); hard_share of the other
slots get difficulty 3 (every 10th slot pattern, deterministic)."""
share_sum = sum(CATEGORY_SHARE.values())
items = []
for cat, share in CATEGORY_SHARE.items():
k_cat = round(N_SLOTS * share / share_sum)
k_err = sum(1 for k in ERROR_CATEGORY if ERROR_CATEGORY[k] == cat) * N_ERROR // 3
k_cat = round(n_slots * share / share_sum)
per_kind = n_error // 3
k_err = sum(1 for k in ERROR_CATEGORY if ERROR_CATEGORY[k] == cat) * per_kind
slots = [x for x in SLOTS if x[0] == cat]
weight = sum(n for _, _, n, _ in slots)
k_rest = k_cat - k_err
@@ -69,16 +72,19 @@ def build_plan():
for kind, text in ERROR_HINTS:
if ERROR_CATEGORY[kind] != cat:
continue
for i in range(N_ERROR // 3):
for i in range(per_kind):
t = ERROR_TYPES[i % len(ERROR_TYPES)]
pos += 1
items.append({"category": cat, "object_type": t, "topic": text, "error_kind": kind,
"frac": pos / (k_cat + 1)})
items.sort(key=lambda x: (x["frac"], x["category"], x["object_type"])) # interleave: a cut keeps the mix
hard_every = round(1 / hard_share) if hard_share else 0
for n, it in enumerate(items):
it.pop("frac")
it["id"] = f"G{FIRST_ID + n:04d}"
it["run_base"] = RUN_BASE + 40 * n
it["id"] = f"G{first_id + n:04d}"
it["run_base"] = run_base + 40 * n
if hard_every and it["category"] != "H" and n % hard_every == hard_every // 2:
it["difficulty"] = 3
return items
@@ -92,12 +98,42 @@ def accepted_count():
return n
def run(part, parts, target, stop_ledger):
STOP_FLAG = os.path.join(ROOT, "runs", "pipeline", "STOP") # the pipeline writes it: workers end after the current slot
BACKLOG_LIMIT = 40 # accepted tasks without a first trajectory run: generation waits above this
PLAN2 = {"n_slots": 500, "n_error": 100, "hard_share": 0.3, "first_id": 1400, "run_base": 100000}
# plan 2 (2026-10-05, Opus 5.5 rules): error tasks 20 % (plan 1: 13 %, +50 %), hard (difficulty 3) 30 % of the
# other slots (plan 1: 20 % assumed from the pilot hard batch), same category shares, ids G1400+, no fixed target
def plan_path(name):
return os.path.join(POOL, name + ".json")
def ensure_plan(name, stop_ledger=27.0, target=200):
os.makedirs(POOL, exist_ok=True)
if not os.path.exists(PLAN):
json.dump({"slots": build_plan(), "ledger_at_start": spent(), "stop_ledger": stop_ledger,
"target": target, "created": time.strftime("%F %T")}, open(PLAN, "w"), indent=1)
plan = json.load(open(PLAN))
if not os.path.exists(plan_path(name)):
if name == "plan":
slots, tgt, stop = build_plan(), target, stop_ledger
else:
slots, tgt, stop = build_plan(**PLAN2), None, 1e6 # only the budget guard and the deadline stop it
json.dump({"slots": slots, "ledger_at_start": spent(), "stop_ledger": stop, "target": tgt,
"created": time.strftime("%F %T")}, open(plan_path(name), "w"), indent=1)
return json.load(open(plan_path(name)))
def backlog():
"""Accepted training tasks that have no first trajectory run yet."""
done = set()
sp = os.path.join(ROOT, "runs", "traj", "summary.jsonl")
if os.path.exists(sp):
done = {json.loads(l)["task"] for l in open(sp) if json.loads(l)["attempt"] == 0}
acc = {os.path.basename(os.path.dirname(f)) for f in glob.glob(os.path.join(POOL, "G*", "generation.json"))
if json.load(open(f)).get("accepted")}
return len(acc - done)
def run(part, parts, target, stop_ledger, plan_name="plan", deadline=None):
plan = ensure_plan(plan_name, stop_ledger, target)
stop_at = plan["ledger_at_start"] + plan["stop_ledger"]
base_url = os.environ.get("LLM_BASE_URL", "http://127.0.0.1:11434/v1")
evals = overlap.load_pool("eval")
@@ -106,7 +142,16 @@ def run(part, parts, target, stop_ledger):
continue
if os.path.exists(os.path.join(POOL, "_logs", s["id"] + ".json")):
continue # done or failed: not tried again (delete the log to retry)
if accepted_count() >= plan["target"]:
while backlog() > BACKLOG_LIMIT and not os.path.exists(STOP_FLAG) \
and not (deadline and time.time() > deadline):
time.sleep(120) # trajectories are the slower side: do not generate tasks that wait for days
if os.path.exists(STOP_FLAG):
print("STOP flag", flush=True)
break
if deadline and time.time() > deadline:
print("DEADLINE", flush=True)
break
if plan["target"] and accepted_count() >= plan["target"]:
print("TARGET reached", flush=True)
break
if spent() >= stop_at:
@@ -124,14 +169,15 @@ def run(part, parts, target, stop_ledger):
f"names {sc['name']:.2f}). Choose a different business topic and different object names."
for i, sc in hits[:3]]
try:
log = generate(s["id"], "train", s["object_type"], s["category"], 2, "deepseek-v4.1-flash:cloud",
log = generate(s["id"], "train", s["object_type"], s["category"], s.get("difficulty", 2),
"deepseek-v4.1-flash:cloud",
base_url, s["run_base"], topic, extra_check=extra)
except BudgetExceeded as e:
print("BUDGET", e, flush=True)
break
except Exception as e: # noqa: BLE001
log = {"id": s["id"], "error": str(e)[:500]}
log.update(error_kind=s.get("error_kind"), spent_total=spent())
log.update(error_kind=s.get("error_kind"), difficulty=s.get("difficulty", 2), spent_total=spent())
os.makedirs(os.path.join(POOL, "_logs"), exist_ok=True)
json.dump(log, open(os.path.join(POOL, "_logs", s["id"] + ".json"), "w"), indent=1)
stray = os.path.join(POOL, "generation.json") # generate() writes here when no task dir exists
@@ -156,7 +202,7 @@ def run_k(count):
continue
used = {json.load(open(f)).get("base_task") for f in glob.glob(os.path.join(POOL, "_logs", "G13*.json"))}
cands = [] # accepted, not K, not H (a stop task has no free-text form), not used yet
for f in sorted(glob.glob(os.path.join(POOL, "_logs", "G1[0-2]*.json"))):
for f in sorted(glob.glob(os.path.join(POOL, "_logs", "G1*.json"))):
lg = json.load(open(f))
if lg.get("accepted") and lg.get("category") not in ("H", "K") and lg["id"] not in used:
cands.append(lg)
@@ -170,8 +216,7 @@ def run_k(count):
otype = min(kinds, key=lambda t: done_types.count(t))
src = kinds[otype][0]
style = K_STYLES_CYCLE[n % 2]
if spent() >= json.load(open(PLAN))["ledger_at_start"] + json.load(open(PLAN))["stop_ledger"]:
print("PHASE LIMIT", flush=True)
if os.path.exists(STOP_FLAG):
return
try:
log = make_k_variant(src["id"], new_id, style, "deepseek-v4.1-flash:cloud", base_url,
@@ -195,6 +240,9 @@ def main():
ap.add_argument("--part", type=int, default=0)
ap.add_argument("--parts", type=int, default=1)
ap.add_argument("--target", type=int, default=200)
ap.add_argument("--plan", default="plan", help="plan (first 223 slots) or plan2 (hard and error share raised)")
ap.add_argument("--deadline", help="YYYY-MM-DDTHH:MM local time: no new slot after it")
ap.add_argument("--k-count", type=int, default=K_COUNT)
ap.add_argument("--stop-ledger", type=float, default=27.0, help="ledger USD for this phase (10 USD usage = 27)")
a = ap.parse_args()
if a.cmd == "plan":
@@ -204,9 +252,10 @@ def main():
print(collections.Counter(x["object_type"] for x in p), collections.Counter(x.get("error_kind") for x in p))
return
if a.cmd == "k":
run_k(K_COUNT)
run_k(a.k_count)
return
run(a.part, a.parts, a.target, a.stop_ledger)
dl = time.mktime(time.strptime(a.deadline, "%Y-%m-%dT%H:%M")) if a.deadline else None
run(a.part, a.parts, a.target, a.stop_ledger, a.plan, dl)
if __name__ == "__main__":

View File

@@ -24,7 +24,7 @@ from .runner import Runner
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
POOL = os.path.join(ROOT, "tasks_gen", "train")
OUT = os.path.join(ROOT, "runs", "traj")
RUN_BASE = 41000 # 41000 + (task number - 1000) * 3 + attempt; below 36**3 * ... (prefix rule)
RUN_BASE = 200000 # 200000 + (task number - 1000) * 3 + attempt (a digit must lead the 4-char base36 run: < 466560)
MODEL = "deepseek-v4.1-flash:cloud"
LOCK = threading.Lock()

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id <> cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg > cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'LOAD'
end as status
}

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id = cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg + cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg > cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'LOAD'
end as status
}

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id = cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg <= cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'LOAD'
end as status
}

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id = cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg > cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 101, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'LOAD'
end as status
}

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id = cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg > cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'ZOAD'
end as status
}

View File

@@ -0,0 +1,19 @@
{
"id": "G1000",
"pool": "train",
"object_type": "DDLS",
"category": "B",
"attempts": [
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 5,
"killed": 5,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,108 @@
CLASS {{p}}t20_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS deviation_below FOR TESTING.
METHODS deviation_zero FOR TESTING.
METHODS deviation_above FOR TESTING.
METHODS percent_rounded FOR TESTING.
METHODS status_hold FOR TESTING.
METHODS status_warn FOR TESTING.
METHODS status_load FOR TESTING.
METHODS status_at_five_pct FOR TESTING.
METHODS unweighed_hidden FOR TESTING.
METHODS customer_shown FOR TESTING.
ENDCLASS.
CLASS {{p}}t20_hidden IMPLEMENTATION.
METHOD class_setup.
go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_CONT_WEIGHT' ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD setup.
DATA lt_cont TYPE STANDARD TABLE OF {{p}}cont WITH EMPTY KEY.
DATA lt_weigh TYPE STANDARD TABLE OF {{p}}weigh WITH EMPTY KEY.
go_env->clear_doubles( ).
lt_cont = VALUE #(
( cont_id = 'C1' customer = 'ALPHA' decl_kg = '1000.00' max_kg = '1200.00' )
( cont_id = 'C2' customer = 'BETA' decl_kg = '1000.00' max_kg = '1200.00' )
( cont_id = 'C3' customer = 'GAMMA' decl_kg = '1000.00' max_kg = '1200.00' )
( cont_id = 'C4' customer = 'DELTA' decl_kg = '500.00' max_kg = '600.00' )
( cont_id = 'C5' customer = 'EPSILON' decl_kg = '1000.00' max_kg = '1200.00' )
( cont_id = 'C6' customer = 'ZETA' decl_kg = '1000.00' max_kg = '1200.00' )
( cont_id = 'C7' customer = 'ETA' decl_kg = '800.00' max_kg = '900.00' )
( cont_id = 'C8' customer = 'THETA' decl_kg = '300.00' max_kg = '400.00' ) ).
lt_weigh = VALUE #(
( cont_id = 'C1' meas_kg = '1020.00' )
( cont_id = 'C2' meas_kg = '1100.00' )
( cont_id = 'C3' meas_kg = '1300.00' )
( cont_id = 'C4' meas_kg = '450.00' )
( cont_id = 'C5' meas_kg = '950.00' )
( cont_id = 'C7' meas_kg = '800.00' )
( cont_id = 'C8' meas_kg = '310.00' ) ).
go_env->insert_test_data( lt_cont ).
go_env->insert_test_data( lt_weigh ).
ENDMETHOD.
METHOD deviation_below.
SELECT SINGLE deviation_kg FROM {{p}}i_cont_weight WHERE cont_id = 'C4' INTO @DATA(lv_dev).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '50.00' )
act = CONV decfloat34( lv_dev ) ).
ENDMETHOD.
METHOD deviation_zero.
SELECT SINGLE deviation_kg FROM {{p}}i_cont_weight WHERE cont_id = 'C7' INTO @DATA(lv_dev).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.00' )
act = CONV decfloat34( lv_dev ) ).
ENDMETHOD.
METHOD deviation_above.
SELECT SINGLE deviation_kg FROM {{p}}i_cont_weight WHERE cont_id = 'C1' INTO @DATA(lv_dev).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '20.00' )
act = CONV decfloat34( lv_dev ) ).
ENDMETHOD.
METHOD percent_rounded.
SELECT SINGLE deviation_pct FROM {{p}}i_cont_weight WHERE cont_id = 'C8' INTO @DATA(lv_pct).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '3.33' )
act = CONV decfloat34( lv_pct ) ).
ENDMETHOD.
METHOD status_hold.
SELECT SINGLE status FROM {{p}}i_cont_weight WHERE cont_id = 'C3' INTO @DATA(lv_status).
cl_abap_unit_assert=>assert_equals( exp = 'HOLD' act = lv_status ).
ENDMETHOD.
METHOD status_warn.
SELECT SINGLE status FROM {{p}}i_cont_weight WHERE cont_id = 'C2' INTO @DATA(lv_status).
cl_abap_unit_assert=>assert_equals( exp = 'WARN' act = lv_status ).
ENDMETHOD.
METHOD status_load.
SELECT SINGLE status FROM {{p}}i_cont_weight WHERE cont_id = 'C1' INTO @DATA(lv_status).
cl_abap_unit_assert=>assert_equals( exp = 'LOAD' act = lv_status ).
ENDMETHOD.
METHOD status_at_five_pct.
SELECT SINGLE status FROM {{p}}i_cont_weight WHERE cont_id = 'C5' INTO @DATA(lv_status).
cl_abap_unit_assert=>assert_equals( exp = 'LOAD' act = lv_status ).
ENDMETHOD.
METHOD unweighed_hidden.
SELECT COUNT(*) FROM {{p}}i_cont_weight INTO @DATA(lv_rows).
cl_abap_unit_assert=>assert_equals( exp = 7 act = lv_rows ).
ENDMETHOD.
METHOD customer_shown.
SELECT SINGLE customer FROM {{p}}i_cont_weight WHERE cont_id = 'C4' INTO @DATA(lv_cust).
cl_abap_unit_assert=>assert_equals( exp = 'DELTA' act = lv_cust ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,65 @@
{
"task": "G1000",
"mutants": [
{
"object": "{{P}}I_CONT_WEIGHT",
"mutant": "line 5: = -> <> (eq)",
"status": "killed",
"hidden": "2/10",
"failed_tests": [
"DEVIATION_ABOVE",
"DEVIATION_BELOW",
"DEVIATION_ZERO",
"PERCENT_ROUNDED",
"STATUS_HOLD",
"STATUS_LOAD",
"STATUS_WARN",
"UNWEIGHED_HIDDEN"
]
},
{
"object": "{{P}}I_CONT_WEIGHT",
"mutant": "line 12: - -> + (arith)",
"status": "killed",
"hidden": "9/10",
"failed_tests": [
"PERCENT_ROUNDED"
]
},
{
"object": "{{P}}I_CONT_WEIGHT",
"mutant": "line 14: > -> <= (rel)",
"status": "killed",
"hidden": "6/10",
"failed_tests": [
"STATUS_AT_FIVE_PCT",
"STATUS_HOLD",
"STATUS_LOAD",
"STATUS_WARN"
]
},
{
"object": "{{P}}I_CONT_WEIGHT",
"mutant": "line 15: 100 -> 101 (const)",
"status": "killed",
"hidden": "9/10",
"failed_tests": [
"STATUS_AT_FIVE_PCT"
]
},
{
"object": "{{P}}I_CONT_WEIGHT",
"mutant": "line 16: 'LOAD' -> 'ZOAD' (lit)",
"status": "killed",
"hidden": "8/10",
"failed_tests": [
"STATUS_AT_FIVE_PCT",
"STATUS_LOAD"
]
}
],
"valid": 5,
"killed": 5,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,18 @@
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Container weight check'
define view entity {{P}}I_CONT_WEIGHT
as select from {{p}}cont as cont
inner join {{p}}weigh as weigh on weigh.cont_id = cont.cont_id
{
key cont.cont_id as cont_id,
cont.customer as customer,
cont.decl_kg as decl_kg,
weigh.meas_kg as meas_kg,
cast( abs( weigh.meas_kg - cont.decl_kg ) as abap.dec(15,2) ) as deviation_kg,
division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) as deviation_pct,
case
when weigh.meas_kg > cont.max_kg then 'HOLD'
when division( abs( weigh.meas_kg - cont.decl_kg ) * 100, cont.decl_kg, 2 ) > 5 then 'WARN'
else 'LOAD'
end as status
}

View File

@@ -0,0 +1,53 @@
CLASS {{p}}t20_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA go_env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS hold_for_heavy FOR TESTING.
METHODS load_for_light FOR TESTING.
ENDCLASS.
CLASS {{p}}t20_test IMPLEMENTATION.
METHOD class_setup.
go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_CONT_WEIGHT' ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD setup.
DATA lt_cont TYPE STANDARD TABLE OF {{p}}cont WITH EMPTY KEY.
DATA lt_weigh TYPE STANDARD TABLE OF {{p}}weigh WITH EMPTY KEY.
go_env->clear_doubles( ).
lt_cont = VALUE #( ( cont_id = 'A' customer = 'CUST_A' decl_kg = '1000.00' max_kg = '1100.00' )
( cont_id = 'B' customer = 'CUST_B' decl_kg = '1000.00' max_kg = '1100.00' ) ).
lt_weigh = VALUE #( ( cont_id = 'A' meas_kg = '1150.00' )
( cont_id = 'B' meas_kg = '1010.00' ) ).
go_env->insert_test_data( lt_cont ).
go_env->insert_test_data( lt_weigh ).
ENDMETHOD.
METHOD hold_for_heavy.
SELECT SINGLE deviation_kg, deviation_pct, status FROM {{p}}i_cont_weight
WHERE cont_id = 'A' INTO @DATA(ls_row).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '150.00' )
act = CONV decfloat34( ls_row-deviation_kg ) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '15.00' )
act = CONV decfloat34( ls_row-deviation_pct ) ).
cl_abap_unit_assert=>assert_equals( exp = 'HOLD' act = ls_row-status ).
ENDMETHOD.
METHOD load_for_light.
SELECT SINGLE deviation_kg, deviation_pct, status FROM {{p}}i_cont_weight
WHERE cont_id = 'B' INTO @DATA(ls_row).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '10.00' )
act = CONV decfloat34( ls_row-deviation_kg ) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1.00' )
act = CONV decfloat34( ls_row-deviation_pct ) ).
cl_abap_unit_assert=>assert_equals( exp = 'LOAD' act = ls_row-status ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,12 @@
@EndUserText.label : 'Container'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}cont {
key client : abap.clnt not null;
key cont_id : abap.char(10) not null;
customer : abap.char(10);
decl_kg : abap.dec(15,2);
max_kg : abap.dec(15,2);
}

View File

@@ -0,0 +1,10 @@
@EndUserText.label : 'Weighbridge record'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}weigh {
key client : abap.clnt not null;
key cont_id : abap.char(10) not null;
meas_kg : abap.dec(15,2);
}

View File

@@ -0,0 +1,41 @@
# 1. Goal
Show the weight check of each container at a container terminal. A report and an
OData service will read the check, so the calculation must be in the database,
in a CDS view.
# 2. Open questions
None.
# 3. Context
- Table {{P}}CONT (container master), package $TMP. Key: CONT_ID (CHAR 10).
Fields: CUSTOMER (CHAR 10), DECL_KG (DEC 15,2, declared weight in kilograms),
MAX_KG (DEC 15,2, maximum permitted weight in kilograms).
- Table {{P}}WEIGH (weighbridge record), package $TMP. Key: CONT_ID (CHAR 10).
Field: MEAS_KG (DEC 15,2, measured weight in kilograms).
# 4. Contract
- Create the CDS view entity {{P}}I_CONT_WEIGHT in package $TMP.
- Elements, with these names: CONT_ID (key), CUSTOMER, DECL_KG, MEAS_KG,
DEVIATION_KG, DEVIATION_PCT, STATUS.
- No authorization check (#NOT_REQUIRED).
# 5. Business rules
1. Show one row for each container that has a weighbridge record. Do not show a
container without a weighbridge record.
2. DEVIATION_KG is the difference between MEAS_KG and DECL_KG, without a sign.
It is always positive or zero and it has 2 decimals.
3. DEVIATION_PCT is DEVIATION_KG as a percentage of DECL_KG. It has 2 decimals.
4. STATUS is 'HOLD' if MEAS_KG is greater than MAX_KG.
5. STATUS is 'WARN' if rule 4 is not true and DEVIATION_PCT is greater than 5.
6. STATUS is 'LOAD' in all other cases.
7. DECL_KG is always greater than zero.
# 6. Constraints
- Release target: 8.16.
- Use Clean ABAP. Keep every method below 40 statements.
- Out of scope: do not change {{P}}CONT and {{P}}WEIGH.
# 7. Acceptance
- The view is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class.

View File

@@ -0,0 +1,68 @@
{
"id": "G1000",
"category": "B",
"object_type": "DDLS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 15
},
"seed": [
{
"type": "TABL",
"name": "{{P}}CONT",
"file": "seed/cont.tabl.asabap",
"description": "Container master"
},
{
"type": "TABL",
"name": "{{P}}WEIGH",
"file": "seed/weigh.tabl.asabap",
"description": "Weighbridge record"
}
],
"contract": [
{
"type": "DDLS",
"name": "{{P}}I_CONT_WEIGHT",
"fields": [
"CONT_ID",
"CUSTOMER",
"DECL_KG",
"MEAS_KG",
"DEVIATION_KG",
"DEVIATION_PCT",
"STATUS"
]
}
],
"out_of_scope": [
"{{P}}CONT",
"{{P}}WEIGH"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T20_HIDDEN",
"file": "hidden/t20_hidden.clas.abap",
"description": "T20 hidden tests"
}
],
"reference": [
{
"type": "DDLS",
"name": "{{P}}I_CONT_WEIGHT",
"file": "reference/i_cont_weight.ddls.asddls",
"description": "Container weight check"
},
{
"type": "CLAS",
"name": "{{P}}T20_TEST",
"file": "reference/t20_test.clas.abap",
"description": "T20 own tests"
}
],
"craft_checks": []
}

View File

@@ -0,0 +1,71 @@
CLASS {{p}}harvest_evaluator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS evaluate
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE {{p}}if_harvest=>tt_member
RETURNING VALUE(rt_result) TYPE {{p}}if_harvest=>tt_result.
PRIVATE SECTION.
TYPES tt_member_key TYPE HASHED TABLE OF {{p}}if_harvest=>ty_member
WITH UNIQUE KEY member_id.
TYPES tt_member_id TYPE SORTED TABLE OF {{p}}if_harvest=>ty_member_id
WITH UNIQUE KEY table_line.
TYPES tt_report_key TYPE SORTED TABLE OF {{p}}if_harvest=>ty_report
WITH NON-UNIQUE KEY variety member_id.
METHODS countable_reports
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE tt_member_key
RETURNING VALUE(rt_report) TYPE tt_report_key.
METHODS is_countable
IMPORTING is_report TYPE {{p}}if_harvest=>ty_report
it_member TYPE tt_member_key
RETURNING VALUE(rv_count) TYPE abap_bool.
ENDCLASS.
CLASS {{p}}harvest_evaluator IMPLEMENTATION.
METHOD evaluate.
DATA lt_member TYPE tt_member_key.
lt_member = it_member.
DATA(lt_report) = countable_reports( it_report = it_report
it_member = lt_member ).
DATA lt_result TYPE {{p}}if_harvest=>tt_result.
DATA lt_member_id TYPE tt_member_id.
LOOP AT lt_report ASSIGNING FIELD-SYMBOL(<ls_report>) GROUP BY <ls_report>-variety.
DATA(lv_variety) = <ls_report>-variety.
DATA(lv_total) = 0.
CLEAR lt_member_id.
LOOP AT GROUP <ls_report> ASSIGNING FIELD-SYMBOL(<ls_grouped>).
lv_total = lv_total - <ls_grouped>-quantity_kg.
INSERT <ls_grouped>-member_id INTO TABLE lt_member_id.
ENDLOOP.
APPEND VALUE {{p}}if_harvest=>ty_result(
variety = lv_variety
total_kg = lv_total
member_count = lines( lt_member_id ) ) TO lt_result.
ENDLOOP.
SORT lt_result BY total_kg DESCENDING variety ASCENDING.
rt_result = lt_result.
ENDMETHOD.
METHOD countable_reports.
LOOP AT it_report INTO DATA(ls_report).
IF is_countable( is_report = ls_report
it_member = it_member ) = abap_false.
CONTINUE.
ENDIF.
INSERT ls_report INTO TABLE rt_report.
ENDLOOP.
ENDMETHOD.
METHOD is_countable.
IF is_report-quantity_kg <= 0.
RETURN.
ENDIF.
rv_count = xsdbool( line_exists( it_member[ member_id = is_report-member_id ] ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,71 @@
CLASS {{p}}harvest_evaluator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS evaluate
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE {{p}}if_harvest=>tt_member
RETURNING VALUE(rt_result) TYPE {{p}}if_harvest=>tt_result.
PRIVATE SECTION.
TYPES tt_member_key TYPE HASHED TABLE OF {{p}}if_harvest=>ty_member
WITH UNIQUE KEY member_id.
TYPES tt_member_id TYPE SORTED TABLE OF {{p}}if_harvest=>ty_member_id
WITH UNIQUE KEY table_line.
TYPES tt_report_key TYPE SORTED TABLE OF {{p}}if_harvest=>ty_report
WITH NON-UNIQUE KEY variety member_id.
METHODS countable_reports
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE tt_member_key
RETURNING VALUE(rt_report) TYPE tt_report_key.
METHODS is_countable
IMPORTING is_report TYPE {{p}}if_harvest=>ty_report
it_member TYPE tt_member_key
RETURNING VALUE(rv_count) TYPE abap_bool.
ENDCLASS.
CLASS {{p}}harvest_evaluator IMPLEMENTATION.
METHOD evaluate.
DATA lt_member TYPE tt_member_key.
lt_member = it_member.
DATA(lt_report) = countable_reports( it_report = it_report
it_member = lt_member ).
DATA lt_result TYPE {{p}}if_harvest=>tt_result.
DATA lt_member_id TYPE tt_member_id.
LOOP AT lt_report ASSIGNING FIELD-SYMBOL(<ls_report>) GROUP BY <ls_report>-variety.
DATA(lv_variety) = <ls_report>-variety.
DATA(lv_total) = 0.
CLEAR lt_member_id.
LOOP AT GROUP <ls_report> ASSIGNING FIELD-SYMBOL(<ls_grouped>).
lv_total = lv_total + <ls_grouped>-quantity_kg.
INSERT <ls_grouped>-member_id INTO TABLE lt_member_id.
ENDLOOP.
APPEND VALUE {{p}}if_harvest=>ty_result(
variety = lv_variety
total_kg = lv_total
member_count = lines( lt_member_id ) ) TO lt_result.
ENDLOOP.
SORT lt_result BY total_kg DESCENDING variety ASCENDING.
rt_result = lt_result.
ENDMETHOD.
METHOD countable_reports.
LOOP AT it_report INTO DATA(ls_report).
IF is_countable( is_report = ls_report
it_member = it_member ) = abap_true.
CONTINUE.
ENDIF.
INSERT ls_report INTO TABLE rt_report.
ENDLOOP.
ENDMETHOD.
METHOD is_countable.
IF is_report-quantity_kg <= 0.
RETURN.
ENDIF.
rv_count = xsdbool( line_exists( it_member[ member_id = is_report-member_id ] ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,71 @@
CLASS {{p}}harvest_evaluator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS evaluate
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE {{p}}if_harvest=>tt_member
RETURNING VALUE(rt_result) TYPE {{p}}if_harvest=>tt_result.
PRIVATE SECTION.
TYPES tt_member_key TYPE HASHED TABLE OF {{p}}if_harvest=>ty_member
WITH UNIQUE KEY member_id.
TYPES tt_member_id TYPE SORTED TABLE OF {{p}}if_harvest=>ty_member_id
WITH UNIQUE KEY table_line.
TYPES tt_report_key TYPE SORTED TABLE OF {{p}}if_harvest=>ty_report
WITH NON-UNIQUE KEY variety member_id.
METHODS countable_reports
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE tt_member_key
RETURNING VALUE(rt_report) TYPE tt_report_key.
METHODS is_countable
IMPORTING is_report TYPE {{p}}if_harvest=>ty_report
it_member TYPE tt_member_key
RETURNING VALUE(rv_count) TYPE abap_bool.
ENDCLASS.
CLASS {{p}}harvest_evaluator IMPLEMENTATION.
METHOD evaluate.
DATA lt_member TYPE tt_member_key.
lt_member = it_member.
DATA(lt_report) = countable_reports( it_report = it_report
it_member = lt_member ).
DATA lt_result TYPE {{p}}if_harvest=>tt_result.
DATA lt_member_id TYPE tt_member_id.
LOOP AT lt_report ASSIGNING FIELD-SYMBOL(<ls_report>) GROUP BY <ls_report>-variety.
DATA(lv_variety) = <ls_report>-variety.
DATA(lv_total) = 0.
CLEAR lt_member_id.
LOOP AT GROUP <ls_report> ASSIGNING FIELD-SYMBOL(<ls_grouped>).
lv_total = lv_total + <ls_grouped>-quantity_kg.
INSERT <ls_grouped>-member_id INTO TABLE lt_member_id.
ENDLOOP.
APPEND VALUE {{p}}if_harvest=>ty_result(
variety = lv_variety
total_kg = lv_total
member_count = lines( lt_member_id ) ) TO lt_result.
ENDLOOP.
SORT lt_result BY total_kg DESCENDING variety ASCENDING.
rt_result = lt_result.
ENDMETHOD.
METHOD countable_reports.
LOOP AT it_report INTO DATA(ls_report).
IF is_countable( is_report = ls_report
it_member = it_member ) = abap_false.
CONTINUE.
ENDIF.
INSERT ls_report INTO TABLE rt_report.
ENDLOOP.
ENDMETHOD.
METHOD is_countable.
IF is_report-quantity_kg > 0.
RETURN.
ENDIF.
rv_count = xsdbool( line_exists( it_member[ member_id = is_report-member_id ] ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,19 @@
{
"id": "G1001",
"pool": "train",
"object_type": "CLAS",
"category": "C",
"attempts": [
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 3,
"killed": 3,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,193 @@
CLASS {{p}}harvest_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}harvest_evaluator.
METHODS setup.
METHODS assert_rows
IMPORTING it_act TYPE {{p}}if_harvest=>tt_result
it_exp TYPE {{p}}if_harvest=>tt_result
iv_msg TYPE string.
METHODS empty_input FOR TESTING.
METHODS unknown_member_ignored FOR TESTING.
METHODS zero_quantity_ignored FOR TESTING.
METHODS negative_quantity_ignored FOR TESTING.
METHODS same_member_counts_once FOR TESTING.
METHODS two_varieties_separated FOR TESTING.
METHODS sorted_by_total_desc FOR TESTING.
METHODS tie_broken_by_variety FOR TESTING.
METHODS all_reports_ignored FOR TESTING.
METHODS member_without_report FOR TESTING.
ENDCLASS.
CLASS {{p}}harvest_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}harvest_evaluator( ).
ENDMETHOD.
METHOD assert_rows.
cl_abap_unit_assert=>assert_equals( exp = lines( it_exp )
act = lines( it_act )
msg = |{ iv_msg }: number of rows| ).
LOOP AT it_exp ASSIGNING FIELD-SYMBOL(<ls_exp>).
READ TABLE it_act ASSIGNING FIELD-SYMBOL(<ls_act>) INDEX sy-tabix.
IF sy-subrc <> 0.
RETURN.
ENDIF.
cl_abap_unit_assert=>assert_equals( exp = <ls_exp>-variety
act = <ls_act>-variety
msg = |{ iv_msg }: variety| ).
cl_abap_unit_assert=>assert_equals( exp = <ls_exp>-total_kg
act = <ls_act>-total_kg
msg = |{ iv_msg }: total_kg| ).
cl_abap_unit_assert=>assert_equals( exp = <ls_exp>-member_count
act = <ls_act>-member_count
msg = |{ iv_msg }: member_count| ).
ENDLOOP.
ENDMETHOD.
METHOD empty_input.
DATA lt_report TYPE {{p}}if_harvest=>tt_report.
DATA lt_member TYPE {{p}}if_harvest=>tt_member.
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( lt_result ) ).
ENDMETHOD.
METHOD unknown_member_ignored.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 100 )
( member_id = 'M00009' variety = 'RIES' quantity_kg = 50 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 100 member_count = 1 ) )
iv_msg = 'unknown member' ).
ENDMETHOD.
METHOD zero_quantity_ignored.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 0 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 30 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 30 member_count = 1 ) )
iv_msg = 'zero quantity' ).
ENDMETHOD.
METHOD negative_quantity_ignored.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = -5 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 20 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 20 member_count = 1 ) )
iv_msg = 'negative quantity' ).
ENDMETHOD.
METHOD same_member_counts_once.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 20 )
( member_id = 'M00002' variety = 'RIES' quantity_kg = 5 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 35 member_count = 2 ) )
iv_msg = 'same member' ).
ENDMETHOD.
METHOD two_varieties_separated.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00002' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00001' variety = 'SPAE' quantity_kg = 7 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 20 member_count = 2 )
( variety = 'SPAE' total_kg = 7 member_count = 1 ) )
iv_msg = 'two varieties' ).
ENDMETHOD.
METHOD sorted_by_total_desc.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' )
( member_id = 'M00003' member_name = 'Hof Zeder' )
( member_id = 'M00004' member_name = 'Hof Eiche' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00002' variety = 'SPAE' quantity_kg = 30 )
( member_id = 'M00003' variety = 'SILV' quantity_kg = 20 )
( member_id = 'M00004' variety = 'SPAE' quantity_kg = 5 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'SPAE' total_kg = 35 member_count = 2 )
( variety = 'SILV' total_kg = 20 member_count = 1 )
( variety = 'RIES' total_kg = 10 member_count = 1 ) )
iv_msg = 'sort by total' ).
ENDMETHOD.
METHOD tie_broken_by_variety.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' )
( member_id = 'M00003' member_name = 'Hof Zeder' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'SPAE' quantity_kg = 10 )
( member_id = 'M00002' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00003' variety = 'SILV' quantity_kg = 10 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 10 member_count = 1 )
( variety = 'SILV' total_kg = 10 member_count = 1 )
( variety = 'SPAE' total_kg = 10 member_count = 1 ) )
iv_msg = 'tie by variety' ).
ENDMETHOD.
METHOD all_reports_ignored.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00009' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 0 )
( member_id = 'M00001' variety = 'SPAE' quantity_kg = -3 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( lt_result ) ).
ENDMETHOD.
METHOD member_without_report.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 5 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
assert_rows( it_act = lt_result
it_exp = VALUE #( ( variety = 'RIES' total_kg = 5 member_count = 1 ) )
iv_msg = 'member without report' ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,66 @@
{
"task": "G1001",
"mutants": [
{
"object": "{{P}}HARVEST_EVALUATOR",
"mutant": "line 42: + -> - (arith)",
"status": "killed",
"hidden": "2/10",
"failed_tests": [
"MEMBER_WITHOUT_REPORT",
"NEGATIVE_QUANTITY_IGNORED",
"SAME_MEMBER_COUNTS_ONCE",
"SORTED_BY_TOTAL_DESC",
"TIE_BROKEN_BY_VARIETY",
"TWO_VARIETIES_SEPARATED",
"UNKNOWN_MEMBER_IGNORED",
"ZERO_QUANTITY_IGNORED"
]
},
{
"object": "{{P}}HARVEST_EVALUATOR",
"mutant": "line 57: = -> <> (eq)",
"status": "invalid",
"hidden": "0/0",
"failed_tests": []
},
{
"object": "{{P}}HARVEST_EVALUATOR",
"mutant": "line 58: abap_false -> abap_true (bool)",
"status": "killed",
"hidden": "1/10",
"failed_tests": [
"ALL_REPORTS_IGNORED",
"MEMBER_WITHOUT_REPORT",
"NEGATIVE_QUANTITY_IGNORED",
"SAME_MEMBER_COUNTS_ONCE",
"SORTED_BY_TOTAL_DESC",
"TIE_BROKEN_BY_VARIETY",
"TWO_VARIETIES_SEPARATED",
"UNKNOWN_MEMBER_IGNORED",
"ZERO_QUANTITY_IGNORED"
]
},
{
"object": "{{P}}HARVEST_EVALUATOR",
"mutant": "line 66: <= -> > (rel)",
"status": "killed",
"hidden": "1/10",
"failed_tests": [
"ALL_REPORTS_IGNORED",
"MEMBER_WITHOUT_REPORT",
"NEGATIVE_QUANTITY_IGNORED",
"SAME_MEMBER_COUNTS_ONCE",
"SORTED_BY_TOTAL_DESC",
"TIE_BROKEN_BY_VARIETY",
"TWO_VARIETIES_SEPARATED",
"UNKNOWN_MEMBER_IGNORED",
"ZERO_QUANTITY_IGNORED"
]
}
],
"valid": 3,
"killed": 3,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,71 @@
CLASS {{p}}harvest_evaluator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS evaluate
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE {{p}}if_harvest=>tt_member
RETURNING VALUE(rt_result) TYPE {{p}}if_harvest=>tt_result.
PRIVATE SECTION.
TYPES tt_member_key TYPE HASHED TABLE OF {{p}}if_harvest=>ty_member
WITH UNIQUE KEY member_id.
TYPES tt_member_id TYPE SORTED TABLE OF {{p}}if_harvest=>ty_member_id
WITH UNIQUE KEY table_line.
TYPES tt_report_key TYPE SORTED TABLE OF {{p}}if_harvest=>ty_report
WITH NON-UNIQUE KEY variety member_id.
METHODS countable_reports
IMPORTING it_report TYPE {{p}}if_harvest=>tt_report
it_member TYPE tt_member_key
RETURNING VALUE(rt_report) TYPE tt_report_key.
METHODS is_countable
IMPORTING is_report TYPE {{p}}if_harvest=>ty_report
it_member TYPE tt_member_key
RETURNING VALUE(rv_count) TYPE abap_bool.
ENDCLASS.
CLASS {{p}}harvest_evaluator IMPLEMENTATION.
METHOD evaluate.
DATA lt_member TYPE tt_member_key.
lt_member = it_member.
DATA(lt_report) = countable_reports( it_report = it_report
it_member = lt_member ).
DATA lt_result TYPE {{p}}if_harvest=>tt_result.
DATA lt_member_id TYPE tt_member_id.
LOOP AT lt_report ASSIGNING FIELD-SYMBOL(<ls_report>) GROUP BY <ls_report>-variety.
DATA(lv_variety) = <ls_report>-variety.
DATA(lv_total) = 0.
CLEAR lt_member_id.
LOOP AT GROUP <ls_report> ASSIGNING FIELD-SYMBOL(<ls_grouped>).
lv_total = lv_total + <ls_grouped>-quantity_kg.
INSERT <ls_grouped>-member_id INTO TABLE lt_member_id.
ENDLOOP.
APPEND VALUE {{p}}if_harvest=>ty_result(
variety = lv_variety
total_kg = lv_total
member_count = lines( lt_member_id ) ) TO lt_result.
ENDLOOP.
SORT lt_result BY total_kg DESCENDING variety ASCENDING.
rt_result = lt_result.
ENDMETHOD.
METHOD countable_reports.
LOOP AT it_report INTO DATA(ls_report).
IF is_countable( is_report = ls_report
it_member = it_member ) = abap_false.
CONTINUE.
ENDIF.
INSERT ls_report INTO TABLE rt_report.
ENDLOOP.
ENDMETHOD.
METHOD is_countable.
IF is_report-quantity_kg <= 0.
RETURN.
ENDIF.
rv_count = xsdbool( line_exists( it_member[ member_id = is_report-member_id ] ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,84 @@
CLASS ltc_harvest_evaluator DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}harvest_evaluator.
METHODS setup.
METHODS sums_quantities FOR TESTING.
METHODS counts_members_once FOR TESTING.
METHODS ignores_unknown_member FOR TESTING.
METHODS ignores_zero_quantity FOR TESTING.
METHODS sorts_by_total FOR TESTING.
ENDCLASS.
CLASS ltc_harvest_evaluator IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}harvest_evaluator( ).
ENDMETHOD.
METHOD sums_quantities.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 15 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 25 act = lt_result[ 1 ]-total_kg ).
ENDMETHOD.
METHOD counts_members_once.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 5 )
( member_id = 'M00002' variety = 'RIES' quantity_kg = 5 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 20 act = lt_result[ 1 ]-total_kg ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lt_result[ 1 ]-member_count ).
ENDMETHOD.
METHOD ignores_unknown_member.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 10 )
( member_id = 'M00009' variety = 'RIES' quantity_kg = 99 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 10 act = lt_result[ 1 ]-total_kg ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lt_result[ 1 ]-member_count ).
ENDMETHOD.
METHOD ignores_zero_quantity.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 0 )
( member_id = 'M00001' variety = 'RIES' quantity_kg = 40 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 40 act = lt_result[ 1 ]-total_kg ).
ENDMETHOD.
METHOD sorts_by_total.
DATA(lt_member) = VALUE {{p}}if_harvest=>tt_member(
( member_id = 'M00001' member_name = 'Hof Adler' )
( member_id = 'M00002' member_name = 'Hof Birke' )
( member_id = 'M00003' member_name = 'Hof Zeder' ) ).
DATA(lt_report) = VALUE {{p}}if_harvest=>tt_report(
( member_id = 'M00001' variety = 'RIES' quantity_kg = 5 )
( member_id = 'M00002' variety = 'SPAE' quantity_kg = 30 )
( member_id = 'M00003' variety = 'SILV' quantity_kg = 20 ) ).
DATA(lt_result) = mo_cut->evaluate( it_report = lt_report
it_member = lt_member ).
cl_abap_unit_assert=>assert_equals( exp = 'SPAE' act = lt_result[ 1 ]-variety ).
cl_abap_unit_assert=>assert_equals( exp = 'SILV' act = lt_result[ 2 ]-variety ).
cl_abap_unit_assert=>assert_equals( exp = 'RIES' act = lt_result[ 3 ]-variety ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,26 @@
INTERFACE {{p}}if_harvest PUBLIC.
TYPES ty_member_id TYPE c LENGTH 6.
TYPES ty_variety TYPE c LENGTH 4.
TYPES ty_name TYPE c LENGTH 30.
TYPES ty_quantity TYPE i.
TYPES: BEGIN OF ty_member,
member_id TYPE ty_member_id,
member_name TYPE ty_name,
END OF ty_member.
TYPES tt_member TYPE STANDARD TABLE OF ty_member WITH EMPTY KEY.
TYPES: BEGIN OF ty_report,
member_id TYPE ty_member_id,
variety TYPE ty_variety,
quantity_kg TYPE ty_quantity,
END OF ty_report.
TYPES tt_report TYPE STANDARD TABLE OF ty_report WITH EMPTY KEY.
TYPES: BEGIN OF ty_result,
variety TYPE ty_variety,
total_kg TYPE ty_quantity,
member_count TYPE i,
END OF ty_result.
TYPES tt_result TYPE STANDARD TABLE OF ty_result WITH EMPTY KEY.
ENDINTERFACE.

View File

@@ -0,0 +1,52 @@
# 1. Goal
A vineyard cooperative collects the harvest reports of its members. The
cooperative needs one evaluation of the harvest for each grape variety. A
planning program calls the evaluation before it plans the transport of the
grapes to the winery.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_HARVEST exists in package $TMP. It is active.
- The interface contains the data types for the harvest reports, for the member
master data, and for the result. Read the interface before you start to work.
- A harvest report has a member, a grape variety, and a quantity in kilograms.
- The member master data has a member ID and a member name.
# 4. Contract
- Create the class {{P}}HARVEST_EVALUATOR in package $TMP.
- The class is public and final. It has a public constructor without
parameters.
- The class has one public method:
EVALUATE
IMPORTING it_report TYPE {{P}}IF_HARVEST=>TT_REPORT
it_member TYPE {{P}}IF_HARVEST=>TT_MEMBER
RETURNING VALUE(rt_result) TYPE {{P}}IF_HARVEST=>TT_RESULT.
- Do not add other public methods.
# 5. Business rules
A harvest report is countable when rule 1 and rule 2 are true.
1. The member_id of the report occurs in it_member. A member_id occurs at most
one time in it_member.
2. The quantity_kg of the report is greater than 0.
3. Group the countable reports by variety. Create one result row for each
variety that has at least one countable report.
4. The total_kg of a result row is the sum of the quantity_kg of all countable
reports of the variety.
5. The member_count of a result row is the number of different member_id
values in the countable reports of the variety.
6. Sort the result rows by total_kg in descending order. When two rows have the
same total_kg, sort these rows by variety in ascending order.
7. When no report is countable, return an empty table.
# 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_HARVEST.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,53 @@
{
"id": "G1001",
"category": "C",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 15
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_HARVEST",
"file": "seed/if_harvest.intf.abap",
"description": "Data types for harvest reports, member master data, and the evaluation result"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}HARVEST_EVALUATOR",
"description": "Evaluates the harvest reports per grape variety"
}
],
"out_of_scope": [
"{{P}}IF_HARVEST"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}HARVEST_HIDDEN",
"file": "hidden/harvest_hidden.clas.abap",
"description": "Hidden tests for the harvest evaluation"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}HARVEST_EVALUATOR",
"file": "reference/harvest_evaluator.clas.abap",
"description": "Harvest evaluator",
"testclasses_file": "reference/harvest_evaluator.testclasses.abap"
}
],
"craft_checks": [
"table_kind",
"key_kind",
"access_path",
"method_length"
]
}

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens / iv_qty.
rv_total = rv_total + iv_coat * iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty >= c_discount_qty.
rv_price = iv_total - iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens * iv_qty.
rv_total = rv_total - iv_coat * iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty >= c_discount_qty.
rv_price = iv_total - iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens * iv_qty.
rv_total = rv_total + iv_coat / iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty >= c_discount_qty.
rv_price = iv_total - iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens * iv_qty.
rv_total = rv_total + iv_coat * iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty < c_discount_qty.
rv_price = iv_total - iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens * iv_qty.
rv_total = rv_total + iv_coat * iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty >= c_discount_qty.
rv_price = iv_total + iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
{
"id": "G1002",
"pool": "train",
"object_type": "CLAS",
"category": "E",
"attempts": [
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 5,
"killed": 5,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,100 @@
CLASS {{p}}glasses_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CONSTANTS c_frame TYPE {{p}}glasses_pricer=>ty_amount VALUE '100.00'.
DATA mo_cut TYPE REF TO {{p}}glasses_pricer.
METHODS setup.
METHODS zero_qty_is_free FOR TESTING.
METHODS negative_qty_is_free FOR TESTING.
METHODS single_vision_price FOR TESTING.
METHODS bifocal_with_coating FOR TESTING.
METHODS progressive_blue_filter FOR TESTING.
METHODS unknown_lens_defaults FOR TESTING.
METHODS unknown_coat_is_free FOR TESTING.
METHODS discount_from_three FOR TESTING.
METHODS no_discount_for_two FOR TESTING.
METHODS qty_capped_at_hundred FOR TESTING.
METHODS discount_on_total FOR TESTING.
ENDCLASS.
CLASS {{p}}glasses_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}glasses_pricer( ).
ENDMETHOD.
METHOD zero_qty_is_free.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '0.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'A' iv_qty = 0 ) ).
ENDMETHOD.
METHOD negative_qty_is_free.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '0.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'P' iv_coat = 'B' iv_qty = -2 ) ).
ENDMETHOD.
METHOD single_vision_price.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 1 ) ).
ENDMETHOD.
METHOD bifocal_with_coating.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '430.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'B' iv_coat = 'A' iv_qty = 2 ) ).
ENDMETHOD.
METHOD progressive_blue_filter.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '235.00' )
act = mo_cut->price( iv_frame = CONV {{p}}glasses_pricer=>ty_amount( '50.00' )
iv_lens = 'P' iv_coat = 'B' iv_qty = 1 ) ).
ENDMETHOD.
METHOD unknown_lens_defaults.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'Z' iv_coat = 'N' iv_qty = 1 ) ).
ENDMETHOD.
METHOD unknown_coat_is_free.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'X' iv_qty = 1 ) ).
ENDMETHOD.
METHOD discount_from_three.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '378.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 3 ) ).
ENDMETHOD.
METHOD no_discount_for_two.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '280.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 2 ) ).
ENDMETHOD.
METHOD qty_capped_at_hundred.
DATA(lv_capped) = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 100 ).
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '12600.00' )
act = lv_capped ).
cl_abap_unit_assert=>assert_equals(
exp = lv_capped
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 150 ) ).
ENDMETHOD.
METHOD discount_on_total.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '499.50' )
act = mo_cut->price( iv_frame = CONV {{p}}glasses_pricer=>ty_amount( '0.00' )
iv_lens = 'P' iv_coat = 'B' iv_qty = 3 ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,71 @@
{
"task": "G1002",
"mutants": [
{
"object": "{{P}}GLASSES_PRICER",
"mutant": "line 99: * -> / (arith)",
"status": "killed",
"hidden": "6/11",
"failed_tests": [
"BIFOCAL_WITH_COATING",
"DISCOUNT_FROM_THREE",
"DISCOUNT_ON_TOTAL",
"NO_DISCOUNT_FOR_TWO",
"QTY_CAPPED_AT_HUNDRED"
]
},
{
"object": "{{P}}GLASSES_PRICER",
"mutant": "line 100: + -> - (arith)",
"status": "killed",
"hidden": "8/11",
"failed_tests": [
"BIFOCAL_WITH_COATING",
"DISCOUNT_ON_TOTAL",
"PROGRESSIVE_BLUE_FILTER"
]
},
{
"object": "{{P}}GLASSES_PRICER",
"mutant": "line 100: * -> / (arith)",
"status": "killed",
"hidden": "9/11",
"failed_tests": [
"BIFOCAL_WITH_COATING",
"DISCOUNT_ON_TOTAL"
]
},
{
"object": "{{P}}GLASSES_PRICER",
"mutant": "line 105: >= -> < (rel)",
"status": "killed",
"hidden": "2/11",
"failed_tests": [
"BIFOCAL_WITH_COATING",
"DISCOUNT_FROM_THREE",
"DISCOUNT_ON_TOTAL",
"NO_DISCOUNT_FOR_TWO",
"PROGRESSIVE_BLUE_FILTER",
"QTY_CAPPED_AT_HUNDRED",
"SINGLE_VISION_PRICE",
"UNKNOWN_COAT_IS_FREE",
"UNKNOWN_LENS_DEFAULTS"
]
},
{
"object": "{{P}}GLASSES_PRICER",
"mutant": "line 106: - -> + (arith)",
"status": "killed",
"hidden": "8/11",
"failed_tests": [
"DISCOUNT_FROM_THREE",
"DISCOUNT_ON_TOTAL",
"QTY_CAPPED_AT_HUNDRED"
]
}
],
"valid": 5,
"killed": 5,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,109 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_lens TYPE c LENGTH 1.
TYPES ty_coat TYPE c LENGTH 1.
METHODS price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_max_qty TYPE i VALUE 100,
c_discount_qty TYPE i VALUE 3,
c_discount_percent TYPE i VALUE 10,
c_lens_standard TYPE ty_amount VALUE '40.00',
c_lens_bifocal TYPE ty_amount VALUE '90.00',
c_lens_progressive TYPE ty_amount VALUE '150.00',
c_coat_simple TYPE ty_amount VALUE '25.00',
c_coat_premium TYPE ty_amount VALUE '35.00',
c_coat_none TYPE ty_amount VALUE '0.00'.
METHODS capped_qty
IMPORTING iv_qty TYPE i
RETURNING VALUE(rv_qty) TYPE i.
METHODS lens_surcharge
IMPORTING iv_lens TYPE ty_lens
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS coat_surcharge
IMPORTING iv_coat TYPE ty_coat
RETURNING VALUE(rv_amount) TYPE ty_amount.
METHODS total_price
IMPORTING iv_frame TYPE ty_amount
iv_lens TYPE ty_amount
iv_coat TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_total) TYPE ty_amount.
METHODS with_discount
IMPORTING iv_total TYPE ty_amount
iv_qty TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA(lv_qty) = capped_qty( iv_qty ).
IF lv_qty < 1.
RETURN.
ENDIF.
DATA(lv_total) = total_price( iv_frame = iv_frame
iv_lens = lens_surcharge( iv_lens )
iv_coat = coat_surcharge( iv_coat )
iv_qty = lv_qty ).
rv_price = with_discount( iv_total = lv_total
iv_qty = lv_qty ).
ENDMETHOD.
METHOD capped_qty.
rv_qty = iv_qty.
IF rv_qty > c_max_qty.
rv_qty = c_max_qty.
ENDIF.
ENDMETHOD.
METHOD lens_surcharge.
CASE iv_lens.
WHEN 'B'.
rv_amount = c_lens_bifocal.
WHEN 'P'.
rv_amount = c_lens_progressive.
WHEN OTHERS.
rv_amount = c_lens_standard.
ENDCASE.
ENDMETHOD.
METHOD coat_surcharge.
CASE iv_coat.
WHEN 'A'.
rv_amount = c_coat_simple.
WHEN 'B'.
rv_amount = c_coat_premium.
WHEN OTHERS.
rv_amount = c_coat_none.
ENDCASE.
ENDMETHOD.
METHOD total_price.
rv_total = iv_frame * iv_qty.
rv_total = rv_total + iv_lens * iv_qty.
rv_total = rv_total + iv_coat * iv_qty.
ENDMETHOD.
METHOD with_discount.
rv_price = iv_total.
IF iv_qty >= c_discount_qty.
rv_price = iv_total - iv_total * c_discount_percent / 100.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,83 @@
CLASS ltc_glasses_pricer DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CONSTANTS c_frame TYPE {{p}}glasses_pricer=>ty_amount VALUE '100.00'.
DATA mo_cut TYPE REF TO {{p}}glasses_pricer.
METHODS setup.
METHODS single_vision FOR TESTING.
METHODS bifocal_with_coating FOR TESTING.
METHODS progressive_with_blue FOR TESTING.
METHODS unknown_lens_defaults FOR TESTING.
METHODS unknown_coat_is_free FOR TESTING.
METHODS discount_from_three FOR TESTING.
METHODS no_discount_for_two FOR TESTING.
METHODS zero_qty_is_free FOR TESTING.
METHODS qty_capped_at_hundred FOR TESTING.
ENDCLASS.
CLASS ltc_glasses_pricer IMPLEMENTATION.
METHOD setup.
mo_cut = NEW #( ).
ENDMETHOD.
METHOD single_vision.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 1 ) ).
ENDMETHOD.
METHOD bifocal_with_coating.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '430.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'B' iv_coat = 'A' iv_qty = 2 ) ).
ENDMETHOD.
METHOD progressive_with_blue.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '235.00' )
act = mo_cut->price( iv_frame = CONV {{p}}glasses_pricer=>ty_amount( '50.00' )
iv_lens = 'P' iv_coat = 'B' iv_qty = 1 ) ).
ENDMETHOD.
METHOD unknown_lens_defaults.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'Z' iv_coat = 'N' iv_qty = 1 ) ).
ENDMETHOD.
METHOD unknown_coat_is_free.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '140.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'X' iv_qty = 1 ) ).
ENDMETHOD.
METHOD discount_from_three.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '378.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 3 ) ).
ENDMETHOD.
METHOD no_discount_for_two.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '280.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 2 ) ).
ENDMETHOD.
METHOD zero_qty_is_free.
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '0.00' )
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'A' iv_qty = 0 ) ).
ENDMETHOD.
METHOD qty_capped_at_hundred.
DATA(lv_capped) = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 100 ).
cl_abap_unit_assert=>assert_equals(
exp = CONV {{p}}glasses_pricer=>ty_amount( '12600.00' )
act = lv_capped ).
cl_abap_unit_assert=>assert_equals(
exp = lv_capped
act = mo_cut->price( iv_frame = c_frame iv_lens = 'S' iv_coat = 'N' iv_qty = 150 ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,75 @@
CLASS {{p}}glasses_pricer DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2 .
TYPES ty_lens TYPE c LENGTH 1 .
TYPES ty_coat TYPE c LENGTH 1 .
METHODS price
IMPORTING
iv_frame TYPE ty_amount
iv_lens TYPE ty_lens
iv_coat TYPE ty_coat
iv_qty TYPE i
RETURNING
VALUE(rv_price) TYPE ty_amount .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS {{p}}glasses_pricer IMPLEMENTATION.
METHOD price.
DATA lv_qty TYPE i .
DATA lv_frame TYPE ty_amount .
DATA lv_lens TYPE ty_amount .
DATA lv_coat TYPE ty_amount .
DATA lv_total TYPE ty_amount .
lv_qty = iv_qty .
IF lv_qty > 100 .
lv_qty = 100 .
ENDIF .
IF lv_qty < 1 .
rv_price = 0 .
RETURN .
ENDIF .
lv_frame = iv_frame * lv_qty .
IF iv_lens = 'S' .
lv_lens = 40 .
ELSEIF iv_lens = 'B' .
lv_lens = 90 .
ELSEIF iv_lens = 'P' .
lv_lens = 150 .
ELSE .
lv_lens = 40 .
ENDIF .
lv_lens = lv_lens * lv_qty .
CASE iv_coat .
WHEN 'A' .
lv_coat = 25 .
WHEN 'B' .
lv_coat = 35 .
WHEN OTHERS .
lv_coat = 0 .
ENDCASE .
lv_coat = lv_coat * lv_qty .
lv_total = lv_frame + lv_lens + lv_coat .
IF lv_qty >= 3 .
lv_total = lv_total - lv_total * 10 / 100 .
ENDIF .
rv_price = lv_total .
ENDMETHOD .
ENDCLASS.

View File

@@ -0,0 +1,12 @@
REPORT {{p}}optic_quote_rpt.
DATA lo_pricer TYPE REF TO {{p}}glasses_pricer.
DATA lv_price TYPE {{p}}glasses_pricer=>ty_amount.
lo_pricer = NEW #( ).
lv_price = lo_pricer->price( iv_frame = CONV {{p}}glasses_pricer=>ty_amount( '120.00' )
iv_lens = 'P'
iv_coat = 'A'
iv_qty = 2 ).
WRITE: / 'Quotation:', lv_price.

View File

@@ -0,0 +1,51 @@
# 1. Goal
The optician shop uses the class {{P}}GLASSES_PRICER to calculate the price of a
glasses order. A sales program calls the class for each quotation. The class is
legacy code. It is hard to read and hard to test. Refactor the class. The
behavior must stay the same.
# 2. Open questions
None.
# 3. Context
- The class {{P}}GLASSES_PRICER exists in package $TMP. It is active.
- The class is public and final. It has a public constructor without parameters.
- The public interface of the class is:
- the type TY_AMOUNT (packed number, length 9, 2 decimals),
- the type TY_LENS (character, length 1),
- the type TY_COAT (character, length 1),
- the method PRICE with the importing parameters IV_FRAME (TY_AMOUNT),
IV_LENS (TY_LENS), IV_COAT (TY_COAT), IV_QTY (TYPE i) and the returning
parameter RV_PRICE (TY_AMOUNT).
- The program {{P}}OPTIC_QUOTE_RPT calls the class.
- The legacy implementation is the specification of the behavior.
# 4. Contract
- Refactor the class {{P}}GLASSES_PRICER in package $TMP. The name stays the same.
- The class stays public and final and keeps its public constructor without
parameters.
- Keep the public types TY_AMOUNT, TY_LENS and TY_COAT.
- Keep the method PRICE with the same parameter names, the same parameter types
and the same sequence.
- Do not add public methods or public attributes.
- Do not create other objects.
# 5. Business rules
- The behavior of the class must not change. For every input, PRICE must return
the same value as the legacy method.
- The legacy code holds all rules, including its limits and its default values.
- Do not add a rule. Do not remove a rule. Do not correct a rule.
- PRICE returns a price in the currency of the order.
# 6. Constraints
- Release target: ABAP Platform 2025 (SAP_BASIS 816).
- Coding standards: Clean ABAP. One method does one thing. Method length below
40 statements. No deep nesting. No magic numbers: use constants. No duplicated
code. No dead code. No global variables. No comment that restates the code.
- Out of scope: do not change the program {{P}}OPTIC_QUOTE_RPT. Do not change
the public interface of the class.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,57 @@
{
"id": "G1002",
"category": "E",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 70,
"max_activations": 18
},
"seed": [
{
"type": "CLAS",
"name": "{{P}}GLASSES_PRICER",
"file": "seed/glasses_pricer.clas.abap",
"description": "Legacy price calculator for glasses orders"
},
{
"type": "PROG",
"name": "{{P}}OPTIC_QUOTE_RPT",
"file": "seed/optic_quote_rpt.prog.abap",
"description": "Report that calls the legacy class"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}GLASSES_PRICER"
}
],
"out_of_scope": [
"{{P}}OPTIC_QUOTE_RPT"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}GLASSES_HIDDEN",
"file": "hidden/glasses_hidden.clas.abap",
"description": "Hidden behavior tests for the refactored class"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}GLASSES_PRICER",
"file": "reference/glasses_pricer.clas.abap",
"description": "Refactored glasses price calculator",
"testclasses_file": "reference/glasses_pricer.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"nesting_depth",
"magic_numbers"
]
}

View File

@@ -0,0 +1,49 @@
CLASS {{p}}drone_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_drone_check.
METHODS required_battery
IMPORTING iv_distance TYPE {{p}}if_drone_check=>ty_distance
RETURNING VALUE(rv_percent) TYPE {{p}}if_drone_check=>ty_percent.
PRIVATE SECTION.
CONSTANTS:
c_max_payload TYPE {{p}}if_drone_check=>ty_weight VALUE '5.00',
c_max_distance TYPE {{p}}if_drone_check=>ty_distance VALUE '20.00',
c_max_wind TYPE {{p}}if_drone_check=>ty_speed VALUE '12.00',
c_reserve TYPE {{p}}if_drone_check=>ty_percent VALUE 10,
c_percent_per_km TYPE {{p}}if_drone_check=>ty_percent VALUE 2.
ENDCLASS.
CLASS {{p}}drone_checker IMPLEMENTATION.
METHOD {{p}}if_drone_check~check.
IF iv_payload <= 0 AND iv_payload > c_max_payload.
rv_reason = {{p}}if_drone_check=>c_reason-payload.
ELSEIF iv_distance <= 0 OR iv_distance > c_max_distance.
rv_reason = {{p}}if_drone_check=>c_reason-distance.
ELSEIF iv_wind_speed > c_max_wind.
rv_reason = {{p}}if_drone_check=>c_reason-wind.
ELSEIF iv_battery < required_battery( iv_distance ).
rv_reason = {{p}}if_drone_check=>c_reason-battery.
ELSEIF iv_in_zone = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-zone.
ELSEIF iv_daylight = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-light.
ELSE.
rv_reason = {{p}}if_drone_check=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD required_battery.
IF iv_distance <= 0.
RETURN.
ENDIF.
DATA lv_km TYPE i.
lv_km = iv_distance.
IF lv_km < iv_distance.
lv_km = lv_km + 1.
ENDIF.
rv_percent = lv_km * c_percent_per_km + c_reserve.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,49 @@
CLASS {{p}}drone_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_drone_check.
METHODS required_battery
IMPORTING iv_distance TYPE {{p}}if_drone_check=>ty_distance
RETURNING VALUE(rv_percent) TYPE {{p}}if_drone_check=>ty_percent.
PRIVATE SECTION.
CONSTANTS:
c_max_payload TYPE {{p}}if_drone_check=>ty_weight VALUE '5.00',
c_max_distance TYPE {{p}}if_drone_check=>ty_distance VALUE '20.00',
c_max_wind TYPE {{p}}if_drone_check=>ty_speed VALUE '12.00',
c_reserve TYPE {{p}}if_drone_check=>ty_percent VALUE 10,
c_percent_per_km TYPE {{p}}if_drone_check=>ty_percent VALUE 2.
ENDCLASS.
CLASS {{p}}drone_checker IMPLEMENTATION.
METHOD {{p}}if_drone_check~check.
IF iv_payload <= 0 OR iv_payload > c_max_payload.
rv_reason = {{p}}if_drone_check=>c_reason-payload.
ELSEIF iv_distance <= 0 OR iv_distance <= c_max_distance.
rv_reason = {{p}}if_drone_check=>c_reason-distance.
ELSEIF iv_wind_speed > c_max_wind.
rv_reason = {{p}}if_drone_check=>c_reason-wind.
ELSEIF iv_battery < required_battery( iv_distance ).
rv_reason = {{p}}if_drone_check=>c_reason-battery.
ELSEIF iv_in_zone = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-zone.
ELSEIF iv_daylight = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-light.
ELSE.
rv_reason = {{p}}if_drone_check=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD required_battery.
IF iv_distance <= 0.
RETURN.
ENDIF.
DATA lv_km TYPE i.
lv_km = iv_distance.
IF lv_km < iv_distance.
lv_km = lv_km + 1.
ENDIF.
rv_percent = lv_km * c_percent_per_km + c_reserve.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,49 @@
CLASS {{p}}drone_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_drone_check.
METHODS required_battery
IMPORTING iv_distance TYPE {{p}}if_drone_check=>ty_distance
RETURNING VALUE(rv_percent) TYPE {{p}}if_drone_check=>ty_percent.
PRIVATE SECTION.
CONSTANTS:
c_max_payload TYPE {{p}}if_drone_check=>ty_weight VALUE '5.00',
c_max_distance TYPE {{p}}if_drone_check=>ty_distance VALUE '20.00',
c_max_wind TYPE {{p}}if_drone_check=>ty_speed VALUE '12.00',
c_reserve TYPE {{p}}if_drone_check=>ty_percent VALUE 10,
c_percent_per_km TYPE {{p}}if_drone_check=>ty_percent VALUE 2.
ENDCLASS.
CLASS {{p}}drone_checker IMPLEMENTATION.
METHOD {{p}}if_drone_check~check.
IF iv_payload <= 0 OR iv_payload > c_max_payload.
rv_reason = {{p}}if_drone_check=>c_reason-payload.
ELSEIF iv_distance <= 0 OR iv_distance > c_max_distance.
rv_reason = {{p}}if_drone_check=>c_reason-distance.
ELSEIF iv_wind_speed > c_max_wind.
rv_reason = {{p}}if_drone_check=>c_reason-wind.
ELSEIF iv_battery < required_battery( iv_distance ).
rv_reason = {{p}}if_drone_check=>c_reason-battery.
ELSEIF iv_in_zone <> abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-zone.
ELSEIF iv_daylight = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-light.
ELSE.
rv_reason = {{p}}if_drone_check=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD required_battery.
IF iv_distance <= 0.
RETURN.
ENDIF.
DATA lv_km TYPE i.
lv_km = iv_distance.
IF lv_km < iv_distance.
lv_km = lv_km + 1.
ENDIF.
rv_percent = lv_km * c_percent_per_km + c_reserve.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,49 @@
CLASS {{p}}drone_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_drone_check.
METHODS required_battery
IMPORTING iv_distance TYPE {{p}}if_drone_check=>ty_distance
RETURNING VALUE(rv_percent) TYPE {{p}}if_drone_check=>ty_percent.
PRIVATE SECTION.
CONSTANTS:
c_max_payload TYPE {{p}}if_drone_check=>ty_weight VALUE '5.00',
c_max_distance TYPE {{p}}if_drone_check=>ty_distance VALUE '20.00',
c_max_wind TYPE {{p}}if_drone_check=>ty_speed VALUE '12.00',
c_reserve TYPE {{p}}if_drone_check=>ty_percent VALUE 10,
c_percent_per_km TYPE {{p}}if_drone_check=>ty_percent VALUE 2.
ENDCLASS.
CLASS {{p}}drone_checker IMPLEMENTATION.
METHOD {{p}}if_drone_check~check.
IF iv_payload <= 0 OR iv_payload > c_max_payload.
rv_reason = {{p}}if_drone_check=>c_reason-payload.
ELSEIF iv_distance <= 0 OR iv_distance > c_max_distance.
rv_reason = {{p}}if_drone_check=>c_reason-distance.
ELSEIF iv_wind_speed > c_max_wind.
rv_reason = {{p}}if_drone_check=>c_reason-wind.
ELSEIF iv_battery < required_battery( iv_distance ).
rv_reason = {{p}}if_drone_check=>c_reason-battery.
ELSEIF iv_in_zone = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-zone.
ELSEIF iv_daylight = abap_true.
rv_reason = {{p}}if_drone_check=>c_reason-light.
ELSE.
rv_reason = {{p}}if_drone_check=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD required_battery.
IF iv_distance <= 0.
RETURN.
ENDIF.
DATA lv_km TYPE i.
lv_km = iv_distance.
IF lv_km < iv_distance.
lv_km = lv_km + 1.
ENDIF.
rv_percent = lv_km * c_percent_per_km + c_reserve.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
{
"id": "G1003",
"pool": "train",
"object_type": "CLAS",
"category": "A",
"attempts": [
{
"stage": "validate",
"oracle": 70.0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 5,
"killed": 4,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,172 @@
CLASS {{p}}t21_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_drone_check.
DATA mo_drone TYPE REF TO {{p}}drone_checker.
METHODS setup.
METHODS ok_flight FOR TESTING.
METHODS payload_zero FOR TESTING.
METHODS payload_too_heavy FOR TESTING.
METHODS distance_zero FOR TESTING.
METHODS distance_too_far FOR TESTING.
METHODS wind_too_strong FOR TESTING.
METHODS battery_too_low FOR TESTING.
METHODS battery_exact_ok FOR TESTING.
METHODS outside_zone FOR TESTING.
METHODS no_daylight FOR TESTING.
METHODS required_battery_rounds_up FOR TESTING.
METHODS order_payload_first FOR TESTING.
ENDCLASS.
CLASS {{p}}t21_hidden IMPLEMENTATION.
METHOD setup.
mo_drone = NEW {{p}}drone_checker( ).
mo_cut = mo_drone.
ENDMETHOD.
METHOD ok_flight.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-ok
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-ok
act = mo_cut->check( iv_payload = '5.00'
iv_distance = '20.00'
iv_wind_speed = '12.00'
iv_battery = 50
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD payload_zero.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-payload
act = mo_cut->check( iv_payload = '0.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD payload_too_heavy.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-payload
act = mo_cut->check( iv_payload = '5.01'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD distance_zero.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-distance
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '0.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD distance_too_far.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-distance
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '20.01'
iv_wind_speed = '5.00'
iv_battery = 60
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD wind_too_strong.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-wind
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '12.01'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD battery_too_low.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-battery
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 29
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-battery
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '7.50'
iv_wind_speed = '5.00'
iv_battery = 25
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD battery_exact_ok.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-ok
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD outside_zone.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-zone
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_false
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD no_daylight.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-light
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_false ) ).
ENDMETHOD.
METHOD required_battery_rounds_up.
cl_abap_unit_assert=>assert_equals( exp = 26 act = mo_drone->required_battery( '7.50' ) ).
cl_abap_unit_assert=>assert_equals( exp = 50 act = mo_drone->required_battery( '20.00' ) ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = mo_drone->required_battery( '0.00' ) ).
ENDMETHOD.
METHOD order_payload_first.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-payload
act = mo_cut->check( iv_payload = '6.00'
iv_distance = '25.00'
iv_wind_speed = '20.00'
iv_battery = 0
iv_in_zone = abap_false
iv_daylight = abap_false ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,65 @@
{
"task": "G1003",
"mutants": [
{
"object": "{{P}}DRONE_CHECKER",
"mutant": "line 21: OR -> AND (logic)",
"status": "killed",
"hidden": "9/12",
"failed_tests": [
"ORDER_PAYLOAD_FIRST",
"PAYLOAD_TOO_HEAVY",
"PAYLOAD_ZERO"
]
},
{
"object": "{{P}}DRONE_CHECKER",
"mutant": "line 23: > -> <= (rel)",
"status": "killed",
"hidden": "5/12",
"failed_tests": [
"BATTERY_EXACT_OK",
"BATTERY_TOO_LOW",
"DISTANCE_TOO_FAR",
"NO_DAYLIGHT",
"OK_FLIGHT",
"OUTSIDE_ZONE",
"WIND_TOO_STRONG"
]
},
{
"object": "{{P}}DRONE_CHECKER",
"mutant": "line 29: = -> <> (eq)",
"status": "killed",
"hidden": "8/12",
"failed_tests": [
"BATTERY_EXACT_OK",
"NO_DAYLIGHT",
"OK_FLIGHT",
"OUTSIDE_ZONE"
]
},
{
"object": "{{P}}DRONE_CHECKER",
"mutant": "line 31: abap_false -> abap_true (bool)",
"status": "killed",
"hidden": "9/12",
"failed_tests": [
"BATTERY_EXACT_OK",
"NO_DAYLIGHT",
"OK_FLIGHT"
]
},
{
"object": "{{P}}DRONE_CHECKER",
"mutant": "line 45: + -> - (arith)",
"status": "survived",
"hidden": "12/12",
"failed_tests": []
}
],
"valid": 5,
"killed": 4,
"kill_rate": 0.8,
"ok": true
}

View File

@@ -0,0 +1,49 @@
CLASS {{p}}drone_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_drone_check.
METHODS required_battery
IMPORTING iv_distance TYPE {{p}}if_drone_check=>ty_distance
RETURNING VALUE(rv_percent) TYPE {{p}}if_drone_check=>ty_percent.
PRIVATE SECTION.
CONSTANTS:
c_max_payload TYPE {{p}}if_drone_check=>ty_weight VALUE '5.00',
c_max_distance TYPE {{p}}if_drone_check=>ty_distance VALUE '20.00',
c_max_wind TYPE {{p}}if_drone_check=>ty_speed VALUE '12.00',
c_reserve TYPE {{p}}if_drone_check=>ty_percent VALUE 10,
c_percent_per_km TYPE {{p}}if_drone_check=>ty_percent VALUE 2.
ENDCLASS.
CLASS {{p}}drone_checker IMPLEMENTATION.
METHOD {{p}}if_drone_check~check.
IF iv_payload <= 0 OR iv_payload > c_max_payload.
rv_reason = {{p}}if_drone_check=>c_reason-payload.
ELSEIF iv_distance <= 0 OR iv_distance > c_max_distance.
rv_reason = {{p}}if_drone_check=>c_reason-distance.
ELSEIF iv_wind_speed > c_max_wind.
rv_reason = {{p}}if_drone_check=>c_reason-wind.
ELSEIF iv_battery < required_battery( iv_distance ).
rv_reason = {{p}}if_drone_check=>c_reason-battery.
ELSEIF iv_in_zone = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-zone.
ELSEIF iv_daylight = abap_false.
rv_reason = {{p}}if_drone_check=>c_reason-light.
ELSE.
rv_reason = {{p}}if_drone_check=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD required_battery.
IF iv_distance <= 0.
RETURN.
ENDIF.
DATA lv_km TYPE i.
lv_km = iv_distance.
IF lv_km < iv_distance.
lv_km = lv_km + 1.
ENDIF.
rv_percent = lv_km * c_percent_per_km + c_reserve.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,110 @@
CLASS ltc_drone_checker DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_drone_check.
DATA mo_drone TYPE REF TO {{p}}drone_checker.
METHODS setup.
METHODS valid_request_is_ok FOR TESTING.
METHODS heavy_payload_is_rejected FOR TESTING.
METHODS strong_wind_is_rejected FOR TESTING.
METHODS low_battery_is_rejected FOR TESTING.
METHODS required_battery_is_rounded_up FOR TESTING.
METHODS zone_and_light_are_checked FOR TESTING.
METHODS reason_order_is_kept FOR TESTING.
ENDCLASS.
CLASS ltc_drone_checker IMPLEMENTATION.
METHOD setup.
mo_drone = NEW {{p}}drone_checker( ).
mo_cut = mo_drone.
ENDMETHOD.
METHOD valid_request_is_ok.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-ok
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD heavy_payload_is_rejected.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-payload
act = mo_cut->check( iv_payload = '5.01'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD strong_wind_is_rejected.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-wind
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '12.01'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD low_battery_is_rejected.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-battery
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 29
iv_in_zone = abap_true
iv_daylight = abap_true ) ).
ENDMETHOD.
METHOD required_battery_is_rounded_up.
cl_abap_unit_assert=>assert_equals( exp = 26 act = mo_drone->required_battery( '7.50' ) ).
cl_abap_unit_assert=>assert_equals( exp = 30 act = mo_drone->required_battery( '10.00' ) ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = mo_drone->required_battery( '0.00' ) ).
ENDMETHOD.
METHOD zone_and_light_are_checked.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-zone
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_false
iv_daylight = abap_true ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-light
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '10.00'
iv_wind_speed = '5.00'
iv_battery = 30
iv_in_zone = abap_true
iv_daylight = abap_false ) ).
ENDMETHOD.
METHOD reason_order_is_kept.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-payload
act = mo_cut->check( iv_payload = '6.00'
iv_distance = '25.00'
iv_wind_speed = '20.00'
iv_battery = 0
iv_in_zone = abap_false
iv_daylight = abap_false ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_drone_check=>c_reason-distance
act = mo_cut->check( iv_payload = '2.00'
iv_distance = '25.00'
iv_wind_speed = '20.00'
iv_battery = 0
iv_in_zone = abap_false
iv_daylight = abap_false ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,25 @@
INTERFACE {{p}}if_drone_check PUBLIC.
TYPES ty_reason TYPE c LENGTH 10.
TYPES ty_weight TYPE p LENGTH 8 DECIMALS 2.
TYPES ty_distance TYPE p LENGTH 8 DECIMALS 2.
TYPES ty_speed TYPE p LENGTH 8 DECIMALS 2.
TYPES ty_percent TYPE i.
CONSTANTS:
BEGIN OF c_reason,
ok TYPE ty_reason VALUE 'OK',
payload TYPE ty_reason VALUE 'PAYLOAD',
distance TYPE ty_reason VALUE 'DISTANCE',
wind TYPE ty_reason VALUE 'WIND',
battery TYPE ty_reason VALUE 'BATTERY',
zone TYPE ty_reason VALUE 'ZONE',
light TYPE ty_reason VALUE 'LIGHT',
END OF c_reason.
METHODS check
IMPORTING iv_payload TYPE ty_weight
iv_distance TYPE ty_distance
iv_wind_speed TYPE ty_speed
iv_battery TYPE ty_percent
iv_in_zone TYPE abap_bool
iv_daylight TYPE abap_bool
RETURNING VALUE(rv_reason) TYPE ty_reason.
ENDINTERFACE.

View File

@@ -0,0 +1,65 @@
# 1. Goal
A drone delivery operator checks a delivery request before it releases a drone.
The dispatch program calls one check for each request. The program needs one
reason code for each request and the battery charge that the flight needs.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_DRONE_CHECK exists in package $TMP. It is active.
- The interface has the types TY_REASON (character, length 10), TY_WEIGHT
(packed number, length 8, 2 decimals), TY_DISTANCE (packed number, length 8,
2 decimals), TY_SPEED (packed number, length 8, 2 decimals) and TY_PERCENT
(integer).
- The interface has the constant structure C_REASON with the components OK,
PAYLOAD, DISTANCE, WIND, BATTERY, ZONE and LIGHT.
- The interface has one method:
CHECK
IMPORTING iv_payload TYPE ty_weight
iv_distance TYPE ty_distance
iv_wind_speed TYPE ty_speed
iv_battery TYPE ty_percent
iv_in_zone TYPE abap_bool
iv_daylight TYPE abap_bool
RETURNING VALUE(rv_reason) TYPE ty_reason.
# 4. Contract
- Create the class {{P}}DRONE_CHECKER in package $TMP.
- The class is public, final, and has a public constructor without parameters.
- The class implements {{P}}IF_DRONE_CHECK.
- The class has the public method REQUIRED_BATTERY:
IMPORTING iv_distance TYPE {{P}}IF_DRONE_CHECK=>TY_DISTANCE
RETURNING VALUE(rv_percent) TYPE {{P}}IF_DRONE_CHECK=>TY_PERCENT.
- Do not add other public methods.
# 5. Business rules
The payload is in kilograms, the distance in kilometres, the wind speed in
metres per second, and the battery charge in percent.
The check applies the rules in this sequence and returns the reason of the
first rule that fails.
1. If the payload is less than or equal to zero, or greater than 5.00, return
PAYLOAD.
2. If the distance is less than or equal to zero, or greater than 20.00, return
DISTANCE.
3. If the wind speed is greater than 12.00, return WIND.
4. If the battery charge is less than the charge that the flight needs, return
BATTERY.
5. If iv_in_zone is abap_false, return ZONE.
6. If iv_daylight is abap_false, return LIGHT.
7. Else return OK.
The charge that a flight needs is the result of REQUIRED_BATTERY:
- If the distance is less than or equal to zero, the result is 0.
- Else the result is 2 percent for each started kilometre plus a reserve of
10 percent. A started kilometre counts as a full kilometre.
# 6. Constraints
- Release target: SAP_BASIS 816 (ABAP Platform 2025).
- 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_DRONE_CHECK.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,51 @@
{
"id": "G1003",
"category": "A",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 80,
"max_activations": 20
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_DRONE_CHECK",
"file": "seed/if_drone_check.intf.abap",
"description": "Drone flight check contract"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}DRONE_CHECKER",
"implements": "{{P}}IF_DRONE_CHECK"
}
],
"out_of_scope": [
"{{P}}IF_DRONE_CHECK"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T21_HIDDEN",
"file": "hidden/t21_hidden.clas.abap",
"description": "T21 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}DRONE_CHECKER",
"file": "reference/drone_checker.clas.abap",
"description": "Drone flight checker",
"testclasses_file": "reference/drone_checker.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"db_operation_in_loop"
]
}

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers <= c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg > c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh > c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m < c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg - iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers > c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg <= c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh > c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m < c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg - iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers > c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg > c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh <= c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m < c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg - iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers > c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg > c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh > c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m >= c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg - iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers > c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg > c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh > c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m < c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg + iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
{
"id": "G1004",
"pool": "train",
"object_type": "CLAS",
"category": "D",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 5,
"killed": 5,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,169 @@
CLASS {{p}}balloon_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}balloon_checker.
METHODS setup.
METHODS free_payload
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i.
METHODS valid_flight_is_released FOR TESTING.
METHODS crew_above_limit FOR TESTING.
METHODS weight_above_limit FOR TESTING.
METHODS wind_above_limit FOR TESTING.
METHODS cloud_base_below_limit FOR TESTING.
METHODS crew_is_checked_first FOR TESTING.
METHODS weight_is_checked_second FOR TESTING.
METHODS wind_is_checked_third FOR TESTING.
METHODS limits_are_accepted FOR TESTING.
METHODS free_payload_is_returned FOR TESTING.
ENDCLASS.
CLASS {{p}}balloon_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}balloon_checker( ).
ENDMETHOD.
METHOD free_payload.
TRY.
rv_free_kg = mo_cut->check_flight( iv_passengers = iv_passengers
iv_weight_kg = iv_weight_kg
iv_wind_kmh = iv_wind_kmh
iv_cloud_base_m = iv_cloud_base_m ).
CATCH {{p}}cx_balloon_flight.
rv_free_kg = -1.
ENDTRY.
ENDMETHOD.
METHOD valid_flight_is_released.
cl_abap_unit_assert=>assert_equals(
exp = 140
act = free_payload( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ) ).
ENDMETHOD.
METHOD crew_above_limit.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 9 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_crew INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Passengers 9 exceeds the limit 8'
act = lv_text ).
ENDMETHOD.
METHOD weight_above_limit.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 4 iv_weight_kg = 700
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_weight INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Weight 700 kg exceeds the limit 640 kg'
act = lv_text ).
ENDMETHOD.
METHOD wind_above_limit.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 25 iv_cloud_base_m = 2000 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_wind INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Wind 25 km/h exceeds the limit 18 km/h'
act = lv_text ).
ENDMETHOD.
METHOD cloud_base_below_limit.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 200 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_cloud INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Cloud base 200 m is below the limit 300 m'
act = lv_text ).
ENDMETHOD.
METHOD crew_is_checked_first.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 9 iv_weight_kg = 700
iv_wind_kmh = 25 iv_cloud_base_m = 100 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_crew INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Passengers 9 exceeds the limit 8'
act = lv_text ).
ENDMETHOD.
METHOD weight_is_checked_second.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 4 iv_weight_kg = 700
iv_wind_kmh = 25 iv_cloud_base_m = 100 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_weight INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Weight 700 kg exceeds the limit 640 kg'
act = lv_text ).
ENDMETHOD.
METHOD wind_is_checked_third.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 25 iv_cloud_base_m = 100 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_wind INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Wind 25 km/h exceeds the limit 18 km/h'
act = lv_text ).
ENDMETHOD.
METHOD limits_are_accepted.
cl_abap_unit_assert=>assert_equals(
exp = 0
act = free_payload( iv_passengers = 8 iv_weight_kg = 640
iv_wind_kmh = 18 iv_cloud_base_m = 300 ) ).
ENDMETHOD.
METHOD free_payload_is_returned.
cl_abap_unit_assert=>assert_equals(
exp = 40
act = free_payload( iv_passengers = 2 iv_weight_kg = 600
iv_wind_kmh = 12 iv_cloud_base_m = 1200 ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,80 @@
{
"task": "G1004",
"mutants": [
{
"object": "{{P}}BALLOON_CHECKER",
"mutant": "line 22: > -> <= (rel)",
"status": "killed",
"hidden": "0/10",
"failed_tests": [
"CLOUD_BASE_BELOW_LIMIT",
"CREW_ABOVE_LIMIT",
"CREW_IS_CHECKED_FIRST",
"FREE_PAYLOAD_IS_RETURNED",
"LIMITS_ARE_ACCEPTED",
"VALID_FLIGHT_IS_RELEASED",
"WEIGHT_ABOVE_LIMIT",
"WEIGHT_IS_CHECKED_SECOND",
"WIND_ABOVE_LIMIT",
"WIND_IS_CHECKED_THIRD"
]
},
{
"object": "{{P}}BALLOON_CHECKER",
"mutant": "line 29: > -> <= (rel)",
"status": "killed",
"hidden": "2/10",
"failed_tests": [
"CLOUD_BASE_BELOW_LIMIT",
"FREE_PAYLOAD_IS_RETURNED",
"LIMITS_ARE_ACCEPTED",
"VALID_FLIGHT_IS_RELEASED",
"WEIGHT_ABOVE_LIMIT",
"WEIGHT_IS_CHECKED_SECOND",
"WIND_ABOVE_LIMIT",
"WIND_IS_CHECKED_THIRD"
]
},
{
"object": "{{P}}BALLOON_CHECKER",
"mutant": "line 36: > -> <= (rel)",
"status": "killed",
"hidden": "4/10",
"failed_tests": [
"CLOUD_BASE_BELOW_LIMIT",
"FREE_PAYLOAD_IS_RETURNED",
"LIMITS_ARE_ACCEPTED",
"VALID_FLIGHT_IS_RELEASED",
"WIND_ABOVE_LIMIT",
"WIND_IS_CHECKED_THIRD"
]
},
{
"object": "{{P}}BALLOON_CHECKER",
"mutant": "line 43: < -> >= (rel)",
"status": "killed",
"hidden": "6/10",
"failed_tests": [
"CLOUD_BASE_BELOW_LIMIT",
"FREE_PAYLOAD_IS_RETURNED",
"LIMITS_ARE_ACCEPTED",
"VALID_FLIGHT_IS_RELEASED"
]
},
{
"object": "{{P}}BALLOON_CHECKER",
"mutant": "line 50: - -> + (arith)",
"status": "killed",
"hidden": "7/10",
"failed_tests": [
"FREE_PAYLOAD_IS_RETURNED",
"LIMITS_ARE_ACCEPTED",
"VALID_FLIGHT_IS_RELEASED"
]
}
],
"valid": 5,
"killed": 5,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,52 @@
CLASS {{p}}balloon_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS check_flight
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i
RAISING {{p}}cx_balloon_flight.
PRIVATE SECTION.
CONSTANTS:
c_max_passengers TYPE i VALUE 8,
c_max_weight_kg TYPE i VALUE 640,
c_max_wind_kmh TYPE i VALUE 18,
c_min_cloud_base_m TYPE i VALUE 300.
ENDCLASS.
CLASS {{p}}balloon_checker IMPLEMENTATION.
METHOD check_flight.
IF iv_passengers > c_max_passengers.
RAISE EXCEPTION TYPE {{p}}cx_balloon_crew
EXPORTING
iv_passengers = iv_passengers
iv_limit = c_max_passengers.
ENDIF.
IF iv_weight_kg > c_max_weight_kg.
RAISE EXCEPTION TYPE {{p}}cx_balloon_weight
EXPORTING
iv_weight_kg = iv_weight_kg
iv_limit_kg = c_max_weight_kg.
ENDIF.
IF iv_wind_kmh > c_max_wind_kmh.
RAISE EXCEPTION TYPE {{p}}cx_balloon_wind
EXPORTING
iv_wind_kmh = iv_wind_kmh
iv_limit_kmh = c_max_wind_kmh.
ENDIF.
IF iv_cloud_base_m < c_min_cloud_base_m.
RAISE EXCEPTION TYPE {{p}}cx_balloon_cloud
EXPORTING
iv_cloud_base_m = iv_cloud_base_m
iv_limit_m = c_min_cloud_base_m.
ENDIF.
rv_free_kg = c_max_weight_kg - iv_weight_kg.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,89 @@
CLASS ltc_balloon_checker DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}balloon_checker.
METHODS setup.
METHODS free_payload
IMPORTING iv_passengers TYPE i
iv_weight_kg TYPE i
iv_wind_kmh TYPE i
iv_cloud_base_m TYPE i
RETURNING VALUE(rv_free_kg) TYPE i.
METHODS released_flight FOR TESTING.
METHODS crew_limit FOR TESTING.
METHODS weight_limit FOR TESTING.
METHODS wind_limit FOR TESTING.
METHODS cloud_limit FOR TESTING.
METHODS check_sequence FOR TESTING.
ENDCLASS.
CLASS ltc_balloon_checker IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}balloon_checker( ).
ENDMETHOD.
METHOD free_payload.
TRY.
rv_free_kg = mo_cut->check_flight( iv_passengers = iv_passengers
iv_weight_kg = iv_weight_kg
iv_wind_kmh = iv_wind_kmh
iv_cloud_base_m = iv_cloud_base_m ).
CATCH {{p}}cx_balloon_flight.
rv_free_kg = -1.
ENDTRY.
ENDMETHOD.
METHOD released_flight.
cl_abap_unit_assert=>assert_equals(
exp = 140
act = free_payload( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ) ).
ENDMETHOD.
METHOD crew_limit.
cl_abap_unit_assert=>assert_equals(
exp = -1
act = free_payload( iv_passengers = 9 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ) ).
ENDMETHOD.
METHOD weight_limit.
cl_abap_unit_assert=>assert_equals(
exp = -1
act = free_payload( iv_passengers = 4 iv_weight_kg = 700
iv_wind_kmh = 10 iv_cloud_base_m = 2000 ) ).
ENDMETHOD.
METHOD wind_limit.
cl_abap_unit_assert=>assert_equals(
exp = -1
act = free_payload( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 25 iv_cloud_base_m = 2000 ) ).
ENDMETHOD.
METHOD cloud_limit.
cl_abap_unit_assert=>assert_equals(
exp = -1
act = free_payload( iv_passengers = 4 iv_weight_kg = 500
iv_wind_kmh = 10 iv_cloud_base_m = 200 ) ).
ENDMETHOD.
METHOD check_sequence.
DATA lv_text TYPE string.
TRY.
mo_cut->check_flight( iv_passengers = 9 iv_weight_kg = 700
iv_wind_kmh = 25 iv_cloud_base_m = 100 ).
cl_abap_unit_assert=>fail( msg = 'No exception was raised' ).
CATCH {{p}}cx_balloon_crew INTO DATA(lo_exc).
lv_text = lo_exc->mv_text.
CATCH {{p}}cx_balloon_flight.
cl_abap_unit_assert=>fail( msg = 'Wrong exception type' ).
ENDTRY.
cl_abap_unit_assert=>assert_equals( exp = 'Passengers 9 exceeds the limit 8'
act = lv_text ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,16 @@
CLASS {{p}}cx_balloon_cloud DEFINITION PUBLIC INHERITING FROM {{p}}cx_balloon_flight FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING iv_cloud_base_m TYPE i
iv_limit_m TYPE i.
PRIVATE SECTION.
CONSTANTS c_dummy TYPE i VALUE 0.
ENDCLASS.
CLASS {{p}}cx_balloon_cloud IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_text = |Cloud base { iv_cloud_base_m } m is below the limit { iv_limit_m } m| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,16 @@
CLASS {{p}}cx_balloon_crew DEFINITION PUBLIC INHERITING FROM {{p}}cx_balloon_flight FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING iv_passengers TYPE i
iv_limit TYPE i.
PRIVATE SECTION.
CONSTANTS c_dummy TYPE i VALUE 0.
ENDCLASS.
CLASS {{p}}cx_balloon_crew IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_text = |Passengers { iv_passengers } exceeds the limit { iv_limit }| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,16 @@
CLASS {{p}}cx_balloon_weight DEFINITION PUBLIC INHERITING FROM {{p}}cx_balloon_flight FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING iv_weight_kg TYPE i
iv_limit_kg TYPE i.
PRIVATE SECTION.
CONSTANTS c_dummy TYPE i VALUE 0.
ENDCLASS.
CLASS {{p}}cx_balloon_weight IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_text = |Weight { iv_weight_kg } kg exceeds the limit { iv_limit_kg } kg| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,16 @@
CLASS {{p}}cx_balloon_wind DEFINITION PUBLIC INHERITING FROM {{p}}cx_balloon_flight FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS constructor
IMPORTING iv_wind_kmh TYPE i
iv_limit_kmh TYPE i.
PRIVATE SECTION.
CONSTANTS c_dummy TYPE i VALUE 0.
ENDCLASS.
CLASS {{p}}cx_balloon_wind IMPLEMENTATION.
METHOD constructor.
super->constructor( iv_text = |Wind { iv_wind_kmh } km/h exceeds the limit { iv_limit_kmh } km/h| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,18 @@
CLASS {{p}}cx_balloon_flight DEFINITION PUBLIC INHERITING FROM cx_static_check CREATE PUBLIC.
PUBLIC SECTION.
DATA mv_text TYPE string READ-ONLY.
METHODS constructor
IMPORTING iv_text TYPE string.
PRIVATE SECTION.
CONSTANTS c_dummy TYPE i VALUE 0.
ENDCLASS.
CLASS {{p}}cx_balloon_flight IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
mv_text = iv_text.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,76 @@
# 1. Goal
Check a hot-air balloon flight before the launch. The flight office calls the
check for each planned flight. The check must stop a flight when a limit is
exceeded. The caller needs one exception for each reason.
# 2. Open questions
None.
# 3. Context
- The exception class {{P}}CX_BALLOON_FLIGHT exists in package $TMP. It is
active. It inherits from CX_STATIC_CHECK. It has the public read-only
attribute MV_TEXT (type string) and a constructor with the importing
parameter IV_TEXT (type string). The constructor writes IV_TEXT to MV_TEXT.
- The flight office knows four figures of a flight: the number of passengers,
the total take-off weight in kilograms, the wind speed in kilometres per
hour, and the height of the cloud base in metres.
# 4. Contract
- Create the class {{P}}BALLOON_CHECKER in package $TMP.
- The class is public and final. It can be created without constructor
parameters.
- The class has the public method CHECK_FLIGHT with these parameters:
- importing IV_PASSENGERS type i
- importing IV_WEIGHT_KG type i
- importing IV_WIND_KMH type i
- importing IV_CLOUD_BASE_M type i
- returning RV_FREE_KG type i
- raising {{P}}CX_BALLOON_FLIGHT
- Do not add other public methods.
- Create these exception classes in package $TMP. Each class inherits from
{{P}}CX_BALLOON_FLIGHT.
- {{P}}CX_BALLOON_CREW with the constructor importing IV_PASSENGERS type i
and IV_LIMIT type i.
- {{P}}CX_BALLOON_WEIGHT with the constructor importing IV_WEIGHT_KG type i
and IV_LIMIT_KG type i.
- {{P}}CX_BALLOON_WIND with the constructor importing IV_WIND_KMH type i and
IV_LIMIT_KMH type i.
- {{P}}CX_BALLOON_CLOUD with the constructor importing IV_CLOUD_BASE_M type i
and IV_LIMIT_M type i.
- Each constructor writes the message text to MV_TEXT. Rule 1 to rule 4 of the
business rules give the text of each exception.
# 5. Business rules
The method CHECK_FLIGHT applies the rules in this sequence. It raises the
exception of the first rule that fails. If no rule fails, the method returns the
free payload.
1. Passengers. The limit is 8. If IV_PASSENGERS is greater than 8, raise
{{P}}CX_BALLOON_CREW. The message text is
"Passengers <passengers> exceeds the limit <limit>". Example: 12 passengers
give the text "Passengers 12 exceeds the limit 8".
2. Weight. The limit is 640 kilograms. If IV_WEIGHT_KG is greater than 640,
raise {{P}}CX_BALLOON_WEIGHT. The message text is
"Weight <weight> kg exceeds the limit <limit> kg". Example: 700 kilograms
give the text "Weight 700 kg exceeds the limit 640 kg".
3. Wind. The limit is 18 kilometres per hour. If IV_WIND_KMH is greater than 18,
raise {{P}}CX_BALLOON_WIND. The message text is
"Wind <wind> km/h exceeds the limit <limit> km/h". Example: 25 kilometres per
hour give the text "Wind 25 km/h exceeds the limit 18 km/h".
4. Cloud base. The limit is 300 metres. If IV_CLOUD_BASE_M is less than 300,
raise {{P}}CX_BALLOON_CLOUD. The message text is
"Cloud base <cloud base> m is below the limit <limit> m". Example: 200 metres
give the text "Cloud base 200 m is below the limit 300 m".
5. Free payload. If no rule fails, the free payload is 640 minus IV_WEIGHT_KG.
Return the free payload in RV_FREE_KG.
# 6. Constraints
- Release target: ABAP Platform 2025 (SAP_BASIS 816).
- 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}}CX_BALLOON_FLIGHT.
# 7. Acceptance
- The classes are active and have no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,90 @@
{
"id": "G1004",
"category": "D",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 70,
"max_activations": 18
},
"seed": [
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_FLIGHT",
"file": "seed/cx_balloon_flight.clas.abap",
"description": "Base exception class of a rejected balloon flight"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}BALLOON_CHECKER"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_CREW"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_WEIGHT"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_WIND"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_CLOUD"
}
],
"out_of_scope": [
"{{P}}CX_BALLOON_FLIGHT"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}BALLOON_HIDDEN",
"file": "hidden/balloon_hidden.clas.abap",
"description": "Hidden tests for the balloon flight check"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_CREW",
"file": "reference/cx_balloon_crew.clas.abap",
"description": "Too many passengers"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_WEIGHT",
"file": "reference/cx_balloon_weight.clas.abap",
"description": "Weight above the limit"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_WIND",
"file": "reference/cx_balloon_wind.clas.abap",
"description": "Wind above the limit"
},
{
"type": "CLAS",
"name": "{{P}}CX_BALLOON_CLOUD",
"file": "reference/cx_balloon_cloud.clas.abap",
"description": "Cloud base below the limit"
},
{
"type": "CLAS",
"name": "{{P}}BALLOON_CHECKER",
"file": "reference/balloon_checker.clas.abap",
"description": "Balloon flight check with class-based exceptions",
"testclasses_file": "reference/balloon_checker.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"db_operation_in_loop"
]
}

View File

@@ -0,0 +1,59 @@
CLASS {{p}}credit_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_credit_check.
PRIVATE SECTION.
CONSTANTS c_income_factor TYPE i VALUE 3.
CONSTANTS c_plan_reduction TYPE {{p}}if_credit_check=>ty_amount VALUE '500.00'.
CONSTANTS c_min_age TYPE i VALUE 18.
CONSTANTS c_full_limit_age TYPE i VALUE 21.
CONSTANTS c_max_open_plans TYPE i VALUE 2.
CONSTANTS c_hundred TYPE i VALUE 100.
METHODS credit_limit_cents
IMPORTING iv_age TYPE i
iv_income TYPE {{p}}if_credit_check=>ty_amount
iv_open_plans TYPE i
RETURNING VALUE(rv_cents) TYPE i.
METHODS to_cents
IMPORTING iv_amount TYPE {{p}}if_credit_check=>ty_amount
RETURNING VALUE(rv_cents) TYPE i.
ENDCLASS.
CLASS {{p}}credit_checker IMPLEMENTATION.
METHOD {{p}}if_credit_check~check.
IF iv_age < c_min_age.
rv_reason = {{p}}if_credit_check=>c_reason-age.
RETURN.
ENDIF.
IF iv_income > 0.
rv_reason = {{p}}if_credit_check=>c_reason-income.
RETURN.
ENDIF.
IF iv_open_plans >= c_max_open_plans.
rv_reason = {{p}}if_credit_check=>c_reason-open_plan.
RETURN.
ENDIF.
IF to_cents( iv_order_amount ) > credit_limit_cents( iv_age = iv_age
iv_income = iv_income
iv_open_plans = iv_open_plans ).
rv_reason = {{p}}if_credit_check=>c_reason-limit.
RETURN.
ENDIF.
rv_reason = {{p}}if_credit_check=>c_reason-ok.
ENDMETHOD.
METHOD credit_limit_cents.
rv_cents = to_cents( iv_income ) * c_income_factor
- to_cents( c_plan_reduction ) * iv_open_plans.
IF iv_age < c_full_limit_age.
rv_cents = rv_cents DIV 2.
rv_cents = rv_cents DIV c_hundred * c_hundred.
ENDIF.
ENDMETHOD.
METHOD to_cents.
rv_cents = iv_amount * c_hundred.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,59 @@
CLASS {{p}}credit_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_credit_check.
PRIVATE SECTION.
CONSTANTS c_income_factor TYPE i VALUE 3.
CONSTANTS c_plan_reduction TYPE {{p}}if_credit_check=>ty_amount VALUE '500.00'.
CONSTANTS c_min_age TYPE i VALUE 18.
CONSTANTS c_full_limit_age TYPE i VALUE 21.
CONSTANTS c_max_open_plans TYPE i VALUE 2.
CONSTANTS c_hundred TYPE i VALUE 100.
METHODS credit_limit_cents
IMPORTING iv_age TYPE i
iv_income TYPE {{p}}if_credit_check=>ty_amount
iv_open_plans TYPE i
RETURNING VALUE(rv_cents) TYPE i.
METHODS to_cents
IMPORTING iv_amount TYPE {{p}}if_credit_check=>ty_amount
RETURNING VALUE(rv_cents) TYPE i.
ENDCLASS.
CLASS {{p}}credit_checker IMPLEMENTATION.
METHOD {{p}}if_credit_check~check.
IF iv_age < c_min_age.
rv_reason = {{p}}if_credit_check=>c_reason-age.
RETURN.
ENDIF.
IF iv_income <= 0.
rv_reason = {{p}}if_credit_check=>c_reason-income.
RETURN.
ENDIF.
IF iv_open_plans >= c_max_open_plans.
rv_reason = {{p}}if_credit_check=>c_reason-open_plan.
RETURN.
ENDIF.
IF to_cents( iv_order_amount ) > credit_limit_cents( iv_age = iv_age
iv_income = iv_income
iv_open_plans = iv_open_plans ).
rv_reason = {{p}}if_credit_check=>c_reason-limit.
RETURN.
ENDIF.
rv_reason = {{p}}if_credit_check=>c_reason-ok.
ENDMETHOD.
METHOD credit_limit_cents.
rv_cents = to_cents( iv_income ) / c_income_factor
- to_cents( c_plan_reduction ) * iv_open_plans.
IF iv_age < c_full_limit_age.
rv_cents = rv_cents DIV 2.
rv_cents = rv_cents DIV c_hundred * c_hundred.
ENDIF.
ENDMETHOD.
METHOD to_cents.
rv_cents = iv_amount * c_hundred.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,59 @@
CLASS {{p}}credit_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_credit_check.
PRIVATE SECTION.
CONSTANTS c_income_factor TYPE i VALUE 3.
CONSTANTS c_plan_reduction TYPE {{p}}if_credit_check=>ty_amount VALUE '500.00'.
CONSTANTS c_min_age TYPE i VALUE 18.
CONSTANTS c_full_limit_age TYPE i VALUE 21.
CONSTANTS c_max_open_plans TYPE i VALUE 2.
CONSTANTS c_hundred TYPE i VALUE 100.
METHODS credit_limit_cents
IMPORTING iv_age TYPE i
iv_income TYPE {{p}}if_credit_check=>ty_amount
iv_open_plans TYPE i
RETURNING VALUE(rv_cents) TYPE i.
METHODS to_cents
IMPORTING iv_amount TYPE {{p}}if_credit_check=>ty_amount
RETURNING VALUE(rv_cents) TYPE i.
ENDCLASS.
CLASS {{p}}credit_checker IMPLEMENTATION.
METHOD {{p}}if_credit_check~check.
IF iv_age < c_min_age.
rv_reason = {{p}}if_credit_check=>c_reason-age.
RETURN.
ENDIF.
IF iv_income <= 0.
rv_reason = {{p}}if_credit_check=>c_reason-income.
RETURN.
ENDIF.
IF iv_open_plans >= c_max_open_plans.
rv_reason = {{p}}if_credit_check=>c_reason-open_plan.
RETURN.
ENDIF.
IF to_cents( iv_order_amount ) > credit_limit_cents( iv_age = iv_age
iv_income = iv_income
iv_open_plans = iv_open_plans ).
rv_reason = {{p}}if_credit_check=>c_reason-limit.
RETURN.
ENDIF.
rv_reason = {{p}}if_credit_check=>c_reason-ok.
ENDMETHOD.
METHOD credit_limit_cents.
rv_cents = to_cents( iv_income ) * c_income_factor
- to_cents( c_plan_reduction ) / iv_open_plans.
IF iv_age < c_full_limit_age.
rv_cents = rv_cents DIV 2.
rv_cents = rv_cents DIV c_hundred * c_hundred.
ENDIF.
ENDMETHOD.
METHOD to_cents.
rv_cents = iv_amount * c_hundred.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,59 @@
CLASS {{p}}credit_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_credit_check.
PRIVATE SECTION.
CONSTANTS c_income_factor TYPE i VALUE 3.
CONSTANTS c_plan_reduction TYPE {{p}}if_credit_check=>ty_amount VALUE '500.00'.
CONSTANTS c_min_age TYPE i VALUE 18.
CONSTANTS c_full_limit_age TYPE i VALUE 21.
CONSTANTS c_max_open_plans TYPE i VALUE 2.
CONSTANTS c_hundred TYPE i VALUE 100.
METHODS credit_limit_cents
IMPORTING iv_age TYPE i
iv_income TYPE {{p}}if_credit_check=>ty_amount
iv_open_plans TYPE i
RETURNING VALUE(rv_cents) TYPE i.
METHODS to_cents
IMPORTING iv_amount TYPE {{p}}if_credit_check=>ty_amount
RETURNING VALUE(rv_cents) TYPE i.
ENDCLASS.
CLASS {{p}}credit_checker IMPLEMENTATION.
METHOD {{p}}if_credit_check~check.
IF iv_age < c_min_age.
rv_reason = {{p}}if_credit_check=>c_reason-age.
RETURN.
ENDIF.
IF iv_income <= 0.
rv_reason = {{p}}if_credit_check=>c_reason-income.
RETURN.
ENDIF.
IF iv_open_plans >= c_max_open_plans.
rv_reason = {{p}}if_credit_check=>c_reason-open_plan.
RETURN.
ENDIF.
IF to_cents( iv_order_amount ) > credit_limit_cents( iv_age = iv_age
iv_income = iv_income
iv_open_plans = iv_open_plans ).
rv_reason = {{p}}if_credit_check=>c_reason-limit.
RETURN.
ENDIF.
rv_reason = {{p}}if_credit_check=>c_reason-ok.
ENDMETHOD.
METHOD credit_limit_cents.
rv_cents = to_cents( iv_income ) * c_income_factor
- to_cents( c_plan_reduction ) * iv_open_plans.
IF iv_age >= c_full_limit_age.
rv_cents = rv_cents DIV 2.
rv_cents = rv_cents DIV c_hundred * c_hundred.
ENDIF.
ENDMETHOD.
METHOD to_cents.
rv_cents = iv_amount * c_hundred.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
{
"id": "G1005",
"pool": "train",
"object_type": "CLAS",
"category": "F",
"attempts": [
{
"stage": "validate",
"oracle": 96.4,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 4,
"killed": 4,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,129 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_credit_check.
METHODS setup.
METHODS too_young FOR TESTING.
METHODS age_eighteen_is_ok FOR TESTING.
METHODS zero_income FOR TESTING.
METHODS two_open_plans FOR TESTING.
METHODS order_above_limit FOR TESTING.
METHODS order_at_limit_is_ok FOR TESTING.
METHODS young_limit_halved FOR TESTING.
METHODS adult_keeps_limit FOR TESTING.
METHODS young_limit_is_floor FOR TESTING.
METHODS plan_reduces_limit FOR TESTING.
METHODS valid_adult FOR TESTING.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}credit_checker( ).
ENDMETHOD.
METHOD too_young.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-age
act = mo_cut->check( iv_age = 17
iv_income = '3000.00'
iv_open_plans = 0
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD age_eighteen_is_ok.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 18
iv_income = '3000.00'
iv_open_plans = 0
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD zero_income.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-income
act = mo_cut->check( iv_age = 30
iv_income = '0.00'
iv_open_plans = 0
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD two_open_plans.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-open_plan
act = mo_cut->check( iv_age = 30
iv_income = '3000.00'
iv_open_plans = 2
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD order_above_limit.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 30
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '3000.01' ) ).
ENDMETHOD.
METHOD order_at_limit_is_ok.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 30
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '3000.00' ) ).
ENDMETHOD.
METHOD young_limit_halved.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 20
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '1500.01' ) ).
ENDMETHOD.
METHOD adult_keeps_limit.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 21
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '1500.01' ) ).
ENDMETHOD.
METHOD young_limit_is_floor.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 20
iv_income = '2500.50'
iv_open_plans = 0
iv_order_amount = '3750.50' ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 20
iv_income = '2500.50'
iv_open_plans = 0
iv_order_amount = '3750.00' ) ).
ENDMETHOD.
METHOD plan_reduces_limit.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 30
iv_income = '1000.00'
iv_open_plans = 1
iv_order_amount = '2500.01' ) ).
ENDMETHOD.
METHOD valid_adult.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 35
iv_income = '2000.00'
iv_open_plans = 1
iv_order_amount = '5500.00' ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,73 @@
{
"task": "G1005",
"mutants": [
{
"object": "{{P}}CREDIT_CHECKER",
"mutant": "line 30: <= -> > (rel)",
"status": "killed",
"hidden": "1/11",
"failed_tests": [
"ADULT_KEEPS_LIMIT",
"AGE_EIGHTEEN_IS_OK",
"ORDER_ABOVE_LIMIT",
"ORDER_AT_LIMIT_IS_OK",
"PLAN_REDUCES_LIMIT",
"TWO_OPEN_PLANS",
"VALID_ADULT",
"YOUNG_LIMIT_HALVED",
"YOUNG_LIMIT_IS_FLOOR",
"ZERO_INCOME"
]
},
{
"object": "{{P}}CREDIT_CHECKER",
"mutant": "line 38: = -> <> (eq)",
"status": "invalid",
"hidden": "0/0",
"failed_tests": []
},
{
"object": "{{P}}CREDIT_CHECKER",
"mutant": "line 48: * -> / (arith)",
"status": "killed",
"hidden": "7/11",
"failed_tests": [
"ADULT_KEEPS_LIMIT",
"ORDER_AT_LIMIT_IS_OK",
"VALID_ADULT",
"YOUNG_LIMIT_IS_FLOOR"
]
},
{
"object": "{{P}}CREDIT_CHECKER",
"mutant": "line 49: * -> / (arith)",
"status": "killed",
"hidden": "5/11",
"failed_tests": [
"ADULT_KEEPS_LIMIT",
"AGE_EIGHTEEN_IS_OK",
"ORDER_ABOVE_LIMIT",
"ORDER_AT_LIMIT_IS_OK",
"YOUNG_LIMIT_HALVED",
"YOUNG_LIMIT_IS_FLOOR"
]
},
{
"object": "{{P}}CREDIT_CHECKER",
"mutant": "line 50: < -> >= (rel)",
"status": "killed",
"hidden": "6/11",
"failed_tests": [
"ADULT_KEEPS_LIMIT",
"ORDER_AT_LIMIT_IS_OK",
"VALID_ADULT",
"YOUNG_LIMIT_HALVED",
"YOUNG_LIMIT_IS_FLOOR"
]
}
],
"valid": 4,
"killed": 4,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,59 @@
CLASS {{p}}credit_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_credit_check.
PRIVATE SECTION.
CONSTANTS c_income_factor TYPE i VALUE 3.
CONSTANTS c_plan_reduction TYPE {{p}}if_credit_check=>ty_amount VALUE '500.00'.
CONSTANTS c_min_age TYPE i VALUE 18.
CONSTANTS c_full_limit_age TYPE i VALUE 21.
CONSTANTS c_max_open_plans TYPE i VALUE 2.
CONSTANTS c_hundred TYPE i VALUE 100.
METHODS credit_limit_cents
IMPORTING iv_age TYPE i
iv_income TYPE {{p}}if_credit_check=>ty_amount
iv_open_plans TYPE i
RETURNING VALUE(rv_cents) TYPE i.
METHODS to_cents
IMPORTING iv_amount TYPE {{p}}if_credit_check=>ty_amount
RETURNING VALUE(rv_cents) TYPE i.
ENDCLASS.
CLASS {{p}}credit_checker IMPLEMENTATION.
METHOD {{p}}if_credit_check~check.
IF iv_age < c_min_age.
rv_reason = {{p}}if_credit_check=>c_reason-age.
RETURN.
ENDIF.
IF iv_income <= 0.
rv_reason = {{p}}if_credit_check=>c_reason-income.
RETURN.
ENDIF.
IF iv_open_plans >= c_max_open_plans.
rv_reason = {{p}}if_credit_check=>c_reason-open_plan.
RETURN.
ENDIF.
IF to_cents( iv_order_amount ) > credit_limit_cents( iv_age = iv_age
iv_income = iv_income
iv_open_plans = iv_open_plans ).
rv_reason = {{p}}if_credit_check=>c_reason-limit.
RETURN.
ENDIF.
rv_reason = {{p}}if_credit_check=>c_reason-ok.
ENDMETHOD.
METHOD credit_limit_cents.
rv_cents = to_cents( iv_income ) * c_income_factor
- to_cents( c_plan_reduction ) * iv_open_plans.
IF iv_age < c_full_limit_age.
rv_cents = rv_cents DIV 2.
rv_cents = rv_cents DIV c_hundred * c_hundred.
ENDIF.
ENDMETHOD.
METHOD to_cents.
rv_cents = iv_amount * c_hundred.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,98 @@
CLASS ltc_credit_checker DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_credit_check.
METHODS setup.
METHODS too_young FOR TESTING.
METHODS zero_income FOR TESTING.
METHODS two_open_plans FOR TESTING.
METHODS order_above_limit FOR TESTING.
METHODS order_at_limit FOR TESTING.
METHODS young_limit_halved FOR TESTING.
METHODS young_limit_is_floor FOR TESTING.
METHODS valid_adult FOR TESTING.
ENDCLASS.
CLASS ltc_credit_checker IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}credit_checker( ).
ENDMETHOD.
METHOD too_young.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-age
act = mo_cut->check( iv_age = 17
iv_income = '3000.00'
iv_open_plans = 0
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD zero_income.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-income
act = mo_cut->check( iv_age = 30
iv_income = '0.00'
iv_open_plans = 0
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD two_open_plans.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-open_plan
act = mo_cut->check( iv_age = 30
iv_income = '3000.00'
iv_open_plans = 2
iv_order_amount = '100.00' ) ).
ENDMETHOD.
METHOD order_above_limit.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 30
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '3000.01' ) ).
ENDMETHOD.
METHOD order_at_limit.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 30
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '3000.00' ) ).
ENDMETHOD.
METHOD young_limit_halved.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 20
iv_income = '1000.00'
iv_open_plans = 0
iv_order_amount = '1500.01' ) ).
ENDMETHOD.
METHOD young_limit_is_floor.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-limit
act = mo_cut->check( iv_age = 20
iv_income = '2500.50'
iv_open_plans = 0
iv_order_amount = '3750.50' ) ).
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 20
iv_income = '2500.50'
iv_open_plans = 0
iv_order_amount = '3750.00' ) ).
ENDMETHOD.
METHOD valid_adult.
cl_abap_unit_assert=>assert_equals(
exp = {{p}}if_credit_check=>c_reason-ok
act = mo_cut->check( iv_age = 35
iv_income = '2000.00'
iv_open_plans = 1
iv_order_amount = '5500.00' ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,18 @@
INTERFACE {{p}}if_credit_check PUBLIC.
TYPES ty_reason TYPE c LENGTH 10.
TYPES ty_amount TYPE p LENGTH 11 DECIMALS 2.
CONSTANTS:
BEGIN OF c_reason,
ok TYPE ty_reason VALUE 'OK',
age TYPE ty_reason VALUE 'AGE',
income TYPE ty_reason VALUE 'INCOME',
open_plan TYPE ty_reason VALUE 'OPEN_PLAN',
limit TYPE ty_reason VALUE 'LIMIT',
END OF c_reason.
METHODS check
IMPORTING iv_age TYPE i
iv_income TYPE ty_amount
iv_open_plans TYPE i
iv_order_amount TYPE ty_amount
RETURNING VALUE(rv_reason) TYPE ty_reason.
ENDINTERFACE.

View File

@@ -0,0 +1,56 @@
# 1. Goal
A furniture shop sells furniture on instalment plans. A checkout program calls one
check before the shop grants an instalment plan to a customer. The program needs
one reason code for each customer.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_CREDIT_CHECK exists in package $TMP. It is active.
- The interface declares the type of the reason, the type of a money amount, the
constants of the reason codes, and one method.
- The method name, the parameter names and the parameter types are not in this
document. Read the interface in the system.
- The reason constants have the names OK, AGE, INCOME, OPEN_PLAN and LIMIT.
# 4. Contract
- Create the class {{P}}CREDIT_CHECKER in package $TMP.
- The class is public and final. It has a public constructor without parameters.
- The class implements the interface {{P}}IF_CREDIT_CHECK.
- Do not add other public methods.
# 5. Business rules
The check receives the age of the customer in years, the net monthly income of the
customer, the number of open instalment plans of the customer, and the amount of
the new order. Apply the rules in this sequence. Return the reason of the first
rule that fails.
1. If the age is less than 18, return AGE.
2. If the net monthly income is zero or less, return INCOME.
3. If the number of open instalment plans is 2 or more, return OPEN_PLAN.
4. Calculate the credit limit:
- Start with three times the net monthly income.
- Subtract 500 for each open instalment plan.
- If the age is less than 21, halve the limit and round the result down to
whole euros. Example: 3750.75 becomes 3750.00.
5. If the amount of the new order is more than the credit limit, return LIMIT.
6. Else return OK.
# 6. Constraints
- Release target: SAP_BASIS 8.16 (ABAP Platform 2025).
- Coding standards: Clean ABAP. Method length below 40 statements. No global
variables. No comment that restates the code.
- Out of scope: do not change the interface {{P}}IF_CREDIT_CHECK.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,51 @@
{
"id": "G1005",
"category": "F",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 70,
"max_activations": 18
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_CREDIT_CHECK",
"file": "seed/if_credit_check.intf.abap",
"description": "Instalment plan check contract"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}CREDIT_CHECKER",
"implements": "{{P}}IF_CREDIT_CHECK"
}
],
"out_of_scope": [
"{{P}}IF_CREDIT_CHECK"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}CREDIT_CHECKER",
"file": "reference/credit_checker.clas.abap",
"description": "Instalment plan credit checker",
"testclasses_file": "reference/credit_checker.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"no_global_variables"
]
}

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_true.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer <= 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 100 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel = 'O'.
lv_premium = lv_premium * 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_false.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer > 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 100 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel = 'O'.
lv_premium = lv_premium * 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_false.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer <= 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 101 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel = 'O'.
lv_premium = lv_premium * 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_false.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer <= 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 100 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel <> 'O'.
lv_premium = lv_premium * 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_false.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer <= 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 100 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel = 'O'.
lv_premium = lv_premium / 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,60 @@
{
"id": "G1006",
"pool": "train",
"object_type": "FUNC",
"category": "G",
"attempts": [
{
"stage": "bundle",
"errors": [
"hidden/t14_hidden.clas.abap line 47: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 53: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 59: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 65: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 71: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 77: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 83: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 89: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"hidden/t14_hidden.clas.abap line 96: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"",
"Too close to task G0150 (similarity spec 0.60, rules 0.63, names 0.25). Choose a different business topic and different object names.",
"Too close to task G0151 (similarity spec 0.70, rules 0.63, names 0.33). Choose a different business topic and different object names.",
"Too close to task G0173 (similarity spec 0.65, rules 0.62, names 0.33). Choose a different business topic and different object names."
],
"by_check": {
"abaplint": 9,
"extra": 3
}
},
{
"stage": "bundle",
"errors": [
"invalid JSON: Invalid \\escape: line 1 column 1374 (char 1373)"
],
"by_check": {
"json": 1
}
},
{
"stage": "bundle",
"errors": [
"hidden/t14_hidden.clas.abap line 82: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"15\"",
"hidden/t14_hidden.clas.abap line 83: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"call\"",
"hidden/t14_hidden.clas.abap line 83: syntax error for release v702 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"56\""
],
"by_check": {
"abaplint": 3
}
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0,
"mutation": {
"valid": 5,
"killed": 5,
"ok": true
}
}
],
"accepted": true
}

View File

@@ -0,0 +1,133 @@
CLASS {{p}}t14_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS call
IMPORTING iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
iv_fee TYPE decfloat34 DEFAULT 7
EXPORTING ev_skip TYPE abap_bool
ev_fee TYPE decfloat34
ev_subrc TYPE sysubrc.
METHODS band_first FOR TESTING.
METHODS band_second FOR TESTING.
METHODS band_third FOR TESTING.
METHODS online_reduction FOR TESTING.
METHODS premium_rounding FOR TESTING.
METHODS minimum_premium FOR TESTING.
METHODS maximum_premium FOR TESTING.
METHODS charity_skips FOR TESTING.
METHODS negative_hammer FOR TESTING.
METHODS skip_false_for_sale FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_hidden IMPLEMENTATION.
METHOD call.
ev_fee = iv_fee.
CALL FUNCTION '{{P}}AUC_PREMIUM'
EXPORTING
iv_lot_type = iv_lot_type
iv_hammer = iv_hammer
iv_channel = iv_channel
IMPORTING
ev_skip = ev_skip
CHANGING
cv_fee = ev_fee
EXCEPTIONS
invalid_hammer = 1
OTHERS = 2.
ev_subrc = sy-subrc.
ENDMETHOD.
METHOD band_first.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 100.
call( EXPORTING iv_lot_type = `S` iv_hammer = 500 iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD band_second.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 320.
call( EXPORTING iv_lot_type = `S` iv_hammer = 2000 iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD band_third.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 1880.
call( EXPORTING iv_lot_type = `S` iv_hammer = 20000 iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD online_reduction.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 90.
call( EXPORTING iv_lot_type = `S` iv_hammer = 500 iv_channel = `O`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD premium_rounding.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
DATA lv_hammer TYPE decfloat34.
lv_exp = '228.15'.
lv_hammer = '1234.56'.
call( EXPORTING iv_lot_type = `S` iv_hammer = lv_hammer iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD minimum_premium.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 5.
call( EXPORTING iv_lot_type = `S` iv_hammer = 10 iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD maximum_premium.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 5000.
call( EXPORTING iv_lot_type = `S` iv_hammer = 100000 iv_channel = `B`
IMPORTING ev_fee = lv_act ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD charity_skips.
DATA lv_skip TYPE abap_bool.
DATA lv_fee TYPE decfloat34.
DATA lv_exp TYPE decfloat34.
lv_fee = 7.
lv_exp = 7.
call( EXPORTING iv_lot_type = `C` iv_hammer = 5000 iv_channel = `B` iv_fee = 7
IMPORTING ev_skip = lv_skip ev_fee = lv_fee ).
cl_abap_unit_assert=>assert_equals( exp = abap_true act = lv_skip ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_fee ).
ENDMETHOD.
METHOD negative_hammer.
DATA lv_rc TYPE sysubrc.
call( EXPORTING iv_lot_type = `S` iv_hammer = -1 iv_channel = `B`
IMPORTING ev_subrc = lv_rc ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_rc ).
ENDMETHOD.
METHOD skip_false_for_sale.
DATA lv_skip TYPE abap_bool.
call( EXPORTING iv_lot_type = `S` iv_hammer = 500 iv_channel = `B`
IMPORTING ev_skip = lv_skip ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = lv_skip ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,60 @@
{
"task": "G1006",
"mutants": [
{
"object": "{{P}}AUC_PREMIUM",
"mutant": "line 15: abap_false -> abap_true (bool)",
"status": "killed",
"hidden": "9/10",
"failed_tests": [
"SKIP_FALSE_FOR_SALE"
]
},
{
"object": "{{P}}AUC_PREMIUM",
"mutant": "line 25: <= -> > (rel)",
"status": "killed",
"hidden": "7/10",
"failed_tests": [
"BAND_SECOND",
"BAND_THIRD",
"PREMIUM_ROUNDING"
]
},
{
"object": "{{P}}AUC_PREMIUM",
"mutant": "line 28: 100 -> 101 (const)",
"status": "killed",
"hidden": "9/10",
"failed_tests": [
"BAND_THIRD"
]
},
{
"object": "{{P}}AUC_PREMIUM",
"mutant": "line 30: = -> <> (eq)",
"status": "killed",
"hidden": "5/10",
"failed_tests": [
"BAND_FIRST",
"BAND_SECOND",
"BAND_THIRD",
"ONLINE_REDUCTION",
"PREMIUM_ROUNDING"
]
},
{
"object": "{{P}}AUC_PREMIUM",
"mutant": "line 31: * -> / (arith)",
"status": "killed",
"hidden": "9/10",
"failed_tests": [
"ONLINE_REDUCTION"
]
}
],
"valid": 5,
"killed": 5,
"kill_rate": 1.0,
"ok": true
}

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}auc_premium
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
DATA lv_premium TYPE decfloat34.
ev_skip = abap_false.
IF iv_hammer < 0.
RAISE invalid_hammer.
ENDIF.
IF iv_lot_type = 'C'.
ev_skip = abap_true.
RETURN.
ENDIF.
IF iv_hammer <= 1000.
lv_premium = iv_hammer * 20 / 100.
ELSEIF iv_hammer <= 10000.
lv_premium = 1000 * 20 / 100 + ( iv_hammer - 1000 ) * 12 / 100.
ELSE.
lv_premium = 1000 * 20 / 100 + 9000 * 12 / 100 + ( iv_hammer - 10000 ) * 6 / 100.
ENDIF.
IF iv_channel = 'O'.
lv_premium = lv_premium * 90 / 100.
ENDIF.
lv_premium = round( val = lv_premium dec = 2 mode = cl_abap_math=>round_half_up ).
IF lv_premium < 5.
lv_premium = 5.
ELSEIF lv_premium > 5000.
lv_premium = 5000.
ENDIF.
cv_fee = lv_premium.
ENDFUNCTION.

View File

@@ -0,0 +1,99 @@
CLASS {{p}}t14_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS premium_for
IMPORTING iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
RETURNING VALUE(rv_fee) TYPE decfloat34.
METHODS band_premium FOR TESTING.
METHODS online_reduction FOR TESTING.
METHODS premium_limits FOR TESTING.
METHODS charity_lot FOR TESTING.
METHODS negative_hammer FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_test IMPLEMENTATION.
METHOD premium_for.
DATA lv_skip TYPE abap_bool.
CALL FUNCTION '{{P}}AUC_PREMIUM'
EXPORTING
iv_lot_type = iv_lot_type
iv_hammer = iv_hammer
iv_channel = iv_channel
IMPORTING
ev_skip = lv_skip
CHANGING
cv_fee = rv_fee
EXCEPTIONS
invalid_hammer = 1
OTHERS = 2.
cl_abap_unit_assert=>assert_subrc( exp = 0 ).
ENDMETHOD.
METHOD band_premium.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 320.
lv_act = premium_for( iv_lot_type = `S` iv_hammer = 2000 iv_channel = `B` ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD online_reduction.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 90.
lv_act = premium_for( iv_lot_type = `S` iv_hammer = 500 iv_channel = `O` ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD premium_limits.
DATA lv_exp TYPE decfloat34.
DATA lv_act TYPE decfloat34.
lv_exp = 5.
lv_act = premium_for( iv_lot_type = `S` iv_hammer = 10 iv_channel = `B` ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
lv_exp = 5000.
lv_act = premium_for( iv_lot_type = `S` iv_hammer = 100000 iv_channel = `B` ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_act ).
ENDMETHOD.
METHOD charity_lot.
DATA lv_skip TYPE abap_bool.
DATA lv_fee TYPE decfloat34.
DATA lv_exp TYPE decfloat34.
lv_fee = 7.
lv_exp = 7.
CALL FUNCTION '{{P}}AUC_PREMIUM'
EXPORTING
iv_lot_type = `C`
iv_hammer = 5000
iv_channel = `B`
IMPORTING
ev_skip = lv_skip
CHANGING
cv_fee = lv_fee
EXCEPTIONS
invalid_hammer = 1
OTHERS = 2.
cl_abap_unit_assert=>assert_subrc( exp = 0 ).
cl_abap_unit_assert=>assert_true( lv_skip ).
cl_abap_unit_assert=>assert_equals( exp = lv_exp act = lv_fee ).
ENDMETHOD.
METHOD negative_hammer.
DATA lv_fee TYPE decfloat34.
CALL FUNCTION '{{P}}AUC_PREMIUM'
EXPORTING
iv_lot_type = `S`
iv_hammer = -5
iv_channel = `B`
CHANGING
cv_fee = lv_fee
EXCEPTIONS
invalid_hammer = 1
OTHERS = 2.
cl_abap_unit_assert=>assert_equals( exp = 1 act = sy-subrc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,15 @@
FUNCTION {{p}}auc_sample
IMPORTING
iv_lot_type TYPE string
iv_hammer TYPE decfloat34
iv_channel TYPE string
EXPORTING
ev_skip TYPE abap_bool
CHANGING
cv_fee TYPE decfloat34
EXCEPTIONS
invalid_hammer.
ev_skip = abap_false.
ENDFUNCTION.

View File

@@ -0,0 +1,47 @@
# 1. Goal
An auction house charges a buyer's premium on each lot that it sells. The
auction program calls one premium module for each sold lot. The premium module
must use the same interface as the sample module {{P}}AUC_SAMPLE, because the
auction program calls all premium modules in the same way.
# 2. Open questions
None.
# 3. Context
- Function group {{P}}FG_AUCT exists in package $TMP.
- Function module {{P}}AUC_SAMPLE exists in this group. It is the sample
premium module. Its interface is the interface that all premium modules must
use.
# 4. Contract
- Create the function module {{P}}AUC_PREMIUM in function group {{P}}FG_AUCT.
- Use exactly the interface of {{P}}AUC_SAMPLE: the same parameter names, kinds,
types, and exceptions.
- The exception is INVALID_HAMMER.
# 5. Business rules
1. If the hammer price is less than 0, raise the exception INVALID_HAMMER and
stop.
2. Set the skip flag to false, except in rule 3.
3. Lot type C (charity): set the skip flag to true. Leave the fee unchanged and
stop.
4. For every other lot type, calculate the band premium from the hammer price:
- the part of the hammer price up to 1,000.00: 20 %
- the part of the hammer price above 1,000.00 up to 10,000.00: 12 %
- the part of the hammer price above 10,000.00: 6 %
5. Channel O (online): the premium is 90 % of the band premium. For every other
channel the premium is the band premium.
6. Round the premium of rule 5 half up to 2 decimals.
7. The minimum premium is 5.00. The maximum premium is 5,000.00.
8. The fee is the premium of rules 4 to 7.
# 6. Constraints
- Release target: 7.02. The code must run on a 7.02 system. Do not use syntax
that was introduced after release 7.02.
- Coding standards: Clean ABAP.
- Out of scope: do not change {{P}}AUC_SAMPLE.
# 7. Acceptance
- The function module is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests for the function module in a global test class.

View File

@@ -0,0 +1,84 @@
{
"id": "G1006",
"category": "G",
"object_type": "FUNC",
"difficulty": 2,
"release_target": "v702",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 15
},
"seed": [
{
"type": "FUGR",
"name": "{{P}}FG_AUCT",
"description": "Buyer's premium modules"
},
{
"type": "FUNC",
"name": "{{P}}AUC_SAMPLE",
"functionGroup": "{{P}}FG_AUCT",
"file": "seed/auc_sample.func.abap",
"description": "Sample premium module"
}
],
"contract": [
{
"type": "FUNC",
"name": "{{P}}AUC_PREMIUM",
"functionGroup": "{{P}}FG_AUCT",
"params": [
{
"name": "iv_lot_type",
"type": "string"
},
{
"name": "iv_hammer",
"type": "decfloat34"
},
{
"name": "iv_channel",
"type": "string"
},
{
"name": "ev_skip",
"type": "abap_bool"
},
{
"name": "cv_fee",
"type": "decfloat34"
}
]
}
],
"out_of_scope": [
"{{P}}AUC_SAMPLE"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T14_HIDDEN",
"file": "hidden/t14_hidden.clas.abap",
"description": "T14 hidden tests"
}
],
"reference": [
{
"type": "FUNC",
"name": "{{P}}AUC_PREMIUM",
"functionGroup": "{{P}}FG_AUCT",
"file": "reference/auc_premium.func.abap",
"description": "Buyer's premium module"
},
{
"type": "CLAS",
"name": "{{P}}T14_TEST",
"file": "reference/t14_test.clas.abap",
"description": "T14 own tests"
}
],
"craft_checks": [
"no_new_syntax"
]
}

View File

@@ -0,0 +1,14 @@
{
"id": "G1007",
"pool": "train",
"object_type": "CLAS",
"category": "H",
"attempts": [
{
"stage": "validate",
"oracle": 100,
"null": 30
}
],
"accepted": true
}

View File

@@ -0,0 +1,56 @@
CLASS {{p}}jump_pricer DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_amount TYPE p LENGTH 8 DECIMALS 2.
METHODS constructor.
METHODS calculate
IMPORTING iv_persons TYPE i
iv_minutes TYPE i
iv_member TYPE abap_bool
RETURNING VALUE(rv_price) TYPE ty_amount.
PRIVATE SECTION.
CONSTANTS:
c_short_price TYPE ty_amount VALUE '8.00',
c_long_price TYPE ty_amount VALUE '12.00',
c_member_factor TYPE ty_amount VALUE '0.80',
c_group_factor TYPE ty_amount VALUE '0.90',
c_group_size TYPE i VALUE 10,
c_long_minutes TYPE i VALUE 60.
METHODS price_per_person
IMPORTING iv_minutes TYPE i
RETURNING VALUE(rv_price) TYPE ty_amount.
ENDCLASS.
CLASS {{p}}jump_pricer IMPLEMENTATION.
METHOD constructor.
ENDMETHOD.
METHOD calculate.
IF iv_persons < 1 OR iv_minutes < 1.
RETURN.
ENDIF.
DATA(lv_price) = CONV ty_amount( price_per_person( iv_minutes ) * iv_persons ).
IF iv_member = abap_true.
lv_price = lv_price * c_member_factor.
ENDIF.
IF iv_persons >= c_group_size.
lv_price = lv_price * c_group_factor.
ENDIF.
rv_price = round( val = lv_price dec = 2 ).
ENDMETHOD.
METHOD price_per_person.
IF iv_minutes < c_long_minutes.
rv_price = c_short_price.
ELSE.
rv_price = c_long_price.
ENDIF.
ENDMETHOD.
ENDCLASS.

Some files were not shown because too many files have changed in this diff Show More