File size: 7,615 Bytes
585f232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""End-to-end check of the real pipeline on sample texts (no UI):
    python devtools/e2e.py [detect|humanize|smart|all] [--full-ai] [--passes N] [--sample tech|social|human]
Prints the before/after AI score, per-sentence changes, integrity checks and timings."""
import argparse
import os
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(Path(__file__).resolve().parent))
for s in (sys.stdout, sys.stderr):
    s.reconfigure(encoding="utf-8", errors="replace")
import env  # noqa: E402
env.load()
os.environ.setdefault("LT_GRAMMAR", os.environ.get("LT_GRAMMAR", ""))

import samples  # noqa: E402

SAMPLES = {"tech": samples.AI_TECH, "social": samples.AI_SOCIAL, "human": samples.HUMAN}


def rep(frac, desc=None):
    if desc:
        print(f"    [{frac:4.0%}] {desc}", flush=True)


TIMES = {}


def profile(mod, name):
    """Accumulate wall time and call count of mod.name (printed at the end)."""
    fn = getattr(mod, name)
    key = f"{mod.__name__}.{name}"

    def wrapped(*a, **k):
        t = time.time()
        try:
            return fn(*a, **k)
        finally:
            c = TIMES.setdefault(key, [0, 0.0, 0])
            c[0] += 1
            c[1] += time.time() - t
            if a and isinstance(a[0], (list, tuple)):
                c[2] += len(a[0])
    setattr(mod, name, wrapped)


def integrity(orig, new):
    """Numbers, citations and technical tokens of the original that are missing in the result."""
    import re
    import textparse as TP
    nums = set(re.findall(r"\d+(?:[.,]\d+)*", orig)) - set(re.findall(r"\d+(?:[.,]\d+)*", new))
    cites = {m.group(0) for m in TP.CITE.finditer(orig)} - {m.group(0) for m in TP.CITE.finditer(new)}
    tech = {m.group(0) for m in TP.TECH_TOKEN.finditer(orig)} - {m.group(0) for m in TP.TECH_TOKEN.finditer(new)}
    return dict(numbers=sorted(nums), citations=sorted(cites), terms=sorted(tech))


def show_pairs(orig, new):
    import humanizer as H
    import meaning as M
    so, sn = H.split_sentences(orig.replace("\n", " ")), H.split_sentences(new.replace("\n", " "))
    if len(so) != len(sn):
        print(f"  !! sentence count changed: {len(so)} -> {len(sn)}")
        print("  OUT:", new)
        return
    sims = M.similarities(list(zip(so, sn)))
    for a, b, s in zip(so, sn, sims):
        if a.strip() == b.strip():
            print(f"  = {a}")
        else:
            print(f"  - {a}\n  + {b}\n      meaning {s:.2f} | new wording {H.words_changed(a, b):.0f}%")


def make_docx(path):
    """A small manuscript with the things a rewrite must not disturb: heading, bold / italic runs inside a
    sentence, a superscript citation, a table, and a reference list."""
    from docx import Document
    d = Document()
    d.add_heading("Hybrid Thermal Management of a 48 V Battery Module", 1)
    paras = samples.AI_TECH.split(chr(10) * 2)
    first = paras[0]
    a, b = first.split("phase change material (PCM)")
    p = d.add_paragraph()
    p.add_run(a)
    p.add_run("phase change material (PCM)").bold = True
    head, tail = b.split(" [12].")
    p.add_run(head)
    p.add_run("12").font.superscript = True
    p.add_run("." + tail)
    d.add_heading("2. Monitoring model", 2)
    p2 = d.add_paragraph()
    x, y = paras[1].split("ResNet-50")
    p2.add_run(x)
    p2.add_run("ResNet-50").italic = True
    p2.add_run(y)
    t = d.add_table(rows=2, cols=2)
    for (r, c), v in {(0, 0): "Cooling", (0, 1): "Max temperature (°C)", (1, 0): "Hybrid", (1, 1): "41.2"}.items():
        t.cell(r, c).text = v
    d.add_heading("References", 1)
    d.add_paragraph("[12] Kumar, A., Lee, B. (2021). Thermal runaway in lithium-ion cells. Journal of Power Sources, "
                    "vol. 400, pp. 1-12. https://doi.org/10.1000/xyz")
    d.save(path)
    return path


def docx_facts(path):
    """What must be identical before and after: headings, formatted runs, table cells, reference entries."""
    from docx import Document
    d = Document(path)
    return dict(headings=[p.text for p in d.paragraphs if p.style.name.startswith("Heading")],
                bold=[r.text for p in d.paragraphs for r in p.runs if r.bold],
                italic=[r.text for p in d.paragraphs for r in p.runs if r.italic],
                sup=[r.text for p in d.paragraphs for r in p.runs if r.font.superscript],
                table=[c.text for t in d.tables for row in t.rows for c in row.cells],
                refs=[p.text for p in d.paragraphs if p.text.startswith("[12]")],
                n_paras=len(d.paragraphs))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("what", nargs="?", default="all")
    ap.add_argument("--sample", default="tech")
    ap.add_argument("--full-ai", action="store_true")
    ap.add_argument("--passes", type=int, default=2)
    ap.add_argument("--vocab", type=float, default=0)
    a = ap.parse_args()
    text = SAMPLES[a.sample]
    t0 = time.time()
    import detector as D
    import humanizer as H
    import smart as S
    import groqai as G
    import meaning as M, fluency as FL, clarity as CL, lexsub as LX
    for mod, name in ((D, "score_texts"), (H, "_generate"), (G, "_chat"), (M, "similarities"), (FL, "scores"),
                      (CL, "assess"), (LX, "variants"), (G, "review"), (G, "polish"), (G, "grammar"), (G, "variants")):
        profile(mod, name)
    print(f"models loaded in {time.time() - t0:.0f}s | groq: {G.status()}")
    H.FULL_AI.set(a.full_ai)
    H.VOCAB_TARGET.set(a.vocab or None)

    if a.what in ("detect", "all"):
        t = time.time()
        for name, tx in SAMPLES.items():
            print(f"[detect] {name}: {S.detector_pct_text(tx)}% AI")
        print(f"[detect] {time.time() - t:.1f}s")

    if a.what in ("humanize", "all"):
        t = time.time()
        out, path, _, summary, _ = H._text_core(text, 0.9, 6, "Auto", rep, max_words=5000)
        print(f"[humanize] {time.time() - t:.1f}s\n{summary}")
        show_pairs(text, out)
        print("  AI score:", S.detector_pct_text(text), "->", S.detector_pct_text(out), "| missing:", integrity(text, out))

    if a.what in ("smart", "all"):
        t = time.time()
        r = S.smart_text(text, 0, a.passes, 0.9, "Auto", 0.80, 0.50, rep)
        print(f"[smart] {time.time() - t:.1f}s  AI {r['before']['pct']}% -> {r['after']['pct']}%")
        for line in r["log"]:
            print("   ", line)
        print("   ", r["note"])
        show_pairs(r["orig"], r["new"])
        print("  missing:", integrity(r["orig"], r["new"]))
    if a.what in ("docx", "all"):
        src = make_docx(str(ROOT / "devtools" / "logs" / "sample.docx"))
        before = docx_facts(src)
        t = time.time()
        r = S.smart_docx(src, 0, a.passes, 0.9, "Auto", 0.80, 0.50, rep)
        after = docx_facts(r["path"])
        print(f"[docx] {time.time() - t:.1f}s  AI {r['before']['pct']}% -> {r['after']['pct']}%  -> {r['path']}")
        for line in r["log"]:
            print("   ", line)
        print("   ", r["note"])
        for k in before:
            print(f"  {'OK  ' if before[k] == after[k] else 'DIFF'} {k}" + ("" if before[k] == after[k] else
                                                                        f": {before[k]} -> {after[k]}"))
        show_pairs(r["orig"], r["new"])
        print("  missing:", integrity(r["orig"], r["new"]))
    print("groq:", G.status())
    for key, (n, sec, items) in sorted(TIMES.items(), key=lambda x: -x[1][1]):
        print(f"  {sec:7.1f}s  {n:4d} calls  {items:5d} items  {key}")


if __name__ == "__main__":
    main()