1:- module(ap_model,
2 [ score/2,
3 likelihood_score/3,
4 spread_score/3,
5 escalation_score/3,
6 pressure_score/3,
7 crowd_bucket/2,
8 risk_band/2,
9 predicted_state/3,
10 top_factors/3
11 ]).
20:- use_module(library(lists)). 21:- use_module(ap_validation). 22:- use_module(ap_legal). 23
24positive_weight(grievance, 0.14, 'akumulasi keluhan').
25positive_weight(trigger, 0.13, 'pemicu langsung').
26positive_weight(salience, 0.10, 'kepentingan isu').
27positive_weight(online, 0.08, 'momentum daring').
28positive_weight(coalition, 0.08, 'koalisi lintas kelompok').
29positive_weight(organization, 0.07, 'kapasitas mobilisasi').
30positive_weight(economic_stress, 0.07, 'tekanan ekonomi').
31positive_weight(institutional_distrust, 0.08, 'ketidakpercayaan institusional').
32positive_weight(incident_shock, 0.08, 'insiden pemicu tambahan').
33positive_weight(recent_precedent, 0.04, 'preseden aksi terbaru').
34positive_weight(media_attention, 0.05, 'perhatian media').
35positive_weight(public_support, 0.05, 'dukungan publik').
36positive_weight(regional_relevance, 0.03, 'relevansi lintas wilayah').
37
38protective_weight(govt_communication, 0.06, 'komunikasi pemerintah').
39protective_weight(govt_responsiveness, 0.08, 'responsivitas/dialog').
40
41spread_weight(online, 0.20, 'momentum daring').
42spread_weight(coalition, 0.15, 'koalisi').
43spread_weight(salience, 0.14, 'kepentingan isu').
44spread_weight(regional_relevance, 0.18, 'relevansi lintas wilayah').
45spread_weight(public_support, 0.12, 'dukungan publik').
46spread_weight(media_attention, 0.11, 'perhatian media').
47spread_weight(recent_precedent, 0.10, 'preseden aksi').
48
49score(Input, Report) :-
50 ap_validation:normalized_scenario(Input, S),
51 likelihood_score(S, Likelihood, LikContrib),
52 spread_score(S, Spread, SpreadContrib),
53 escalation_score(S, Escalation, EscContrib),
54 pressure_score(S, Pressure, _),
55 confidence_score(S, Confidence),
56 risk_band(Likelihood, LikBand),
57 risk_band(Spread, SpreadBand),
58 risk_band(Escalation, EscBand),
59 predicted_state(Likelihood, Escalation, State),
60 predicted_scale(S, Likelihood, Spread, Scale),
61 turnout_index(S, Likelihood, Spread, TurnoutIndex),
62 crowd_bucket(TurnoutIndex, CrowdBucket),
63 continuation_score(S, Likelihood, Spread, Continue),
64 policy_outlook(S, Likelihood, Pressure, Outlook),
65 ap_legal:legal_note(S, Legal),
66 top_factors(LikContrib, 5, TopPositive),
67 protective_factors(S, Protective),
68 Report = report{
69 scenario:S,
70 likelihood:Likelihood,
71 likelihood_band:LikBand,
72 spread:Spread,
73 spread_band:SpreadBand,
74 escalation:Escalation,
75 escalation_band:EscBand,
76 political_pressure:Pressure,
77 confidence:Confidence,
78 predicted_state:State,
79 predicted_scale:Scale,
80 crowd_bucket:CrowdBucket,
81 continuation:Continue,
82 policy_outlook:Outlook,
83 legal_note:Legal,
84 top_factors:TopPositive,
85 protective_factors:Protective,
86 contributions:_{likelihood:LikContrib, spread:SpreadContrib, escalation:EscContrib},
87 model_status:'HEURISTIC_UNCALIBRATED_V0_1'
88 }.
89
90likelihood_score(S, Score, Contributions) :-
91 findall(C-Label-Key,
92 ( positive_weight(Key, W, Label),
93 get_dict(Key, S, V),
94 C is V*W
95 ), Pos),
96 sum_contributions(Pos, P),
97 findall(C-Label-Key,
98 ( protective_weight(Key, W, Label),
99 get_dict(Key, S, V),
100 C is -(V*W)
101 ), Neg),
102 sum_contributions(Neg, N),
103 scope_likelihood_bonus(S.scope, SB),
104 Raw is 8 + P + N + SB,
105 ap_validation:clamp(0, 100, Raw, Capped),
106 Score is round(Capped),
107 append(Pos, Neg, Contributions).
108
109spread_score(S, Score, Contributions) :-
110 findall(C-Label-Key,
111 ( spread_weight(Key, W, Label),
112 get_dict(Key, S, V),
113 C is V*W
114 ), Cs),
115 sum_contributions(Cs, Base),
116 scope_spread_bonus(S.scope, Bonus),
117 Raw is Base + Bonus,
118 ap_validation:clamp(0, 100, Raw, Capped),
119 Score is round(Capped),
120 Contributions = Cs.
121
122escalation_score(S, Score, Contributions) :-
123 DurationPressure is min(100, S.duration_days*12),
124 LowControl is 100-S.organizer_control,
125 LowResponse is 100-S.govt_responsiveness,
126 Pairs = [
127 grievance-0.14-'akumulasi keluhan',
128 incident_shock-0.20-'insiden pemicu tambahan',
129 intervention_pressure-0.18-'tekanan/intervensi',
130 institutional_distrust-0.11-'ketidakpercayaan institusional',
131 trigger-0.10-'pemicu langsung'
132 ],
133 findall(C-Label-Key,
134 ( member(Key-W-Label, Pairs),
135 get_dict(Key, S, V), C is V*W
136 ), C0),
137 C1 is DurationPressure*0.08,
138 C2 is LowControl*0.10,
139 C3 is LowResponse*0.09,
140 Extra = [C1-'durasi tekanan'-duration_days,
141 C2-'kontrol penyelenggara rendah'-organizer_control,
142 C3-'respons kebijakan rendah'-govt_responsiveness],
143 append(C0, Extra, Contributions),
144 sum_contributions(Contributions, Raw0),
145 Raw is Raw0*0.94,
146 ap_validation:clamp(0, 100, Raw, Capped),
147 Score is round(Capped).
148
149pressure_score(S, Score, Contributions) :-
150 Pairs = [
151 public_support-0.24-'dukungan publik',
152 salience-0.18-'kepentingan isu',
153 coalition-0.15-'koalisi',
154 media_attention-0.13-'perhatian media',
155 spread_proxy-0.15-'potensi penyebaran',
156 grievance-0.15-'akumulasi keluhan'
157 ],
158 spread_score(S, Spread, _),
159 findall(C-Label-Key,
160 ( member(Key-W-Label, Pairs),
161 pressure_value(Key, S, Spread, V), C is V*W
162 ), Contributions),
163 sum_contributions(Contributions, Raw),
164 ap_validation:clamp(0, 100, Raw, Capped),
165 Score is round(Capped).
166
167pressure_value(spread_proxy, _S, Spread, Spread) :- !.
168pressure_value(Key, S, _Spread, V) :- get_dict(Key, S, V).
169
170confidence_score(S, Score) :-
171 Q = S.evidence_quality,
172 Base is 25 + Q*0.40,
173 ( S.incident_shock > 0 -> Bonus = 3 ; Bonus = 0 ),
174 Raw is Base + Bonus,
175 ap_validation:clamp(20, 68, Raw, Capped),
176 Score is round(Capped).
177
178continuation_score(S, Likelihood, Spread, Score) :-
179 Raw is Likelihood*0.42 + Spread*0.31 + S.grievance*0.17 +
180 (100-S.govt_responsiveness)*0.10,
181 ap_validation:clamp(0, 100, Raw, Capped),
182 Score is round(Capped).
183
184policy_outlook(S, _Likelihood, Pressure, Outlook) :-
185 R = S.govt_responsiveness,
186 ( Pressure >= 75, R >= 60 -> Outlook = 'peluang_konsesi_atau_review_tinggi'
187 ; Pressure >= 60, R >= 40 -> Outlook = 'peluang_dialog_atau_penyesuaian_moderat'
188 ; Pressure >= 60, R < 40 -> Outlook = 'tekanan_tinggi_respons_rendah'
189 ; Pressure < 40 -> Outlook = 'tekanan_kebijakan_relatively_rendah'
190 ; Outlook = 'hasil_belum_jelas'
191 ).
192
193predicted_scale(S, Likelihood, Spread, Scale) :-
194 Base is Likelihood*0.45 + Spread*0.55,
195 scope_scale_adjust(S.scope, A), X is Base + A,
196 ( X < 35 -> Scale = 'terbatas/lokal'
197 ; X < 52 -> Scale = 'kota/area'
198 ; X < 68 -> Scale = 'lintas-kota'
199 ; X < 82 -> Scale = 'multi-provinsi'
200 ; Scale = 'nasional-potensial'
201 ).
202
203turnout_index(S, Likelihood, Spread, Index) :-
204 PopFactor is min(100, 25 + log(S.population_millions+1)*22),
205 Raw is Likelihood*0.36 + Spread*0.22 + S.organization*0.17 +
206 S.public_support*0.15 + PopFactor*0.10,
207 ap_validation:clamp(0, 100, Raw, Capped),
208 Index is round(Capped).
209
211crowd_bucket(I, 'very small (<20)') :- I < 25, !.
212crowd_bucket(I, 'small (20-99)') :- I < 42, !.
213crowd_bucket(I, 'medium (100-999)') :- I < 62, !.
214crowd_bucket(I, 'large (1,000-9,999)') :- I < 80, !.
215crowd_bucket(_, 'massive (10,000+)').
216
217risk_band(S, sangat_rendah) :- S < 20, !.
218risk_band(S, rendah) :- S < 40, !.
219risk_band(S, sedang) :- S < 60, !.
220risk_band(S, tinggi) :- S < 80, !.
221risk_band(_, sangat_tinggi).
222
223predicted_state(L, _E, 'tidak_ada_aksi_besar_terdeteksi') :- L < 35, !.
224predicted_state(_L, E, 'peaceful_protest_paling_mungkin') :- E < 35, !.
225predicted_state(_L, E, 'risiko_protest_with_intervention') :- E < 58, !.
226predicted_state(_L, E, 'risiko_excessive_force_or_violent_demonstration_meningkat') :- E >= 58.
227
228scope_likelihood_bonus(lokal, 0) :- !.
229scope_likelihood_bonus(kota, 1) :- !.
230scope_likelihood_bonus(provinsi, 2) :- !.
231scope_likelihood_bonus(multi_provinsi, 4) :- !.
232scope_likelihood_bonus(nasional, 5) :- !.
233scope_likelihood_bonus(_, 0).
234
235scope_spread_bonus(lokal, -8) :- !.
236scope_spread_bonus(kota, -3) :- !.
237scope_spread_bonus(provinsi, 1) :- !.
238scope_spread_bonus(multi_provinsi, 5) :- !.
239scope_spread_bonus(nasional, 8) :- !.
240scope_spread_bonus(_, 0).
241
242scope_scale_adjust(lokal, -12) :- !.
243scope_scale_adjust(kota, -5) :- !.
244scope_scale_adjust(provinsi, 0) :- !.
245scope_scale_adjust(multi_provinsi, 8) :- !.
246scope_scale_adjust(nasional, 12) :- !.
247scope_scale_adjust(_, 0).
248
249sum_contributions(Cs, Sum) :-
250 findall(V, member(V-_-_, Cs), Vs), sum_list(Vs, Sum).
251
252top_factors(Contributions, N, Top) :-
253 include(positive_contribution, Contributions, Positive),
254 predsort(compare_contrib_desc, Positive, Sorted),
255 take(N, Sorted, Top0),
256 maplist(contrib_dict, Top0, Top).
257
258positive_contribution(V-_-_) :- V > 0.
259compare_contrib_desc(Order, A-_-_, B-_-_) :- compare(Order, B, A).
260
261take(0, _Xs, []) :- !.
262take(_, [], []) :- !.
263take(N, [X|Xs], [X|Ys]) :- N1 is N-1, take(N1, Xs, Ys).
264
265contrib_dict(V-Label-Key, _{field:Key, label:Label, contribution:Rounded}) :-
266 Rounded is round(V*10)/10.
267
268protective_factors(S, Factors) :-
269 findall(_{field:Key,label:Label,value:V,effect:Effect},
270 ( protective_weight(Key, W, Label),
271 get_dict(Key, S, V), Effect is round(V*W*10)/10
272 ), Factors)
Model heuristik explainable Antisipasi Pejabat.
Koefisien v0.1.0.0 adalah bobot heuristik terdokumentasi. Bobot belum merupakan hasil estimasi kausal dan tidak boleh dipresentasikan sebagai probabilitas empiris terkalibrasi tanpa evaluasi data historis yang sesuai.