kibi-core 0.13.0 → 0.14.0

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/src/status.pl CHANGED
@@ -19,7 +19,16 @@ status_meta_dict(StatusDict) :-
19
19
  snapshot_id(SnapshotId),
20
20
  synced_at(DataFile, SyncedAt),
21
21
  freshness_state(DataFile, Dirty, SyncState),
22
- stale_reasons(StaleReasons, StaleReasonCount, StaleReasonsTruncated),
22
+ ( SyncState == fresh
23
+ -> % fresh means no indexed source is newer or missing and no knowledge
24
+ % lane or documentation file is newer than the snapshot: exactly the
25
+ % conditions stale_reasons enumerates. Skip its full entity scan,
26
+ % which dominates status latency on large KBs.
27
+ StaleReasons = [],
28
+ StaleReasonCount = 0,
29
+ StaleReasonsTruncated = false
30
+ ; stale_reasons(StaleReasons, StaleReasonCount, StaleReasonsTruncated)
31
+ ),
23
32
  StatusDict = _{
24
33
  branch: Branch,
25
34
  snapshotId: SnapshotId,
@@ -250,11 +259,7 @@ documentation_tree_changed(SnapshotTime) :-
250
259
  !.
251
260
 
252
261
  directory_tree_newer(Path, SnapshotTime) :-
253
- exists_file(Path),
254
- entity_documentation_file(Path),
255
- \+ ignored_documentation_file(Path),
256
- time_file(Path, EntryTime),
257
- EntryTime > SnapshotTime,
262
+ newer_entity_documentation_file(Path, SnapshotTime),
258
263
  !.
259
264
 
260
265
  directory_tree_newer(Path, SnapshotTime) :-
@@ -268,11 +273,7 @@ directory_tree_newer(Path, SnapshotTime) :-
268
273
  !.
269
274
 
270
275
  directory_tree_newer_path(Path, SnapshotTime, Path) :-
271
- exists_file(Path),
272
- entity_documentation_file(Path),
273
- \+ ignored_documentation_file(Path),
274
- time_file(Path, EntryTime),
275
- EntryTime > SnapshotTime.
276
+ newer_entity_documentation_file(Path, SnapshotTime).
276
277
  directory_tree_newer_path(Path, SnapshotTime, ChildPath) :-
277
278
  exists_directory(Path),
278
279
  directory_files(Path, Entries),
@@ -282,9 +283,22 @@ directory_tree_newer_path(Path, SnapshotTime, ChildPath) :-
282
283
  directory_file_path(Path, Entry, Candidate),
283
284
  directory_tree_newer_path(Candidate, SnapshotTime, ChildPath).
284
285
 
286
+ :- dynamic workspace_root_memo/2.
287
+
288
+ % Resolved once per attached KB path: status maps every entity source to a
289
+ % workspace-relative path, and re-deriving the root per entity dominated it.
285
290
  attached_workspace_root(WorkspaceRoot) :-
286
291
  kb:kb_attached(KbPath),
287
- branch_workspace_from_kb_path(KbPath, _Branch, WorkspaceRoot).
292
+ ( workspace_root_memo(KbPath, Memo)
293
+ -> Memo = found(WorkspaceRoot)
294
+ ; ( once(branch_workspace_from_kb_path(KbPath, _Branch, Root))
295
+ -> Memo = found(Root)
296
+ ; Memo = none
297
+ ),
298
+ retractall(workspace_root_memo(_, _)),
299
+ assertz(workspace_root_memo(KbPath, Memo)),
300
+ Memo = found(WorkspaceRoot)
301
+ ).
288
302
 
289
303
  branch_workspace_from_kb_path(KbPath, Branch, WorkspaceRoot) :-
290
304
  branch_path_segments(KbPath, BranchesDir, Segments),
@@ -366,12 +380,24 @@ ignored_documentation_file(Path) :-
366
380
  ignored_documentation_file(Path) :-
367
381
  sub_atom(Path, _, _, _, '/tests/benchmarks/').
368
382
 
383
+ %% newer_entity_documentation_file(+Path, +SnapshotTime) is semidet.
384
+ % Cheap path and mtime tests run before the file is read, and the content
385
+ % test is deterministic. Previously each sub_string/5 left a choicepoint per
386
+ % occurrence, so a failing mtime test backtracked through every combination
387
+ % of id:/title:/status: positions (millions of redos on large trees).
388
+ newer_entity_documentation_file(Path, SnapshotTime) :-
389
+ exists_file(Path),
390
+ \+ ignored_documentation_file(Path),
391
+ time_file(Path, EntryTime),
392
+ EntryTime > SnapshotTime,
393
+ entity_documentation_file(Path).
394
+
369
395
  entity_documentation_file(Path) :-
370
396
  read_file_to_string(Path, Content, []),
371
397
  sub_string(Content, 0, 3, _, "---"),
372
- sub_string(Content, _, _, _, "id:"),
373
- sub_string(Content, _, _, _, "title:"),
374
- sub_string(Content, _, _, _, "status:").
398
+ once(sub_string(Content, _, _, _, "id:")),
399
+ once(sub_string(Content, _, _, _, "title:")),
400
+ once(sub_string(Content, _, _, _, "status:")).
375
401
 
376
402
  dict_json_string(Dict, JsonString) :-
377
403
  with_output_to(string(JsonString), json_write_dict(current_output, Dict, [])).
package/src/units.pl ADDED
@@ -0,0 +1,146 @@
1
+ % Module: units
2
+ % Deterministic unit canonicalization for property_value comparison.
3
+ %
4
+ % Authored values are stored unchanged. Checks canonicalize a numeric value to
5
+ % the base unit of its family before computing logical ground signatures and
6
+ % before contradiction, redundancy, or implication comparison, so "30 min" and
7
+ % "1800 s" share one comparable term. Only exact, fixed-ratio conversions are
8
+ % listed: calendar units (month, year) have no fixed length and stay as-is.
9
+ % Unknown units are never equated with anything but themselves.
10
+ %
11
+ % implements REQ-kibi-unit-canonicalization
12
+
13
+ :- module(units, [
14
+ canonical_quantity/6, % +ValueType, +Value, +Unit, -CanonType, -CanonValue, -CanonUnit
15
+ unit_base/3 % ?Unit, -BaseUnit, -Factor
16
+ ]).
17
+
18
+ % Aliases are grouped by unit family rather than by predicate.
19
+ :- discontiguous unit_symbol/3, unit_word/3.
20
+
21
+ %% unit_base(?Unit, -BaseUnit, -Factor)
22
+ % Factor converts one Unit into BaseUnit (Value * Factor). Symbols match
23
+ % exactly (case matters: MB is a megabyte, Mb is not listed); spelled-out words
24
+ % match case-insensitively. Ambiguous spellings (KB, Mb, bps, month, year) are
25
+ % deliberately absent so they are never silently equated.
26
+ unit_base(Unit, BaseUnit, Factor) :-
27
+ unit_symbol(Unit, BaseUnit, Factor).
28
+ unit_base(Unit, BaseUnit, Factor) :-
29
+ unit_word(Unit, BaseUnit, Factor).
30
+
31
+ % Duration family, base unit: s (second).
32
+ unit_symbol(ns, s, 1.0e-9).
33
+ unit_symbol(us, s, 1.0e-6).
34
+ unit_symbol(ms, s, 1.0e-3).
35
+ unit_symbol(s, s, 1).
36
+ unit_symbol(sec, s, 1).
37
+ unit_symbol(secs, s, 1).
38
+ unit_symbol(min, s, 60).
39
+ unit_symbol(mins, s, 60).
40
+ unit_symbol(h, s, 3600).
41
+ unit_symbol(hr, s, 3600).
42
+ unit_symbol(hrs, s, 3600).
43
+ unit_symbol(d, s, 86400).
44
+ unit_symbol(wk, s, 604800).
45
+ unit_word(nanosecond, s, 1.0e-9).
46
+ unit_word(nanoseconds, s, 1.0e-9).
47
+ unit_word(microsecond, s, 1.0e-6).
48
+ unit_word(microseconds, s, 1.0e-6).
49
+ unit_word(millisecond, s, 1.0e-3).
50
+ unit_word(milliseconds, s, 1.0e-3).
51
+ unit_word(second, s, 1).
52
+ unit_word(seconds, s, 1).
53
+ unit_word(minute, s, 60).
54
+ unit_word(minutes, s, 60).
55
+ unit_word(hour, s, 3600).
56
+ unit_word(hours, s, 3600).
57
+ unit_word(day, s, 86400).
58
+ unit_word(days, s, 86400).
59
+ unit_word(week, s, 604800).
60
+ unit_word(weeks, s, 604800).
61
+
62
+ % Data size family, base unit: byte. SI prefixes are decimal (kB = 1000 B);
63
+ % IEC prefixes are binary (KiB = 1024 B).
64
+ unit_symbol('B', byte, 1).
65
+ unit_symbol(kB, byte, 1000).
66
+ unit_symbol('MB', byte, 1000000).
67
+ unit_symbol('GB', byte, 1000000000).
68
+ unit_symbol('TB', byte, 1000000000000).
69
+ unit_symbol('KiB', byte, 1024).
70
+ unit_symbol('MiB', byte, 1048576).
71
+ unit_symbol('GiB', byte, 1073741824).
72
+ unit_symbol('TiB', byte, 1099511627776).
73
+ unit_word(byte, byte, 1).
74
+ unit_word(bytes, byte, 1).
75
+ unit_word(kilobyte, byte, 1000).
76
+ unit_word(kilobytes, byte, 1000).
77
+ unit_word(megabyte, byte, 1000000).
78
+ unit_word(megabytes, byte, 1000000).
79
+ unit_word(gigabyte, byte, 1000000000).
80
+ unit_word(gigabytes, byte, 1000000000).
81
+ unit_word(terabyte, byte, 1000000000000).
82
+ unit_word(terabytes, byte, 1000000000000).
83
+ unit_word(kibibyte, byte, 1024).
84
+ unit_word(kibibytes, byte, 1024).
85
+ unit_word(mebibyte, byte, 1048576).
86
+ unit_word(mebibytes, byte, 1048576).
87
+ unit_word(gibibyte, byte, 1073741824).
88
+ unit_word(gibibytes, byte, 1073741824).
89
+ unit_word(tebibyte, byte, 1099511627776).
90
+ unit_word(tebibytes, byte, 1099511627776).
91
+
92
+ % Percentage family, base unit: percent.
93
+ unit_symbol('%', percent, 1).
94
+ unit_word(percent, percent, 1).
95
+ unit_word(percentage, percent, 1).
96
+ unit_word(pct, percent, 1).
97
+ unit_word(basis_point, percent, 0.01).
98
+ unit_word(basis_points, percent, 0.01).
99
+
100
+ %% canonical_quantity(+ValueType, +Value, +Unit, -CanonType, -CanonValue, -CanonUnit)
101
+ % Numeric values with a known unit are converted to the family base unit.
102
+ % Integral numeric results are represented as int so int/number spellings of
103
+ % the same quantity compare equal. Non-numeric values and unknown units pass
104
+ % through unchanged.
105
+ canonical_quantity(ValueType, Value, Unit, CanonType, CanonValue, CanonUnit) :-
106
+ numeric_value_type(ValueType),
107
+ number(Value),
108
+ !,
109
+ ( known_unit(Unit, BaseUnit, Factor)
110
+ -> Scaled is Value * Factor,
111
+ CanonUnit = BaseUnit
112
+ ; Scaled = Value,
113
+ CanonUnit = Unit
114
+ ),
115
+ canonical_number(Scaled, CanonType, CanonValue).
116
+ canonical_quantity(ValueType, Value, Unit, ValueType, Value, Unit).
117
+
118
+ numeric_value_type(int).
119
+ numeric_value_type(number).
120
+
121
+ known_unit(Unit, BaseUnit, Factor) :-
122
+ atom(Unit),
123
+ Unit \== '',
124
+ ( unit_symbol(Unit, BaseUnit, Factor)
125
+ -> true
126
+ ; downcase_atom(Unit, Lower),
127
+ unit_word(Lower, BaseUnit, Factor)
128
+ ),
129
+ !.
130
+
131
+ % Integers (including big integers) stay exact. Floats have binary noise
132
+ % rounded away (0.1 h * 3600 = 360.00000000000006) before deciding whether the
133
+ % canonical value is integral; floats too large to round stay as they are.
134
+ canonical_number(Value, int, Value) :-
135
+ integer(Value),
136
+ !.
137
+ canonical_number(Value, Type, Canonical) :-
138
+ catch(Rounded is round(Value * 1.0e9) / 1.0e9, _, fail),
139
+ !,
140
+ ( Rounded =:= round(Rounded)
141
+ -> Type = int,
142
+ Canonical is integer(round(Rounded))
143
+ ; Type = number,
144
+ Canonical is float(Rounded)
145
+ ).
146
+ canonical_number(Value, number, Value).