dopetalk does not endorse any advertised product nor does it accept any liability for it's use or misuse


Our Discord Notification Server invitation link is https://discord.gg/jB2qmRrxyD

Author Topic: token_preprocessor.py  (Read 11 times)

Online Chip (OP)

  • Server Admin
  • Hero Member
  • *****
  • Administrator
  • *****
  • Join Date: Dec 2014
  • Location: Australia
  • Posts: 7256
  • Reputation Power: 0
  • Chip has hidden their reputation power
  • Gender: Male
  • Last Login:Today at 07:27:55 AM
  • Deeply Confused Learner
  • Profession: IT Engineer now retired
token_preprocessor.py
« on: Today at 03:16:20 AM »
WinSCP Preferences for &Binary added a mask for "*.py; *.ai

Code: [Select]
bash file:
/python/my36project/bin/python3 $1 $2 $3 $4

run:
python3 token_preprocessor.py transcript.txt > query2.ai

Code: [Select]
#!/usr/bin/env python3
"""
token_preprocessor.py

Cleans forum transcript text (BBCode + PDF-extraction artifacts) before
pasting into an LLM conversation, to reduce token count without losing
meaning.

Usage:
    python3 token_preprocessor.py input.txt output.txt
    python3 token_preprocessor.py input.txt          # prints to stdout
    cat input.txt | python3 token_preprocessor.py -   # read from stdin

What it does:
    1. Strips BBCode tags ([b], [color=...], [/color], etc.) - pure
       formatting overhead, zero semantic content.
    2. Fixes broken-hyphenation artifacts from PDF text extraction
       (e.g. "the- kind- of" -> "the kind of").
    3. Collapses "===== Page NN =====" page-break markers.
    4. Replaces common verbose phrases with shorter equivalents
       (e.g. "in order to" -> "to", "utilize" -> "use").
    5. Collapses excess whitespace/blank lines.

What it deliberately does NOT do:
    - Touch precise technical terms (won't blindly simplify vocabulary
      that carries real information, e.g. "positional encoding").
    - Rewrite sentence structure or meaning.
"""

import re
import sys


# ---------------------------------------------------------------------
# 0. Fix double-encoded UTF-8 (mojibake), e.g. "—" instead of "—"
#    This happens when UTF-8 bytes get misread as cp1252/Latin-1 and
#    then re-saved as UTF-8. Common with copy/paste through some PDF
#    extraction and terminal pipelines.
# ---------------------------------------------------------------------
def fix_mojibake(text: str) -> str:
    try:
        repaired = text.encode('cp1252').decode('utf-8')
        return repaired
    except (UnicodeEncodeError, UnicodeDecodeError):
        # Original text wasn't double-encoded this way; leave it alone.
        return text

# ---------------------------------------------------------------------
# 1. BBCode stripping
# ---------------------------------------------------------------------
BBCODE_TAG_RE = re.compile(r'\[/?(?:color|b|i|u|size|url|quote|img)[^\]]*\]', re.IGNORECASE)

def strip_bbcode(text: str) -> str:
    return BBCODE_TAG_RE.sub('', text)


# ---------------------------------------------------------------------
# 2. Fix "word- broken- like- this" hyphenation artifacts
#    Pattern: letter + "-" + optional space + letter, repeated, where
#    the hyphen was inserted by PDF line-wrap extraction rather than
#    being a genuine compound word.
# ---------------------------------------------------------------------
BROKEN_HYPHEN_RE = re.compile(r'(\w)-\s+(\w)')

def fix_broken_hyphenation(text: str) -> str:
    # Repeatedly collapse until no more matches (handles multi-part breaks
    # like "the- kind- of" -> "the kind of")
    prev = None
    while prev != text:
        prev = text
        text = BROKEN_HYPHEN_RE.sub(r'\1 \2', text)
    return text


