13
14:- module(takeoffmetric,
15 [ by_id/2, 16 search/2, 17 all/1, 18 sources/1 19 ]). 20:- use_module(library(csv)). 21:- use_module(library(apply)). 22:- use_module(library(lists)).
44all(Constants) :-
45 findall(C, constant_(_, C), Constants).
52by_id(Id, Constant) :-
53 text_to_string(Id, S),
54 constant_(S, C),
55 !,
56 Constant = C.
64search(Query, Constants) :-
65 text_to_string(Query, Q0),
66 string_lower(Q0, Q),
67 split_string(Q, " \t\n\r\v\f", " \t\n\r\v\f", Parts),
68 exclude(==(""), Parts, Words),
69 ( Words == []
70 -> Constants = []
71 ; findall(C, (constant_(_, C), matches(Words, C)), Constants)
72 ).
73
74matches(Words, C) :-
75 atomics_to_string([C.id, " ", C.category, " ", C.material_or_item, " ",
76 C.quantity, " ", C.condition, " ", C.source.publisher,
77 " ", C.notes], Text0),
78 string_lower(Text0, Text),
79 forall(member(W, Words), sub_string(Text, _, _, _, W)).
86sources(Sources) :-
87 findall(k(P, T, U, Ty), ( constant_(_, C),
88 _{publisher:P, title:T, url:U, type:Ty} :< C.source
89 ), Keys),
90 sort(Keys, Sorted),
91 maplist(key_source, Sorted, Sources).
92
93key_source(k(P, T, U, Ty), _{publisher:P, title:T, url:U, type:Ty}).
94
97
([id, category, material_or_item, quantity, value, unit, value_si,
99 unit_si, condition, source_publisher, source_title, source_url,
100 source_type, date_opened, used_by_tool_url, engine_file,
101 engine_symbol, notes, values_withheld, withheld_hosts]).
102
103table_clauses(File, Clauses) :-
104 csv_read_file(File, [Header|Rows], [convert(false), strip(false)]),
105 Header =.. [_|Names],
106 ( header(Names)
107 -> true
108 ; throw(error(format("~w: unexpected header", [File]), _))
109 ),
110 foldl(row_clause, Rows, Clauses, 2, _).
111
112row_clause(Row, constant_(Id, C), Line0, Line) :-
113 Line is Line0 + 1,
114 Row =.. [_|Atoms],
115 maplist(atom_string, Atoms, Fields),
116 catch(parse(Fields, C),
117 error(format(Fmt, Args), _),
118 ( format(string(Msg), Fmt, Args),
119 throw(error(format("data/constants.csv row ~w: ~w", [Line0, Msg]), _)) )),
120 Id = C.id.
121
122parse(Fields, C) :-
123 ( length(Fields, 20)
124 -> true
125 ; length(Fields, N), throw(error(format("~w fields, want 20", [N]), _))
126 ),
127 Fields = [Id, Category, Material, Quantity, Value, Unit, ValueSI, UnitSI,
128 Condition, Publisher, Title, URL, Type, Date, UsedBy, EngineFile,
129 EngineSymbol, Notes, Withheld0, Hosts],
130 withheld(Withheld0, Value, ValueSI, Hosts, Withheld),
131 date(Date, DateOpened),
132 split_list(UsedBy, "; ", UsedByList),
133 split_list(EngineFile, "; ", EngineFileList),
134 split_list(EngineSymbol, "; ", EngineSymbolList),
135 split_list(Hosts, ";", HostList),
136 C0 = _{id:Id, category:Category, material_or_item:Material,
137 quantity:Quantity, value_text:Value, unit:Unit, unit_si:UnitSI,
138 condition:Condition,
139 source:_{publisher:Publisher, title:Title, url:URL, type:Type},
140 date_opened:DateOpened, used_by_tool_url:UsedByList,
141 engine_file:EngineFileList, engine_symbol:EngineSymbolList,
142 notes:Notes, withheld:Withheld, withheld_hosts:HostList},
143 optional_number(Withheld, value, Value, C0, C1),
144 optional_number(false, value_si, ValueSI, C1, C).
145
146withheld("true", Value, ValueSI, _, true) :-
147 !,
148 149 ( Value == "", ValueSI == ""
150 -> true
151 ; throw(error(format("withheld row has a value", []), _))
152 ).
153withheld("false", Value, _, Hosts, false) :-
154 !,
155 ( Hosts \== ""
156 -> throw(error(format("withheld_hosts set on a row that is not withheld", []), _))
157 ; Value == ""
158 -> throw(error(format("value missing on a row that is not withheld", []), _))
159 ; true
160 ).
161withheld(Other, _, _, _, _) :-
162 throw(error(format("values_withheld: \"~w\", want true or false", [Other]), _)).
163
164optional_number(true, _, _, C, C) :- !.
165optional_number(_, _, "", C, C) :- !.
166optional_number(_, Key, Text, C0, C) :-
167 ( catch(number_string(N, Text), _, fail), number(N)
168 -> F is float(N),
169 put_dict(Key, C0, F, C)
170 ; throw(error(format("~w: \"~w\"", [Key, Text]), _))
171 ).
172
173date(Text, date(Y, M, D)) :-
174 ( split_string(Text, "-", "", [YS, MS, DS]),
175 string_length(YS, 4), string_length(MS, 2), string_length(DS, 2),
176 number_string(Y, YS), number_string(M, MS), number_string(D, DS),
177 integer(Y), integer(M), integer(D),
178 between(1, 12, M), D >= 1,
179 days_in_month(Y, M, Max), D =< Max
180 -> true
181 ; throw(error(format("date_opened: \"~w\"", [Text]), _))
182 ).
183
184days_in_month(Y, 2, D) :-
185 !,
186 ( ( Y mod 4 =:= 0, Y mod 100 =\= 0 ; Y mod 400 =:= 0 ) -> D = 29 ; D = 28 ).
187days_in_month(_, M, D) :-
188 nth1(M, [31, _, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], D).
189
190split_list("", _, []) :- !.
191split_list(Text, Separator, Parts) :-
192 atomic_list_concat(Atoms, Separator, Text),
193 maplist(atom_string, Atoms, Parts).
194
195term_expansion(constant_table, Clauses) :-
196 prolog_load_context(directory, Dir),
197 atomic_list_concat([Dir, '/../data/constants.csv'], File0),
198 absolute_file_name(File0, File, [access(read)]),
199 table_clauses(File, Clauses).
200
201constant_table
TakeoffMetric US construction estimating constants (lookup only)
A constant is a dict with the keys
id,category,material_or_item,quantity,value_text,unit,unit_si,condition,source,date_opened(a termdate(Y,M,D)),used_by_tool_url,engine_file,engine_symbol(lists of strings),notes,withheld(true or false) andwithheld_hosts(list of strings), plusvalueandvalue_si(floats). Text values are strings.sourceis a dict with the keyspublisher,title,urlandtype.When
withheldis true the publisher does not allow republication: the dict has novalueand novalue_sikey,value_textis the empty string, and the value is read at the URL of the source.value_siis also absent when the table leaves it empty.