# preagregaty i analiza AI
This commit is contained in:
@@ -1,31 +1,224 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os, sys, json
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
analysisAI.py — pobiera dane z MySQL, liczy preagregaty, renderuje HTML i dokłada analizę AI.
|
||||||
|
|
||||||
|
ZMIENNE ŚRODOWISKOWE (mają domyślne wartości):
|
||||||
|
OPENAI_API_KEY - klucz do OpenAI (gdy pusty -> skrypt pokaże wersję bez AI)
|
||||||
|
OPENAI_MODEL - np. gpt-4.1 (domyślnie)
|
||||||
|
MYSQL_HOST - host MySQL (domyślnie: localhost)
|
||||||
|
MYSQL_USER - użytkownik MySQL (domyślnie: root)
|
||||||
|
MYSQL_PASSWORD - hasło MySQL (domyślnie: rootpassword)
|
||||||
|
MYSQL_DATABASE - nazwa bazy (domyślnie: preDb_0dcc87940d3655fa574b253df04ca1c3)
|
||||||
|
MYSQL_PORT - port MySQL (domyślnie: 3306)
|
||||||
|
PERIOD_FROM - data od (YYYY-MM-DD); gdy brak -> poprzedni pełny miesiąc
|
||||||
|
PERIOD_TO - data do (YYYY-MM-DD, exclusive); gdy brak -> pierwszy dzień bieżącego miesiąca
|
||||||
|
INVOICE_TYPE - typ dokumentu (domyślnie: normal)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, sys, json, math, time, warnings
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
API_KEY = "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"
|
||||||
|
|
||||||
|
|
||||||
|
# Wycisz ostrzeżenie urllib3 (LibreSSL na macOS itp.)
|
||||||
|
try:
|
||||||
|
from urllib3.exceptions import NotOpenSSLWarning
|
||||||
|
warnings.filterwarnings("ignore", category=NotOpenSSLWarning)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import mysql.connector
|
||||||
from preaggregates import compute_preaggregates, serialize_for_ai
|
from preaggregates import compute_preaggregates, serialize_for_ai
|
||||||
|
|
||||||
try:
|
# --------- utils ---------
|
||||||
import mysql.connector
|
|
||||||
except Exception as e:
|
|
||||||
sys.stderr.write("MySQL connector not available: %s\n" % e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def getenv(key, default=None):
|
def getenv(k, d=None):
|
||||||
return os.environ.get(key, default)
|
return os.environ.get(k, d)
|
||||||
|
|
||||||
|
def last_full_month_bounds():
|
||||||
|
"""Zwraca (from_iso, to_iso) dla poprzedniego pełnego miesiąca."""
|
||||||
|
today_first = date.today().replace(day=1)
|
||||||
|
to_dt = today_first
|
||||||
|
prev_last = today_first - timedelta(days=1)
|
||||||
|
from_dt = prev_last.replace(day=1)
|
||||||
|
return from_dt.isoformat(), to_dt.isoformat()
|
||||||
|
|
||||||
|
def compact_table(table, limit=30):
|
||||||
|
"""Przytnij listę rekordów i znormalizuj liczby (NaN/Inf -> None)."""
|
||||||
|
out = []
|
||||||
|
if not table:
|
||||||
|
return out
|
||||||
|
for i, row in enumerate(table):
|
||||||
|
if i >= int(limit): break
|
||||||
|
new = {}
|
||||||
|
for k, v in row.items():
|
||||||
|
if isinstance(v, float):
|
||||||
|
new[k] = round(v, 6) if math.isfinite(v) else None
|
||||||
|
else:
|
||||||
|
new[k] = v
|
||||||
|
out.append(new)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def build_ai_payload(serialized, period_label):
|
||||||
|
"""Kompaktowy JSON do AI (ograniczone rozmiary)."""
|
||||||
|
return {
|
||||||
|
"kpis_hint": {"period_label": period_label},
|
||||||
|
"daily_sales": compact_table(serialized.get("daily_sales"), 30),
|
||||||
|
"product_summary": compact_table(serialized.get("product_summary"), 50),
|
||||||
|
"customer_summary": compact_table(serialized.get("customer_summary"), 50),
|
||||||
|
"top10_products_by_sales": compact_table(serialized.get("top10_products_by_sales"), 10),
|
||||||
|
"top10_customers_by_sales": compact_table(serialized.get("top10_customers_by_sales"), 10),
|
||||||
|
"product_daily_sample": compact_table(serialized.get("product_daily"), 40),
|
||||||
|
}
|
||||||
|
|
||||||
|
def call_openai_chat(api_key, model, system_prompt, user_payload_json,
|
||||||
|
temperature=0.3, connect_timeout=10, read_timeout=90, max_retries=3):
|
||||||
|
"""Wywołanie Chat Completions (retry + backoff). Zwraca HTML (sekcję) od AI."""
|
||||||
|
url = "https://api.openai.com/v1/chat/completions"
|
||||||
|
headers = {"Authorization": "Bearer " + api_key, "Content-Type": "application/json"}
|
||||||
|
body = {
|
||||||
|
"model": model,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": system_prompt},
|
||||||
|
{"role": "user", "content": "Dane (JSON):\n\n" + user_payload_json},
|
||||||
|
],
|
||||||
|
"temperature": temperature,
|
||||||
|
# "max_tokens": 1200, # możesz odkomentować, aby ograniczyć długość odpowiedzi
|
||||||
|
}
|
||||||
|
last_err = None
|
||||||
|
for attempt in range(1, int(max_retries) + 1):
|
||||||
|
try:
|
||||||
|
r = requests.post(url, headers=headers, json=body, timeout=(connect_timeout, read_timeout))
|
||||||
|
if 200 <= r.status_code < 300:
|
||||||
|
data = r.json()
|
||||||
|
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||||
|
last_err = RuntimeError("OpenAI HTTP {}: {}".format(r.status_code, r.text))
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
last_err = e
|
||||||
|
time.sleep(min(2 ** attempt, 10))
|
||||||
|
raise RuntimeError("OpenAI request failed: {}".format(last_err))
|
||||||
|
|
||||||
|
def fmt_money(v):
|
||||||
|
try:
|
||||||
|
return "{:,.2f}".format(float(v)).replace(",", " ").replace(".", ",")
|
||||||
|
except Exception:
|
||||||
|
return str(v)
|
||||||
|
|
||||||
|
def html_table(records, title=None, max_rows=20):
|
||||||
|
"""Proste generowanie tabeli HTML z listy dict-ów."""
|
||||||
|
if not records:
|
||||||
|
return '<div class="empty">Brak danych</div>'
|
||||||
|
cols = list(records[0].keys())
|
||||||
|
body_rows = records[:max_rows]
|
||||||
|
thead = "".join("<th>{}</th>".format(c) for c in cols)
|
||||||
|
trs = []
|
||||||
|
for r in body_rows:
|
||||||
|
tds = []
|
||||||
|
for c in cols:
|
||||||
|
val = r.get(c, "")
|
||||||
|
if isinstance(val, (int, float)):
|
||||||
|
# format dla kolumn „sales”, „qty”, „asp”, itp. – lekko ładniej
|
||||||
|
if "sales" in c or "total" in c or "netto" in c:
|
||||||
|
tds.append('<td class="num">{}</td>'.format(fmt_money(val)))
|
||||||
|
else:
|
||||||
|
tds.append('<td class="num">{}</td>'.format(val))
|
||||||
|
else:
|
||||||
|
tds.append('<td>{}</td>'.format(val))
|
||||||
|
trs.append("<tr>{}</tr>".format("".join(tds)))
|
||||||
|
cap = '<div class="tbl-title">{}</div>'.format(title) if title else ""
|
||||||
|
return (
|
||||||
|
cap +
|
||||||
|
'<div class="tbl-wrap"><table class="tbl">'
|
||||||
|
'<thead><tr>{}</tr></thead><tbody>{}</tbody></table></div>'.format(thead, "".join(trs))
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_report_html(period_label, kpis, parts, ai_section):
|
||||||
|
"""Składa finalny jeden <div> z lekkim CSS inline."""
|
||||||
|
css = (
|
||||||
|
"font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;"
|
||||||
|
"max-width:1200px;margin:24px auto;padding:16px 20px;border:1px solid #e5e7eb;"
|
||||||
|
"border-radius:12px;background:#fff;color:#111827"
|
||||||
|
)
|
||||||
|
kpi_item = (
|
||||||
|
'<div class="kpi"><div class="kpi-label">{label}</div>'
|
||||||
|
'<div class="kpi-value">{value}</div></div>'
|
||||||
|
)
|
||||||
|
kpi_html = "".join(
|
||||||
|
kpi_item.format(label=lbl, value=val) for (lbl, val) in kpis
|
||||||
|
)
|
||||||
|
sections_html = "".join(parts)
|
||||||
|
# jeśli AI nie zwróciło <div>, owiń
|
||||||
|
if ai_section and not ai_section.lstrip().startswith("<div"):
|
||||||
|
ai_section = '<div class="ai-section">{}</div>'.format(ai_section)
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<div style="{css}">
|
||||||
|
<h2 style="margin:0 0 12px;font-size:22px;">Raport sprzedaży — {period_label}</h2>
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:12px 0 20px;">
|
||||||
|
{kpi_html}
|
||||||
|
</div>
|
||||||
|
{sections_html}
|
||||||
|
<div style="margin-top:20px;border-top:1px solid #e5e7eb;padding-top:16px;">
|
||||||
|
<h3 style="margin:0 0 8px;font-size:18px;">Analiza i rekomendacje (AI)</h3>
|
||||||
|
{ai_section if ai_section else '<div style="color:#6b7280">Brak odpowiedzi AI (brak OPENAI_API_KEY)</div>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.kpi {{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:12px;}}
|
||||||
|
.kpi-label {{font-size:12px;color:#6b7280;margin-bottom:4px;}}
|
||||||
|
.kpi-value {{font-size:18px;font-weight:700;}}
|
||||||
|
.tbl-title {{font-weight:600;margin:16px 0 8px;font-size:15px;}}
|
||||||
|
.tbl-wrap {{overflow-x:auto;border:1px solid #e5e7eb;border-radius:8px;}}
|
||||||
|
.tbl {{border-collapse:collapse;width:100%;font-size:14px;}}
|
||||||
|
.tbl thead th {{text-align:left;background:#f3f4f6;padding:8px;border-bottom:1px solid #e5e7eb;white-space:nowrap;}}
|
||||||
|
.tbl tbody td {{padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;}}
|
||||||
|
.tbl td.num {{text-align:right;white-space:nowrap;}}
|
||||||
|
.empty {{color:#6b7280;font-style:italic;margin:8px 0;}}
|
||||||
|
.ai-section {{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:12px;}}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --------- main ---------
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
# Konfiguracja DB
|
||||||
cfg = {
|
cfg = {
|
||||||
#"host": getenv("MYSQL_HOST", "twinpol-mysql56"),
|
"host": getenv("MYSQL_HOST", "twinpol-mysql56"),
|
||||||
"host": getenv("MYSQL_HOST", "localhost"),
|
# "host": getenv("MYSQL_HOST", "localhost"),
|
||||||
"user": getenv("MYSQL_USER", "root"),
|
"user": getenv("MYSQL_USER", "root"),
|
||||||
"password": getenv("MYSQL_PASSWORD", "rootpassword"),
|
"password": getenv("MYSQL_PASSWORD", "rootpassword"),
|
||||||
"database": getenv("MYSQL_DATABASE", "preDb_0dcc87940d3655fa574b253df04ca1c3"),
|
"database": getenv("MYSQL_DATABASE", "preDb_0dcc87940d3655fa574b253df04ca1c3"),
|
||||||
"port": int(getenv("MYSQL_PORT", "3306")),
|
"port": int(getenv("MYSQL_PORT", "3306")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Zakres dat
|
||||||
|
period_from = getenv("PERIOD_FROM")
|
||||||
|
period_to = getenv("PERIOD_TO")
|
||||||
|
if not period_from or not period_to:
|
||||||
|
period_from, period_to = last_full_month_bounds()
|
||||||
|
period_label = "{} .. {}".format(period_from, period_to)
|
||||||
|
invoice_type = getenv("INVOICE_TYPE", "normal")
|
||||||
|
|
||||||
|
# Konfiguracja AI
|
||||||
|
#api_key = getenv("OPENAI_API_KEY", "")
|
||||||
|
api_key = API_KEY
|
||||||
|
model = getenv("OPENAI_MODEL", "gpt-4.1")
|
||||||
|
system_prompt = (
|
||||||
|
"Jesteś analitykiem sprzedaży. Zwróć TYLKO jedną sekcję HTML (bez <html>/<head>/<body>), "
|
||||||
|
"może być pojedynczy <div> z nagłówkami i listami. Podsumuj trendy, wskaż kluczowe produkty/klientów, "
|
||||||
|
"anomalia/odchylenia oraz daj 3–6 praktycznych rekomendacji. Krótko, konkretnie, po polsku."
|
||||||
|
)
|
||||||
|
|
||||||
|
# SQL -> rows
|
||||||
try:
|
try:
|
||||||
cnx = mysql.connector.connect(**cfg)
|
cnx = mysql.connector.connect(**cfg)
|
||||||
cur = cnx.cursor()
|
cur = cnx.cursor()
|
||||||
#cur.execute("SELECT COUNT(*) FROM ecminvoiceouts WHERE YEAR(register_date)=2025")
|
cur.execute(
|
||||||
cur.execute("""
|
"""
|
||||||
SELECT i.document_no,
|
SELECT i.document_no,
|
||||||
i.parent_name,
|
i.parent_name,
|
||||||
DATE(i.register_date) AS register_date,
|
DATE(i.register_date) AS register_date,
|
||||||
@@ -38,37 +231,88 @@ def main():
|
|||||||
WHERE i.register_date >= %s
|
WHERE i.register_date >= %s
|
||||||
AND i.register_date < %s
|
AND i.register_date < %s
|
||||||
AND i.type = %s
|
AND i.type = %s
|
||||||
""", ("2025-07-01", "2025-08-01", "normal"))
|
""",
|
||||||
|
(period_from, period_to, invoice_type),
|
||||||
|
)
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
|
|
||||||
results = compute_preaggregates(rows)
|
|
||||||
|
|
||||||
# 2) podejrzyj wyniki
|
|
||||||
# ['daily_sales', 'product_summary', 'customer_summary', 'product_daily',
|
|
||||||
# 'top10_products_by_sales', 'top10_customers_by_sales']
|
|
||||||
print(">> available tables:", list(results.keys()))
|
|
||||||
# print(results["daily_sales"].head(10))
|
|
||||||
# print(results["product_summary"])
|
|
||||||
# print(results["customer_summary"])
|
|
||||||
# print(results["product_daily"])
|
|
||||||
# print(results["top10_products_by_sales"])
|
|
||||||
# print(results["top10_customers_by_sales"])
|
|
||||||
results["daily_sales"].head(10)
|
|
||||||
results["product_summary"]
|
|
||||||
results["customer_summary"]
|
|
||||||
results["product_daily"]
|
|
||||||
results["top10_products_by_sales"]
|
|
||||||
results["top10_customers_by_sales"]
|
|
||||||
|
|
||||||
# 3) zserializuj do lekkiego JSON-a (np. do AI lub do pliku)
|
|
||||||
ai_payload = serialize_for_ai(results)
|
|
||||||
print(json.dumps(ai_payload, ensure_ascii=False, indent=2, default=str))
|
|
||||||
|
|
||||||
cur.close()
|
cur.close()
|
||||||
cnx.close()
|
cnx.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sys.stderr.write("Query error: %s\n" % e)
|
sys.stdout.write(
|
||||||
|
'<div style="font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;'
|
||||||
|
'max-width:900px;margin:24px auto;padding:16px 20px;border:1px solid #fecaca;'
|
||||||
|
'border-radius:12px;background:#fff5f5;color:#991b1b;">'
|
||||||
|
'<h3 style="margin:0 0 8px;font-size:18px;">Błąd połączenia/zapytania MySQL</h3>'
|
||||||
|
'<p style="margin:0;">{}</p></div>'.format(str(e))
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Preagregaty
|
||||||
|
try:
|
||||||
|
results = compute_preaggregates(rows)
|
||||||
|
serialized = serialize_for_ai(results)
|
||||||
|
except Exception as e:
|
||||||
|
sys.stdout.write(
|
||||||
|
'<div style="font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;'
|
||||||
|
'max-width:900px;margin:24px auto;padding:16px 20px;border:1px solid #fecaca;'
|
||||||
|
'border-radius:12px;background:#fff5f5;color:#991b1b;">'
|
||||||
|
'<h3 style="margin:0 0 8px;font-size:18px;">Błąd preagregacji</h3>'
|
||||||
|
'<p style="margin:0;">{}</p></div>'.format(str(e))
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# KPI (na podstawie daily_sales)
|
||||||
|
daily = serialized.get("daily_sales") or []
|
||||||
|
total_sales = sum((r.get("sales") or 0) for r in daily)
|
||||||
|
total_qty = sum((r.get("qty") or 0) for r in daily)
|
||||||
|
total_docs = sum((r.get("docs") or 0) for r in daily)
|
||||||
|
asp = (total_sales / total_qty) if total_qty else None
|
||||||
|
kpis = [
|
||||||
|
("Sprzedaż (PLN)", fmt_money(total_sales)),
|
||||||
|
("Ilość (szt.)", "{:,.0f}".format(total_qty).replace(",", " ")),
|
||||||
|
("Dokumenty", "{:,.0f}".format(total_docs).replace(",", " ")),
|
||||||
|
("ASP (PLN/szt.)", fmt_money(asp) if asp is not None else "—"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sekcje HTML z Twoich preagregatów
|
||||||
|
top_prod = serialized.get("top10_products_by_sales") or []
|
||||||
|
top_cli = serialized.get("top10_customers_by_sales") or []
|
||||||
|
prod_tbl = html_table(top_prod, title="Top 10 produktów (po sprzedaży)", max_rows=10)
|
||||||
|
cust_tbl = html_table(top_cli, title="Top 10 klientów (po sprzedaży)", max_rows=10)
|
||||||
|
|
||||||
|
# Dane do AI
|
||||||
|
ai_data = build_ai_payload(serialized, period_label)
|
||||||
|
ai_json = json.dumps(ai_data, ensure_ascii=False, separators=(",", ":"), default=str)
|
||||||
|
|
||||||
|
# Wołanie AI (opcjonalne)
|
||||||
|
ai_section = ""
|
||||||
|
if api_key:
|
||||||
|
try:
|
||||||
|
ai_section = call_openai_chat(
|
||||||
|
api_key=api_key,
|
||||||
|
model=model,
|
||||||
|
system_prompt=system_prompt,
|
||||||
|
user_payload_json=ai_json,
|
||||||
|
temperature=0.3,
|
||||||
|
connect_timeout=10,
|
||||||
|
read_timeout=90,
|
||||||
|
max_retries=3,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
ai_section = (
|
||||||
|
'<div style="color:#991b1b;background:#fff5f5;border:1px solid #fecaca;'
|
||||||
|
'padding:10px;border-radius:8px;">Błąd wywołania AI: {}</div>'.format(str(e))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finalny HTML (jeden <div>)
|
||||||
|
report_html = render_report_html(
|
||||||
|
period_label=period_label,
|
||||||
|
kpis=kpis,
|
||||||
|
parts=[prod_tbl, cust_tbl],
|
||||||
|
ai_section=ai_section
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.stdout.write(report_html)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -117,8 +117,8 @@ def top10_customers_by_sales(df: pd.DataFrame) -> pd.DataFrame:
|
|||||||
|
|
||||||
# ------------------- Runner -------------------
|
# ------------------- Runner -------------------
|
||||||
|
|
||||||
# def compute_preaggregates(rows: List[tuple]) -> dict[str, pd.DataFrame]:
|
def compute_preaggregates(rows: List[tuple]) -> dict[str, pd.DataFrame]:
|
||||||
def compute_preaggregates(rows):
|
#def compute_preaggregates(rows):
|
||||||
"""Główny punkt wejścia: rows -> df -> uruchom wszystkie agregatory."""
|
"""Główny punkt wejścia: rows -> df -> uruchom wszystkie agregatory."""
|
||||||
df = to_df(rows)
|
df = to_df(rows)
|
||||||
# results: dict[str, pd.DataFrame] = {}
|
# results: dict[str, pd.DataFrame] = {}
|
||||||
|
|||||||
@@ -1,44 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
$bins = [
|
$python = '/usr/local/bin/python3';
|
||||||
'/var/www/venv/bin/python',
|
$script = '/var/www/html/modules/EcmInvoiceOuts/ai/analysisAI.py';
|
||||||
'/usr/bin/python3.11',
|
$cmd = escapeshellcmd("$python $script");
|
||||||
'/usr/bin/python3.10',
|
|
||||||
'/usr/bin/python3.9',
|
// odczyt
|
||||||
'/usr/local/bin/python3.11',
|
$output = [];
|
||||||
'/usr/local/bin/python3.10',
|
$returnVar = 0;
|
||||||
'/usr/local/bin/python3.9',
|
exec($cmd . ' 2>&1', $output, $returnVar);
|
||||||
'/usr/bin/python3',
|
$body = implode("\n", $output);
|
||||||
'python3',
|
|
||||||
];
|
// błąd Pythona
|
||||||
foreach ($bins as $b) {
|
if ($returnVar !== 0) {
|
||||||
$out = []; $ret = 0;
|
// pokaż błąd jako tekst
|
||||||
exec("$b -V 2>&1", $out, $ret);
|
while (ob_get_level()) { ob_end_clean(); }
|
||||||
echo htmlspecialchars("$b -> ".($out ? implode(' ', $out) : "not found / not executable")." (ret=$ret)")."<br>";
|
header_remove();
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Error running Python script:\n".$body;
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
//$python = '/usr/bin/python3';
|
|
||||||
//$script = '/var/www/html/modules/EcmInvoiceOuts/ai/analysisAI.py';
|
// --- WYMUSZENIE RENDEROWANIA HTML ---
|
||||||
//$cmd = escapeshellcmd("$python $script");
|
while (ob_get_level()) { ob_end_clean(); } // wyczyść wszystkie bufory
|
||||||
//
|
header_remove(); // usuń nagłówki ustawione wcześniej przez framework
|
||||||
//// odczyt
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
//$output = [];
|
echo $body;
|
||||||
//$returnVar = 0;
|
exit; // ZATRZYMAJ framework (np. SugarCRM), żeby nic już nie dopisywał
|
||||||
//exec($cmd . ' 2>&1', $output, $returnVar);
|
|
||||||
//$body = implode("\n", $output);
|
|
||||||
//
|
|
||||||
//// błąd Pythona
|
|
||||||
//if ($returnVar !== 0) {
|
|
||||||
// // pokaż błąd jako tekst
|
|
||||||
// while (ob_get_level()) { ob_end_clean(); }
|
|
||||||
// header_remove();
|
|
||||||
// header('Content-Type: text/plain; charset=utf-8');
|
|
||||||
// http_response_code(500);
|
|
||||||
// echo "Error running Python script:\n".$body;
|
|
||||||
// exit;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// --- WYMUSZENIE RENDEROWANIA HTML ---
|
|
||||||
//while (ob_get_level()) { ob_end_clean(); } // wyczyść wszystkie bufory
|
|
||||||
//header_remove(); // usuń nagłówki ustawione wcześniej przez framework
|
|
||||||
//header('Content-Type: text/html; charset=utf-8');
|
|
||||||
//echo $body;
|
|
||||||
//exit; // ZATRZYMAJ framework (np. SugarCRM), żeby nic już nie dopisywał
|
|
||||||
|
|||||||
Reference in New Issue
Block a user