mirror of
https://github.com/hoshikawa2/rfp_response_automation.git
synced 2026-03-06 18:21:02 +00:00
first commit
This commit is contained in:
83
files/modules/architecture/routes.py
Normal file
83
files/modules/architecture/routes.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
import uuid
|
||||
import json
|
||||
from pathlib import Path
|
||||
from modules.core.audit import audit_log
|
||||
|
||||
from modules.core.security import requires_app_auth
|
||||
from .service import start_architecture_job
|
||||
from .store import ARCH_JOBS, ARCH_LOCK
|
||||
|
||||
architecture_bp = Blueprint("architecture", __name__)
|
||||
|
||||
ARCH_FOLDER = Path("architecture")
|
||||
|
||||
@architecture_bp.route("/architecture/start", methods=["POST"])
|
||||
@requires_app_auth
|
||||
def architecture_start():
|
||||
data = request.get_json(force=True) or {}
|
||||
question = (data.get("question") or "").strip()
|
||||
|
||||
if not question:
|
||||
return jsonify({"error": "Empty question"}), 400
|
||||
|
||||
job_id = str(uuid.uuid4())
|
||||
audit_log("ARCHITECTURE", f"job_id={job_id}")
|
||||
|
||||
with ARCH_LOCK:
|
||||
ARCH_JOBS[job_id] = {
|
||||
"status": "RUNNING",
|
||||
"logs": []
|
||||
}
|
||||
|
||||
start_architecture_job(job_id, question)
|
||||
return jsonify({"job_id": job_id})
|
||||
|
||||
|
||||
@architecture_bp.route("/architecture/<job_id>/status", methods=["GET"])
|
||||
@requires_app_auth
|
||||
def architecture_status(job_id):
|
||||
job_dir = ARCH_FOLDER / job_id
|
||||
status_file = job_dir / "status.json"
|
||||
|
||||
# fallback 1: status persistido
|
||||
if status_file.exists():
|
||||
try:
|
||||
return jsonify(json.loads(status_file.read_text(encoding="utf-8")))
|
||||
except Exception:
|
||||
return jsonify({"status": "ERROR", "detail": "Invalid status file"}), 500
|
||||
|
||||
# fallback 2: status em memória
|
||||
with ARCH_LOCK:
|
||||
job = ARCH_JOBS.get(job_id)
|
||||
|
||||
if job:
|
||||
return jsonify({"status": job.get("status", "PROCESSING")})
|
||||
|
||||
return jsonify({"status": "NOT_FOUND"}), 404
|
||||
|
||||
|
||||
@architecture_bp.route("/architecture/<job_id>/logs", methods=["GET"])
|
||||
@requires_app_auth
|
||||
def architecture_logs(job_id):
|
||||
with ARCH_LOCK:
|
||||
job = ARCH_JOBS.get(job_id, {})
|
||||
return jsonify({"logs": job.get("logs", [])})
|
||||
|
||||
@architecture_bp.route("/architecture/<job_id>/result", methods=["GET"])
|
||||
@requires_app_auth
|
||||
def architecture_result(job_id):
|
||||
job_dir = ARCH_FOLDER / job_id
|
||||
result_file = job_dir / "architecture.json"
|
||||
|
||||
# ainda não terminou
|
||||
if not result_file.exists():
|
||||
return jsonify({"error": "not ready"}), 404
|
||||
|
||||
try:
|
||||
raw = result_file.read_text(encoding="utf-8")
|
||||
plan = json.loads(raw)
|
||||
return jsonify(plan)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
56
files/modules/architecture/service.py
Normal file
56
files/modules/architecture/service.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import threading
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from .store import ARCH_JOBS, ARCH_LOCK
|
||||
from oci_genai_llm_graphrag_rerank_rfp import call_architecture_planner, architecture_to_mermaid
|
||||
|
||||
ARCH_FOLDER = Path("architecture")
|
||||
ARCH_FOLDER.mkdir(exist_ok=True)
|
||||
|
||||
def make_job_logger(job_id: str):
|
||||
def _log(msg):
|
||||
with ARCH_LOCK:
|
||||
job = ARCH_JOBS.get(job_id)
|
||||
if job:
|
||||
job["logs"].append(str(msg))
|
||||
return _log
|
||||
|
||||
def start_architecture_job(job_id: str, question: str):
|
||||
job_dir = ARCH_FOLDER / job_id
|
||||
job_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
status_file = job_dir / "status.json"
|
||||
result_file = job_dir / "architecture.json"
|
||||
|
||||
def write_status(state: str, detail: str | None = None):
|
||||
payload = {"status": state}
|
||||
if detail:
|
||||
payload["detail"] = detail
|
||||
status_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
with ARCH_LOCK:
|
||||
if job_id in ARCH_JOBS:
|
||||
ARCH_JOBS[job_id]["status"] = state
|
||||
if detail:
|
||||
ARCH_JOBS[job_id]["detail"] = detail
|
||||
|
||||
write_status("PROCESSING")
|
||||
|
||||
def background():
|
||||
try:
|
||||
logger = make_job_logger(job_id)
|
||||
|
||||
plan = call_architecture_planner(question, log=logger)
|
||||
if not isinstance(plan, dict):
|
||||
raise TypeError(f"Planner returned {type(plan)}")
|
||||
|
||||
plan["mermaid"] = architecture_to_mermaid(plan)
|
||||
|
||||
result_file.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
write_status("DONE")
|
||||
|
||||
except Exception as e:
|
||||
write_status("ERROR", str(e))
|
||||
|
||||
threading.Thread(target=background, daemon=True).start()
|
||||
4
files/modules/architecture/store.py
Normal file
4
files/modules/architecture/store.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from threading import Lock
|
||||
|
||||
ARCH_LOCK = Lock()
|
||||
ARCH_JOBS = {}
|
||||
Reference in New Issue
Block a user