# ---------------------------------------------------------------------
# 3. Remove page-break markers
# ---------------------------------------------------------------------
PAGE_MARKER_RE = re.compile(r'=+\s*Page\s*\d+\s*=+', re.IGNORECASE)

def strip_page_markers(text: str) -> str:
    return PAGE_MARKER_RE.sub('', text)


# ---------------------------------------------------------------------
# 4. Verbose -> plain phrase dictionary
#    Ordered longest-first so multi-word phrases match before single
#    words that might be substrings.
# ---------------------------------------------------------------------
PHRASE_MAP = [
    ("in order to", "to"),
    ("due to the fact that", "because"),
    ("in the event that", "if"),
    ("with regard to", "about"),
    ("with respect to", "about"),
    ("in spite of the fact that", "although"),
    ("for the purpose of", "to"),
    ("at this point in time", "now"),
    ("in the process of", "while"),
    ("a large number of", "many"),
    ("the majority of", "most"),
    ("utilize", "use"),
    ("utilization", "use"),
    ("commence", "start"),
    ("terminate", "end"),
    ("facilitate", "help"),
    ("subsequently", "then"),
    ("approximately", "about"),
    ("in conjunction with", "with"),
    ("prior to", "before"),
    ("subsequent to", "after"),
]

def shorten_phrases(text: str) -> str:
    for verbose, plain in PHRASE_MAP:
        pattern = re.compile(r'\b' + re.escape(verbose) + r'\b', re.IGNORECASE)
        text = pattern.sub(plain, text)
    return text


# ---------------------------------------------------------------------
# 5. Whitespace cleanup
# ---------------------------------------------------------------------
def collapse_whitespace(text: str) -> str:
    text = re.sub(r'[ \t]+', ' ', text)
    text = re.sub(r'\n{3,}', '\n\n', text)
    text = re.sub(r' +\n', '\n', text)
    return text.strip()


def preprocess(text: str) -> str:
    text = fix_mojibake(text)
    text = strip_bbcode(text)
    text = strip_page_markers(text)
    text = fix_broken_hyphenation(text)
    text = shorten_phrases(text)
    text = collapse_whitespace(text)
    return text


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    src = sys.argv[1]
    if src == '-':
        raw = sys.stdin.read()
    else:
        with open(src, 'r', encoding='utf-8') as f:
            raw = f.read()

    cleaned = preprocess(raw)

    if len(sys.argv) >= 3:
        with open(sys.argv[2], 'w', encoding='utf-8') as f:
            f.write(cleaned)
        orig_words = len(raw.split())
        new_words = len(cleaned.split())
        print(f"Original words: {orig_words}")
        print(f"Cleaned words:  {new_words}")
        print(f"Reduction:      {orig_words - new_words} words "
              f"({100*(orig_words-new_words)/max(orig_words,1):.1f}%)")
        print(f"Written to {sys.argv[2]}")
    else:
        print(cleaned)


if __name__ == '__main__':
    main()
« Last Edit: Today at 03:22:36 AM by Chip »
friendly
0
funny
0
informative
0
agree
0
disagree
0
like
0
dislike
0
No reactions
No reactions
No reactions
No reactions
No reactions
No reactions
No reactions
Our Discord Server invitation link is https://discord.gg/jB2qmRrxyD

Tags:
 


dopetalk does not endorse any advertised product nor does it accept any liability for it's use or misuse





TERMS AND CONDITIONS

In no event will d&u or any person involved in creating, producing, or distributing site information be liable for any direct, indirect, incidental, punitive, special or consequential damages arising out of the use of or inability to use d&u. You agree to indemnify and hold harmless d&u, its domain founders, sponsors, maintainers, server administrators, volunteers and contributors from and against all liability, claims, damages, costs and expenses, including legal fees, that arise directly or indirectly from the use of any part of the d&u site.


TO USE THIS WEBSITE YOU MUST AGREE TO THE TERMS AND CONDITIONS ABOVE


Founded December 2014
SimplePortal 2.3.6 © 2008-2014, SimplePortal