1:- module(ap_validation, 2 [ validate_scenario/2, 3 clamp/4, 4 safe_number/2, 5 safe_atom/2, 6 normalized_scenario/2 7 ]). 8 9:- use_module(library(apply)). 10:- use_module(ap_defaults). 11 12clamp(Min, Max, X, Y) :- 13 ( X < Min -> Y = Min 14 ; X > Max -> Y = Max 15 ; Y = X 16 ). 17 18safe_number(X, N) :- number(X), !, N = X. 19safe_number(X, N) :- atom(X), atom_number(X, N), !. 20safe_number(X, N) :- string(X), number_string(N, X), !. 21 22safe_atom(X, A) :- atom(X), !, A = X. 23safe_atom(X, A) :- string(X), !, atom_string(A, X). 24 25normalized_scenario(In, Out) :- 26 ap_defaults:default_scenario(D), 27 ( is_dict(In) -> put_dict(In, D, Merged) ; Merged = D ), 28 normalize_numeric_fields(Merged, Out0), 29 normalize_misc(Out0, Out). 30 31normalize_numeric_fields(S0, S) :- 32 ap_defaults:scenario_numeric_keys(Keys), 33 foldl(normalize_numeric, Keys, S0, S). 34 35normalize_numeric(Key, S0, S) :- 36 get_dict(Key, S0, Raw), 37 ( safe_number(Raw, N0) -> true ; N0 = 50 ), 38 clamp(0, 100, N0, N), 39 put_dict(Key, S0, N, S). 40 41normalize_misc(S0, S) :- 42 normalize_duration(S0, S1), 43 normalize_population(S1, S2), 44 normalize_seed(S2, S3), 45 normalize_category(scope, S3, S4), 46 normalize_category(issue, S4, S5), 47 normalize_category(target, S5, S6), 48 normalize_category(notified, S6, S). 49 50normalize_duration(S0, S) :- 51 get_dict(duration_days, S0, Raw), 52 ( safe_number(Raw, N0) -> N1 is round(N0) ; N1 = 1 ), 53 clamp(1, 365, N1, N), 54 put_dict(duration_days, S0, N, S). 55 56normalize_population(S0, S) :- 57 get_dict(population_millions, S0, Raw), 58 ( safe_number(Raw, N0) -> true ; N0 = 2.0 ), 59 clamp(0.01, 300.0, N0, N), 60 put_dict(population_millions, S0, N, S). 61 62normalize_seed(S0, S) :- 63 get_dict(seed, S0, Raw), 64 ( safe_number(Raw, N0) -> N is round(N0) ; N = 1998 ), 65 put_dict(seed, S0, N, S). 66 67normalize_category(Key, S0, S) :- 68 get_dict(Key, S0, Raw), 69 ( safe_atom(Raw, A) -> true ; A = unknown ), 70 put_dict(Key, S0, A, S). 71 72validate_scenario(In, Errors) :- 73 ( is_dict(In) -> E0 = [] ; E0 = ['Skenario harus berupa dict SWI-Prolog'] ), 74 required_keys(Keys), 75 findall(Msg, 76 ( member(K, Keys), 77 ( get_dict(K, In, _) -> fail 78 ; format(string(Msg), 'Field wajib hilang: ~w', [K]) 79 ) 80 ), Missing), 81 append(E0, Missing, Errors). 82 83required_keys([name, scope, province, city, issue, subissue, target])