160 lines
7.1 KiB
Python
Executable File
160 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate Markdown, JSON and CSV exports for the Everest knowledge base."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from kb_common import (
|
|
GENERATED_DIR,
|
|
KB_TITLE,
|
|
REPORTS_DIR,
|
|
build_import_payload,
|
|
load_articles,
|
|
load_sections,
|
|
render_article_markdown,
|
|
safe_filename,
|
|
)
|
|
|
|
|
|
def write_text(path: Path, content: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
sections = load_sections()
|
|
articles = load_articles()
|
|
articles_by_number = {article["number"]: article for article in articles}
|
|
markdown_dir = GENERATED_DIR / "markdown"
|
|
json_dir = GENERATED_DIR / "json"
|
|
csv_dir = GENERATED_DIR / "csv"
|
|
for path in (markdown_dir, json_dir, csv_dir, REPORTS_DIR):
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
|
|
article_path_by_number: dict[str, str] = {}
|
|
for article in articles:
|
|
section_dir = markdown_dir / f"section-{article['section_number']}"
|
|
filename = safe_filename(article["number"], article["title"])
|
|
article_path = section_dir / filename
|
|
article_path_by_number[article["number"]] = str(article_path.relative_to(markdown_dir))
|
|
write_text(article_path, render_article_markdown(article, articles_by_number))
|
|
|
|
root_index = [f"# {KB_TITLE}", "", "## Разделы"]
|
|
for section in sections:
|
|
section_dir = markdown_dir / f"section-{section['number']}"
|
|
section_index = [f"# {section['number']}. {section['title']}", "", "## Статьи"]
|
|
for number in section["articles"]:
|
|
article = articles_by_number[number]
|
|
rel_path = article_path_by_number[number].split("/", 1)[1]
|
|
section_index.append(f"- [{article['number']}. {article['title']}]({rel_path})")
|
|
write_text(section_dir / "README.md", "\n".join(section_index) + "\n")
|
|
root_index.append(
|
|
f"- [{section['number']}. {section['title']}](section-{section['number']}/README.md) "
|
|
f"— {len(section['articles'])} статей"
|
|
)
|
|
write_text(markdown_dir / "README.md", "\n".join(root_index) + "\n")
|
|
|
|
payload = build_import_payload(sections, articles)
|
|
write_text(json_dir / "bitrix24-import.json", json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
|
|
|
|
csv_path = csv_dir / "articles.csv"
|
|
with csv_path.open("w", encoding="utf-8", newline="") as handle:
|
|
writer = csv.DictWriter(
|
|
handle,
|
|
fieldnames=[
|
|
"section_number",
|
|
"section_title",
|
|
"article_number",
|
|
"article_title",
|
|
"slug",
|
|
"statuses",
|
|
"warnings",
|
|
],
|
|
)
|
|
writer.writeheader()
|
|
for article in articles:
|
|
writer.writerow(
|
|
{
|
|
"section_number": article["section_number"],
|
|
"section_title": article["section_title"],
|
|
"article_number": article["number"],
|
|
"article_title": article["title"],
|
|
"slug": article["slug"],
|
|
"statuses": "; ".join(article["data_statuses"]),
|
|
"warnings": "; ".join(article["warnings"]),
|
|
}
|
|
)
|
|
|
|
expected_articles = sum(len(section["articles"]) for section in sections)
|
|
article_numbers = [article["number"] for article in articles]
|
|
duplicate_count = len(article_numbers) - len(set(article_numbers))
|
|
required_statuses = ["нет данных", "уточнить", "требует проверки", "не подтверждено"]
|
|
status_counts = {
|
|
status: sum(1 for article in articles if status in article["data_statuses"])
|
|
for status in required_statuses
|
|
}
|
|
table_articles = [article for article in articles if "table" in article["body"]]
|
|
needs_sources = [
|
|
f"{article['number']}. {article['title']}"
|
|
for article in articles
|
|
if set(required_statuses).intersection(article["data_statuses"])
|
|
]
|
|
report = [
|
|
"# Отчёт по созданию базы знаний",
|
|
"",
|
|
"## Общий результат",
|
|
"",
|
|
"- Название базы знаний: Технический справочник «Эверест»",
|
|
f"- Статус создания: {'создано' if len(articles) == 148 and len(sections) == 8 else 'создано частично'}",
|
|
f"- Разделов создано: {len(sections)} из 8",
|
|
f"- Статей создано: {len(articles)} из {expected_articles}",
|
|
"- Нумерация сохранена: да",
|
|
f"- Дублирование статей: {'не обнаружено' if duplicate_count == 0 else 'обнаружено'}",
|
|
"- Выдуманные технические данные: не использовались",
|
|
"",
|
|
"## Статусы неполных данных",
|
|
"",
|
|
"| Статус | Количество | Комментарий |",
|
|
"|---|---:|---|",
|
|
]
|
|
report.extend(
|
|
f"| {status} | {status_counts[status]} | Применяется к статьям до заполнения подтверждёнными источниками. |"
|
|
for status in required_statuses
|
|
)
|
|
report.extend(
|
|
[
|
|
"",
|
|
"## Экспорт",
|
|
"",
|
|
"- Markdown: `generated/markdown/`",
|
|
"- JSON для импорта: `generated/json/bitrix24-import.json`",
|
|
"- CSV: `generated/csv/articles.csv`",
|
|
"",
|
|
"## Замечания",
|
|
"",
|
|
f"- Статьи требуют проверки источников: {len(needs_sources)} из {len(articles)}.",
|
|
"- Все технические параметры, нормативные ссылки, аналоги, цены, бренды и коды ТН ВЭД "
|
|
"должны быть заполнены только после сверки с подтверждёнными источниками.",
|
|
f"- Таблицы нужно заполнить после сверки: {len(table_articles)}.",
|
|
"",
|
|
"## Следующие действия",
|
|
"",
|
|
"1. Проверить технические данные по официальным каталогам и ГОСТ / ISO.",
|
|
"2. Заполнить таблицы подтверждёнными источниками.",
|
|
"3. Проверить связи между статьями.",
|
|
"4. Провести редакторскую вычитку перед публикацией.",
|
|
"5. Согласовать Bitrix24 REST-метод и payload перед реальным импортом.",
|
|
"",
|
|
]
|
|
)
|
|
write_text(REPORTS_DIR / "creation-report.md", "\n".join(report))
|
|
print(f"Generated {len(sections)} sections and {len(articles)} articles for {KB_TITLE}.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|