๐Ÿ“ ISE_Manual.mdv4.4 · 2026-09-05

ISE โ€” Intelligent Search Engine

Operator manual

A custom search mod for SMF. Started as forum-wide post search ("ISE"), PDF-attachment search ("ISEpdf"), and text-attachment search ("ISEmisc") as options in the existing CustomSearch dropdown; now five search sources โ€” this update (2026-09-23) adds full coverage of the two that had grown past what this manual documented: ISEmega (a combined view across the first three) and ISEmedia (image/video search by meaning and keyword). This is the single consolidated operator manual for all five; ISE_Python_Scripts_Manual.md is the companion code-level reference (per-script options/internals), and ISE_help.html is the in-app, end-user-facing help page โ€” all three were audited together against the real source (v9.5) for this update, not just cross-checked against each other's claims.


1. What it does

ISE/ISEpdf/ISEmisc/ISEmega bypass the database at query time โ€” they read pre-built JSON indexes for speed, so those indexes must be rebuilt manually after posts or attachments change (see ยง4). ISEmedia works the same way in spirit (pre-built embeddings in media_store.json, not a live DB/CLIP-encode-everything pass per search) but is built via its own two-stage pipeline (MediaIndexer.py then MediaProcessor.py), covered in ISE_Python_Scripts_Manual.md rather than duplicated here.


2. File layout

