Переделан слегка индекс, наброски алго поиска с сокращениями
This commit is contained in:
parent
9f67e139d6
commit
955c72c01c
26
README.md
26
README.md
@ -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, Вы можете использовать любое приемлемое сочетание.
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
14
aore/fias/wordvariation.py
Normal file
14
aore/fias/wordvariation.py
Normal 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
|
@ -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)
|
@ -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 }}
|
||||
|
Loading…
x
Reference in New Issue
Block a user