aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSix <unknown>2011-05-19 22:46:48 -0400
committerSix <unknown>2011-05-19 22:46:48 -0400
commita825456d3a44c21975822e5b5e64e1d3093a3803 (patch)
treeb4a027b48d715f15b9ea18de9ba1977960fca943
parenteed3f81529d7f0109b554031c9121cb5be1a2137 (diff)
downloadd2-a825456d3a44c21975822e5b5e64e1d3093a3803.tar.bz2
d2-a825456d3a44c21975822e5b5e64e1d3093a3803.tar.xz
d2-a825456d3a44c21975822e5b5e64e1d3093a3803.zip
created new search to handle multiple plots. Mainly for the Empty occupant
-rw-r--r--lib/d2/app/adapters/detail.py164
-rw-r--r--lib/d2/app/adapters/search.py190
-rw-r--r--lib/d2/bin/d2_agi_db_setup.py45
3 files changed, 338 insertions, 61 deletions
diff --git a/lib/d2/app/adapters/detail.py b/lib/d2/app/adapters/detail.py
index 7231faa..738230b 100644
--- a/lib/d2/app/adapters/detail.py
+++ b/lib/d2/app/adapters/detail.py
@@ -2,6 +2,9 @@ from d2.app.adapters import BaseAdapter
2from datetime import datetime 2from datetime import datetime
3from d2.db import DetailType 3from d2.db import DetailType
4from d2.db import Detail 4from d2.db import Detail
5from d2.db import Forge
6from d2.db import Plot
7from d2.db import Occupant
5from sqlalchemy import or_ 8from sqlalchemy import or_
6from sqlalchemy import func 9from sqlalchemy import func
7from d2.config import Config 10from d2.config import Config
@@ -12,12 +15,16 @@ from whoosh.index import create_in
12 15
13class DetailAdapter(BaseAdapter): 16class DetailAdapter(BaseAdapter):
14 17
15 def __init__(self, db, log, static, detail, detail_type): 18 def __init__(self, db, log, static, detail, detail_type, forge, plot,
19 occupant):
16 self._db = db 20 self._db = db
17 self._log = log 21 self._log = log
18 self._static = static 22 self._static = static
19 self._detail = detail 23 self._detail = detail
20 self._detail_type = detail_type 24 self._detail_type = detail_type
25 self._forge = forge
26 self._plot = plot
27 self._occupant = occupant
21 self._search_schema_ = None 28 self._search_schema_ = None
22 self._search_index_ = None 29 self._search_index_ = None
23 self._label_detail_type_ids_ = None 30 self._label_detail_type_ids_ = None
@@ -28,7 +35,8 @@ class DetailAdapter(BaseAdapter):
28 def load(cls, config=None, static=None): 35 def load(cls, config=None, static=None):
29 config = config or Config.load() 36 config = config or Config.load()
30 static = static or StaticData.load(config.db)() 37 static = static or StaticData.load(config.db)()
31 return cls(config.db, config.log, static, Detail, DetailType) 38 return cls(config.db, config.log, static, Detail, DetailType, Forge,
39 Plot, Occupant)
32 40
33 @property 41 @property
34 def _label_detail_type_ids(self): 42 def _label_detail_type_ids(self):
@@ -45,7 +53,7 @@ class DetailAdapter(BaseAdapter):
45 def _multiple_value_labels(self): 53 def _multiple_value_labels(self):
46 if not self._multiple_value_labels_: 54 if not self._multiple_value_labels_:
47 self._multiple_value_labels_ = [ 55 self._multiple_value_labels_ = [
48 self._static.detail_type.network_jack, 56 self._static.detail_type.network_jack,
49 ] 57 ]
50 return self._multiple_value_labels_ 58 return self._multiple_value_labels_
51 59
@@ -103,6 +111,8 @@ class DetailAdapter(BaseAdapter):
103 ).first() 111 ).first()
104 112
105 def _fetch_details(self, plot_id, occupant_id, date_start): 113 def _fetch_details(self, plot_id, occupant_id, date_start):
114 if not plot_id and not occupant_id:
115 return []
106 date_start = self.build_date(date_start) 116 date_start = self.build_date(date_start)
107 obj = self._db.session.query(self._detail 117 obj = self._db.session.query(self._detail
108 ).filter(self._detail.plot_id==plot_id 118 ).filter(self._detail.plot_id==plot_id
@@ -117,6 +127,7 @@ class DetailAdapter(BaseAdapter):
117 else: 127 else:
118 return obj 128 return obj
119 129
130
120 def insert_occupant_detail(self, occupant_id, detail_type_id, data, 131 def insert_occupant_detail(self, occupant_id, detail_type_id, data,
121 date_start=None, date_end=None): 132 date_start=None, date_end=None):
122 """Inserts detail data for an occupant 133 """Inserts detail data for an occupant
@@ -221,6 +232,14 @@ class DetailAdapter(BaseAdapter):
221 results = self.fetch_all_plot_details(plot_id, date_start) 232 results = self.fetch_all_plot_details(plot_id, date_start)
222 return self._details_to_tuple(results) 233 return self._details_to_tuple(results)
223 234
235 def occupant_dict(self, occupant_id, date_start=None):
236 details = self.fetch_all_occupant_details(occupant_id, date_start)
237 return self.details_to_dict(details)
238
239 def plot_dict(self, plot_id, date_start=None):
240 details = self.fetch_all_plot_details(plot_id, date_start)
241 return self.details_to_dict(details)
242
224 def _label_from_details(self, details): 243 def _label_from_details(self, details):
225 if details: 244 if details:
226 out = [] 245 out = []
@@ -243,6 +262,56 @@ class DetailAdapter(BaseAdapter):
243 out.append(u'Unknown') 262 out.append(u'Unknown')
244 return u' '.join(out) 263 return u' '.join(out)
245 264
265 def details_to_dict(self, details):
266 out = {}
267 if details:
268 out[u'label'] = self._label_from_details(details)
269 out[u'plot_id'] = details[0].plot_id
270 out[u'occupant_id'] = details[0].occupant_id
271 for detail in details:
272 if (detail.detail_type_id not in self._label_detail_type_ids
273 and detail.data):
274 name = detail.detail_type.name
275 key = name.lower().strip().replace(u' ', u'_')
276 if detail.detail_type_id in self._multiple_value_labels:
277 if key in out:
278 name = out[key][u'name'].strip()
279 if not name.endswith(u's'):
280 name = u"{0}s".format(name)
281 out[key][u'name'] = name
282 data = out[key][u'data']
283 data.append(detail.data)
284 out[key][u'data'] = data
285 else:
286 out[key] = {}
287 out[key][u'name'] = name
288 out[key][u'data'] = [detail.data]
289 else:
290 out[key] = {}
291 out[key][u'name'] = name
292 out[key][u'data'] = detail.data
293 return out
294
295 def _details_dict_to_tuple(self, details):
296 """Takes a details dictionary and turnes it into a named tuple
297 """
298 if details:
299 keys = [u'label', u'plot_id', u'occupant_id']
300 key_hold = []
301 vals = []
302 for key in details.keys():
303 if key not in keys and key not in key_hold:
304 key_hold.append(key)
305 key_hold.sort()
306 keys = keys + key_hold
307 for key in keys:
308 val = details[key]
309 if isinstance(val, dict):
310 val = self._make(val[u'name'], val[u'data'])
311 vals.append(val)
312 make = namedtuple('details', keys)
313 return make(*vals)
314
246 def _details_to_tuple(self, details): 315 def _details_to_tuple(self, details):
247 if details: 316 if details:
248 hold = {} 317 hold = {}
@@ -250,11 +319,11 @@ class DetailAdapter(BaseAdapter):
250 vals = [] 319 vals = []
251 hold[u'label'] = self._label_from_details(details) 320 hold[u'label'] = self._label_from_details(details)
252 if details[0].plot_id: 321 if details[0].plot_id:
253 hold[u'id'] = details[0].plot_id 322 hold[u'plot_id'] = details[0].plot_id
254 hold[u'id_type'] = u'plot' 323 hold[u'occupant_id'] = None
255 else: 324 else:
256 hold[u'id'] = details[0].occupant_id 325 hold[u'plot_id'] = None
257 hold[u'id_type'] = u'occupant' 326 hold[u'occupant_id'] = details[0].occupant_id
258 for detail in details: 327 for detail in details:
259 if (detail.detail_type_id not in self._label_detail_type_ids 328 if (detail.detail_type_id not in self._label_detail_type_ids
260 and detail.data): 329 and detail.data):
@@ -278,3 +347,84 @@ class DetailAdapter(BaseAdapter):
278 vals.append(hold[key]) 347 vals.append(hold[key])
279 make = namedtuple('details', keys) 348 make = namedtuple('details', keys)
280 return make(*vals) 349 return make(*vals)
350
351 def details(self, plot_id=None, occupant_id=None):
352 """ Returns a named tuple with complete details.
353 """
354 plot_id, occupant_id = self._validate(plot_id, occupant_id)
355 plot = self.details_to_dict(self.fetch_all_plot_details(plot_id))
356 occupant = self.details_to_dict(self.fetch_all_occupant_details(
357 occupant_id))
358
359 if plot and occupant:
360 occupant[u'plot_id'] = plot[u'plot_id']
361 occupant[u'plot'] = {
362 u'name': u'Plot',
363 u'data': plot['label']
364 }
365 del plot[u'plot_id']
366 del plot[u'occupant_id']
367 del plot[u'label']
368
369 for key, val in plot.items():
370 occupant[key] = val
371 return self._details_dict_to_tuple(occupant)
372
373 if plot:
374 return self._details_dict_to_tuple(plot)
375
376 if occupant:
377 return self._details_dict_to_tuple(occupant)
378
379 def _validate(self, plot_id, occupant_id, start_date=None):
380 """Validates that the given plot_id and occupant_id
381 are valid. Also checks forge
382 """
383 if not self._is_valid_plot(plot_id):
384 plot_id = None
385 if not self._is_valid_occupant(occupant_id):
386 occupant_id = None
387 if plot_id and occupant_id:
388 if not self._is_valid_forge(plot_id, occupant_id, start_date):
389 plot_id = None
390 return plot_id, occupant_id
391
392 def _is_valid_plot(self, plot_id):
393 """Validates that the given plot_id actually exists
394 """
395 if plot_id:
396 obj = self._db.session.query(self._plot
397 ).filter(self._plot.id==plot_id
398 ).first()
399 if obj:
400 return True
401 return False
402
403 def _is_valid_occupant(self, occupant_id):
404 """Validates that the given occupant_id actually exists
405 """
406 if occupant_id:
407 obj = self._db.session.query(self._occupant
408 ).filter(self._occupant.id==occupant_id
409 ).first()
410 if obj:
411 return True
412 return False
413
414 def _is_valid_forge(self, plot_id, occupant_id, date_start=None):
415 """Validates that the given plot_id and occupant_id are related
416 in the forge table.
417 """
418 date_start = self.build_date(date_start)
419 obj = self._db.session.query(self._forge
420 ).filter(self._forge.plot_id==plot_id
421 ).filter(self._forge.occupant_id==occupant_id
422 ).filter(self._forge.date_override==None
423 ).filter(self._forge.date_start<=date_start
424 ).filter(or_(self._forge.date_end>date_start,
425 self._forge.date_end==None)
426 ).first()
427 if obj:
428 return True
429 else:
430 return False
diff --git a/lib/d2/app/adapters/search.py b/lib/d2/app/adapters/search.py
index 92d0ee5..693cdda 100644
--- a/lib/d2/app/adapters/search.py
+++ b/lib/d2/app/adapters/search.py
@@ -13,16 +13,20 @@ from whoosh.index import create_in
13from whoosh.index import open_dir 13from whoosh.index import open_dir
14from whoosh.qparser import QueryParser 14from whoosh.qparser import QueryParser
15from whoosh.spelling import SpellChecker 15from whoosh.spelling import SpellChecker
16from d2.app.adapters.forge import ForgeAdapter
16import os 17import os
17import stat 18import stat
18import re 19import re
20from copy import deepcopy
21from collections import namedtuple
19 22
20class SearchAdapter(BaseAdapter): 23class SearchAdapter(BaseAdapter):
21 24
22 PERM_DIR = stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR 25 PERM_DIR = stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
26 SEARCH_ARGS = ['label', 'description', 'plot_id', 'occupant_id']
23 27
24 def __init__(self, db, log, index_directory, static, detail, detail_type, 28 def __init__(self, db, log, index_directory, static, detail, detail_type,
25 detail_adapter): 29 detail_adapter, forge_adapter):
26 self._db = db 30 self._db = db
27 self._log = log 31 self._log = log
28 self._index_directory = index_directory 32 self._index_directory = index_directory
@@ -30,18 +34,22 @@ class SearchAdapter(BaseAdapter):
30 self._detail = detail 34 self._detail = detail
31 self._detail_type = detail_type 35 self._detail_type = detail_type
32 self._detail_adapter = detail_adapter 36 self._detail_adapter = detail_adapter
37 self._forge_adapter = forge_adapter
33 self._label_detail_type_ids_ = None 38 self._label_detail_type_ids_ = None
34 self._search_schema_ = None 39 self._search_schema_ = None
35 self._index_ = None 40 self._index_ = None
36 self._index_directory_ = None 41 self._index_directory_ = None
42 self._make_search_result = namedtuple('Result', self.SEARCH_ARGS)
37 43
38 @classmethod 44 @classmethod
39 def load(cls, config=None, static=None, detail_adapter=None): 45 def load(cls, config=None, static=None, detail_adapter=None,
46 forge_adapter=None):
40 config = config or Config.load() 47 config = config or Config.load()
41 static = static or StaticData.load(config.db)() 48 static = static or StaticData.load(config.db)()
42 detail_adapter = detail_adapter or DetailAdapter.load(config, static) 49 detail_adapter = detail_adapter or DetailAdapter.load(config, static)
50 forge_adapter = forge_adapter or ForgeAdapter.load(config, static)
43 return cls(config.db, config.log, config.index_directory, static, 51 return cls(config.db, config.log, config.index_directory, static,
44 Detail, DetailType, detail_adapter) 52 Detail, DetailType, detail_adapter, forge_adapter)
45 53
46 @property 54 @property
47 def _label_detail_type_ids(self): 55 def _label_detail_type_ids(self):
@@ -58,8 +66,9 @@ class SearchAdapter(BaseAdapter):
58 if not self._search_schema_: 66 if not self._search_schema_:
59 self._search_schema_ = Schema( 67 self._search_schema_ = Schema(
60 label=TEXT(stored=True), 68 label=TEXT(stored=True),
61 id=NUMERIC(stored=True), 69 description=TEXT(stored=True),
62 id_type=ID(stored=True), 70 plot_id=NUMERIC(stored=True),
71 occupant_id=NUMERIC(stored=True),
63 context=TEXT) 72 context=TEXT)
64 return self._search_schema_ 73 return self._search_schema_
65 74
@@ -81,7 +90,9 @@ class SearchAdapter(BaseAdapter):
81 def build_index(self): 90 def build_index(self):
82 index = create_in(self.index_directory, self.search_schema) 91 index = create_in(self.index_directory, self.search_schema)
83 writer = index.writer() 92 writer = index.writer()
84 results = self.search_index_data() 93 results = self._search_index_data()
94 results = self._add_descriptions(results)
95 results = self._build_search_dictionaries(results)
85 for row in results: 96 for row in results:
86 writer.add_document(**row) 97 writer.add_document(**row)
87 writer.commit() 98 writer.commit()
@@ -92,8 +103,8 @@ class SearchAdapter(BaseAdapter):
92 speller = SpellChecker(ix.storage) 103 speller = SpellChecker(ix.storage)
93 speller.add_field(ix, "context") 104 speller.add_field(ix, "context")
94 105
95 def search_index_data(self, date_start=None): 106 def _search_index_data(self, date_start=None):
96 hold = {u'plot':{}, u'occupant':{}} 107 hold = {}
97 data = [] 108 data = []
98 date_start = self.build_date(date_start) 109 date_start = self.build_date(date_start)
99 results = self._db.session.query(self._detail 110 results = self._db.session.query(self._detail
@@ -103,45 +114,137 @@ class SearchAdapter(BaseAdapter):
103 self._detail.date_end==None) 114 self._detail.date_end==None)
104 ).all() 115 ).all()
105 for row in results: 116 for row in results:
106 if row.plot_id: 117 key = (row.plot_id, row.occupant_id)
107 if row.plot_id not in hold[u'plot']: 118 if key not in hold:
108 hold[u'plot'][row.plot_id] = [] 119 hold[key] = []
109 hold[u'plot'][row.plot_id].append(row) 120 hold[key].append(row)
110 else: 121
111 if row.occupant_id not in hold[u'occupant']: 122 for key, details in hold.items():
112 hold[u'occupant'][row.occupant_id] = [] 123 data.append(self._detail_adapter.details_to_dict(details))
113 hold[u'occupant'][row.occupant_id].append(row) 124
114 for key, details in hold[u'plot'].items():
115 data.append(self._details_to_search_dictionary(details))
116 for key, details in hold[u'occupant'].items():
117 data.append(self._details_to_search_dictionary(details))
118 return data 125 return data
119 126
127 def _add_descriptions(self, results):
128 out = []
129 for details in results:
130 if details[u'occupant_id']:
131 plots = self._get_plots(details[u'occupant_id'])
132 if plots:
133 for plot_id in plots:
134 details_ = deepcopy(details)
135 details_[u'plot_id'] = plot_id
136 details_[u'description'] = \
137 self._build_occupant_decription_with_plot(
138 details_)
139 out.append(details_)
140 else:
141 details[u'description'] = \
142 self._build_occupant_decription(details)
143 out.append(details)
144
145 else:
146 occupants = self._get_occupants(details[u'plot_id'])
147 details[u'description'] = \
148 self._build_plot_description(details, occupants)
149 out.append(details)
150 return out
151
152 def _get_plots(self, occupant_id, date_start=None):
153 """Returns a set of plot_ids from forge with the given occupant_id
154 """
155 out = set()
156 forges = self._forge_adapter.get_forge_by_occupant_id(occupant_id,
157 date_start)
158 for forge in forges:
159 if forge.plot_id:
160 out.add(forge.plot_id)
161 return out
162
163 def _get_occupants(self, plot_id, date_start=None):
164 """Returns a set of occupant_ids from forge with the given plot_id
165 """
166 out = set()
167 forges = self._forge_adapter.get_forge_by_plot_id(plot_id, date_start)
168 for forge in forges:
169 if forge.occupant_id:
170 out.add(forge.occupant_id)
171 return out
172
173 def _build_occupant_decription_with_plot(self, details):
174 out = []
175 plot_dict = self._detail_adapter.plot_dict(details[u'plot_id'])
176 out.append(u'Plot: {0}'.format(plot_dict[u'label']))
177 if u'title' in details:
178 out.append(details[u'title'][u'data'])
179 if u'department' in details:
180 out.append(details[u'department'][u'data'])
181 return u' '.join(out)
182
183 def _build_occupant_decription(self, details):
184 out = []
185 if u'title' in details:
186 out.append(details[u'title'][u'data'])
187 if u'department' in details:
188 out.append(details[u'department'][u'data'])
189 return u' '.join(out)
190
191 def _build_plot_description(self, details, occupants):
192 out = []
193 for occupant_id in occupants:
194 occupant = self._detail_adapter.occupant_dict(occupant_id)
195 out.append(occupant[u'label'])
196 return u', '.join(out)
197
198
120 def _split_label(self, data): 199 def _split_label(self, data):
121 name_pattern = re.compile("([A-Z]\w*)([A-Z].*)") 200 name_pattern = re.compile("([A-Z]\w*)([A-Z].*)")
122 return re.sub(name_pattern, r"\1" + " " + r"\2", data) 201 return re.sub(name_pattern, r"\1" + " " + r"\2", data)
123 202
203 def _label_to_initials(self, data):
204 out = []
205 data = data.split(u' ')
206 for word in data:
207 out.append(word[0])
208 if len(out) >= 2:
209 break
210 if len(out) == 2:
211 out = u''.join(out)
212 return out.upper()
213
214
215 def _build_search_dictionaries(self, results):
216 out = []
217 for details in results:
218 out.append(self._details_to_search_dictionary(details))
219 return out
220
124 def _details_to_search_dictionary(self, details): 221 def _details_to_search_dictionary(self, details):
222 out = {}
125 if details: 223 if details:
126 out = {} 224 context = [details[u'label']]
127 label = self._detail_adapter._label_from_details(details) 225 out[u'label'] = details[u'label']
128 out['label'] = label 226 out[u'plot_id'] = details[u'plot_id']
129 alt_label = self._split_label(label) 227 out[u'occupant_id'] = details[u'occupant_id']
130 hold = [label] 228 out[u'description'] = details[u'description']
131 if alt_label != label: 229 if details[u'plot_id']:
132 hold.append(alt_label) 230 if u'empty' in out['description'].lower():
133 231 context.append(u'Empty')
134 if details[0].plot_id:
135 out['id'] = details[0].plot_id
136 out['id_type'] = u'plot'
137 else: 232 else:
138 out['id'] = details[0].occupant_id 233 alt_label = self._split_label(out[u'label'])
139 out['id_type'] = u'occupant' 234 if alt_label != out[u'label']:
140 for detail in details: 235 context.append(alt_label)
141 if (detail.detail_type_id not in self._label_detail_type_ids 236 initials = self._label_to_initials(out[u'label'])
142 and detail.data): 237 if initials:
143 hold.append(detail.data) 238 context.append(initials)
144 out['context'] = u' '.join(hold) 239
240 for key, detail in details.items():
241 if isinstance(detail, dict):
242 if isinstance(detail[u'data'], list):
243 for data in detail[u'data']:
244 context.append(data)
245 else:
246 context.append(detail[u'data'])
247 out['context'] = u' '.join(context)
145 return out 248 return out
146 249
147 def _all_terms(self, user_query): 250 def _all_terms(self, user_query):
@@ -191,9 +294,10 @@ class SearchAdapter(BaseAdapter):
191 myquery = query.parse(user_query_string) 294 myquery = query.parse(user_query_string)
192 results = searcher.search(myquery) 295 results = searcher.search(myquery)
193 for row in results[0:len(results)]: 296 for row in results[0:len(results)]:
194 if row[u'id_type'] == 'plot': 297 out.append(self._make_search_result(
195 out.append(self._detail_adapter.plot_details(row[u'id'])) 298 row[u'label'],
196 else: 299 row[u'description'],
197 out.append(self._detail_adapter.occupant_details( 300 row[u'plot_id'],
198 row[u'id'])) 301 row[u'occupant_id']
302 ))
199 return out 303 return out
diff --git a/lib/d2/bin/d2_agi_db_setup.py b/lib/d2/bin/d2_agi_db_setup.py
index f9bdf96..4cf3602 100644
--- a/lib/d2/bin/d2_agi_db_setup.py
+++ b/lib/d2/bin/d2_agi_db_setup.py
@@ -1,22 +1,45 @@
1import argparse 1import argparse
2from d2.config import Config as D2Config 2from d2.config import Config
3from d2.app.model.static import StaticData
3from d2.db import Base 4from d2.db import Base
4from d2.db import Location 5from d2.db import Location
5from d2.db import Map 6from d2.db import Map
6 7from d2.db import Room
7class Config(object): 8from d2.db import Floor
8 9
9 DESCRIPTION = """ 10class Args(object):
10 Populate static tables that are AGI specific. 11
11 The database connection information is read from the d2 configuration 12 DATA = {
12 file: ~/.d2/config 13 u'Cleveland': {
13 """ 14 u'floors': [u'1', u'2', u'3'],
14 def __init__(self): 15 u'rooms': [u'a', u'b', u'c']
16 },
17 u'Bucharest': {
18 u'floors': [u'1', u'2', u'3', u'4', u'5', u'6', u'7']
19 }
20 }
21
22 DESCRIPTION = "Populate static tables that are AGI specific. The "\
23 "database connection information is read from the d2 "\
24 "configuration file: ~/.d2/config"
25
26 def __init__(self, config, static):
27 self._config = config
28 self._static = static
15 self._parser = argparse.ArgumentParser(description=self.DESCRIPTION) 29 self._parser = argparse.ArgumentParser(description=self.DESCRIPTION)
16 30
31 @classmethod
32 def load(cls, config=None):
33 config = config or Config.load()
34 static = StaticData.load(config.db)()
35 obj = cls(config, static)
36 return obj()
37
17 def __call__(self): 38 def __call__(self):
18 args = self._parser.parse_args() 39 args = self._parser.parse_args()
19 return D2Config.load() 40 args.config = self._config
41 args.static = self._static
42 return args
20 43
21 44
22class PopulateLocation(object): 45class PopulateLocation(object):