"""피드백·오류를 터미널에서 확인하고 처리하는 도구. .venv/bin/python -m server.cli feedback # 처리 대기(new, in_progress) 목록 .venv/bin/python -m server.cli feedback --all # 전체 .venv/bin/python -m server.cli show 12 # 한 건 자세히 (대상 용어 현재 값 포함) .venv/bin/python -m server.cli resolve 12 "정의 문장을 교재 p.188 기준으로 수정" .venv/bin/python -m server.cli status 12 in_progress .venv/bin/python -m server.cli term 지역사회 간호사정 # 용어 현재 값 확인 """ import argparse import json from . import db def _print_fb(r, full=False): tgt = f" [{r['target_type']}:{r['target_label'] or r['target_id']}]" if r["target_type"] else "" occ = f" ×{r['occurrences']}" if (r["occurrences"] or 1) > 1 else "" print(f"#{r['id']:<4} {r['status']:<11} {r['kind']:<12} {r['category'] or ''}{tgt}{occ} {r['created_at']}") msg = r["message"] or "" print(" " + (msg if full else msg[:160].replace("\n", " "))) if r["suggestion"]: print(" → 제안: " + (r["suggestion"] if full else r["suggestion"][:160])) if full: for k in ("page_url", "reporter", "contact", "admin_note", "resolution", "handled_by", "user_agent"): if r[k]: print(f" {k}: {r[k]}") if r["context"]: print(" context:", r["context"][:3000]) def main(): ap = argparse.ArgumentParser() sub = ap.add_subparsers(dest="cmd", required=True) f = sub.add_parser("feedback") f.add_argument("--all", action="store_true") f.add_argument("--kind") s = sub.add_parser("show") s.add_argument("id", type=int) r = sub.add_parser("resolve") r.add_argument("id", type=int) r.add_argument("resolution") st = sub.add_parser("status") st.add_argument("id", type=int) st.add_argument("status", choices=["new", "in_progress", "resolved", "rejected", "duplicate"]) t = sub.add_parser("term") t.add_argument("name", nargs="+") a = ap.parse_args() con = db.connect() if a.cmd == "feedback": where = "1=1" if a.all else "status IN ('new','in_progress')" args = [] if a.kind: where += " AND kind=?" args.append(a.kind) rows = con.execute(f"SELECT * FROM feedback WHERE {where} ORDER BY id", args).fetchall() print(f"{len(rows)}건") for r in rows: _print_fb(r) elif a.cmd == "show": r = con.execute("SELECT * FROM feedback WHERE id=?", (a.id,)).fetchone() if not r: return print("없음") _print_fb(r, full=True) if r["target_type"] == "term" and r["target_id"]: _show_term(con, r["target_id"]) elif a.cmd in ("resolve", "status"): status = "resolved" if a.cmd == "resolve" else a.status with con: if a.cmd == "resolve": con.execute("UPDATE feedback SET status=?, resolution=?, handled_by='developer', updated_at=? WHERE id=?", (status, a.resolution, db.now(), a.id)) else: con.execute("UPDATE feedback SET status=?, handled_by='developer', updated_at=? WHERE id=?", (status, db.now(), a.id)) db.audit(con, "developer(cli)", "update", "feedback", str(a.id), None, {"status": status}) print("완료") elif a.cmd == "term": name = " ".join(a.name) r = con.execute("SELECT term_id FROM terms WHERE name=? OR term_id=?", (name, name)).fetchone() if not r: rows = con.execute("SELECT term_id,name FROM terms WHERE name LIKE ? LIMIT 20", (f"%{name}%",)).fetchall() for x in rows: print(x["term_id"], x["name"]) return _show_term(con, r["term_id"]) def _show_term(con, term_id): t = db.row(con.execute("SELECT * FROM terms WHERE term_id=?", (term_id,)).fetchone()) print("\n--- 대상 용어 현재 값") print(json.dumps({k: t[k] for k in ("term_id", "name", "aliases", "galaxy_id", "section_id", "definition_text", "definition_book", "definition_page", "easy", "example", "review_status")}, ensure_ascii=False, indent=1)) for s in con.execute("SELECT book_id,page,path,snippet FROM term_sources WHERE term_id=?", (term_id,)): print(f" 출처 {s['book_id']} p.{s['page']} {s['path']}\n {(s['snippet'] or '')[:150]}") if __name__ == "__main__": main()