<SMF root>/
โ”œโ”€โ”€ Sources/Search.php โ€” dispatcher: case 'ISE' / 'ISEpdf' / 'ISEmisc'
โ”œโ”€โ”€ Sources/ManageSettings.php โ€” CustomSearch_use_ISE / _ISEpdf / _ISEmisc
โ”‚ checkboxes, plus ise_reset_pdfjs /
โ”‚ ise_reset_isedata reset checkboxes
โ”œโ”€โ”€ Sources/Admin.php โ€” registers CustomSearch in admin menu
โ”œโ”€โ”€ Themes/default/index.template.php โ€” dropdown options
โ”œโ”€โ”€ Themes/default/languages/Modifications.english.php โ€” label strings
โ”‚ (also patched at Themes/default/language/ โ€” singular โ€” for installs
โ”‚ that use that spelling instead)
โ”œโ”€โ”€ ISE_Data/ โ€” generated indexes + logs (777, apache-owned)
โ”‚ โ”œโ”€โ”€ word_index.json / post_store.json โ€” forum post index
โ”‚ โ”œโ”€โ”€ word_index_pdf.json / pdf_store.json โ€” PDF index
โ”‚ โ”œโ”€โ”€ word_index_misc.json / misc_store.json โ€” Misc (.md/.txt/.text/.json/.html content, plus filename-only code/config types) index
โ”‚ โ”œโ”€โ”€ media_store.json โ€” ISEmedia catalog (MediaIndexer.py) + CLIP embeddings/captions (MediaProcessor.py)
โ”‚ โ”œโ”€โ”€ hf_cache/ โ€” ISEmedia only: shared, apache-readable Hugging Face model cache
โ”‚ โ”‚ (CLIP checkpoint) โ€” see ยง3/ยง11; nothing else in ISE uses this
โ”‚ โ”œโ”€โ”€ Settings_ISE.json โ€” per-install {"environment", "install"} selector read by ise_settings.py
โ”‚ โ”œโ”€โ”€ ise_trace.log โ€” shared tracing log, ISE/ISEpdf/ISEmisc/ISEmega/ISEmedia
โ”‚ โ””โ”€โ”€ *.log โ€” other trace/debug logs
โ”œโ”€โ”€ The_ISE_Project/ โ€” all scripts
โ”‚ โ”œโ”€โ”€ qf_search_bridge.php โ€” web entry point for ISE
โ”‚ โ”œโ”€โ”€ qf_PDFsearch_bridge.php โ€” web entry point for ISEpdf
โ”‚ โ”œโ”€โ”€ qf_Miscsearch_bridge.php โ€” web entry point for ISEmisc
โ”‚ โ”œโ”€โ”€ qf_MegaSearch_bridge.php โ€” web entry point for ISEmega
โ”‚ โ”œโ”€โ”€ qf_Mediasearch_bridge.php โ€” web entry point for ISEmedia (the only bridge that
โ”‚ โ”‚ resolves python3.8 explicitly โ€” see ยง3/ยง11)
โ”‚ โ”œโ”€โ”€ search.py โ€” forum post search (called by bridge)
โ”‚ โ”œโ”€โ”€ PDFsearch.py โ€” PDF search (called by bridge)
โ”‚ โ”œโ”€โ”€ MiscSearch.py โ€” Misc search (called by bridge)
โ”‚ โ”œโ”€โ”€ MegaSearch.py โ€” combined Posts+PDF+Misc search (called by bridge)
โ”‚ โ”œโ”€โ”€ MediaSearch.py โ€” image/video search (called by bridge; needs python3.8)
โ”‚ โ”œโ”€โ”€ ISE_help.html โ€” shared help page, all five sources
โ”‚ โ”‚ (see ยง9) โ€” served on "/help" query,
โ”‚ โ”‚ linked from every results page footer
โ”‚ โ”œโ”€โ”€ IndexBuilder.py โ€” builds word_index.json / post_store.json
โ”‚ โ”œโ”€โ”€ PDFIndexer.py โ€” builds word_index_pdf.json / pdf_store.json
โ”‚ โ”œโ”€โ”€ MiscIndexer.py โ€” builds word_index_misc.json / misc_store.json
โ”‚ โ”œโ”€โ”€ MediaIndexer.py โ€” catalogs image/video attachments into media_store.json
โ”‚ โ”‚ (needs python3.8; settings via Collabware.core_utils,
โ”‚ โ”‚ not ise_settings.py โ€” a different codebase glued in,
โ”‚ โ”‚ see ISE_Python_Scripts_Manual.md)
โ”‚ โ”œโ”€โ”€ MediaProcessor.py โ€” computes CLIP embeddings + captions into media_store.json
โ”‚ โ”‚ (needs python3.8; same Collabware.core_utils note)
โ”‚ โ”œโ”€โ”€ IndexLookup.py โ€” shared index-reading helper (posts)
โ”‚ โ”œโ”€โ”€ ise_trace.py โ€” shared tracing module (all five search scripts;
โ”‚ โ”‚ NOT used by MediaIndexer.py/MediaProcessor.py, which
โ”‚ โ”‚ log via their own --monitor/--verbose telemetry instead)
โ”‚ โ”œโ”€โ”€ ise_settings.py โ€” shared base-URL/project-URL resolver (Settings_ISE.json
โ”‚ โ”‚ lookup) for search.py/PDFsearch.py/MiscSearch.py/
โ”‚ โ”‚ MegaSearch.py/MediaSearch.py
โ”‚ โ”œโ”€โ”€ SortEngine.py โ€” shared /sort directive parser, all five search scripts
โ”‚ โ”œโ”€โ”€ reset_ise_data.php โ€” install-time code hook, see ยง3
โ”‚ โ”œโ”€โ”€ orphan_check.py โ€” standalone admin/maintenance tool (cron), not part
โ”‚ โ”‚ of the search UI/dropdowns; finds attachments whose
โ”‚ โ”‚ id_msg no longer resolves to a real post
โ”‚ โ””โ”€โ”€ RankingEngine.py, QueryParser.py, etc. โ€” scoring/parsing internals (posts/PDF/misc;
โ”‚ ISEmedia currently falls back to its own plain similarity score rather than a
โ”‚ dedicated ranking-engine interface โ€” see ISE_Python_Scripts_Manual.md)
โ””โ”€โ”€ pdfjs/ โ€” bundled PDF.js viewer

Each SMF instance (live, smf20, smf21, ...) has its own complete, independent copy of The_ISE_Project/ and ISE_Data/ โ€” nothing is shared between instances by design.


