File size: 5,732 Bytes
ede84b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""

webapi/versions.py — ตอนนี้ Space รันข้อมูลกับโมเดลชุดไหน (28 ต.ค.) · ใช้กับ /health และ /health/detail



เก็บครั้งเดียวหลังบอทโหลดเสร็จ (ก่อนมีเทิร์นแรก — Qdrant แบบไฟล์ไม่ควรถูกอ่านพร้อม retriever)

อ่านอย่างเดียวทุกอย่าง: นับจุด / อ่าน alias / hash ไฟล์ — ไม่เขียน collection ไหนเลย (Movex ก็แค่นับ)

ไม่ส่งพาธเซิร์ฟเวอร์ออกไป — ชื่อไฟล์อย่างเดียว · แต่ละส่วนพังแยกกัน ไม่ทำให้ /health ล้ม

"""
from __future__ import annotations

import hashlib
import os
from dataclasses import asdict, is_dataclass
from typing import Any

DATA_KEYS = ("catalogue", "products", "graph_db")


def _sha12(path: str) -> str | None:
    try:
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(1 << 20), b""):
                h.update(chunk)
        return h.hexdigest()[:12]
    except OSError:
        return None


def _retriever_parts(plugin) -> tuple[Any, list[str]]:
    """(client, [ชื่อ collection ที่ retriever ใช้]) — Movex ห่อ retriever ไว้ใน .inner"""
    rt = getattr(plugin, "runtime", None)
    r = getattr(rt, "retriever", None)
    r = getattr(r, "inner", r)
    client, cfg = getattr(r, "client", None), getattr(r, "cfg", None)
    names = [n for n in (getattr(cfg, "text_collection", None), getattr(cfg, "image_collection", None))
             if n and not str(n).endswith("__not_built")]
    return client, names


def _collections(plugin) -> list[dict]:
    client, names = _retriever_parts(plugin)
    if client is None:
        return []
    try:
        aliases = {a.alias_name: a.collection_name for a in client.get_aliases().aliases}
    except Exception:                                    # noqa: BLE001
        aliases = {}
    out = []
    for n in names:
        row: dict = {"name": n}
        if n in aliases:
            row["target"] = aliases[n]
        try:
            row["points"] = client.get_collection(n).points_count
        except Exception as e:                           # noqa: BLE001 — collection ไม่มี (เช่น รูป Movex ปิดอยู่)
            row["points"] = None
            row["error"] = type(e).__name__
        out.append(row)
    return out


def collect(mod: Any) -> dict:
    from core import model_lock
    brands: dict = {}
    plugins = getattr(mod, "plugins", {}) or {}
    gates = getattr(mod, "_gates", {}) or {}
    for b in getattr(mod, "_configs", []) or []:
        part: dict = {}
        try:
            data = b.config.get("data") or {}
            part["data"] = [{"file": os.path.basename(p), "sha256": _sha12(p)}
                            for p in (b.path(data.get(k)) for k in DATA_KEYS) if p]
        except Exception as e:                           # noqa: BLE001
            part["data_error"] = type(e).__name__
        try:
            part["collections"] = _collections(plugins.get(b.key))
        except Exception as e:                           # noqa: BLE001
            part["collections_error"] = type(e).__name__
        img = getattr(b, "image", None)
        if img is not None:
            spec = getattr(img, "spec", None)
            part["image"] = {"enabled": bool(img.enabled), "vit_revision": getattr(spec, "revision", None)}
        g = gates.get(b.key)
        if g is not None or b.config.get("image_gate"):
            th = getattr(g, "th", None)
            part["gate"] = {"loaded": g is not None,
                            "thresholds": asdict(th) if is_dataclass(th) else None,
                            "model": (b.config.get("image_gate") or {}).get("model")}
        brands[b.key] = part

    try:
        models = model_lock.status()
    except Exception as e:                               # noqa: BLE001
        models = {"error": f"{type(e).__name__}: {e}"}

    manifest: dict = {"present": False}
    try:
        import data_manifest
        if os.path.isfile(data_manifest.OUT):
            manifest = {"present": True, "problems": data_manifest.check()}
    except Exception as e:                               # noqa: BLE001
        manifest = {"present": None, "error": type(e).__name__}

    return {"brands": brands, "models": models,
            "llm": {"model": getattr(mod, "GEMINI_MODEL", None)},
            "manifest": manifest}


def summary(v: dict) -> dict:
    """ส่วนสั้นสำหรับ /health สาธารณะ — ไม่มีชื่อ collection หรือ hash ยาว"""
    if not v:
        return {}
    cats = [d["file"].rsplit(".", 1)[0] for b in v.get("brands", {}).values() for d in b.get("data", [])
            if d.get("file", "").startswith("modutech_v")]
    models = v.get("models") or {}
    locked = [m for m in models.values() if isinstance(m, dict) and m.get("locked")]
    mismatch = [k for k, m in models.items() if isinstance(m, dict) and m.get("match") is False]
    man = v.get("manifest") or {}
    return {"catalogue": cats[0] if cats else None,
            "models_locked": len(locked), "models_mismatch": len(mismatch),
            "data_ok": (not man.get("problems")) if man.get("present") else None}