193 lines
6.4 KiB
Python
193 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
from argparse import ArgumentParser
|
|
from pathlib import Path
|
|
import json
|
|
import sys
|
|
|
|
from sqlalchemy import select
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from services.shared.core import new_id, utc_now_iso
|
|
from services.shared.db import get_session
|
|
from services.shared.kb_localization import normalize_kb_language, resolve_article_group_id
|
|
from services.shared.sql_models import KBArticleRow, KBCategoryRow
|
|
|
|
|
|
def _parser() -> ArgumentParser:
|
|
parser = ArgumentParser(description="Seed KB article localizations without creating duplicates.")
|
|
parser.add_argument(
|
|
"--seed-file",
|
|
default=str(ROOT / "scripts" / "kb_voice_basic_kz_localizations.json"),
|
|
help="Path to the localization seed JSON file.",
|
|
)
|
|
parser.add_argument(
|
|
"--apply",
|
|
action="store_true",
|
|
help="Persist changes. Without this flag the script runs in dry-run mode.",
|
|
)
|
|
parser.add_argument(
|
|
"--update-existing",
|
|
action="store_true",
|
|
help="Update existing localized articles instead of leaving them untouched.",
|
|
)
|
|
return parser
|
|
|
|
|
|
def _load_seed(path: Path) -> dict:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _find_category(session, category_name: str) -> KBCategoryRow:
|
|
row = session.execute(
|
|
select(KBCategoryRow).where(KBCategoryRow.name == category_name)
|
|
).scalar_one_or_none()
|
|
if row is None:
|
|
raise RuntimeError(f"KB category not found: {category_name}")
|
|
return row
|
|
|
|
|
|
def _find_source_article(
|
|
session,
|
|
*,
|
|
category_id: str,
|
|
source_title: str,
|
|
source_language: str,
|
|
) -> KBArticleRow:
|
|
rows = session.execute(
|
|
select(KBArticleRow)
|
|
.where(KBArticleRow.category_id == category_id)
|
|
.where(KBArticleRow.title == source_title)
|
|
.where(KBArticleRow.language == source_language)
|
|
.order_by(KBArticleRow.id.asc())
|
|
).scalars().all()
|
|
if not rows:
|
|
raise RuntimeError(f"Source article not found: {source_title}")
|
|
if len(rows) > 1:
|
|
raise RuntimeError(f"Multiple source articles found for title: {source_title}")
|
|
return rows[0]
|
|
|
|
|
|
def _find_localized_article(
|
|
session,
|
|
*,
|
|
article_group_id: str,
|
|
target_language: str,
|
|
) -> KBArticleRow | None:
|
|
return session.execute(
|
|
select(KBArticleRow)
|
|
.where(KBArticleRow.article_group_id == article_group_id)
|
|
.where(KBArticleRow.language == target_language)
|
|
).scalar_one_or_none()
|
|
|
|
|
|
def main() -> None:
|
|
args = _parser().parse_args()
|
|
seed_file = Path(args.seed_file).resolve()
|
|
seed = _load_seed(seed_file)
|
|
source_language = normalize_kb_language(seed.get("source_language"))
|
|
target_language = normalize_kb_language(seed.get("target_language"))
|
|
category_name = str(seed.get("category_name") or "").strip()
|
|
if not category_name:
|
|
raise RuntimeError("Seed file must define category_name")
|
|
|
|
session = get_session()
|
|
try:
|
|
category = _find_category(session, category_name)
|
|
summary = {
|
|
"category_id": category.category_id,
|
|
"category_name": category.name,
|
|
"seed_file": str(seed_file),
|
|
"source_language": source_language,
|
|
"target_language": target_language,
|
|
"apply": bool(args.apply),
|
|
"update_existing": bool(args.update_existing),
|
|
"created": [],
|
|
"updated": [],
|
|
"skipped": [],
|
|
}
|
|
|
|
for item in seed.get("localizations", []):
|
|
source_title = str(item.get("source_title") or "").strip()
|
|
title = str(item.get("title") or "").strip()
|
|
body = str(item.get("body") or "").strip()
|
|
tags = [str(tag).strip() for tag in item.get("tags", []) if str(tag).strip()]
|
|
if not source_title or not title or not body:
|
|
raise RuntimeError(f"Incomplete localization entry: {item!r}")
|
|
|
|
source_article = _find_source_article(
|
|
session,
|
|
category_id=category.category_id,
|
|
source_title=source_title,
|
|
source_language=source_language,
|
|
)
|
|
article_group_id = resolve_article_group_id(
|
|
source_article.article_id,
|
|
source_article.article_group_id,
|
|
)
|
|
existing = _find_localized_article(
|
|
session,
|
|
article_group_id=article_group_id,
|
|
target_language=target_language,
|
|
)
|
|
if existing is None:
|
|
row = KBArticleRow(
|
|
article_id=new_id("kba"),
|
|
category_id=category.category_id,
|
|
article_group_id=article_group_id,
|
|
language=target_language,
|
|
title=title,
|
|
body=body,
|
|
tags_json=json.dumps(tags, ensure_ascii=False),
|
|
created_at=utc_now_iso(),
|
|
updated_at=utc_now_iso(),
|
|
)
|
|
session.add(row)
|
|
summary["created"].append(
|
|
{
|
|
"source_title": source_title,
|
|
"article_group_id": article_group_id,
|
|
"target_title": title,
|
|
}
|
|
)
|
|
continue
|
|
|
|
if args.update_existing:
|
|
existing.title = title
|
|
existing.body = body
|
|
existing.tags_json = json.dumps(tags, ensure_ascii=False)
|
|
existing.updated_at = utc_now_iso()
|
|
summary["updated"].append(
|
|
{
|
|
"source_title": source_title,
|
|
"article_id": existing.article_id,
|
|
"article_group_id": article_group_id,
|
|
"target_title": title,
|
|
}
|
|
)
|
|
else:
|
|
summary["skipped"].append(
|
|
{
|
|
"source_title": source_title,
|
|
"article_id": existing.article_id,
|
|
"article_group_id": article_group_id,
|
|
"target_title": existing.title,
|
|
}
|
|
)
|
|
|
|
if args.apply:
|
|
session.commit()
|
|
else:
|
|
session.rollback()
|
|
|
|
print(json.dumps(summary, ensure_ascii=True, indent=2))
|
|
finally:
|
|
session.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|