3. Installing

  1. Install prerequisite mods first, in order (see readme.txt in the package): Board-color-and-icons, Automatic Package Version Emulation, CustomSearch.
  2. Install ISE_V<version>.zip via SMF's package manager as normal.
  3. install_ise.php runs automatically and creates The_ISE_Project/ and ISE_Data/ with 777 permissions, then kicks off IndexBuilder.py, PDFIndexer.py, MiscIndexer.py, and MediaIndexer.py in the background (non-blocking โ€” the install request itself returns immediately). Progress/output for each lands in its own log file under ISE_Data/. As of v9.5, this step also self-provisions ISE_Data/hf_cache (ISEmedia's CLIP model cache) if it's not already populated โ€” a cached copy from root's own Hugging Face cache if the install is running as root, otherwise a backgrounded fresh download straight into hf_cache; either way this doesn't block the install request finishing. See ยง11 if a fresh environment's ISEmedia search still fails after install with a cache-related error.
  4. Go to Admin > Configuration > Modification Settings > CustomSearch and uncheck then recheck ISE, ISEpdf, ISEmisc, ISEmega, and ISEmedia, saving each time โ€” this forces the dropdown to pick up all five options.
  5. Test a search using each of the five dropdown options before relying on any of them โ€” ISEmedia specifically needs its own separate confirmation that python3.8 and the HF cache are both actually reachable by the web server user (apache), not just installed; a working ISE/ISEpdf/ISEmisc/ISEmega search says nothing about whether ISEmedia will work, since it runs on a different Python interpreter and has its own model-cache dependency (see ยง2, ยง11).

Uninstalling reverses the install.xml file edits and deletes The_ISE_Project/ only. ISE_Data/ and pdfjs/ are deliberately left untouched on uninstall โ€” both hold real generated data (indexes, trace logs, PDF.js viewer assets, hf_cache) that shouldn't vanish just because the mod entry is removed from Package Manager's list.


4. Building the indexes (manual, run after every install and periodically after)

cd <SMF root>/The_ISE_Project
python3 IndexBuilder.py --monitor # forum posts โ†’ word_index.json / post_store.json
python3 PDFIndexer.py --monitor # PDF attachments โ†’ word_index_pdf.json / pdf_store.json
python3 MiscIndexer.py --monitor # .md/.txt/.text/.json โ†’ word_index_misc.json / misc_store.json

IndexBuilder.py and MiscIndexer.py are both full-rebuild-only โ€” no resume/incremental mode, safe to re-run anytime. PDFIndexer.py has its own resume-by-default behavior; see the Python Scripts Manual for details.

Suggested cron schedule (adjust to your forum's actual posting/upload volume):

# Every 2 hours (on the hour) โ€” rebuild the Post index
0 */2 * * * python3 /var/www/html/The_ISE_Project/IndexBuilder.py >> /var/www/html/ISE_Data/indexbuilder.log 2>&1
# Every 2 hours (on the half-hour) โ€” rebuild the Misc/text index
30 */2 * * * python3 /var/www/html/The_ISE_Project/MiscIndexer.py --monitor >> /var/www/html/ISE_Data/misc_indexer.log 2>&1
# Every 6 hours โ€” fast rebuild of the PDF index
5 */6 * * * python3 /var/www/html/The_ISE_Project/PDFIndexer.py --fast --monitor >> /var/www/html/ISE_Data/pdfindexer_fast.log 2>&1
# Every 2 days โ€” full union rebuild (both extractors) of the PDF index
10 0 */2 * * python3 /var/www/html/The_ISE_Project/PDFIndexer.py --monitor >> /var/www/html/ISE_Data/pdfindexer_union.log 2>&1

5. The reset checkboxes

Two checkboxes on the CustomSearch settings page control a one-shot, self-clearing reset mechanism for the next install/reinstall:

Both default to unchecked. Each is a one-shot flag: tick it, save, run the install/reinstall, and it clears itself back to unchecked automatically once it fires โ€” so it can't accidentally wipe data again on a later reinstall you didn't intend to reset.


6. Wildcard search

Supported for ISEpdf and ISEmisc; not yet for ISE (forum posts) โ€” see the in-app help page for the user-facing explanation of */? syntax and per-source behavior: <boardurl>/The_ISE_Project/ISE_help.html, or type /help in any of the three search boxes (ยง9).

Implementation note: a term with no wildcard characters still uses the original fast exact-match (content) / substring-match (filename/topic) path in both scripts โ€” wildcards only trigger the slower full-vocabulary scan when actually present in the term. ISE (posts) doesn't have this yet because its matching runs through QueryParser.py โ†’ Tokeniser.py โ†’ ANDMatcher.py โ†’ IndexLookup.py, a different pipeline from ISEpdf/ISEmisc's direct dict-lookup approach โ€” wildcard support there needs its own implementation against those modules, not a copy of the ISEpdf/ISEmisc approach.


7. Result sorting (/sort)

Built in v9.3.0 โ€” see changelog.md. v9.2 shipped the /help page text promising this (/sort newest|oldest|newmod|oldmod) before the code actually did it; v9.3.0 closes that gap for real. ISEmedia (built later, 2026-09-20 onward) picked up the same newest/oldest support via the same shared SortEngine.py โ€” see the ISEmedia-specific note below, its behavior isn't a plain drop-in of the other four sources' pattern.

Typed directly in the search box, same convention as /help and the older #ise-order-<mode> syntax (still works, kept for back-compat; /sort wins if a query somehow contains both). Parsing happens in Python, inside each source's own get_ranked_results() โ€” via the new shared SortEngine.extract_order_mode() โ€” not at the PHP bridge layer; the bridges still just pass the raw query string straight through unmodified, same as they always have.

Why newmod/oldmod is posts-only: ISEpdf/ISEmisc attachments carry poster_time only โ€” attachments themselves aren't edited the way a forum post is, so there's no modified_time field anywhere in pdf_store.json/misc_store.json to sort by. newest/oldest work on all three individual sources.

ISEmega: newest/oldest work as a genuine flat chronological merge across the three sources it covers โ€” posts, PDF, misc; ISEmega does not include ISEmedia at all, see ยง1 โ€” via ise_render.merge_by_date(). Not a re-sort layered on top of the usual score-interlaced merge. The two are different merge strategies: interlacing exists specifically to stop one source dominating a relevance-score merge, which isn't a concern once you're sorting by date, so a date sort bypasses interlace_results() entirely rather than re-sorting its output. newmod/oldmod are not offered at the mega level โ€” there's no coherent way to merge a posts-only "last edited" ordering against two sources that don't have the concept at all; use ISE (posts) search directly for those.

ISEmedia โ€” a real behavioral difference, not just a missing feature: newest/oldest work, but only ever among items that already passed a literal keyword-match gate (v1.2.0) when the query has real search terms. For ISE/ISEpdf/ISEmisc/ISEmega, "sort by date, ignore relevance score" is always safe, because each source's own index lookup already narrowed the candidate list down to genuine matches before any sort runs. ISEmedia has no equivalent lookup โ€” its CLIP similarity search treats every indexed image/video as a candidate regardless of query โ€” so without the v1.2.0 gate, /sort newest brown would have silently shown every recent upload, "brown" or not. Fixed live, same night it was caught (2026-09-23) โ€” see ISE_Python_Scripts_Manual.md's MediaSearch.py section for the full incident and the accepted trade-off (a genuinely relevant but uncaptioned image no longer surfaces for a worded query). A bare /sort newest with no other search term is unaffected โ€” nothing to gate by, so every item's a candidate, same as it always was.

The "Why is this ranked #N?" explainer on every result card correctly reflects whichever sort is active โ€” it says "sorted by newest, not relevance score" (etc.) instead of claiming a relevance ranking that isn't what actually produced the order.


8. Known quirks (this SMF fork specifically)

9. In-app help (/help and the footer link)

All three search UIs share one built-in help page, The_ISE_Project/ISE_help.html โ€” a single static file (web-accessible under The_ISE_Project/ per the .htaccess *.html rule) that explains each source's search methods and, accurately, how unquoted/'single'/ "double" quoting and */? wildcards behave differently across ISE, ISEpdf, and ISEmisc (including the real gap where single-quoting a word in ISE search returns nothing โ€” documented plainly rather than glossed over). It carries its own theme switcher (same 13 themes as the results pages) and picks up whichever theme was last selected on any of the three sources' results pages.

Two ways to reach it:

10. Planned, not yet built


11. Troubleshooting checklist

Symptom Likely cause
Same search results regardless of query Bridge PHP silently failed and served a stale cached output file
PDF search always returns โ‰ค20 results --limit cap in PDFsearch.py (should default to unlimited)
Search returns almost nothing on a populated forum Index wasn't rebuilt after content changed, or IndexBuilder.py/PDFIndexer.py/MiscIndexer.py pointed at the wrong instance's Settings.php/attachments folder
One instance's search shows another instance's data A script has a hardcoded absolute path instead of resolving relative to its own file location โ€” check ATTACH_DIR, WORD_INDEX_PATH, *_STORE_PATH, and the bridge's $script variable
Dropdown missing ISE/ISEpdf/ISEmisc options after install index.template.php operation didn't match โ€” check tab count/whitespace against the live file with cat -A
install_ise.php runs but nothing happens Check file_exists() on the target script paths โ€” likely means The_ISE_Project wasn't extracted yet (code step must run after the extract steps in package-info.xml)
Checkbox appears on the settings page but with no label text (blank) The language-string <search> anchor in install.xml/a mini-mod didn't match โ€” usually a whitespace/alignment mismatch against the live file's actual (often hand-edited, inconsistently spaced) content. Always verify the anchor byte-for-byte against a fresh copy of the live file (cat -A or od -c), not a pasted/remembered version of it
Package Manager shows "Skipping Search" for a file operation The <search> text genuinely wasn't found in the target file at install time โ€” pull the file straight off the server and diff it against the anchor text exactly; don't assume the anchor that worked in a previous package version still matches, especially for blocks that get touched by more than one mod over time (see ยง7)
A patch appears to have installed successfully (no warning, shows in Package Manager's installed list) but the change isn't visible on the live page Check the raw file on the server directly (not the rendered browser page) to isolate whether the source was actually patched. If the source was patched correctly but the page still shows the old version, suspect PHP OPcache or SMF's own settings/menu cache serving stale content โ€” clear both (PHP-FPM/Apache restart or opcache_reset(), plus SMF's Admin > Maintenance cache-clear) before concluding the patch itself failed
"A broad ISE/ISEpdf/ISEmisc search never seems capped, but ISEmega/ISEmedia stop at 180" Not a bug โ€” result caps genuinely differ by source. ISE/ISEpdf/ISEmisc are uncapped by default via the UI (their bridges never pass --limit); ISEmega defaults to a 180-result soft cap (bypassable via the page's "Show all" link) and ISEmedia's bridge always passes --limit 180 explicitly. Everything is still searched/ranked in full either way โ€” the cap only limits how many result cards get drawn. See ISE_Python_Scripts_Manual.md for the exact mechanism per source (confirmed against real source, 2026-09-23).
Standalone/SSH PDFsearch.py "some query with no matches" prints "ISE ERROR โ€” see ..." instead of "No matches." Confirmed real bug, fixed in v7.5.2 (2026-09-23): get_ranked_results() returned a bare [] on a genuine zero-match instead of the (results, order_mode) tuple every caller expects, so the unpack itself raised and got caught by the outer exception handler. Via the UI bridge this was invisible (the fallback path shows the same "No results found" text either way); via MegaSearch.py it was silently absorbed but logged misleadingly as "pdf source failed". Fixed by returning [], order_mode on that path; see ISE_Python_Scripts_Manual.md's PDFsearch.py section.
orphan_check.py fails immediately, every mode (CLI/--html/cron), with ImportError: cannot import name 'BOARD_URL' from 'PDFsearch' Confirmed real bug, fixed in v7.4.2 (2026-09-24): PDFsearch.py's base-URL constant was renamed BOARD_URL โ†’ BASE_URL when its settings-loading moved onto ise_settings.py (v4.0, 2026-08-21) โ€” two days after orphan_check.py's last edit โ€” and orphan_check.py was never updated. BOARD_URL was never actually used anywhere in the file, so the fix drops it from the import entirely rather than re-importing it unused. See ISE_Python_Scripts_Manual.md's orphan_check.py section.
ISEmedia search fails with ModuleNotFoundError: No module named 'numpy' (or torch) Wrong Python interpreter โ€” MediaSearch.py/MediaIndexer.py/MediaProcessor.py need python3.8 specifically; a bare python3 under Apache's thinner shell_exec() PATH can resolve to the system default instead. qf_Mediasearch_bridge.php resolves this itself (resolve_media_python()); if you're testing manually over SSH, use the full path (/usr/local/bin/python3.8), not a bare python3/python3.8 alias โ€” sudo's own secure_path may not include /usr/local/bin either. See ยง2/ยง3 and ISE_Python_Scripts_Manual.md.
ISEmedia search fails with PermissionError: [Errno 13] ... /.cache HF cache not reachable by apache. open_clip tries to download/cache its CLIP checkpoint under the invoking user's home directory by default; apache's home (/var/www) isn't writable. install_ise.php v9.5 self-provisions a shared, apache-readable ISE_Data/hf_cache on install (see ยง3) โ€” if this error shows up anyway, check that hf_cache/hub/models--timm--vit_base_patch32_clip_224.openai actually exists and is apache:apache-owned, and check ISE_Data/hf_cache_setup_status.log for whether both of install_ise.php's provisioning paths failed to even start (e.g. no root cache to copy from and no outbound network access to huggingface.co).
ISEmedia search returns results with no visible relation to the query word Check the version. Pre-v1.2.0, MediaSearch.py ranked by pure CLIP semantic similarity with no required literal keyword match โ€” a real, now-fixed gap (v1.1.0 added a score boost, v1.2.0 made it a hard gate for every sort mode). If a live instance is still showing this behavior, it's running a version older than v1.2.0 โ€” update it, don't assume the ranking logic itself needs another look.