146 lines
6.7 KiB
Python
146 lines
6.7 KiB
Python
import json
|
||
import os
|
||
import hashlib
|
||
import requests
|
||
from datetime import datetime
|
||
|
||
# Конфигурация
|
||
URL_PARSER_ROOT = "https://fazziclay.com/api/v1/vstu_schedule_parser_v2/parser.json"
|
||
BASE_URL_FILES = "https://fazziclay.com/api/v1/vstu_schedule_parser_v2/parsed/"
|
||
FILE_RESULT = "result.json"
|
||
FILE_CACHE = "raw_cache.json"
|
||
FILE_TO_AI = "to_ai.txt"
|
||
FILE_FROM_AI = "from_ai.txt"
|
||
|
||
def get_raw_hash(raw_list):
|
||
normalized = "|".join(sorted([str(i).strip() for i in raw_list]))
|
||
return hashlib.sha1(normalized.encode('utf-8')).hexdigest()
|
||
|
||
def load_json(filename, default):
|
||
if os.path.exists(filename):
|
||
with open(filename, 'r', encoding='utf-8') as f:
|
||
try: return json.load(f)
|
||
except: return default
|
||
return default
|
||
|
||
def save_json(filename, data):
|
||
with open(filename, 'w', encoding='utf-8') as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
def process_ai_input(cache):
|
||
if not os.path.exists(FILE_FROM_AI): return cache
|
||
with open(FILE_FROM_AI, 'r', encoding='utf-8') as f:
|
||
content = f.read().strip()
|
||
if not content: return cache
|
||
try:
|
||
new_data = json.loads(content)
|
||
for r_hash, resolved_obj in new_data.items():
|
||
cache[r_hash] = resolved_obj
|
||
print(f"[*] Добавлено {len(new_data)} записей из ИИ в кэш.")
|
||
with open(FILE_FROM_AI, 'w', encoding='utf-8') as f: f.write("")
|
||
except Exception as e:
|
||
print(f"[!] Ошибка парсинга from_ai.txt: {e}")
|
||
return cache
|
||
|
||
def main():
|
||
print(f"--- Система Совместимости V1-V2 [{datetime.now().strftime('%H:%M:%S')}] ---")
|
||
cache = load_json(FILE_CACHE, {})
|
||
cache = process_ai_input(cache)
|
||
save_json(FILE_CACHE, cache)
|
||
|
||
try:
|
||
parser_data = requests.get(URL_PARSER_ROOT).json()
|
||
except Exception as e:
|
||
print(f"[!] Ошибка сети: {e}"); return
|
||
|
||
final_groups = {}
|
||
unknown_raws = {}
|
||
|
||
for file_info in parser_data.get("all_files", []):
|
||
file_url = f"{BASE_URL_FILES}{requests.utils.quote(file_info['json_represent'])}"
|
||
print(f"[*] Рендеринг: {file_info['json_represent']}")
|
||
|
||
try: faculty_data = requests.get(file_url).json()
|
||
except: continue
|
||
|
||
sheets = faculty_data.get("sheets", {})
|
||
for sheet_data in sheets.values():
|
||
groups = sheet_data.get("groups", {})
|
||
|
||
for group_id, group_data in groups.items():
|
||
# Инициализация группы в формате V1
|
||
if group_id not in final_groups:
|
||
final_groups[group_data["name"]] = {
|
||
"name": group_data["name"],
|
||
"facultet": faculty_data['excel']['facultet'],
|
||
"position": group_data.get("position"),
|
||
"position_human": group_data.get("position_human"),
|
||
"slots": {},
|
||
"data_source_hash": "TODO"
|
||
}
|
||
|
||
slots = group_data.get("slots", {})
|
||
for slot_key, pair_value in slots.items():
|
||
if not isinstance(pair_value, dict): continue
|
||
|
||
if slot_key not in final_groups[group_data["name"]]["slots"]:
|
||
final_groups[group_data["name"]]["slots"][slot_key] = {}
|
||
|
||
for pair_key, pair_data in pair_value.items():
|
||
# Фильтр мета-ключей (пропускаем excel_range и т.д.)
|
||
if not (isinstance(pair_key, str) and '-' in pair_key): continue
|
||
|
||
# НОВЫЙ БЛОК: Обработка списка событий (если пара раздвоена)
|
||
# Превращаем всё в список, даже если там один объект
|
||
events = pair_data if isinstance(pair_data, list) else [pair_data]
|
||
|
||
for i, event in enumerate(events):
|
||
# Если событий больше одного, добавляем суффикс к ключу (напр. "5-6_1")
|
||
current_pair_id = pair_key if i == 0 else f"{pair_key}_{i}"
|
||
|
||
# Теперь event — это гарантированно словарь
|
||
if not isinstance(event, dict): continue
|
||
|
||
raw_list = event.get("raw", [])
|
||
r_hash = get_raw_hash(raw_list)
|
||
|
||
if r_hash in cache:
|
||
res = cache[r_hash]
|
||
|
||
# Парсим списки
|
||
locs = [l.strip() for l in res.get("location", "").split(",")] if res.get("location") and res.get("location") != "Не указана" else []
|
||
leads = [l.strip() for l in res.get("teacher", "").split(",")] if res.get("teacher") and res.get("teacher") != "Не указан" else []
|
||
|
||
# Записываем в финальную структуру
|
||
final_groups[group_data["name"]]["slots"][slot_key][current_pair_id] = {
|
||
"discipline_name": res.get("subject", "Не указан"),
|
||
"locations": locs,
|
||
"leads": leads,
|
||
"is_solid": event.get("is_solid", True),
|
||
"is_flow": event.get("is_flow", False),
|
||
"raw": raw_list,
|
||
"weekday": event.get("weekday"),
|
||
"weeknum": event.get("weeknum"),
|
||
"excel_range": event.get("excel_range"),
|
||
"excel_pos": event.get("excel_pos")
|
||
}
|
||
else:
|
||
unknown_raws[r_hash] = raw_list
|
||
|
||
# Управление to_ai.txt
|
||
if unknown_raws:
|
||
save_json(FILE_TO_AI, unknown_raws)
|
||
print(f"[!] Найдено {len(unknown_raws)} новых записей. См. {FILE_TO_AI}")
|
||
else:
|
||
with open(FILE_TO_AI, 'w', encoding='utf-8') as f: f.write("")
|
||
|
||
# Сохранение итогового результата
|
||
output = {
|
||
"actual_at": int(datetime.now().timestamp()),
|
||
"groups": final_groups
|
||
}
|
||
save_json(FILE_RESULT, output)
|
||
print(f"[*] Успешно: {FILE_RESULT} обновлен.")
|
||
|
||
if __name__ == "__main__":
|
||
main() |