Steps 3-8: config, blueprints (upload/pipeline/api/prompts/health/pages), new app.py (no VM proxy), process.py adapted to generator, VM_API=''
This commit is contained in:
+53
-117
@@ -1,128 +1,64 @@
|
||||
"""contracts-flask — Flask-фронтенд для сверки договоров.
|
||||
1:1 функционал с Lucee-версией.
|
||||
Все тяжёлые операции — на ВМ (contracts.kube5s.ru).
|
||||
"""contracts-flask v2.0 — полный перенос с ВМ на Flask.
|
||||
Больше никаких прокси на contracts.kube5s.ru — всё локально.
|
||||
"""
|
||||
import os
|
||||
import httpx
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
VM_API = os.environ.get("VM_API", "https://contracts.kube5s.ru")
|
||||
LLM_URL = os.environ.get("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
|
||||
LLM_KEY = os.environ.get("LLM_API_KEY", "")
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-oss-120b")
|
||||
from flask import Flask
|
||||
from app.config import VERSION, MAX_CONTENT_LENGTH
|
||||
from app.routes import register_routes
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html", vm_api=VM_API)
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config["VERSION"] = VERSION
|
||||
app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH
|
||||
|
||||
# DB auto-seed — schema migration + seed prompts + crash recovery
|
||||
_init_db()
|
||||
|
||||
# Регистрация всех blueprint'ов
|
||||
register_routes(app)
|
||||
|
||||
# no_cache на все ответы
|
||||
@app.after_request
|
||||
def no_cache(response):
|
||||
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
return response
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@app.route("/chat", methods=["POST"])
|
||||
def chat():
|
||||
contract_id = request.args.get("contract_id", "")
|
||||
question = request.form.get("question", "")
|
||||
if not contract_id or not question:
|
||||
return jsonify({"ok": False, "error": "contract_id and question required"})
|
||||
|
||||
def _init_db():
|
||||
"""Schema migration + recovery: сброс застрявших classify."""
|
||||
try:
|
||||
with httpx.Client(timeout=10) as client:
|
||||
resp = client.get(f"{VM_API}/api/spec-current", params={"contract_id": contract_id})
|
||||
resp.raise_for_status()
|
||||
rows = resp.json().get("rows", [])
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": f"VM error: {e}"})
|
||||
from app.db.connection import execute
|
||||
|
||||
if not rows:
|
||||
return jsonify({"ok": True, "answer": "Нет данных. Сначала запустите сравнение."})
|
||||
for sql in [
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_raw text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_input text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS zip_source text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS content_hash text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS batch_id uuid",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS classify_status text DEFAULT 'pending'",
|
||||
"ALTER TABLE documents ALTER COLUMN classify_status SET DEFAULT 'pending'",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS doc_type text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS own_number text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS parent_number text",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS doc_date date",
|
||||
"ALTER TABLE documents ADD COLUMN IF NOT EXISTS counterparty text",
|
||||
]:
|
||||
execute(sql)
|
||||
|
||||
ctx = "\n".join(f"{r.get('name','')} | цена={r.get('price')} | объём={r.get('qty')} | сумма={r.get('sum')} | начало={r.get('date_start')}" for r in rows)
|
||||
prompt = f"Ты — анализатор договоров. Данные:\n{ctx}\n\nВопрос: {question}\nОтветь кратко, только по данным."
|
||||
from app.db import prompts as db_prompts
|
||||
db_prompts.seed_defaults()
|
||||
|
||||
try:
|
||||
with httpx.Client(http2=True, timeout=120) as client:
|
||||
resp = client.post(LLM_URL, json={
|
||||
"model": LLM_MODEL,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"max_tokens": 1000, "temperature": 0.1,
|
||||
}, headers={"Authorization": f"Bearer {LLM_KEY}", "Content-Type": "application/json"})
|
||||
resp.raise_for_status()
|
||||
return jsonify({"ok": True, "answer": resp.json()["choices"][0]["message"]["content"]})
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": f"LLM error: {e}"})
|
||||
# Crash recovery: сбросить застрявшие classify
|
||||
execute(
|
||||
"UPDATE documents SET classify_status='pending', error_message=NULL "
|
||||
"WHERE classify_status='processing'"
|
||||
)
|
||||
except Exception:
|
||||
pass # БД недоступна при старте — не фатально
|
||||
|
||||
|
||||
@app.route("/api/prompts", methods=["GET"])
|
||||
def prompts_get():
|
||||
try:
|
||||
with httpx.Client(timeout=10) as client:
|
||||
resp = client.get(f"{VM_API}/api/prompts", params=request.args)
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/prompts/list", methods=["GET"])
|
||||
def prompts_list():
|
||||
try:
|
||||
with httpx.Client(timeout=10) as client:
|
||||
resp = client.get(f"{VM_API}/api/prompts/list")
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/prompts/save", methods=["POST"])
|
||||
def prompts_save():
|
||||
try:
|
||||
with httpx.Client(timeout=10) as client:
|
||||
resp = client.post(f"{VM_API}/api/prompts/save", json=request.get_json())
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/prompts/activate", methods=["POST"])
|
||||
def prompts_activate():
|
||||
try:
|
||||
with httpx.Client(timeout=10) as client:
|
||||
resp = client.post(f"{VM_API}/api/prompts/activate", json=request.get_json())
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "error": str(e)})
|
||||
|
||||
|
||||
@app.route("/architect")
|
||||
def architect():
|
||||
return render_template("architect.html")
|
||||
|
||||
|
||||
@app.route("/ci-cd")
|
||||
def ci_cd():
|
||||
return render_template("ci-cd.html")
|
||||
|
||||
|
||||
@app.route("/drhider")
|
||||
@app.route("/DrHider")
|
||||
def drhider():
|
||||
return render_template("drhider.html", vm_api=VM_API)
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return {"ok": True, "service": "contracts-flask"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, threaded=True)
|
||||
|
||||
# ── Совместимость с Lucee-путями ──────────────────────────────
|
||||
|
||||
@app.route("/chat.cfm", methods=["POST"])
|
||||
def chat_cfm():
|
||||
return chat()
|
||||
|
||||
@app.route("/prompt.cfm", methods=["GET"])
|
||||
def prompt_cfm():
|
||||
return prompts_get()
|
||||
app = create_app()
|
||||
|
||||
Reference in New Issue
Block a user