← Files The Business EngineerARCHIVED FILE

scripts/library.py

12.1 KB · Oct 2, 2026 · 00:33 UTC

↓ Download file

#!/usr/bin/env python3
"""Read-only ontology retrieval and explicit local exports; Python standard library only."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import re
import shutil
import sys
import unicodedata

ROOT = Path(__file__).resolve().parents[1]

def normalized(value):
    text = unicodedata.normalize('NFKD', str(value or '')).lower()
    return re.sub(r'[^a-z0-9]+', ' ', ''.join(c for c in text if not unicodedata.combining(c))).strip()

class Library:
    def __init__(self, path=None):
        self.data = json.loads(Path(path or ROOT / 'data/library.json').read_text())
        self.entries = {e['id']: e for e in self.data['entries']}
        self.instruments = {e['id']: e for e in self.data['instruments']}
        self.concepts = {e['id']: e for e in self.data['concepts']}
        self.items = {**self.entries, **self.instruments, **self.concepts}
        self.pointers = {p['id']: p for p in self.data['pointers']}
        self.disciplines = {d['id']: d for d in self.data['disciplines']}
        self.incoming = {}
        for e in list(self.entries.values()) + list(self.instruments.values()):
            for target in e.get('coreRefs', []) + e.get('instrumentRefs', []):
                self.incoming.setdefault(target, []).append(e['id'])
        self.pointer_terms = {}
        for p in self.pointers.values():
            self.pointer_terms.setdefault(p['canonicalId'], []).extend([p['id'], p['name'], p.get('discipline', '')])
        self.index = {e['id']: normalized(' '.join(str(e.get(k, '')) for k in ['id','name','sourceNumber','essence','body','definition','keyQuestion','apply','use_when','aliases','sectionName','discipline']) + ' ' + ' '.join(self.pointer_terms.get(e['id'], []))) for e in self.items.values()}

    def resolve(self, token):
        if token.isdigit():
            raise ValueError('A number alone is ambiguous. Use core:'+token+' or concept:'+token+', or a full entry ID.')
        if token.lower().startswith('core:'):
            number = token.split(':', 1)[1]
            if not number.isdigit(): raise ValueError('Core IDs require a numeric model number.')
            token = 'BE-00-' + str(int(number)).zfill(3)
        elif token.lower().startswith('concept:'):
            number = token.split(':', 1)[1]
            if not number.isdigit(): raise ValueError('Concept IDs require a numeric concept number.')
            token = 'BE-M' + str(int(number)).zfill(4)
        elif token.lower().startswith('instrument:'):
            token = 'BE-P-' + token.split(':', 1)[1].upper()
        if token not in self.items and token not in self.pointers:
            raise ValueError('Unknown entry: '+token+'. Search by name or use stats to inspect the collections.')
        return token

    def collection(self, entry_id):
        if entry_id in self.concepts: return 'concepts'
        if entry_id in self.instruments: return 'instruments'
        return 'core' if self.entries[entry_id]['discipline'] == 'core' else 'disciplines'

    def summary(self, e):
        return {'id':e['id'], 'name':e['name'], 'collection':self.collection(e['id']),
                'discipline':e.get('discipline'), 'type':e.get('kind', e.get('form')),
                'essence':e.get('essence', e.get('definition','')),
                'key_question':e.get('keyQuestion',''), 'source_line':e.get('sourceLine'),
                'status':e.get('status'), 'atlas_fragment':'#atlas?entry='+e['id']}

    def get(self, token):
        requested = self.resolve(token)
        pointer = self.pointers.get(requested)
        canonical = pointer['canonicalId'] if pointer else requested
        entry = self.items[canonical]
        return {'requested_id':requested, 'canonical_id':canonical, 'collection':self.collection(canonical),
                'entry':entry, 'reference_context':pointer,
                'provenance':{'source':self.data['source']['filename'] if canonical not in self.concepts else 'Earlier reconciled ontology',
                              'source_line':entry.get('sourceLine'), 'source_sha256':self.data['source']['sha256'] if canonical not in self.concepts else None,
                              'attribution':self.data['source']['attribution']},
                'atlas_fragment':'#atlas?entry='+requested}

    def pool(self, collection='catalog'):
        if collection == 'all': return list(self.items.values())
        if collection == 'instruments': return list(self.instruments.values())
        if collection == 'concepts': return list(self.concepts.values())
        if collection in ['core','disciplines']: return [e for e in self.entries.values() if self.collection(e['id']) == collection]
        return list(self.entries.values())

    def search(self, query='', collection='catalog', discipline=None, part=None, kind=None, limit=8, offset=0):
        if discipline and discipline not in self.disciplines: raise ValueError('Unknown discipline: '+discipline)
        if not 1 <= limit <= 50 or offset < 0: raise ValueError('Use limit 1–50 and a nonnegative offset.')
        q = normalized(query); words = q.split()
        found = []
        for e in self.pool(collection):
            if discipline and e.get('discipline') != discipline: continue
            if part and e.get('part') != part: continue
            if kind and e.get('kind',e.get('form')) != kind: continue
            if not all(w in self.index[e['id']] for w in words): continue
            title = normalized(e['name'])
            score = (120 if q and normalized(e['id']) == q else 0) + (100 if q and title == q else 0) + (80 if q and str(e.get('sourceNumber')) == query else 0) + (30 if q and q in title else 0) + sum(4 for w in words if w in title)
            if e['id'] in self.pointer_terms and q in [normalized(x) for x in self.pointer_terms[e['id']]]: score += 120
            found.append((score,e))
        found.sort(key=lambda pair:-pair[0])
        chosen = found[offset:offset+limit]
        return {'query':query, 'collection':collection, 'total':len(found), 'offset':offset,
                'next_offset':offset+limit if offset+limit < len(found) else None,
                'results':[self.summary(e) for _,e in chosen],
                'note':'Retrieval matches, not a recommendation score. Read get results before applying an entry.'}

    def links(self, token):
        record = self.get(token); key = record['canonical_id']; e = record['entry']; links = []
        def add(other, kind, direction, evidence):
            if other in self.items and other != key:
                links.append({'id':other,'name':self.items[other]['name'],'collection':self.collection(other),'type':kind,'direction':direction,'evidence':evidence})
        if key in self.concepts:
            for r in self.data['relations']:
                if r['subject'] == key: add(r['object'],r['type'],'outgoing',r['description'])
                elif r['object'] == key: add(r['subject'],r['type'],'incoming',r['description'])
            for other in self.entries.values():
                if key in other.get('concepts',[]): add(other['id'],'Title match','comparison','Comparable title in the consolidated catalog; identity is not asserted.')
        else:
            for ref in e.get('coreRefs',[]): add(ref,'Core reference','outgoing','Numbered reference in the consolidated source.')
            for ref in e.get('instrumentRefs',[]): add(ref,'Practice reference','outgoing','Instrument reference in the consolidated source.')
            for ref in self.incoming.get(key,[]): add(ref,'Cited by','incoming','This entry cites the selected model or instrument.')
            for theme in self.data['themes']:
                if theme['id'] in e.get('themes',[]):
                    for ref in theme['entries']: add(ref,'Shared thread','undirected',theme['name']+'; editorial navigation, not a causal assertion.')
            for ref in e.get('concepts',[]): add(ref,'Title match','comparison','Comparable title in the earlier ontology; identity is not asserted.')
        seen = set(); unique = []
        for link in links:
            signature = (link['id'],link['type'],link['direction'],link['evidence'])
            if signature not in seen: unique.append(link); seen.add(signature)
        return {'requested_id':record['requested_id'], 'canonical_id':key, 'links':unique,
                'canonical_references':[p for p in self.pointers.values() if p['canonicalId'] == key],
                'note':'Models connected by the same assumption do not provide independent confirmation.'}

    def routes(self, route_id=None):
        if not route_id:
            return [{'id':r['id'],'name':r['name'],'question':r['question'],'steps':len(r['steps'])} for r in self.data['routes']]
        route = next((r for r in self.data['routes'] if r['id'] == route_id), None)
        if not route: raise ValueError('Unknown decision path: '+route_id)
        return {**route, 'steps':[{**s,'entry_name':self.get(s['entry'])['entry']['name']} for s in route['steps']],
                'counter':{**route['counter'],'entry_name':self.get(route['counter']['entry'])['entry']['name']},
                'note':'Authored reading path, not a validated decision score.'}

    def stats(self):
        return {'counts':self.data['counts'], 'source':{k:v for k,v in self.data['source'].items() if k!='text'},
                'disciplines':[{'id':d['id'],'name':d['name'],'entries':len(d['entries']),'question':d['question']} for d in self.data['disciplines']],
                'meaning':'The 1,052-entry catalog includes 261 core models. Instruments (15) and the overlapping earlier ontology (258) are separate collections.'}

def export_asset(kind, output, force=False):
    source = ROOT / 'assets' / ('Business-Engineer-Atlas.html' if kind=='atlas' else 'business-analysis-template.html')
    target = Path(output).expanduser().resolve()
    if target.exists() and not force: raise ValueError('Output exists. Choose another path, or use --force for an intended replacement.')
    target.parent.mkdir(parents=True,exist_ok=True)
    shutil.copyfile(source,target)
    return {'file':str(target),'bytes':target.stat().st_size,'sha256':hashlib.sha256(target.read_bytes()).hexdigest()}

def parser():
    p=argparse.ArgumentParser(description=__doc__);sub=p.add_subparsers(dest='command',required=True)
    sub.add_parser('stats')
    search=sub.add_parser('search');search.add_argument('query',nargs='?',default='')
    search.add_argument('--collection',choices=['catalog','core','disciplines','instruments','concepts','all'],default='catalog')
    search.add_argument('--discipline');search.add_argument('--part',choices=['I','II','III','IV','V','VI','VII','VIII']);search.add_argument('--kind');search.add_argument('--limit',type=int,default=8);search.add_argument('--offset',type=int,default=0)
    get=sub.add_parser('get');get.add_argument('ids',nargs='+')
    links=sub.add_parser('links');links.add_argument('id')
    routes=sub.add_parser('routes');routes.add_argument('id',nargs='?')
    practice=sub.add_parser('practice');practice.add_argument('section',choices=['engine','modes','routing','quality','spine'])
    export=sub.add_parser('export');export.add_argument('kind',choices=['atlas','report-template']);export.add_argument('output');export.add_argument('--force',action='store_true')
    return p

def main():
    args=parser().parse_args()
    try:
        if args.command=='export': result=export_asset(args.kind,args.output,args.force)
        else:
            lib=Library()
            if args.command=='stats': result=lib.stats()
            elif args.command=='search': result=lib.search(args.query,args.collection,args.discipline,args.part,args.kind,args.limit,args.offset)
            elif args.command=='get': result=[lib.get(token) for token in args.ids]
            elif args.command=='links': result=lib.links(args.id)
            elif args.command=='routes': result=lib.routes(args.id)
            else: result=lib.data['spine'] if args.section=='spine' else lib.data['practice'][args.section]
        print(json.dumps(result,ensure_ascii=False,indent=2))
    except (ValueError,OSError) as error:
        print(json.dumps({'error':str(error)},ensure_ascii=False),file=sys.stderr)
        return 2
    return 0

if __name__=='__main__': sys.exit(main())

SHA-256: 60e4ad35ac1a5daa3d95f2fb2610a9a876ed3d4a04382d0403950a28a3119fca