kibi-core 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -5
- package/schema/entities.pl +9 -0
- package/schema/relationships.pl +2 -0
- package/schema/validation.pl +43 -3
- package/src/checks.pl +80 -2
- package/src/derived_chr.pl +65 -0
- package/src/discovery.pl +3 -3
- package/src/kb.pl +53 -0
- package/src/sparql_client.pl +150 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Core Prolog modules and RDF graph logic for Kibi",
|
|
6
6
|
"type": "module",
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
"bun": ">=1.0"
|
|
10
10
|
},
|
|
11
11
|
"main": "./src/kb.pl",
|
|
12
|
-
"files": [
|
|
13
|
-
"src/**/*.pl",
|
|
14
|
-
"schema/**/*.pl"
|
|
15
|
-
],
|
|
12
|
+
"files": ["src/**/*.pl", "schema/**/*.pl"],
|
|
16
13
|
"license": "AGPL-3.0-or-later",
|
|
17
14
|
"author": "Piotr Franczyk",
|
|
18
15
|
"repository": {
|
package/schema/entities.pl
CHANGED
|
@@ -47,6 +47,15 @@ entity_property(fact, closed_world, boolean).
|
|
|
47
47
|
entity_property(fact, valid_from, datetime).
|
|
48
48
|
entity_property(fact, valid_to, datetime).
|
|
49
49
|
entity_property(fact, canonical_key, string).
|
|
50
|
+
entity_property(fact, predicate_name, string).
|
|
51
|
+
entity_property(fact, predicate_namespace, string).
|
|
52
|
+
entity_property(fact, predicate_arity, integer).
|
|
53
|
+
entity_property(fact, argument_names, list).
|
|
54
|
+
entity_property(fact, argument_types, list).
|
|
55
|
+
entity_property(fact, argument_descriptions, list).
|
|
56
|
+
entity_property(fact, aliases, list).
|
|
57
|
+
entity_property(fact, examples, list).
|
|
58
|
+
entity_property(fact, predicate_args, list).
|
|
50
59
|
|
|
51
60
|
% Typed test verification fields - only valid for test entities
|
|
52
61
|
entity_property(test, verification_scope, atom).
|
package/schema/relationships.pl
CHANGED
|
@@ -18,6 +18,7 @@ relationship_type(relates_to).
|
|
|
18
18
|
relationship_type(supersedes).
|
|
19
19
|
relationship_type(constrains).
|
|
20
20
|
relationship_type(requires_property).
|
|
21
|
+
relationship_type(requires_predicate).
|
|
21
22
|
|
|
22
23
|
% valid_relationship(RelType, FromType, ToType).
|
|
23
24
|
valid_relationship(depends_on, req, req).
|
|
@@ -38,6 +39,7 @@ valid_relationship(publishes, symbol, event).
|
|
|
38
39
|
valid_relationship(consumes, symbol, event).
|
|
39
40
|
valid_relationship(constrains, req, fact).
|
|
40
41
|
valid_relationship(requires_property, req, fact).
|
|
42
|
+
valid_relationship(requires_predicate, req, fact).
|
|
41
43
|
|
|
42
44
|
%% supersedes(+NewAdrId, +OldAdrId)
|
|
43
45
|
%% NewAdrId is the decision that replaces OldAdrId.
|
package/schema/validation.pl
CHANGED
|
@@ -88,6 +88,15 @@ is_fact_only_field(closed_world).
|
|
|
88
88
|
is_fact_only_field(valid_from).
|
|
89
89
|
is_fact_only_field(valid_to).
|
|
90
90
|
is_fact_only_field(canonical_key).
|
|
91
|
+
is_fact_only_field(predicate_name).
|
|
92
|
+
is_fact_only_field(predicate_namespace).
|
|
93
|
+
is_fact_only_field(predicate_arity).
|
|
94
|
+
is_fact_only_field(argument_names).
|
|
95
|
+
is_fact_only_field(argument_types).
|
|
96
|
+
is_fact_only_field(argument_descriptions).
|
|
97
|
+
is_fact_only_field(aliases).
|
|
98
|
+
is_fact_only_field(examples).
|
|
99
|
+
is_fact_only_field(predicate_args).
|
|
91
100
|
|
|
92
101
|
% is_test_only_field(+Key) - true if Key is a test-specific field
|
|
93
102
|
is_test_only_field(verification_scope).
|
|
@@ -97,7 +106,7 @@ is_test_only_field(verification_perspective).
|
|
|
97
106
|
validate_fact_shape(subject, Props) :-
|
|
98
107
|
memberchk(subject_key=_Val, Props),
|
|
99
108
|
valid_optional_fact_enums(Props),
|
|
100
|
-
|
|
109
|
+
valid_strict_polarity_in_props(Props).
|
|
101
110
|
validate_fact_shape(property_value, Props) :-
|
|
102
111
|
memberchk(subject_key=_Subject, Props),
|
|
103
112
|
memberchk(property_key=_Property, Props),
|
|
@@ -108,7 +117,7 @@ validate_fact_shape(property_value, Props) :-
|
|
|
108
117
|
exactly_one_value_field(Props),
|
|
109
118
|
value_type_matches_field(VT, Props),
|
|
110
119
|
valid_optional_fact_enums(Props),
|
|
111
|
-
|
|
120
|
+
valid_strict_polarity_in_props(Props).
|
|
112
121
|
validate_fact_shape(observation, Props) :-
|
|
113
122
|
valid_optional_fact_enums(Props),
|
|
114
123
|
valid_polarity_in_props(Props).
|
|
@@ -116,9 +125,26 @@ validate_fact_shape(meta, Props) :-
|
|
|
116
125
|
% Meta facts are allowed but don't require full strict property tuple yet
|
|
117
126
|
valid_optional_fact_enums(Props),
|
|
118
127
|
valid_polarity_in_props(Props).
|
|
128
|
+
validate_fact_shape(predicate_schema, Props) :-
|
|
129
|
+
memberchk(predicate_name=_Name, Props),
|
|
130
|
+
memberchk(predicate_arity=Arity, Props),
|
|
131
|
+
Arity >= 1,
|
|
132
|
+
memberchk(argument_names=ArgumentNames, Props),
|
|
133
|
+
memberchk(argument_types=ArgumentTypes, Props),
|
|
134
|
+
same_length(ArgumentNames, ArgumentTypes),
|
|
135
|
+
length(ArgumentNames, Arity),
|
|
136
|
+
valid_optional_fact_enums(Props),
|
|
137
|
+
valid_polarity_in_props(Props).
|
|
138
|
+
validate_fact_shape(predicate, Props) :-
|
|
139
|
+
memberchk(predicate_name=_Name, Props),
|
|
140
|
+
memberchk(predicate_args=Args, Props),
|
|
141
|
+
Args \= [],
|
|
142
|
+
memberchk(canonical_key=_CanonicalKey, Props),
|
|
143
|
+
valid_optional_fact_enums(Props),
|
|
144
|
+
valid_predicate_polarity_in_props(Props).
|
|
119
145
|
validate_fact_shape(Kind, _Props) :-
|
|
120
146
|
% Unknown fact_kind values fail validation
|
|
121
|
-
\+ memberchk(Kind, [subject, property_value, observation, meta]),
|
|
147
|
+
\+ memberchk(Kind, [subject, property_value, observation, meta, predicate_schema, predicate]),
|
|
122
148
|
fail.
|
|
123
149
|
|
|
124
150
|
% valid_operator(+Op)
|
|
@@ -140,6 +166,12 @@ valid_value_type(bool).
|
|
|
140
166
|
valid_polarity_in_props(Props) :-
|
|
141
167
|
( memberchk(polarity=P, Props) -> valid_polarity(P) ; true ).
|
|
142
168
|
|
|
169
|
+
valid_strict_polarity_in_props(Props) :-
|
|
170
|
+
( memberchk(polarity=P, Props) -> valid_strict_polarity(P) ; true ).
|
|
171
|
+
|
|
172
|
+
valid_predicate_polarity_in_props(Props) :-
|
|
173
|
+
( memberchk(polarity=P, Props) -> valid_predicate_polarity(P) ; true ).
|
|
174
|
+
|
|
143
175
|
% valid_optional_fact_enums(+Props)
|
|
144
176
|
% Validates enum-typed fact fields whenever they are present
|
|
145
177
|
valid_optional_fact_enums(Props) :-
|
|
@@ -155,6 +187,14 @@ valid_optional_test_enums(Props) :-
|
|
|
155
187
|
% valid_polarity(+P)
|
|
156
188
|
valid_polarity(require).
|
|
157
189
|
valid_polarity(forbid).
|
|
190
|
+
valid_polarity(assert).
|
|
191
|
+
valid_polarity(deny).
|
|
192
|
+
|
|
193
|
+
valid_strict_polarity(require).
|
|
194
|
+
valid_strict_polarity(forbid).
|
|
195
|
+
|
|
196
|
+
valid_predicate_polarity(assert).
|
|
197
|
+
valid_predicate_polarity(deny).
|
|
158
198
|
|
|
159
199
|
% valid_verification_scope(+Scope)
|
|
160
200
|
valid_verification_scope(unit).
|
package/src/checks.pl
CHANGED
|
@@ -33,7 +33,7 @@ required_fields([id, title, status, created_at, updated_at, source]).
|
|
|
33
33
|
% Relationship types to check for dangling references
|
|
34
34
|
all_relationship_types([
|
|
35
35
|
depends_on, verified_by, validates, specified_by,
|
|
36
|
-
constrains, requires_property, supersedes, relates_to
|
|
36
|
+
constrains, requires_property, requires_predicate, supersedes, relates_to
|
|
37
37
|
]).
|
|
38
38
|
|
|
39
39
|
%% check_all(-ViolationsDict)
|
|
@@ -387,9 +387,25 @@ strict_fact_shape_violation(violation(
|
|
|
387
387
|
-> fail % Observation facts have no required fields beyond fact_kind
|
|
388
388
|
; Kind = meta
|
|
389
389
|
-> fail % Meta facts have no required fields beyond fact_kind
|
|
390
|
+
; Kind = predicate_schema
|
|
391
|
+
-> findall(Msg, predicate_schema_shape_error(Props, Msg), Errors),
|
|
392
|
+
( Errors = []
|
|
393
|
+
-> fail
|
|
394
|
+
; Errors = [First|_],
|
|
395
|
+
Description = First,
|
|
396
|
+
Suggestion = "Ensure predicate_schema facts have predicate_name, predicate_arity, argument_names, and argument_types with matching arity"
|
|
397
|
+
)
|
|
398
|
+
; Kind = predicate
|
|
399
|
+
-> findall(Msg, predicate_shape_error(Props, Msg), Errors),
|
|
400
|
+
( Errors = []
|
|
401
|
+
-> fail
|
|
402
|
+
; Errors = [First|_],
|
|
403
|
+
Description = First,
|
|
404
|
+
Suggestion = "Ensure predicate facts have predicate_name, non-empty predicate_args, canonical_key, and assert/deny polarity when present"
|
|
405
|
+
)
|
|
390
406
|
; % Unknown fact_kind - report as malformed
|
|
391
407
|
format(string(Description), "Unknown fact_kind: ~w", [Kind]),
|
|
392
|
-
Suggestion = "Use one of: subject, property_value, observation, meta"
|
|
408
|
+
Suggestion = "Use one of: subject, property_value, observation, meta, predicate_schema, predicate"
|
|
393
409
|
),
|
|
394
410
|
|
|
395
411
|
( memberchk(source=Source0, Props)
|
|
@@ -435,6 +451,68 @@ value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
|
435
451
|
value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
|
|
436
452
|
value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
|
|
437
453
|
|
|
454
|
+
predicate_schema_shape_error(Props, "Predicate schema fact missing required field: predicate_name") :-
|
|
455
|
+
\+ memberchk(predicate_name=_, Props).
|
|
456
|
+
predicate_schema_shape_error(Props, "Predicate schema fact missing required field: predicate_arity") :-
|
|
457
|
+
memberchk(predicate_name=_, Props),
|
|
458
|
+
\+ memberchk(predicate_arity=_, Props).
|
|
459
|
+
predicate_schema_shape_error(Props, "Predicate schema fact missing required field: argument_names") :-
|
|
460
|
+
memberchk(predicate_arity=_, Props),
|
|
461
|
+
\+ memberchk(argument_names=_, Props).
|
|
462
|
+
predicate_schema_shape_error(Props, "Predicate schema fact missing required field: argument_types") :-
|
|
463
|
+
memberchk(argument_names=_, Props),
|
|
464
|
+
\+ memberchk(argument_types=_, Props).
|
|
465
|
+
predicate_schema_shape_error(Props, "Predicate schema fact argument_names length must match predicate_arity") :-
|
|
466
|
+
memberchk(predicate_arity=RawArity, Props),
|
|
467
|
+
checks_normalize_integer(RawArity, Arity),
|
|
468
|
+
memberchk(argument_names=RawNames, Props),
|
|
469
|
+
checks_normalize_atom_list(RawNames, Names),
|
|
470
|
+
length(Names, Count),
|
|
471
|
+
Count =\= Arity.
|
|
472
|
+
predicate_schema_shape_error(Props, "Predicate schema fact argument_types length must match predicate_arity") :-
|
|
473
|
+
memberchk(predicate_arity=RawArity, Props),
|
|
474
|
+
checks_normalize_integer(RawArity, Arity),
|
|
475
|
+
memberchk(argument_types=RawTypes, Props),
|
|
476
|
+
checks_normalize_atom_list(RawTypes, Types),
|
|
477
|
+
length(Types, Count),
|
|
478
|
+
Count =\= Arity.
|
|
479
|
+
|
|
480
|
+
predicate_shape_error(Props, "Predicate fact missing required field: predicate_name") :-
|
|
481
|
+
\+ memberchk(predicate_name=_, Props).
|
|
482
|
+
predicate_shape_error(Props, "Predicate fact missing required field: predicate_args") :-
|
|
483
|
+
memberchk(predicate_name=_, Props),
|
|
484
|
+
\+ memberchk(predicate_args=_, Props).
|
|
485
|
+
predicate_shape_error(Props, "Predicate fact predicate_args must be non-empty") :-
|
|
486
|
+
memberchk(predicate_args=RawArgs, Props),
|
|
487
|
+
checks_normalize_atom_list(RawArgs, Args),
|
|
488
|
+
Args = [].
|
|
489
|
+
predicate_shape_error(Props, "Predicate fact missing required field: canonical_key") :-
|
|
490
|
+
memberchk(predicate_args=_, Props),
|
|
491
|
+
\+ memberchk(canonical_key=_, Props).
|
|
492
|
+
predicate_shape_error(Props, "Predicate fact polarity must be assert or deny") :-
|
|
493
|
+
memberchk(polarity=RawPolarity, Props),
|
|
494
|
+
normalize_term_atom(RawPolarity, Polarity),
|
|
495
|
+
\+ memberchk(Polarity, [assert, deny]).
|
|
496
|
+
|
|
497
|
+
checks_normalize_integer(Raw, Integer) :-
|
|
498
|
+
( Raw = ^^(Value, _Type)
|
|
499
|
+
-> checks_normalize_integer(Value, Integer)
|
|
500
|
+
; integer(Raw)
|
|
501
|
+
-> Integer = Raw
|
|
502
|
+
; atom(Raw)
|
|
503
|
+
-> atom_number(Raw, Integer)
|
|
504
|
+
; string(Raw)
|
|
505
|
+
-> number_string(Integer, Raw)
|
|
506
|
+
).
|
|
507
|
+
|
|
508
|
+
checks_normalize_atom_list(Raw, Atoms) :-
|
|
509
|
+
( Raw = ^^(Value, _Type)
|
|
510
|
+
-> checks_normalize_atom_list(Value, Atoms)
|
|
511
|
+
; is_list(Raw)
|
|
512
|
+
-> maplist(normalize_term_atom, Raw, Atoms)
|
|
513
|
+
; Atoms = []
|
|
514
|
+
).
|
|
515
|
+
|
|
438
516
|
%% check_domain_contradictions(-Violations)
|
|
439
517
|
% Finds all pairs of requirements with contradicting required properties.
|
|
440
518
|
check_domain_contradictions(Violations) :-
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
% Module: derived_chr
|
|
2
|
+
% Internal CHR pilot for bounded derived validation facts.
|
|
3
|
+
|
|
4
|
+
:- module(derived_chr, [
|
|
5
|
+
derive_chr_facts/0,
|
|
6
|
+
clear_chr_facts/0,
|
|
7
|
+
derived_coverage_gap/2,
|
|
8
|
+
derived_symbol_gap/2
|
|
9
|
+
]).
|
|
10
|
+
|
|
11
|
+
:- use_module(library(chr)).
|
|
12
|
+
:- use_module('kb.pl').
|
|
13
|
+
|
|
14
|
+
:- chr_constraint seen_must_req/1, seen_scenario/1, seen_test/1,
|
|
15
|
+
seen_production_symbol/1, seen_symbol_req_coverage/1.
|
|
16
|
+
|
|
17
|
+
:- dynamic derived_coverage_gap/2.
|
|
18
|
+
:- dynamic derived_symbol_gap/2.
|
|
19
|
+
:- dynamic snapshot_must_req/1.
|
|
20
|
+
:- dynamic snapshot_scenario/1.
|
|
21
|
+
:- dynamic snapshot_test/1.
|
|
22
|
+
:- dynamic snapshot_production_symbol/1.
|
|
23
|
+
:- dynamic snapshot_symbol_req_coverage/1.
|
|
24
|
+
|
|
25
|
+
seen_must_req(Req) <=> assertz(snapshot_must_req(Req)).
|
|
26
|
+
seen_scenario(Req) <=> assertz(snapshot_scenario(Req)).
|
|
27
|
+
seen_test(Req) <=> assertz(snapshot_test(Req)).
|
|
28
|
+
seen_production_symbol(Symbol) <=> assertz(snapshot_production_symbol(Symbol)).
|
|
29
|
+
seen_symbol_req_coverage(Symbol) <=> assertz(snapshot_symbol_req_coverage(Symbol)).
|
|
30
|
+
|
|
31
|
+
clear_chr_facts :-
|
|
32
|
+
retractall(derived_coverage_gap(_, _)),
|
|
33
|
+
retractall(derived_symbol_gap(_, _)),
|
|
34
|
+
retractall(snapshot_must_req(_)),
|
|
35
|
+
retractall(snapshot_scenario(_)),
|
|
36
|
+
retractall(snapshot_test(_)),
|
|
37
|
+
retractall(snapshot_production_symbol(_)),
|
|
38
|
+
retractall(snapshot_symbol_req_coverage(_)).
|
|
39
|
+
|
|
40
|
+
derive_chr_facts :-
|
|
41
|
+
clear_chr_facts,
|
|
42
|
+
forall(kb:must_requirement(Req), seen_must_req(Req)),
|
|
43
|
+
forall(kb:has_scenario(Req), seen_scenario(Req)),
|
|
44
|
+
forall(kb:has_test(Req), seen_test(Req)),
|
|
45
|
+
forall(kb:production_symbol(Symbol), seen_production_symbol(Symbol)),
|
|
46
|
+
forall(kb:production_symbol_covered_for_requirement(Symbol, _), seen_symbol_req_coverage(Symbol)),
|
|
47
|
+
derive_coverage_gaps,
|
|
48
|
+
derive_symbol_gaps.
|
|
49
|
+
|
|
50
|
+
derive_coverage_gaps :-
|
|
51
|
+
forall(
|
|
52
|
+
( snapshot_must_req(Req),
|
|
53
|
+
\+ snapshot_scenario(Req),
|
|
54
|
+
\+ snapshot_test(Req)
|
|
55
|
+
),
|
|
56
|
+
assertz(derived_coverage_gap(Req, missing_scenario_and_test))
|
|
57
|
+
).
|
|
58
|
+
|
|
59
|
+
derive_symbol_gaps :-
|
|
60
|
+
forall(
|
|
61
|
+
( snapshot_production_symbol(Symbol),
|
|
62
|
+
\+ snapshot_symbol_req_coverage(Symbol)
|
|
63
|
+
),
|
|
64
|
+
assertz(derived_symbol_gap(Symbol, no_qualifying_production_coverage))
|
|
65
|
+
).
|
package/src/discovery.pl
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
]).
|
|
9
9
|
|
|
10
10
|
:- use_module(library(http/json)).
|
|
11
|
+
:- use_module(library(aggregate)).
|
|
11
12
|
:- use_module('kb.pl').
|
|
12
13
|
:- use_module('status.pl', [status_meta_dict/1]).
|
|
13
14
|
:- use_module('../schema/relationships.pl', [relationship_type/1]).
|
|
@@ -90,10 +91,9 @@ relationship_counts(Id, CountsDict) :-
|
|
|
90
91
|
dict_pairs(CountsDict, relationship_counts, Pairs).
|
|
91
92
|
|
|
92
93
|
relationship_count(Id, Relationship, Count) :-
|
|
93
|
-
|
|
94
|
+
aggregate_all(count,
|
|
94
95
|
(kb_relationship(Relationship, Id, _); kb_relationship(Relationship, _, Id)),
|
|
95
|
-
|
|
96
|
-
length(Matches, Count).
|
|
96
|
+
Count).
|
|
97
97
|
|
|
98
98
|
coverage_rows(req, Tags, IncludePassing, IncludeTransitive, Rows, Summary) :-
|
|
99
99
|
!,
|
package/src/kb.pl
CHANGED
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
adr_chain/2,
|
|
40
40
|
deprecated_no_successor/1,
|
|
41
41
|
symbol_no_req_coverage/2,
|
|
42
|
+
predicate_schema/6,
|
|
43
|
+
predicate_fact/5,
|
|
42
44
|
contradicting_reqs/3,
|
|
43
45
|
check_req_contradiction/1,
|
|
44
46
|
normalize_term_atom/2,
|
|
@@ -962,6 +964,57 @@ fact_property_tuple(FactId, Subject, Property, Op, ValType, Value, Unit, Scope,
|
|
|
962
964
|
( memberchk(scope=ScopeRaw, Props) -> normalize_term_atom(ScopeRaw, Scope) ; Scope = '' ),
|
|
963
965
|
( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = require ).
|
|
964
966
|
|
|
967
|
+
%% predicate_schema(+FactId, -Namespace, -Name, -Arity, -ArgumentNames, -ArgumentTypes)
|
|
968
|
+
% Read one project-local ontology predicate schema fact.
|
|
969
|
+
predicate_schema(FactId, Namespace, Name, Arity, ArgumentNames, ArgumentTypes) :-
|
|
970
|
+
kb_entity(FactId, fact, Props),
|
|
971
|
+
memberchk(fact_kind=KindRaw, Props),
|
|
972
|
+
normalize_term_atom(KindRaw, predicate_schema),
|
|
973
|
+
memberchk(predicate_name=NameRaw, Props),
|
|
974
|
+
normalize_term_atom(NameRaw, Name),
|
|
975
|
+
( memberchk(predicate_namespace=NamespaceRaw, Props) -> normalize_term_atom(NamespaceRaw, Namespace) ; Namespace = default ),
|
|
976
|
+
memberchk(predicate_arity=ArityRaw, Props),
|
|
977
|
+
normalize_term_integer(ArityRaw, Arity),
|
|
978
|
+
memberchk(argument_names=ArgumentNamesRaw, Props),
|
|
979
|
+
normalize_term_atom_list(ArgumentNamesRaw, ArgumentNames),
|
|
980
|
+
memberchk(argument_types=ArgumentTypesRaw, Props),
|
|
981
|
+
normalize_term_atom_list(ArgumentTypesRaw, ArgumentTypes).
|
|
982
|
+
|
|
983
|
+
%% predicate_fact(+FactId, -Namespace, -Name, -Args, -Polarity)
|
|
984
|
+
% Read one ground ontology predicate fact.
|
|
985
|
+
predicate_fact(FactId, Namespace, Name, Args, Polarity) :-
|
|
986
|
+
kb_entity(FactId, fact, Props),
|
|
987
|
+
memberchk(fact_kind=KindRaw, Props),
|
|
988
|
+
normalize_term_atom(KindRaw, predicate),
|
|
989
|
+
memberchk(predicate_name=NameRaw, Props),
|
|
990
|
+
normalize_term_atom(NameRaw, Name),
|
|
991
|
+
( memberchk(predicate_namespace=NamespaceRaw, Props) -> normalize_term_atom(NamespaceRaw, Namespace) ; Namespace = default ),
|
|
992
|
+
memberchk(predicate_args=ArgsRaw, Props),
|
|
993
|
+
normalize_term_atom_list(ArgsRaw, Args),
|
|
994
|
+
( memberchk(polarity=PolarityRaw, Props) -> normalize_term_atom(PolarityRaw, Polarity) ; Polarity = assert ).
|
|
995
|
+
|
|
996
|
+
normalize_term_atom_list(List, Atoms) :-
|
|
997
|
+
is_list(List),
|
|
998
|
+
!,
|
|
999
|
+
maplist(normalize_term_atom, List, Atoms).
|
|
1000
|
+
normalize_term_atom_list(Raw, Atoms) :-
|
|
1001
|
+
unwrap_rdf_value(Raw, Value),
|
|
1002
|
+
is_list(Value),
|
|
1003
|
+
!,
|
|
1004
|
+
maplist(normalize_term_atom, Value, Atoms).
|
|
1005
|
+
|
|
1006
|
+
normalize_term_integer(Raw, Integer) :-
|
|
1007
|
+
unwrap_rdf_value(Raw, Value),
|
|
1008
|
+
( integer(Value)
|
|
1009
|
+
-> Integer = Value
|
|
1010
|
+
; atom(Value)
|
|
1011
|
+
-> atom_number(Value, Integer)
|
|
1012
|
+
; string(Value)
|
|
1013
|
+
-> number_string(Integer, Value)
|
|
1014
|
+
; Value = ^^(Nested, _Type)
|
|
1015
|
+
-> normalize_term_integer(Nested, Integer)
|
|
1016
|
+
).
|
|
1017
|
+
|
|
965
1018
|
%% fact_valid_interval(+FactId, -ValidFrom, -ValidTo)
|
|
966
1019
|
% Extract optional validity bounds for property_value facts.
|
|
967
1020
|
fact_valid_interval(FactId, ValidFrom, ValidTo) :-
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
% Module: kibi_sparql_client
|
|
2
|
+
% Explicit remote SPARQL client wrapper.
|
|
3
|
+
|
|
4
|
+
:- module(kibi_sparql_client, [
|
|
5
|
+
remote_sparql_select_json/4
|
|
6
|
+
]).
|
|
7
|
+
|
|
8
|
+
:- use_module(library(error)).
|
|
9
|
+
:- use_module(library(http/json)).
|
|
10
|
+
:- use_module(library(semweb/sparql_client)).
|
|
11
|
+
|
|
12
|
+
remote_sparql_select_json(Endpoint, Query, Options, JsonString) :-
|
|
13
|
+
validate_non_empty_atom(endpoint, Endpoint),
|
|
14
|
+
validate_non_empty_atom(query, Query),
|
|
15
|
+
validate_remote_endpoint(Endpoint),
|
|
16
|
+
validate_select_query(Query),
|
|
17
|
+
validate_sparql_options(Options, SparqlOptions),
|
|
18
|
+
sparql_query(Query, Rows, [endpoint(Endpoint), variable_names(Variables)|SparqlOptions]),
|
|
19
|
+
maplist(row_json(Variables), Rows, JsonRows),
|
|
20
|
+
with_output_to(string(JsonString), json_write_dict(current_output, _{rows: JsonRows}, [])).
|
|
21
|
+
|
|
22
|
+
validate_non_empty_atom(Name, Value) :-
|
|
23
|
+
must_be(atom, Value),
|
|
24
|
+
( Value == ''
|
|
25
|
+
-> throw(error(domain_error(non_empty_atom, Name), _))
|
|
26
|
+
; true
|
|
27
|
+
).
|
|
28
|
+
|
|
29
|
+
validate_remote_endpoint(Endpoint) :-
|
|
30
|
+
( sub_atom(Endpoint, 0, _, _, 'http://')
|
|
31
|
+
; sub_atom(Endpoint, 0, _, _, 'https://')
|
|
32
|
+
),
|
|
33
|
+
endpoint_host(Endpoint, Host),
|
|
34
|
+
\+ local_or_private_host(Host),
|
|
35
|
+
!.
|
|
36
|
+
validate_remote_endpoint(Endpoint) :-
|
|
37
|
+
( sub_atom(Endpoint, 0, _, _, 'http://')
|
|
38
|
+
; sub_atom(Endpoint, 0, _, _, 'https://')
|
|
39
|
+
),
|
|
40
|
+
!,
|
|
41
|
+
throw(error(domain_error(public_remote_endpoint, Endpoint), _)).
|
|
42
|
+
validate_remote_endpoint(Endpoint) :-
|
|
43
|
+
throw(error(domain_error(remote_http_endpoint, Endpoint), _)).
|
|
44
|
+
|
|
45
|
+
validate_select_query(Query) :-
|
|
46
|
+
normalize_space(atom(Normalized), Query),
|
|
47
|
+
downcase_atom(Normalized, Lower),
|
|
48
|
+
sub_atom(Lower, 0, 6, After, 'select'),
|
|
49
|
+
( After =:= 0
|
|
50
|
+
; sub_atom(Lower, 6, 1, _, ' ')
|
|
51
|
+
; sub_atom(Lower, 6, 1, _, '*')
|
|
52
|
+
),
|
|
53
|
+
!.
|
|
54
|
+
validate_select_query(Query) :-
|
|
55
|
+
throw(error(domain_error(sparql_select_query, Query), _)).
|
|
56
|
+
|
|
57
|
+
endpoint_host(Endpoint, Host) :-
|
|
58
|
+
strip_endpoint_scheme(Endpoint, WithoutScheme),
|
|
59
|
+
segment_before(WithoutScheme, '/', WithoutPath),
|
|
60
|
+
segment_before(WithoutPath, '?', WithoutQuery),
|
|
61
|
+
segment_before(WithoutQuery, '#', Authority),
|
|
62
|
+
strip_userinfo(Authority, HostWithPort),
|
|
63
|
+
strip_port(HostWithPort, Host0),
|
|
64
|
+
downcase_atom(Host0, Host).
|
|
65
|
+
|
|
66
|
+
strip_endpoint_scheme(Endpoint, WithoutScheme) :-
|
|
67
|
+
sub_atom(Endpoint, 0, 7, _, 'http://'),
|
|
68
|
+
!,
|
|
69
|
+
sub_atom(Endpoint, 7, _, 0, WithoutScheme).
|
|
70
|
+
strip_endpoint_scheme(Endpoint, WithoutScheme) :-
|
|
71
|
+
sub_atom(Endpoint, 0, 8, _, 'https://'),
|
|
72
|
+
sub_atom(Endpoint, 8, _, 0, WithoutScheme).
|
|
73
|
+
|
|
74
|
+
segment_before(Atom, Delimiter, Segment) :-
|
|
75
|
+
sub_atom(Atom, Before, _, _, Delimiter),
|
|
76
|
+
!,
|
|
77
|
+
sub_atom(Atom, 0, Before, _, Segment).
|
|
78
|
+
segment_before(Atom, _Delimiter, Atom).
|
|
79
|
+
|
|
80
|
+
strip_userinfo(Authority, HostWithPort) :-
|
|
81
|
+
sub_atom(Authority, _, _, After, '@'),
|
|
82
|
+
!,
|
|
83
|
+
sub_atom(Authority, _, After, 0, HostWithPort).
|
|
84
|
+
strip_userinfo(Authority, Authority).
|
|
85
|
+
|
|
86
|
+
strip_port(HostWithPort, Host) :-
|
|
87
|
+
sub_atom(HostWithPort, 0, 1, _, '['),
|
|
88
|
+
sub_atom(HostWithPort, 1, Length, _, ']'),
|
|
89
|
+
!,
|
|
90
|
+
sub_atom(HostWithPort, 1, Length, _, Host).
|
|
91
|
+
strip_port(HostWithPort, Host) :-
|
|
92
|
+
segment_before(HostWithPort, ':', Host).
|
|
93
|
+
|
|
94
|
+
local_or_private_host(localhost).
|
|
95
|
+
local_or_private_host('0.0.0.0').
|
|
96
|
+
local_or_private_host('::1').
|
|
97
|
+
local_or_private_host(Host) :-
|
|
98
|
+
sub_atom(Host, _, _, 0, '.localhost').
|
|
99
|
+
local_or_private_host(Host) :-
|
|
100
|
+
sub_atom(Host, 0, _, _, '127.').
|
|
101
|
+
local_or_private_host(Host) :-
|
|
102
|
+
sub_atom(Host, 0, _, _, '10.').
|
|
103
|
+
local_or_private_host(Host) :-
|
|
104
|
+
sub_atom(Host, 0, _, _, '192.168.').
|
|
105
|
+
local_or_private_host(Host) :-
|
|
106
|
+
sub_atom(Host, 0, _, _, '169.254.').
|
|
107
|
+
local_or_private_host(Host) :-
|
|
108
|
+
sub_atom(Host, 0, _, _, 'fe80:').
|
|
109
|
+
local_or_private_host(Host) :-
|
|
110
|
+
sub_atom(Host, 0, 2, _, Prefix),
|
|
111
|
+
memberchk(Prefix, ['fc', 'fd']).
|
|
112
|
+
local_or_private_host(Host) :-
|
|
113
|
+
sub_atom(Host, 0, _, _, '172.'),
|
|
114
|
+
atomic_list_concat([_, SecondAtom|_], '.', Host),
|
|
115
|
+
atom_number(SecondAtom, Second),
|
|
116
|
+
Second >= 16,
|
|
117
|
+
Second =< 31.
|
|
118
|
+
|
|
119
|
+
validate_sparql_options([], []).
|
|
120
|
+
validate_sparql_options([result_format(json)|Rest], SparqlOptions) :-
|
|
121
|
+
!,
|
|
122
|
+
validate_sparql_options(Rest, SparqlOptions).
|
|
123
|
+
validate_sparql_options([timeout(Timeout)|Rest], [timeout(Timeout)|SparqlOptions]) :-
|
|
124
|
+
!,
|
|
125
|
+
validate_sparql_options(Rest, SparqlOptions).
|
|
126
|
+
validate_sparql_options([Option|_], _) :-
|
|
127
|
+
throw(error(domain_error(sparql_client_option, Option), _)).
|
|
128
|
+
|
|
129
|
+
row_json(Variables, Row, Dict) :-
|
|
130
|
+
Row =.. [row|Values],
|
|
131
|
+
pairs_json(Variables, Values, Pairs),
|
|
132
|
+
dict_create(Dict, row, Pairs).
|
|
133
|
+
|
|
134
|
+
pairs_json([], [], []).
|
|
135
|
+
pairs_json([Variable|Variables], [Value|Values], [Variable-JsonValue|Pairs]) :-
|
|
136
|
+
sparql_value_json(Value, JsonValue),
|
|
137
|
+
pairs_json(Variables, Values, Pairs).
|
|
138
|
+
|
|
139
|
+
sparql_value_json(Value, JsonValue) :-
|
|
140
|
+
atomic(Value),
|
|
141
|
+
!,
|
|
142
|
+
JsonValue = Value.
|
|
143
|
+
sparql_value_json(literal(Value), JsonValue) :-
|
|
144
|
+
!,
|
|
145
|
+
sparql_value_json(Value, JsonValue).
|
|
146
|
+
sparql_value_json(type(_Type, Value), JsonValue) :-
|
|
147
|
+
!,
|
|
148
|
+
sparql_value_json(Value, JsonValue).
|
|
149
|
+
sparql_value_json(Value, JsonValue) :-
|
|
150
|
+
term_string(Value, JsonValue).
|