Загрузить файлы в «/»
This commit is contained in:
86
database.py
Normal file
86
database.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def search_database(database_file, query):
|
||||||
|
"""
|
||||||
|
Поиск по базе данных с группировкой тегов по приоритетам.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
database: список словарей с данными
|
||||||
|
query: список списков тегов, например [["Памятник"], ["Архитектура"]]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
список найденных записей с добавленными полями priority и измененным category_id
|
||||||
|
"""
|
||||||
|
with open(database_file, 'r', encoding='utf-8') as f:
|
||||||
|
database = json.load(f)
|
||||||
|
tag_mapping = {}
|
||||||
|
tag_counter = 1
|
||||||
|
|
||||||
|
# Создаем маппинг тегов
|
||||||
|
any_mode = False
|
||||||
|
for priority_group_idx, tag_group in enumerate(query, start=1):
|
||||||
|
if any_mode:
|
||||||
|
tag_counter+=1
|
||||||
|
if 'ANY' in tag_group:
|
||||||
|
tag_group.remove('ANY')
|
||||||
|
any_mode = True
|
||||||
|
else:
|
||||||
|
any_mode = False
|
||||||
|
for tag in tag_group:
|
||||||
|
if tag not in tag_mapping:
|
||||||
|
tag_mapping[tag] = {
|
||||||
|
'priority': priority_group_idx,
|
||||||
|
'tag_number': tag_counter
|
||||||
|
}
|
||||||
|
if not any_mode:
|
||||||
|
tag_counter += 1
|
||||||
|
|
||||||
|
# Поиск и обработка записей
|
||||||
|
results = []
|
||||||
|
seen_ids = set()
|
||||||
|
|
||||||
|
for entry in database:
|
||||||
|
categories = entry.get('category_id')
|
||||||
|
categories =list(categories.split(', '))
|
||||||
|
found = False
|
||||||
|
for ctg in categories:
|
||||||
|
if ctg in tag_mapping:
|
||||||
|
found = True
|
||||||
|
category= ctg
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
entry_id = (entry.get('coordinate'), entry.get('title'))
|
||||||
|
|
||||||
|
if entry_id not in seen_ids:
|
||||||
|
seen_ids.add(entry_id)
|
||||||
|
result_entry = entry.copy()
|
||||||
|
result_entry['type'] = tag_mapping[category]['tag_number']
|
||||||
|
result_entry['priority'] = tag_mapping[category]['priority']
|
||||||
|
results.append(result_entry)
|
||||||
|
|
||||||
|
# Сортируем по приоритету
|
||||||
|
tag_priority = {v['tag_number']: v['priority'] for v in tag_mapping.values()}
|
||||||
|
# Then verify no conflicts
|
||||||
|
if len(set((v['tag_number'], v['priority']) for v in tag_mapping.values())) != len(tag_priority):
|
||||||
|
raise ValueError("Conflicting priorities for same tag_number")
|
||||||
|
return results,tag_priority
|
||||||
|
|
||||||
|
|
||||||
|
# Пример использования
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Загрузка базы данных из файла
|
||||||
|
with open('output.json', 'r', encoding='utf-8') as f:
|
||||||
|
database = json.load(f)
|
||||||
|
|
||||||
|
# Запрос с группами тегов
|
||||||
|
query = [["Памятник", "Музей"], ["Архитектура"], ["Парк", "Сквер"]]
|
||||||
|
|
||||||
|
# Выполнение поиска
|
||||||
|
results = search_database(database, query)
|
||||||
|
print(results)
|
||||||
|
# Сохранение результатов
|
||||||
|
with open('search_results.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f"Найдено записей: {len(results)}")
|
||||||
389
new_parser_test.py
Normal file
389
new_parser_test.py
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
API_KEY = "AIzaSyBXGBGH5NDY8L_jVmq2zb4i8xYEV2qN-48"
|
||||||
|
API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"
|
||||||
|
|
||||||
|
# Базовый список доступных тегов
|
||||||
|
TAG_LIST = [
|
||||||
|
"Памятник", "Архитектура", "Мозаика", "Панно",
|
||||||
|
"Парк", "Водоем", "Озеро", "Ботанический сад",
|
||||||
|
"Архитектура", "Площадь", "Набережная", "Канатная дорога",
|
||||||
|
"Сквер", "Музей", "Собор", "Церковь",
|
||||||
|
"Храм", "Театр", "Выставка", "Мост", "Художественное пространство", "Вокзал", "Фонтан", "Лестница", "Кинотеатр",
|
||||||
|
"Дом Культуры", "Планетарий", "Галерея", "Ресторан", "Бар", "Бистро", "Кафе"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_extraction_prompt(conversation_history: List[Dict[str, str]], tag_list: List[str]) -> str:
|
||||||
|
"""
|
||||||
|
Строим промпт для ИИ-парсера с историей диалога
|
||||||
|
"""
|
||||||
|
example_user_1 = "Я хочу культурно провести время, у меня 2 часа, я сейчас на улице Доброй около дома 1."
|
||||||
|
example_json_1 = {
|
||||||
|
"tags": [["Музей", "Галерея", "Театр","Выставка","ANY"]],
|
||||||
|
"user_location": "улица Добрая, дом 1",
|
||||||
|
"time": 120
|
||||||
|
}
|
||||||
|
example_user_2 = "Я хочу сходить в музей пройти по парку и где-нибудь поесть, у меня 3.5 часа, я на Коминтерна, 115"
|
||||||
|
example_json_2 = {
|
||||||
|
"tags": [["Музей", "Парк"], ["Ресторан", "Бистро", "Кафе", "ANY"]],
|
||||||
|
"user_location": "улица Коминтерна, дом 115",
|
||||||
|
"time": 120
|
||||||
|
}
|
||||||
|
instr = f"""
|
||||||
|
Ты — ИИ-парсер на русском языке.
|
||||||
|
Задача: из диалога с пользователем извлечь строго JSON c полями:
|
||||||
|
|
||||||
|
- tags: упорядоченный список релевантных тегов ТОЛЬКО из данного tag_list (не придумывай новых), теги должны быть сгруппированы по приоритету посещения
|
||||||
|
- user_location: строка с текущим местоположением пользователя (примерный адрес), должен содержать название улицы/площади/... Но не может быть ориентиром на объект без конкретного адреса или чем-то абстрактным: дома, у памятника Пушкину, у станции метро, на автобусной остановке... не подходят, но я около площади Революции подходит.
|
||||||
|
- time: целое число минут на прогулку (преобразуй выражения типа "2 часа" -> 120, "полчаса" -> 30 и т.п.). Пользователь может указать до скольки он свободен тогда используй фиксированный формат until <время в 24 формате HH:MM>. 'Я хочу погулять до 6 вечера' -> тогда в поле время 'until 18:00'.
|
||||||
|
|
||||||
|
Работа с тегами:
|
||||||
|
Группируй теги по ПРИОРИТЕТУ посещения:
|
||||||
|
- Если пользователь говорит "сначала музей, потом кофе" -> [['Музей'], ['Кофейня']]
|
||||||
|
- Если "хочу в музей и кофе" (без явного порядка) -> [['Музей', 'Кофейня']]
|
||||||
|
- Если неопределённые предпочтения "погулять по интересным местам", добавь ключевое слово 'ANY' к группе подходящих тегов.
|
||||||
|
- Запрос пользователя может быть абстрактным "я хочу погулять по улице". Подбери релевантные теги на открытом воздухе (т.к. погулять можно интерпретировать как походить по улице не заходя куда-либо) например: [['Архитектура','Сквер','Площадь', 'ANY']]. Запрос может быть абстрактным, но не может быть полностью неопределенным. Примеры неопределенных ответов: "Хочу куда-нибудь сходить", "Я не знаю куда сходить", "Куда мне сходить?"
|
||||||
|
- Указывай все теги из tag_list которые явно подходят!!! Примеры могут быть неполными!
|
||||||
|
|
||||||
|
ИСПОЛЬЗУЙ ТОЛЬКО ТЕГИ ИЗ ДАННОГО СПИСКА!
|
||||||
|
|
||||||
|
Требования:
|
||||||
|
- Верни ТОЛЬКО валидный JSON без префиксов, комментариев и форматирования в кодовых блоках.
|
||||||
|
- Если в диалоге с пользователем нет данных для какого-либо поля, поставь null в этом поле.
|
||||||
|
- Не добавляй никаких дополнительных полей, только: tags, user_location, time.
|
||||||
|
- tags должны быть подмножеством из tag_list. Не включай нерелевантные теги.
|
||||||
|
|
||||||
|
tag_list = {json.dumps(tag_list, ensure_ascii=False, indent=0)}
|
||||||
|
|
||||||
|
Пример 1:
|
||||||
|
Пользователь: "{example_user_1}"
|
||||||
|
Ожидаемый JSON:
|
||||||
|
{json.dumps(example_json_1, ensure_ascii=False, indent=2)}
|
||||||
|
|
||||||
|
Пример 2:
|
||||||
|
Пользователь: "{example_user_2}"
|
||||||
|
Ожидаемый JSON:
|
||||||
|
{json.dumps(example_json_2, ensure_ascii=False, indent=2)}
|
||||||
|
|
||||||
|
|
||||||
|
История диалога:
|
||||||
|
"""
|
||||||
|
|
||||||
|
for msg in conversation_history:
|
||||||
|
role = msg["role"]
|
||||||
|
content = msg["content"]
|
||||||
|
if role == "user":
|
||||||
|
instr += f"\nПользователь: \"{content}\""
|
||||||
|
elif role == "assistant":
|
||||||
|
instr += f"\nАссистент: \"{content}\""
|
||||||
|
|
||||||
|
instr += "\n\nТеперь извлеки JSON из диалога выше."
|
||||||
|
|
||||||
|
return instr.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def build_conversational_prompt(conversation_history: List[Dict[str, str]], missing: List[str],
|
||||||
|
tag_list: List[str]) -> str:
|
||||||
|
"""
|
||||||
|
Строим промпт для генерации уточняющего вопроса
|
||||||
|
"""
|
||||||
|
missing_desc = {
|
||||||
|
"tags": "какие места/категории интересны",
|
||||||
|
"user_location": "текущее местоположение (адрес)",
|
||||||
|
"time": "время на прогулку"
|
||||||
|
}
|
||||||
|
|
||||||
|
missing_fields_str = ", ".join([missing_desc[f] for f in missing])
|
||||||
|
|
||||||
|
instr = f"""
|
||||||
|
Ты — вежливый ИИ-ассистент, который помогает пользователю спланировать прогулку.
|
||||||
|
Твоя задача — задать краткие вопросы, чтобы уточнить недостающую информацию.
|
||||||
|
|
||||||
|
Недостающие данные: {missing_fields_str}
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
- Задай ТОЛЬКО простык, вежливый и краткий вопрос.
|
||||||
|
- НЕ давай рекомендаций, комментариев или предложений.
|
||||||
|
- НЕ предлагай варианты, кроме случаев когда нужно выбрать категории (tags).
|
||||||
|
- Если нужно уточнить категории (tags), предложи категории ИЗ ЭТОГО СПИСКА: {json.dumps(tag_list, ensure_ascii=False)}
|
||||||
|
- Верни ТОЛЬКО текст вопроса без дополнительных пояснений.
|
||||||
|
|
||||||
|
История диалога:
|
||||||
|
"""
|
||||||
|
|
||||||
|
for msg in conversation_history:
|
||||||
|
role = msg["role"]
|
||||||
|
content = msg["content"]
|
||||||
|
if role == "user":
|
||||||
|
instr += f"\nПользователь: {content}"
|
||||||
|
elif role == "assistant":
|
||||||
|
instr += f"\nАссистент: {content}"
|
||||||
|
|
||||||
|
instr += "\n\nТеперь задай уточняющий вопрос:"
|
||||||
|
|
||||||
|
return instr.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def call_gemini(prompt: str) -> str:
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-goog-api-key": API_KEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"parts": [{"text": prompt}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = requests.post(API_URL, headers=headers, json=payload, timeout=30)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
# Извлекаем текст первого кандидата
|
||||||
|
text = ""
|
||||||
|
try:
|
||||||
|
text = data["candidates"][0]["content"]["parts"][0]["text"]
|
||||||
|
except Exception:
|
||||||
|
# Попытка fallback: иногда ответ может приходить иначе
|
||||||
|
text = json.dumps(data, ensure_ascii=False)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def strip_code_fences(text: str) -> str:
|
||||||
|
s = text.strip()
|
||||||
|
# Убираем ограду `````` или ``````
|
||||||
|
if s.startswith("```"):
|
||||||
|
# срезаем первые ```
|
||||||
|
s = s[3:].lstrip()
|
||||||
|
# если указан язык (например, json), уберём первую строку
|
||||||
|
first_nl = s.find("\n")
|
||||||
|
if first_nl != -1:
|
||||||
|
lang = s[:first_nl].strip().lower()
|
||||||
|
# если это похоже на метку языка, отбрасываем её
|
||||||
|
if lang in ("json", "yaml", "yml", "xml", "markdown", "md", "txt"):
|
||||||
|
s = s[first_nl + 1:]
|
||||||
|
else:
|
||||||
|
# если метка не признана, всё равно продолжаем с текущим s
|
||||||
|
pass
|
||||||
|
# убираем завершающие ```
|
||||||
|
if s.rstrip().endswith("```"):
|
||||||
|
s = s.rstrip()[:-3]
|
||||||
|
|
||||||
|
return s.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_first_json(text: str):
|
||||||
|
# 1) снимаем ограду
|
||||||
|
s = strip_code_fences(text)
|
||||||
|
|
||||||
|
# 2) пробуем распарсить как есть
|
||||||
|
try:
|
||||||
|
return json.loads(s)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 3) ищем первый сбалансированный объект { ... } без рекурсивного regex
|
||||||
|
start = s.find("{")
|
||||||
|
while start != -1:
|
||||||
|
depth = 0
|
||||||
|
in_str = False
|
||||||
|
esc = False
|
||||||
|
for i in range(start, len(s)):
|
||||||
|
ch = s[i]
|
||||||
|
if in_str:
|
||||||
|
if esc:
|
||||||
|
esc = False
|
||||||
|
elif ch == "\\":
|
||||||
|
esc = True
|
||||||
|
elif ch == '"':
|
||||||
|
in_str = False
|
||||||
|
else:
|
||||||
|
if ch == '"':
|
||||||
|
in_str = True
|
||||||
|
elif ch == "{":
|
||||||
|
depth += 1
|
||||||
|
elif ch == "}":
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
cand = s[start:i + 1]
|
||||||
|
try:
|
||||||
|
return json.loads(cand)
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
# ищем следующий '{', если текущий блок не распарсился
|
||||||
|
start = s.find("{", start + 1)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_complete(result: Dict[str, Any]) -> bool:
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Проверяем наличие ключей
|
||||||
|
for key in ["tags", "user_location", "time"]:
|
||||||
|
if key not in result:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Проверяем содержимое
|
||||||
|
tags_ok = isinstance(result["tags"], list) and len(result["tags"]) > 0
|
||||||
|
loc_ok = isinstance(result["user_location"], str) and len(result["user_location"].strip()) > 0
|
||||||
|
|
||||||
|
# time может быть int или строка формата "until HH:MM"
|
||||||
|
time_val = result["time"]
|
||||||
|
if isinstance(time_val, int) and time_val > 0:
|
||||||
|
time_ok = True
|
||||||
|
elif isinstance(time_val, str) and time_val.startswith("until "):
|
||||||
|
time_ok = True
|
||||||
|
else:
|
||||||
|
time_ok = False
|
||||||
|
|
||||||
|
return tags_ok and loc_ok and time_ok
|
||||||
|
|
||||||
|
|
||||||
|
def missing_fields(result: Dict[str, Any]) -> List[str]:
|
||||||
|
missing = []
|
||||||
|
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
return ["tags", "user_location", "time"]
|
||||||
|
|
||||||
|
if "tags" not in result or not isinstance(result["tags"], list) or len(result["tags"]) == 0:
|
||||||
|
missing.append("tags")
|
||||||
|
|
||||||
|
if "user_location" not in result or not isinstance(result["user_location"], str) or not result[
|
||||||
|
"user_location"].strip():
|
||||||
|
missing.append("user_location")
|
||||||
|
|
||||||
|
time_val = result.get("time")
|
||||||
|
if isinstance(time_val, int) and time_val > 0:
|
||||||
|
pass # OK
|
||||||
|
elif isinstance(time_val, str) and time_val.startswith("until "):
|
||||||
|
pass # OK
|
||||||
|
else:
|
||||||
|
missing.append("time")
|
||||||
|
|
||||||
|
return missing
|
||||||
|
|
||||||
|
|
||||||
|
def convert_time_to_minutes(time_val, current_time: datetime) -> int:
|
||||||
|
"""
|
||||||
|
Конвертирует значение времени в минуты.
|
||||||
|
Если time_val — это строка формата "until HH:MM", вычисляет разницу от текущего времени.
|
||||||
|
"""
|
||||||
|
if isinstance(time_val, int):
|
||||||
|
return time_val
|
||||||
|
|
||||||
|
if isinstance(time_val, str) and time_val.startswith("until "):
|
||||||
|
time_str = time_val.replace("until ", "").strip()
|
||||||
|
try:
|
||||||
|
target_time = datetime.strptime(time_str, "%H:%M")
|
||||||
|
# Устанавливаем дату как текущую
|
||||||
|
target_datetime = current_time.replace(
|
||||||
|
hour=target_time.hour,
|
||||||
|
minute=target_time.minute,
|
||||||
|
second=0,
|
||||||
|
microsecond=0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Если целевое время уже прошло сегодня, предполагаем завтра
|
||||||
|
if target_datetime <= current_time:
|
||||||
|
target_datetime += timedelta(days=1)
|
||||||
|
|
||||||
|
delta = target_datetime - current_time
|
||||||
|
minutes = int(delta.total_seconds() / 60)
|
||||||
|
return minutes
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_with_ai(conversation_history: List[Dict[str, str]]) -> Dict[str, Any]:
|
||||||
|
prompt = build_extraction_prompt(conversation_history, TAG_LIST)
|
||||||
|
model_text = call_gemini(prompt)
|
||||||
|
parsed = extract_first_json(model_text) or {}
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def ask_ai_for_clarification(conversation_history: List[Dict[str, str]], missing: List[str]) -> str:
|
||||||
|
"""
|
||||||
|
Генерируем уточняющий вопрос через ИИ
|
||||||
|
"""
|
||||||
|
prompt = build_conversational_prompt(conversation_history, missing, TAG_LIST)
|
||||||
|
question = call_gemini(prompt)
|
||||||
|
return question.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def parse():
|
||||||
|
if not API_KEY or API_KEY.startswith("<"):
|
||||||
|
raise RuntimeError("Установите переменную окружения GEMINI_API_KEY с вашим ключом API.")
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Опишите ваш запрос (например: 'Я хочу культурно провести время потом попить кофе, у меня 2 часа, я сейчас на улице Дальней около дома 8.')")
|
||||||
|
|
||||||
|
conversation_history = []
|
||||||
|
|
||||||
|
# Первое сообщение от пользователя
|
||||||
|
user_input = input("> ").strip()
|
||||||
|
conversation_history.append({"role": "user", "content": user_input})
|
||||||
|
|
||||||
|
# Текущее время (Москва, UTC+3)
|
||||||
|
current_time = datetime.now()
|
||||||
|
|
||||||
|
# Первая попытка извлечения
|
||||||
|
result = extract_with_ai(conversation_history)
|
||||||
|
|
||||||
|
# Итеративный диалог
|
||||||
|
max_tries = 10
|
||||||
|
tries = 0
|
||||||
|
|
||||||
|
while (not is_complete(result)) and tries < max_tries:
|
||||||
|
missing = missing_fields(result)
|
||||||
|
|
||||||
|
if not missing:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Генерируем уточняющий вопрос через ИИ
|
||||||
|
ai_question = ask_ai_for_clarification(conversation_history, missing)
|
||||||
|
print(ai_question)
|
||||||
|
|
||||||
|
conversation_history.append({"role": "assistant", "content": ai_question})
|
||||||
|
|
||||||
|
# Получаем ответ пользователя
|
||||||
|
user_response = input("> ").strip()
|
||||||
|
conversation_history.append({"role": "user", "content": user_response})
|
||||||
|
|
||||||
|
# Повторно извлекаем данные с учётом новой информации
|
||||||
|
result = extract_with_ai(conversation_history)
|
||||||
|
tries += 1
|
||||||
|
|
||||||
|
# Конвертируем время если нужно
|
||||||
|
if "time" in result and isinstance(result["time"], str):
|
||||||
|
minutes = convert_time_to_minutes(result["time"], current_time)
|
||||||
|
if minutes is not None:
|
||||||
|
result["time"] = minutes
|
||||||
|
|
||||||
|
# Печатаем финальный JSON
|
||||||
|
out = {
|
||||||
|
"tags": result.get("tags", None),
|
||||||
|
"user_location": result.get("user_location", None),
|
||||||
|
"time": result.get("time", None),
|
||||||
|
}
|
||||||
|
return json.dumps(out, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
out = parse()
|
||||||
|
print(out)
|
||||||
|
|
||||||
7
places_from_xlsx.py
Normal file
7
places_from_xlsx.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
# Читаем конкретный лист из Excel
|
||||||
|
df = pd.read_excel('cultural_objects_mnn_final.xlsx', sheet_name='list1')
|
||||||
|
|
||||||
|
# Сохраняем в JSON с красивым форматированием
|
||||||
|
df.to_json('output.json', orient='records', force_ascii=False, indent=4)
|
||||||
454
route.py
Normal file
454
route.py
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
import math
|
||||||
|
import itertools
|
||||||
|
import requests
|
||||||
|
from typing import List, Tuple, Dict, Optional, Set
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
def __init__(self, coord: List[float], tag: str, visit_time: int):
|
||||||
|
self.coord = coord
|
||||||
|
self.tag = tag
|
||||||
|
self.visit_time = visit_time
|
||||||
|
self.matrix_index = None # Индекс точки в матрице расстояний
|
||||||
|
self.estimated_time = None # Оценочное время (перемещение + посещение)
|
||||||
|
|
||||||
|
def haversine(coord1: List[float], coord2: List[float]) -> float:
|
||||||
|
"""Calculate the great-circle distance between two points in kilometers."""
|
||||||
|
lat1, lon1 = coord1
|
||||||
|
lat2, lon2 = coord2
|
||||||
|
R = 6371 # Earth radius in km
|
||||||
|
|
||||||
|
dlat = math.radians(lat2 - lat1)
|
||||||
|
dlon = math.radians(lon2 - lon1)
|
||||||
|
a = (math.sin(dlat/2) * math.sin(dlat/2) +
|
||||||
|
math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
|
||||||
|
math.sin(dlon/2) * math.sin(dlon/2))
|
||||||
|
return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||||
|
|
||||||
|
def filter_points_by_time(start_coord: List[float], points: List[Point], total_time: int) -> List[Point]:
|
||||||
|
"""Filter points based on straight-line distance and visit time."""
|
||||||
|
filtered = []
|
||||||
|
for point in points:
|
||||||
|
distance = haversine(start_coord, point.coord)
|
||||||
|
travel_time = distance * 10 # Assume 6 km/h walking speed (10 min/km)
|
||||||
|
point.estimated_time = travel_time + point.visit_time
|
||||||
|
if point.estimated_time <= total_time:
|
||||||
|
filtered.append(point)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
def filter_points_by_tag_proximity(points: List[Point], max_per_tag: int = 10) -> List[Point]:
|
||||||
|
"""For each tag, keep only the closest points (by estimated time)."""
|
||||||
|
# Group points by tag
|
||||||
|
tag_to_points = {}
|
||||||
|
for point in points:
|
||||||
|
if point.tag not in tag_to_points:
|
||||||
|
tag_to_points[point.tag] = []
|
||||||
|
tag_to_points[point.tag].append(point)
|
||||||
|
|
||||||
|
# For each tag, sort by estimated time and keep top max_per_tag
|
||||||
|
filtered_points = []
|
||||||
|
for tag, tag_points in tag_to_points.items():
|
||||||
|
# Sort by estimated time (ascending)
|
||||||
|
sorted_points = sorted(tag_points, key=lambda p: p.estimated_time)
|
||||||
|
# Keep at most max_per_tag points
|
||||||
|
kept_points = sorted_points[:max_per_tag]
|
||||||
|
filtered_points.extend(kept_points)
|
||||||
|
print(f"Tag '{tag}': kept {len(kept_points)} out of {len(tag_points)} points")
|
||||||
|
|
||||||
|
return filtered_points
|
||||||
|
|
||||||
|
def get_duration_matrix(points: List[List[float]]) -> Optional[Tuple[List[List[float]], List[List[float]]]]:
|
||||||
|
"""Get duration matrix from server."""
|
||||||
|
url = "https://ha1m-maap-pdmc.gw-1a.dockhost.net/table"
|
||||||
|
payload = {"points": points}
|
||||||
|
headers = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, headers=headers, timeout=30)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data.get("distances"), data.get("durations")
|
||||||
|
else:
|
||||||
|
print(f"Server error: {response.status_code}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error requesting duration matrix: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def group_points_by_significance(points: List[Point], tag_importance: Dict[str, int]) -> Dict[int, List[Point]]:
|
||||||
|
"""Group points by their importance level."""
|
||||||
|
grouped = {}
|
||||||
|
for point in points:
|
||||||
|
importance = tag_importance.get(point.tag, float('inf'))
|
||||||
|
if importance not in grouped:
|
||||||
|
grouped[importance] = []
|
||||||
|
grouped[importance].append(point)
|
||||||
|
return grouped
|
||||||
|
|
||||||
|
def calculate_route_time_with_matrix(route: List[Point], start_coord: List[float],
|
||||||
|
duration_matrix: List[List[float]]) -> float:
|
||||||
|
"""Calculate total time for a route using the duration matrix."""
|
||||||
|
total_time = 0
|
||||||
|
current_index = 0 # Start point index
|
||||||
|
|
||||||
|
for point in route:
|
||||||
|
next_index = point.matrix_index
|
||||||
|
travel_time_seconds = duration_matrix[current_index][next_index]
|
||||||
|
travel_time_minutes = travel_time_seconds / 60.0
|
||||||
|
total_time += travel_time_minutes + point.visit_time
|
||||||
|
current_index = next_index
|
||||||
|
|
||||||
|
return total_time
|
||||||
|
|
||||||
|
def check_tags_constraint(points: List[Point]) -> bool:
|
||||||
|
"""Check if there are no more than 5 unique tags."""
|
||||||
|
unique_tags = set(point.tag for point in points)
|
||||||
|
return len(unique_tags) <= 5
|
||||||
|
|
||||||
|
def generate_routes_exact_tags(grouped_points: Dict[int, List[Point]],
|
||||||
|
all_tags: Set[str],
|
||||||
|
tag_importance: Dict[str, int]) -> List[List[Point]]:
|
||||||
|
"""Generate routes where each tag is visited exactly once using different coordinates."""
|
||||||
|
# Create a mapping from tag to points
|
||||||
|
tag_to_points = {}
|
||||||
|
for points_list in grouped_points.values():
|
||||||
|
for point in points_list:
|
||||||
|
if point.tag not in tag_to_points:
|
||||||
|
tag_to_points[point.tag] = []
|
||||||
|
tag_to_points[point.tag].append(point)
|
||||||
|
|
||||||
|
# For each tag, we need to select exactly one point
|
||||||
|
tag_selections = []
|
||||||
|
for tag in all_tags:
|
||||||
|
tag_selections.append(tag_to_points[tag])
|
||||||
|
|
||||||
|
# Generate all combinations of points (one per tag)
|
||||||
|
all_routes = []
|
||||||
|
print(len(list(itertools.product(*tag_selections))))
|
||||||
|
for point_combination in itertools.product(*tag_selections):
|
||||||
|
|
||||||
|
# Check if all points have unique coordinates
|
||||||
|
coords = [tuple(point.coord) for point in point_combination]
|
||||||
|
if len(coords) != len(set(coords)):
|
||||||
|
continue # Skip if any coordinates are duplicated
|
||||||
|
|
||||||
|
# Group points by importance
|
||||||
|
points_by_importance = {}
|
||||||
|
for point in point_combination:
|
||||||
|
imp = tag_importance[point.tag]
|
||||||
|
if imp not in points_by_importance:
|
||||||
|
points_by_importance[imp] = []
|
||||||
|
points_by_importance[imp].append(point)
|
||||||
|
|
||||||
|
# Sort by importance
|
||||||
|
sorted_importances = sorted(points_by_importance.keys())
|
||||||
|
|
||||||
|
# Generate all permutations within each importance group
|
||||||
|
importance_groups = [points_by_importance[imp] for imp in sorted_importances]
|
||||||
|
|
||||||
|
for ordering in itertools.product(*[itertools.permutations(group) for group in importance_groups]):
|
||||||
|
route = []
|
||||||
|
for group in ordering:
|
||||||
|
route.extend(group)
|
||||||
|
all_routes.append(route)
|
||||||
|
|
||||||
|
return all_routes
|
||||||
|
|
||||||
|
def generate_routes_with_repeats(grouped_points: Dict[int, List[Point]],
|
||||||
|
all_tags: Set[str],
|
||||||
|
tag_importance: Dict[str, int],
|
||||||
|
num_points: int) -> List[List[Point]]:
|
||||||
|
"""Generate routes when we need to repeat tags to reach the required number of points, ensuring unique coordinates."""
|
||||||
|
# Create a mapping from tag to points
|
||||||
|
tag_to_points = {}
|
||||||
|
for points_list in grouped_points.values():
|
||||||
|
for point in points_list:
|
||||||
|
if point.tag not in tag_to_points:
|
||||||
|
tag_to_points[point.tag] = []
|
||||||
|
tag_to_points[point.tag].append(point)
|
||||||
|
|
||||||
|
all_routes = []
|
||||||
|
|
||||||
|
# First, select one point for each tag (mandatory points)
|
||||||
|
mandatory_selections = [tag_to_points[tag] for tag in all_tags]
|
||||||
|
|
||||||
|
# Generate all combinations of mandatory points (one per tag)
|
||||||
|
for mandatory_combo in itertools.product(*mandatory_selections):
|
||||||
|
mandatory_points = list(mandatory_combo)
|
||||||
|
|
||||||
|
# Check if mandatory points have unique coordinates
|
||||||
|
mandatory_coords = [tuple(point.coord) for point in mandatory_points]
|
||||||
|
if len(mandatory_coords) != len(set(mandatory_coords)):
|
||||||
|
continue # Skip if any coordinates are duplicated in mandatory points
|
||||||
|
|
||||||
|
# We need to add (num_points - len(mandatory_points)) additional points
|
||||||
|
num_additional = num_points - len(mandatory_points)
|
||||||
|
|
||||||
|
if num_additional == 0:
|
||||||
|
# We have exactly the right number of points
|
||||||
|
points_by_importance = {}
|
||||||
|
for point in mandatory_points:
|
||||||
|
imp = tag_importance[point.tag]
|
||||||
|
if imp not in points_by_importance:
|
||||||
|
points_by_importance[imp] = []
|
||||||
|
points_by_importance[imp].append(point)
|
||||||
|
|
||||||
|
sorted_importances = sorted(points_by_importance.keys())
|
||||||
|
importance_groups = [points_by_importance[imp] for imp in sorted_importances]
|
||||||
|
|
||||||
|
for ordering in itertools.product(*[itertools.permutations(group) for group in importance_groups]):
|
||||||
|
route = []
|
||||||
|
for group in ordering:
|
||||||
|
route.extend(group)
|
||||||
|
all_routes.append(route)
|
||||||
|
else:
|
||||||
|
# We need to add additional points (can be from any tag, including repeats)
|
||||||
|
# But we must ensure all coordinates are unique
|
||||||
|
|
||||||
|
# Get all available points excluding mandatory points
|
||||||
|
all_available_points = []
|
||||||
|
for points_list in grouped_points.values():
|
||||||
|
all_available_points.extend(points_list)
|
||||||
|
|
||||||
|
# Remove mandatory points from available points
|
||||||
|
available_points = [p for p in all_available_points if p not in mandatory_points]
|
||||||
|
|
||||||
|
# Generate combinations of additional points
|
||||||
|
for additional_combo in itertools.combinations(available_points, num_additional):
|
||||||
|
# Check if additional points have unique coordinates and don't duplicate with mandatory
|
||||||
|
additional_coords = [tuple(point.coord) for point in additional_combo]
|
||||||
|
if len(additional_coords) != len(set(additional_coords)):
|
||||||
|
continue # Skip if any coordinates are duplicated in additional points
|
||||||
|
|
||||||
|
# Check if additional points don't duplicate with mandatory points
|
||||||
|
all_coords = mandatory_coords + additional_coords
|
||||||
|
if len(all_coords) != len(set(all_coords)):
|
||||||
|
continue # Skip if any coordinates are duplicated between mandatory and additional
|
||||||
|
|
||||||
|
full_route_candidate = mandatory_points + list(additional_combo)
|
||||||
|
|
||||||
|
# Group by importance
|
||||||
|
points_by_importance = {}
|
||||||
|
for point in full_route_candidate:
|
||||||
|
imp = tag_importance[point.tag]
|
||||||
|
if imp not in points_by_importance:
|
||||||
|
points_by_importance[imp] = []
|
||||||
|
points_by_importance[imp].append(point)
|
||||||
|
|
||||||
|
sorted_importances = sorted(points_by_importance.keys())
|
||||||
|
importance_groups = [points_by_importance[imp] for imp in sorted_importances]
|
||||||
|
|
||||||
|
for ordering in itertools.product(*[itertools.permutations(group) for group in importance_groups]):
|
||||||
|
route = []
|
||||||
|
for group in ordering:
|
||||||
|
route.extend(group)
|
||||||
|
all_routes.append(route)
|
||||||
|
|
||||||
|
return all_routes
|
||||||
|
|
||||||
|
def form_point_list(data):
|
||||||
|
point_list =[]
|
||||||
|
for entry in data:
|
||||||
|
point = Point(list(map(float,entry['coordinate'].split(', '))),entry['type'],entry['time_to_visit'])
|
||||||
|
point_list.append(point)
|
||||||
|
return point_list
|
||||||
|
|
||||||
|
def build_route(data, mapping,start_coord,total_time,n_nodes):
|
||||||
|
# Example input data - теперь с не более чем 5 уникальными тегами
|
||||||
|
|
||||||
|
start_coord_test = [56.331576, 44.003277]
|
||||||
|
total_time_test = 180 # Увеличим время до 4 часов для большего выбора
|
||||||
|
points = form_point_list(data)
|
||||||
|
tag_importance =mapping
|
||||||
|
# Используем 3 уникальных тега для демонстрации
|
||||||
|
points_test = [
|
||||||
|
Point([56.32448, 43.983546], "Памятник", 20),
|
||||||
|
Point([56.335607, 43.97481], "Архитектура", 20),
|
||||||
|
Point([56.313472, 43.990747], "Памятник", 20),
|
||||||
|
#Point([56.324157, 44.002696], "Памятник", 20),
|
||||||
|
#Point([56.316436, 43.994177], "Памятник", 20),
|
||||||
|
#Point([56.32377, 44.001879], "Памятник", 20),
|
||||||
|
#Point([56.329867, 43.99687], "Памятник", 20),
|
||||||
|
Point([56.311066, 43.94595], "Памятник", 20),
|
||||||
|
Point([56.333265, 43.972417], "Памятник", 20),
|
||||||
|
# Point([56.332166, 44.012111], "Памятник", 20),
|
||||||
|
#Point([56.326786, 44.006836], "Памятник", 20),
|
||||||
|
Point([56.330232, 44.010941], "Парк", 20),
|
||||||
|
Point([56.282221, 43.979263], "Парк", 20),
|
||||||
|
Point([56.277315, 43.921408], "Мозаика", 20),
|
||||||
|
Point([56.284829, 44.01893], "Парк", 20),
|
||||||
|
Point([56.308973, 43.99821], "Парк", 20),
|
||||||
|
Point([56.321545, 44.001921], "Парк", 20),
|
||||||
|
#Point([56.301798, 44.044003], "Мозаика", 20),
|
||||||
|
Point([56.268282, 43.919475], "Парк", 20),
|
||||||
|
Point([56.239625, 43.854551], "Парк", 20),
|
||||||
|
#Point([56.311214, 43.933981], "Парк", 20),
|
||||||
|
Point([56.314984, 44.007347], "Парк", 20),
|
||||||
|
Point([56.32509, 43.983433], "Парк", 20),
|
||||||
|
Point([56.27449, 43.973357], "Парк", 20),
|
||||||
|
Point([56.278073, 43.940886], "Парк", 20),
|
||||||
|
Point([56.358805, 43.825376], "Парк", 20),
|
||||||
|
Point([56.329995, 44.009444], "Памятник", 20),
|
||||||
|
Point([56.328551, 43.998718], "Памятник", 20),
|
||||||
|
Point([56.330355, 43.993105], "Архитектура", 20),
|
||||||
|
Point([56.321416, 43.973897], "Архитектура", 20),
|
||||||
|
# Point([56.327298, 44.005706], "Архитектура", 20),
|
||||||
|
#Point([56.328757, 43.998183], "Архитектура", 20),
|
||||||
|
# Point([56.328908, 43.995645], "Архитектура", 20),
|
||||||
|
Point([56.317578, 43.995805], "Архитектура", 20),
|
||||||
|
Point([56.329433, 44.012764], "Архитектура", 20),
|
||||||
|
Point([56.3301, 44.008831], "Архитектура", 20),
|
||||||
|
#Point([56.32995, 43.999495], "Архитектура", 20),
|
||||||
|
Point([56.327454, 44.041745], "Архитектура", 20),
|
||||||
|
#Point([56.328576, 44.004872], "Архитектура", 20),
|
||||||
|
Point([56.3275, 44.007658], "Архитектура", 20),
|
||||||
|
Point([56.330679, 44.013874], "Архитектура", 20),
|
||||||
|
# Point([56.331541, 44.001747], "Архитектура", 20),
|
||||||
|
# Point([56.335071, 43.974627], "Архитектура", 20),
|
||||||
|
#Point([56.317707, 43.995847], "Архитектура", 20),
|
||||||
|
#Point([56.323851, 43.985939], "Архитектура", 20),
|
||||||
|
Point([56.325701, 44.001527], "Архитектура", 20),
|
||||||
|
Point([56.328754, 43.998954], "Архитектура", 20),
|
||||||
|
#Point([56.323937, 43.990728], "Музей", 20),
|
||||||
|
#Point([56.2841, 43.84621], "Музей", 20),
|
||||||
|
#Point([56.328646, 44.028973], "Музей", 20),
|
||||||
|
Point([56.327391, 43.857522], "Мозаика", 20),
|
||||||
|
#Point([56.252239, 43.889066], "Мозаика", 20),
|
||||||
|
#Point([56.248436, 43.88106], "Мозаика", 20),
|
||||||
|
#Point([56.321257, 43.94545], "Мозаика", 20),
|
||||||
|
# Point([56.365284, 43.823251], "Мозаика", 20),
|
||||||
|
Point([56.294371, 43.912625], "Мозаика", 20),
|
||||||
|
#Point([56.241768, 43.859687], "Мозаика", 20),
|
||||||
|
#Point([56.300073, 43.938526], "Мозаика", 20),
|
||||||
|
#Point([56.229652, 43.947973], "Мозаика", 20),
|
||||||
|
# Point([56.269486, 43.9238], "Мозаика", 20),
|
||||||
|
Point([56.299251, 43.985146], "Мозаика", 20),
|
||||||
|
Point([56.293297, 44.034095], "Мозаика", 20),
|
||||||
|
Point([56.299251, 43.985146], "Мозаика", 20),
|
||||||
|
Point([56.229652, 43.947973], "Мозаика", 20),
|
||||||
|
Point([56.269486, 43.9238], "Мозаика", 20),
|
||||||
|
#Point([56.293297, 44.034095], "Мозаика", 20),
|
||||||
|
#Point([56.229652, 43.947973], "Мозаика", 20)
|
||||||
|
]
|
||||||
|
|
||||||
|
tag_importance_test = {
|
||||||
|
"Памятник": 1,
|
||||||
|
"Парк": 1,
|
||||||
|
"Мозаика": 1,
|
||||||
|
"Архитектура": 1,
|
||||||
|
#"Музей": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check tags constraint
|
||||||
|
if not check_tags_constraint(points):
|
||||||
|
print("Error: More than 5 unique tags in the input data")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Input data validation: OK (5 or fewer unique tags)")
|
||||||
|
|
||||||
|
# Step 1: Filter points using straight-line distance and total time
|
||||||
|
filtered_by_time = filter_points_by_time(start_coord, points, total_time)
|
||||||
|
print(f"After initial time filtering: {len(filtered_by_time)} points")
|
||||||
|
|
||||||
|
if len(filtered_by_time) < 3:
|
||||||
|
print("Not enough points after time filtering")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 2: Filter points by tag proximity (keep max 10 closest points per tag)
|
||||||
|
filtered_points = filter_points_by_tag_proximity(filtered_by_time, max_per_tag=10)
|
||||||
|
print(f"After tag proximity filtering: {len(filtered_points)} points")
|
||||||
|
|
||||||
|
if len(filtered_points) < 3:
|
||||||
|
print("Not enough points after tag proximity filtering")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 3: Prepare points for server request (start point + filtered points)
|
||||||
|
points_for_matrix = [start_coord] + [point.coord for point in filtered_points]
|
||||||
|
|
||||||
|
print("Requesting duration matrix from server...")
|
||||||
|
# Step 4: Get duration matrix from server
|
||||||
|
result = get_duration_matrix(points_for_matrix)
|
||||||
|
if result is None:
|
||||||
|
print("Failed to get duration matrix from server")
|
||||||
|
return
|
||||||
|
|
||||||
|
distances_matrix, durations_matrix = result
|
||||||
|
print("Duration matrix received successfully")
|
||||||
|
|
||||||
|
# Assign matrix indices to points
|
||||||
|
for i, point in enumerate(filtered_points):
|
||||||
|
point.matrix_index = i + 1 # +1 because index 0 is the start point
|
||||||
|
|
||||||
|
# Step 5: Group by importance
|
||||||
|
grouped_points = group_points_by_significance(filtered_points, tag_importance)
|
||||||
|
|
||||||
|
# Get all unique tags
|
||||||
|
all_tags = set(point.tag for point in filtered_points)
|
||||||
|
num_unique_tags = len(all_tags)
|
||||||
|
|
||||||
|
print(f"Unique tags: {all_tags} ({num_unique_tags} tags)")
|
||||||
|
|
||||||
|
# Step 6: Generate possible routes
|
||||||
|
print("Generating possible routes...")
|
||||||
|
|
||||||
|
# Determine the number of points in the route
|
||||||
|
if num_unique_tags >= n_nodes:
|
||||||
|
# Each tag must be visited exactly once
|
||||||
|
print("Each tag will be visited exactly once with unique coordinates")
|
||||||
|
possible_routes = generate_routes_exact_tags(grouped_points, all_tags, tag_importance)
|
||||||
|
else:
|
||||||
|
# We have fewer than 3 unique tags, need to repeat some tags
|
||||||
|
print(f"Only {num_unique_tags} unique tags available, will repeat tags to reach 3 points with unique coordinates")
|
||||||
|
possible_routes = generate_routes_with_repeats(grouped_points, all_tags, tag_importance, n_nodes)
|
||||||
|
|
||||||
|
if not possible_routes:
|
||||||
|
print("No valid routes found that cover all tags with unique coordinates")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 7: Calculate time for each route and filter by total_time
|
||||||
|
valid_routes = []
|
||||||
|
for route in possible_routes:
|
||||||
|
route_time = calculate_route_time_with_matrix(route, start_coord, durations_matrix)
|
||||||
|
if route_time <= total_time:
|
||||||
|
valid_routes.append((route, route_time))
|
||||||
|
|
||||||
|
if not valid_routes:
|
||||||
|
print("No valid routes found within time constraint")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 8: Find optimal route (minimum time)
|
||||||
|
optimal_route, min_time = min(valid_routes, key=lambda x: x[1])
|
||||||
|
|
||||||
|
print(f"\nOptimal route (time: {min_time:.2f} min):")
|
||||||
|
for i, point in enumerate(optimal_route, 1):
|
||||||
|
print(f"{i}. {point.tag} at {point.coord} ({point.visit_time} min)")
|
||||||
|
|
||||||
|
# Print route details with travel times
|
||||||
|
print("\nRoute details:")
|
||||||
|
current_index = 0
|
||||||
|
total_route_time = 0
|
||||||
|
for i, point in enumerate(optimal_route):
|
||||||
|
travel_time_seconds = durations_matrix[current_index][point.matrix_index]
|
||||||
|
travel_time_minutes = travel_time_seconds / 60.0
|
||||||
|
segment_time = travel_time_minutes + point.visit_time
|
||||||
|
total_route_time += segment_time
|
||||||
|
|
||||||
|
print(f"Segment {i+1}: {travel_time_minutes:.2f} min travel + {point.visit_time} min visit = {segment_time:.2f} min")
|
||||||
|
current_index = point.matrix_index
|
||||||
|
|
||||||
|
print(f"Total route time: {total_route_time:.2f} min")
|
||||||
|
|
||||||
|
# Display all tags covered by the route
|
||||||
|
route_tags = set(point.tag for point in optimal_route)
|
||||||
|
print(f"\nTags covered in this route: {', '.join(route_tags)}")
|
||||||
|
if all_tags.issubset(route_tags):
|
||||||
|
print("All tags are covered in this route!")
|
||||||
|
|
||||||
|
# Verify all coordinates are unique
|
||||||
|
route_coords = [tuple(point.coord) for point in optimal_route]
|
||||||
|
if len(route_coords) == len(set(route_coords)):
|
||||||
|
print("All coordinates in the route are unique!")
|
||||||
|
else:
|
||||||
|
print("ERROR: Duplicate coordinates found in the route!")
|
||||||
|
|
||||||
|
#if __name__ == "__main__":
|
||||||
|
# build_route()
|
||||||
26
wrapper.py
Normal file
26
wrapper.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from database import search_database
|
||||||
|
from geocoder import validate_address
|
||||||
|
from new_parser_test import parse
|
||||||
|
from route import build_route
|
||||||
|
import json
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
user_input = parse()
|
||||||
|
user_input = json.loads(user_input)
|
||||||
|
print(user_input)
|
||||||
|
query =user_input['tags']
|
||||||
|
user_address =user_input['user_location']
|
||||||
|
user_time =user_input['time']
|
||||||
|
val_output = validate_address('addresses.sqlite',user_address)
|
||||||
|
print(val_output)
|
||||||
|
found_points,mapping = search_database('output.json', query)
|
||||||
|
print(len(found_points))
|
||||||
|
print(mapping)
|
||||||
|
user_position =[]
|
||||||
|
if val_output['valid']:
|
||||||
|
user_position.append(val_output['coordinates']['lat'])
|
||||||
|
user_position.append(val_output['coordinates']['lon'])
|
||||||
|
else:
|
||||||
|
print('Адрес не найден')
|
||||||
|
route = build_route(found_points, mapping,user_position,user_time,5)
|
||||||
|
print(route)
|
||||||
Reference in New Issue
Block a user