File size: 2,828 Bytes
a30d31a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations
import hashlib,json
from .affordance_engine import mine_rules,RULE_META,GENERIC_TYPES
from .claim_ir import claim_key,summary

def _hash(x):return hashlib.sha256(json.dumps(x,sort_keys=True,ensure_ascii=False,separators=(',',':')).encode()).hexdigest()

class IncrementalMiner:
    """Type-indexed agenda: only rules touched by changed interfaces are recomputed; downstream claim rules propagate once."""
    def __init__(self):
        self.type_sig={};self.claim_hash={};self.claims={};self.rule_claims={k:[] for k in RULE_META};self.recompute_count=0;self.rule_evaluations=0
    def _type_sigs(self,facts):
        grouped={}
        for t in GENERIC_TYPES:
            rows=[{k:f.get(k) for k in ('id','type','payload','assumptions','domain','provenance')} for f in facts if f.get('type')==t]
            if rows:grouped[t]=_hash(sorted(rows,key=lambda x:str(x.get('id'))))
        return grouped
    def update(self,facts:list[dict])->dict:
        sig=self._type_sigs(facts);changed={t for t in set(sig)|set(self.type_sig) if sig.get(t)!=self.type_sig.get(t)}
        if not changed:return {'changed':False,'delta':[],'recompute_count':self.recompute_count,'rule_evaluations':self.rule_evaluations}
        self.type_sig=sig;self.recompute_count+=1
        active={name for name,(ft,_) in RULE_META.items() if ft & changed}
        # claim-dependent rules are activated when their upstream producer is active.
        produced_kinds=set()
        for name in list(active):
            if name=='relation_extent':produced_kinds.add('relation_extent')
        active|={name for name,(_,ck) in RULE_META.items() if ck & produced_kinds}
        prior=[c for rows in self.rule_claims.values() for c in rows]
        out=mine_rules(facts,prior,active);self.rule_evaluations+=len(active)
        for name in active:self.rule_claims[name]=out.get(name,[])
        # Re-run downstream inverse-map once with refreshed extents if needed.
        if 'relation_extent' in active and 'inverse_map' in active:
            prior=[c for rows in self.rule_claims.values() for c in rows]
            self.rule_claims['inverse_map']=mine_rules(facts,prior,{'inverse_map'}).get('inverse_map',[]);self.rule_evaluations+=1
        claims=[c for rows in self.rule_claims.values() for c in rows]
        current={};delta=[]
        for c in claims:
            key=claim_key(c);h=_hash(c);current[key]=c
            if self.claim_hash.get(key)!=h:delta.append({'key':key,'claim':c,'ir':summary(c)})
        self.claim_hash={k:_hash(v) for k,v in current.items()};self.claims=current
        return {'changed':True,'delta':delta,'recompute_count':self.recompute_count,'rule_evaluations':self.rule_evaluations}
    def full_results(self):return list(self.claims.values())