File size: 2,945 Bytes
06c77d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""LLM layer: DeepSeek plain-language review of the BOQ analysis.
Falls back to a rule-based summary when the API is unavailable (offline demo safety)."""
import json
import os
import urllib.request
import ssl


def _key():
    try:
        for line in open(r"C:\Users\Benjamin\AppData\Local\hermes\.env", encoding="utf-8", errors="ignore"):
            if line.strip().startswith("DEEPSEEK_API_KEY="):
                return line.strip().split("=", 1)[1].strip().strip('"').strip("'")
    except Exception:
        pass
    return os.environ.get("DEEPSEEK_API_KEY", "")


def llm_review(items_count, trades, flags, grand_total, market_ctx=None):
    key = _key()
    if not key:
        return None, "llm_unavailable"
    flags_text = "\n".join(
        f"- [{f['severity']}] {f['description']}: {f['detail']}" for f in flags
    ) or "None"
    trades_text = ", ".join(f"{k} ~HK${v['amount']:,.0f}" for k, v in trades.items())
    prompt = (
        "You are a quantity surveying assistant reviewing an automated BOQ screening.\n"
        f"Items: {items_count}. Estimated total: HK${grand_total:,.0f} (reference-based).\n"
        f"Trades: {trades_text}.\n"
        f"Flags:\n{flags_text}\n"
        "Write a concise plain-language review for a non-expert project manager: "
        "1) is the estimate plausible, 2) which flags matter most and why, "
        "3) one concrete next step. Max 120 words. No markdown headers."
    )
    payload = json.dumps({
        "model": "deepseek-chat",
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 300,
    }).encode()
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    try:
        req = urllib.request.Request(
            "https://api.deepseek.com/chat/completions",
            data=payload,
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {key}"},
        )
        resp = json.loads(urllib.request.urlopen(req, timeout=90, context=ctx).read())
        return resp["choices"][0]["message"]["content"], "llm_ok"
    except Exception as e:
        return None, f"llm_error: {str(e)[:120]}"


def fallback_review(flags, grand_total):
    if not flags:
        return (f"The estimate (HK${grand_total:,.0f}) raised no automatic flags. "
                "It still needs a QS eye for scope omissions and provisional sums.")
    crit = [f for f in flags if f["severity"] == "critical"]
    warn = [f for f in flags if f["severity"] == "warning"]
    head = "Critical issue" if len(crit) == 1 else "Critical issues"
    body = f"Estimate HK${grand_total:,.0f}. {head}: "
    body += "; ".join(f"{f['description']} ({f['detail']})" for f in crit[:3])
    if warn:
        body += f". Plus {len(warn)} warning(s), including {warn[0]['description']}."
    body += " Next step: verify the flagged rates and quantities against the tender drawings before pricing."
    return body