Переделан слегка индекс, наброски алго поиска с сокращениями

This commit is contained in:
Jack Stdin 2016-02-20 16:18:09 +03:00
parent 9f67e139d6
commit 955c72c01c
6 changed files with 129 additions and 86 deletions

View File

@ -135,18 +135,22 @@ _Внимание_! Только Python 2.7, только PostgreSQL, тольк
## Настройка
### Первоначальная настройка базы данных
1. Настроим конфиг, он лежит в `aore/config/__init__.py`, в этом файле можно изменить `.dev` на `.prod`,
отредактировать, соотвественно, dev.py или prod.py: прописать параметры доступа к базе и путь,
куда будут сохраняться данные Sphinx; по этому пути дополнительно необходимо создать 3 папки: log, run и data
отредактировать, соотвественно, dev.py или prod.py: прописать параметры доступа к базе и путь,
куда будут сохраняться данные Sphinx; по этому пути дополнительно необходимо создать 3 папки: log, run и data
2. Создадим базу:
`sudo -u phias python manage.py -b create -s /tmp/fias_xml.rar` - из архива.
`sudo -u phias python manage.py -b create -s /tmp/fias_xml_unpacked` - из директории.
`sudo -u phias python manage.py -b create -s http` - онлайн, с сервера ФНС.
Также, можно указать конкретную версию ФИАС олько_ при http загрузке, с ключом `--update-version <num>`, где num -
номер версии ФИАС, все доступные версии можно получить, выполнив `manage.py -v`
- из архива `sudo -u phias python manage.py -b create -s /tmp/fias_xml.rar`
- из директории `sudo -u phias python manage.py -b create -s /tmp/fias_xml_unpacked`
- онлайн, с сервера ФНС `sudo -u phias python manage.py -b create -s http`
Также, можно указать конкретную версию ФИАС олько_ при http загрузке, с ключом `--update-version <num>`, где num -
номер версии ФИАС, все доступные версии можно получить, выполнив `manage.py -v`.
3. Проиндексируем Sphinx:
Windows: `python manage.py -c -i C://sphinx//indexer.exe -o C://sphinx//sphinx.conf`
Debian: `sudo python manage.py -c -i indexer -o /usr/local/sphinx/etc/sphinx.conf`
- Windows: `python manage.py -c -i C://sphinx//indexer.exe -o C://sphinx//sphinx.conf`
- Debian: `sudo python manage.py -c -i indexer -o /usr/local/sphinx/etc/sphinx.conf`
4. Затем запустим searchd:
Windows: `net start sphinxsearch`, при этом файл настройки должен быть доступен Sphinx'у.
Debian: `sudo searchd --config /usr/local/sphinx/etc/sphinx.conf`
- Windows: `net start sphinxsearch`, при этом файл настройки должен быть доступен Sphinx'у.
- Debian: `sudo searchd --config /usr/local/sphinx/etc/sphinx.conf`
5. Настроим WSGI server, я использую nginx + passenger, Вы можете использовать любое приемлемое сочетание.

View File

@ -1,31 +1,27 @@
# -*- coding: utf-8 -*-
import re
import time
import Levenshtein
import sphinxapi
import time
from aore.config import basic
from aore.config import sphinx_conf
from aore.fias.wordentry import WordEntry
from aore.fias.wordvariation import VariationType
from aore.miscutils.trigram import trigram
from aore.config import basic
class SphinxSearch:
# Config's
delta_len = 2
rating_limit_soft = 0.41
rating_limit_soft_count = 6
rating_limit_hard = 0.82
rating_limit_hard_count = 3
default_rating_delta = 2
regression_coef = 0.08
max_result = 10
exclude_freq_words = True
# Конфиги, которые в будущем, возможно, будут настраиваемы пользователем (как strong)
search_freq_words = True
def __init__(self, db):
self.db = db
@ -39,16 +35,17 @@ class SphinxSearch:
self.client_show.SetLimits(0, self.max_result)
self.client_show.SetConnectTimeout(3.0)
def __configure(self, index_name, wlen=None):
def __configure(self, index_name, word_len=None):
self.client_sugg.ResetFilters()
if index_name == sphinx_conf.index_sugg and wlen:
if index_name == sphinx_conf.index_sugg and word_len:
self.client_sugg.SetRankingMode(sphinxapi.SPH_RANK_WORDCOUNT)
self.client_sugg.SetFilterRange("len", int(wlen) - self.delta_len, int(wlen) + self.delta_len)
self.client_sugg.SetSelect("word, len, @weight+{}-abs(len-{}) AS krank".format(self.delta_len, wlen))
self.client_sugg.SetFilterRange("len", int(word_len) - self.delta_len, int(word_len) + self.delta_len)
self.client_sugg.SetSelect("word, len, @weight+{}-abs(len-{}) AS krank".format(self.delta_len, word_len))
self.client_sugg.SetSortMode(sphinxapi.SPH_SORT_EXTENDED, "krank DESC")
else:
self.client_show.SetRankingMode(sphinxapi.SPH_RANK_BM25)
#self.client_show.SetSortMode(sphinxapi.SPH_SORT_)
#self.client_show.SetSelect("aoid, fullname, @weight AS krank")
#self.client_show.SetSortMode(sphinxapi.SPH_SORT_EXTENDED, "krank DESC")
def __get_suggest(self, word, rating_limit, count):
word_len = str(len(word) / 2)
@ -83,35 +80,12 @@ class SphinxSearch:
return outlist
def __add_word_variations(self, word_entry, strong):
if word_entry.MT_MANY_SUGG and not strong:
suggs = self.__get_suggest(word_entry.word, self.rating_limit_soft, self.rating_limit_soft_count)
for suggestion in suggs:
word_entry.add_variation(suggestion[0])
if word_entry.MT_SOME_SUGG and not strong:
suggs = self.__get_suggest(word_entry.word, self.rating_limit_hard, self.rating_limit_hard_count)
for suggestion in suggs:
word_entry.add_variation(suggestion[0])
if word_entry.MT_LAST_STAR:
word_entry.add_variation(word_entry.word + '*')
if word_entry.MT_AS_IS:
word_entry.add_variation(word_entry.word)
if word_entry.MT_ADD_SOCR:
word_entry.add_variation_socr()
# Получает список объектов (слово), пропуская часто используемые слова
# Получает список объектов (слово)
def __get_word_entries(self, words, strong):
we_list = []
for word in words:
if word != '':
we = WordEntry(self.db, word)
if self.exclude_freq_words and we.is_freq_word:
pass
else:
self.__add_word_variations(we, strong)
assert we.get_variations() != "", "Cannot process sentence."
we_list.append(we)
we_list.append(WordEntry(self.db, word))
return we_list
@ -123,19 +97,44 @@ class SphinxSearch:
# сплитим текст на слова
words = split_phrase(text)
# получаем список объектов
# получаем список объектов (слов)
word_entries = self.__get_word_entries(words, strong)
word_count = len(word_entries)
# проверяем, есть ли вообще что-либо в списке объектов слов (или же все убрали как частое)
assert word_count > 0, "No legal words is specified"
# формируем строки для поиска в Сфинксе
for x in range(word_count, max(0, word_count - 3), -1):
self.client_show.AddQuery("\"{}\"/{} \"ул \"/0".format(" ".join(x.get_variations() for x in word_entries), x),
sphinx_conf.index_addjobj)
# получаем все вариации слов
all_variations = []
for we in word_entries:
for vari in we.variations_gen(strong, self.__get_suggest):
all_variations.append(vari)
self.__configure(sphinx_conf.index_addjobj)
good_vars = [v for v in all_variations if v.var_type == VariationType.normal]
freq_vars = [v for v in all_variations if v.var_type == VariationType.freq]
good_vars_word_count = len(set([v.parent for v in good_vars]))
freq_vars_word_count = len(set([v.parent for v in freq_vars]))
# формируем строки для поиска в Сфинксе
for i in range(good_vars_word_count, max(0, good_vars_word_count - 3), -1):
first_q = "\"{}\"/{}".format(" ".join(good_var.text for good_var in good_vars), i)
if self.search_freq_words:
for j in range(freq_vars_word_count, -1, -1):
if j == 0:
second_q = ""
else:
second_q = " \"{}\"/{}".format(" ".join(freq_var.text for freq_var in freq_vars), j)
second_q = second_q.replace("*", "")
print first_q + second_q
self.client_show.AddQuery(first_q + second_q, sphinx_conf.index_addjobj)
else:
print first_q
self.client_show.AddQuery(first_q, sphinx_conf.index_addjobj)
self.__configure(sphinx_conf.index_addjobj, word_count)
start_t = time.time()
rs = self.client_show.RunQueries()
@ -154,8 +153,9 @@ class SphinxSearch:
if not ma['attrs']['aoid'] in parsed_ids:
parsed_ids.append(ma['attrs']['aoid'])
results.append(
dict(aoid=ma['attrs']['aoid'], text=unicode(ma['attrs']['fullname']), ratio=ma['weight'], cort=i))
dict(aoid=ma['attrs']['aoid'], text=unicode(ma['attrs']['fullname']), ratio=ma['weight'],
cort=i))
# results.sort(key=lambda x: Levenshtein.ratio(text, x['text']), reverse=True)
# results.sort(key=lambda x: Levenshtein.ratio(text, x['text']), reverse=False)
return results

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
import re
from aore.config import basic
from aore.config import sphinx_conf
from aore.fias.wordvariation import WordVariation, VariationType
class WordEntry:
@ -42,42 +42,68 @@ class WordEntry:
MT_ADD_SOCR=['..10', '..x0']
)
rating_limit_soft = 0.41
rating_limit_soft_count = 6
rating_limit_hard = 0.82
rating_limit_hard_count = 3
def __init__(self, db, word):
self.db = db
self.word = str(word)
self.word_len = len(unicode(self.word))
self.variations = []
self.scname = None
self.is_freq_word = False
self.ranks = self.__get_ranks()
self.parameters = dict(IS_FREQ=False, SOCR_WORD=None)
self.ranks = self.__init_ranks()
for x, y in self.match_types.iteritems():
self.__dict__[x] = False
for z in y:
self.__dict__[x] = self.__dict__[x] or re.search(z, self.ranks) is not None
# Заполняем параметры слова
for mt_name, mt_values in self.match_types.iteritems():
self.__dict__[mt_name] = False
for mt_value in mt_values:
self.__dict__[mt_name] = self.__dict__[mt_name] or re.search(mt_value, self.ranks) is not None
# Если ищем по лайку, то точное совпадение не ищем (оно и так будет включено)
if self.MT_LAST_STAR:
self.MT_AS_IS = False
# Строка слишком котроткая, то по лайку не ищем, будет очень долго
if self.MT_LAST_STAR and self.word_len < sphinx_conf.min_length_to_star:
# Строка слишком котроткая, то по лайку не ищем, сфинкс такого не прожует
# Если найдено сокращение, то по лайку тоже не ищем TODO добавить это в правила
if self.MT_LAST_STAR and (self.word_len < sphinx_conf.min_length_to_star or self.MT_ADD_SOCR):
self.MT_LAST_STAR = False
self.MT_AS_IS = True
def add_variation_socr(self):
if self.scname:
self.add_variation(self.scname)
def variations_gen(self, strong, suggestion_func):
default_var_type = VariationType.normal
# Если слово встречается часто, ставим у всех вариантов тип VariationType.freq
if self.parameters['IS_FREQ']:
default_var_type = VariationType.freq
def add_variation(self, variation_string):
self.variations.append(variation_string)
# Добавляем подсказки (много штук)
if self.MT_MANY_SUGG and not strong:
suggs = suggestion_func(self.word, self.rating_limit_soft, self.rating_limit_soft_count)
for suggestion in suggs:
yield WordVariation(self, suggestion[0], default_var_type)
def get_variations(self):
if len(self.variations) == 1:
return self.variations[0]
return "{}".format(" ".join(self.variations))
# Добавляем подсказки (немного)
if self.MT_SOME_SUGG and not strong:
suggs = suggestion_func(self.word, self.rating_limit_hard, self.rating_limit_hard_count)
for suggestion in suggs:
yield WordVariation(self, suggestion[0], default_var_type)
def __get_ranks(self):
# Добавляем звездочку на конце
if self.MT_LAST_STAR:
yield WordVariation(self, self.word + '*', default_var_type)
# Добавляем слово "как есть"
if self.MT_AS_IS:
yield WordVariation(self, self.word, default_var_type)
# -- Дополнительные функции --
# Добавляем сокращение
if self.MT_ADD_SOCR:
if self.parameters['SOCR_WORD']:
yield WordVariation(self, self.parameters['SOCR_WORD'], VariationType.freq)
def __init_ranks(self):
sql_qry = "SELECT COUNT(*), NULL FROM \"AOTRIG\" WHERE word LIKE '{}%' AND LENGTH(word) > {} " \
"UNION ALL SELECT COUNT(*), NULL FROM \"AOTRIG\" WHERE word='{}' " \
"UNION ALL SELECT COUNT(*), MAX(scname) FROM \"SOCRBASE\" WHERE socrname ILIKE '{}'" \
@ -88,12 +114,12 @@ class WordEntry:
result = self.db.get_rows(sql_qry)
# Проставляем "сокращенное" сокращение, если нашли полное
if not self.scname:
self.scname = result[2][1]
if not self.parameters['SOCR_WORD']:
self.parameters['SOCR_WORD'] = result[2][1]
# Проверяем, если слово встречается слишком много раз
if len(result) == 5 and result[4][0] > 30000:
self.is_freq_word = True
self.parameters['IS_FREQ'] = True
# Формируем список найденных величин совпадений:
# result[x]
@ -118,4 +144,3 @@ class WordEntry:
def __str__(self):
return str(self.word)

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from enum import Enum # Типы вариаций слова
class VariationType(Enum):
normal = 0
freq = 1
class WordVariation:
def __init__(self, parent_word, text, var_type=VariationType.normal):
self.parent = parent_word
self.text = text
self.var_type = var_type

View File

@ -9,6 +9,6 @@
PATH.fullname || ', ' || child.shortname || ' ' || child.formalname AS fullname
FROM "ADDROBJ" AS child
, PATH
WHERE child.parentguid = PATH.aoguid
WHERE child.parentguid = PATH.aoguid AND actstatus = TRUE AND livestatus = TRUE AND nextid IS NULL
)
SELECT * FROM PATH WHERE AOLEVEL NOT IN (1,3)
SELECT p.cnt, p.aoid, p.fullname, length(p.fullname)-length(replace(p.fullname, ' ', '')) as wordcount FROM PATH p WHERE p.AOLEVEL NOT IN (1, 3)

View File

@ -10,9 +10,9 @@ source {{index_name}}
sql_query = {{!sql_query}}
sql_field_string = fullname
sql_attr_uint = len
sql_attr_string = aoid
sql_attr_string = aoguid
sql_attr_uint = aolevel
}
index {{ index_name }}