Repository navigation
Expand file tree
/
Copy pathlab-insights.js
More file actions
36 lines (36 loc) · 12.3 KB
/
Copy pathlab-insights.js
File metadata and controls
36 lines (36 loc) · 12.3 KB
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
import {baseline,changed} from './lab-examples.js';
/** Presentation only: values and ledger decisions are read from recorded runtime traces. */
const clone=x=>structuredClone(x);
export function occurrence(record){return record?.result?.occurrences?.[0]??record?.occurrences?.[0];}
export function traceValue(record,id){const row=occurrence(record)?.trace?.find(r=>r.nodeId===id);return row?{available:true,value:clone(row.value)}:{available:false};}
const value=(r,id)=>traceValue(r,id).value;
export const gatherCheckNames=Object.freeze(['skill','route','reservation','capacity','owner permission','remaining uses']);
const guardNames={found:'named offer found',unexpired:'offer not expired',hasInventory:'inventory covers quantity',hasStock:'offer stock covers quantity',withinAllowance:'allowance covers quantity',positive:'quantity is positive'};
/** Keep stable raw node IDs alongside readable names in the UI. */
export function nodeLabel(recipeId,node){const id=typeof node==='string'?node:node.id;const trade={a:'Offer found and unexpired',b:'Offer valid and inventory sufficient',c:'Offer, inventory and stock checks pass',d:'Offer, inventory, stock and allowance pass'};const labels={chosenId:'Chosen food ID',food:'Rank fresh edible candidates',validNeed:'Current snapshot and positive hunger',eligible:'All six named checks',notFuture:'Observation is not in the future',young:'Observation age within limit',fresh:'Observation passes both time checks',rawProceeds:'Price × requested quantity',quotedQuantity:'Guarded sale quantity',proceeds:'Guarded predicted proceeds',haul:'Haul evidence provenance',arrival:'Arrival evidence provenance',ledger:'Evidence provenance',result:'Reconcile latest attempt states',schedule:'Dependency start and end estimates',withinDeadline:'Makespan within deadline'};return recipeId==='trade-preview'?(trade[id]??guardNames[id]??labels[id]??id):(labels[id]??id);}
/** False checks are supplied facts; freshness failures come from evaluated guard nodes. */
export function gatherFailures(record){const checks=value(record,'checks')??record?.result?.bindings?.checks;return [...(checks??[]).flatMap((ok,i)=>ok===false?[{id:'checks['+i+']',label:gatherCheckNames[i]}]:[]),...['notFuture','young'].flatMap(id=>value(record,id)===false?[{id,label:id==='notFuture'?'observation is in the future':'observation is too old'}]:[])];}
export function tradeFailures(record){return Object.entries(guardNames).flatMap(([id,label])=>value(record,id)===false?[{id,label}]:[]);}
/** Expose the kernel's actual used/skipped records, without redoing freshness filtering. */
export function evidenceProvenance(recipeId,record){return (recipeId==='confirmed-checkpoints'?['haul','arrival']:['ledger']).flatMap(id=>{const ledger=value(record,id);return ledger?[{claim:id,...ledger}]:[];});}
/** This is an input-history view, not another reconciliation algorithm. Only completed,
* validated records receive latest-state markers. Runtime totals remain authoritative. */
export function receiptTimeline(record){const inputs=record?.result?.bindings??record?.bindings;const valid=occurrence(record)?.status==='completed';if(!inputs)return [];const groups=new Map();for(const row of inputs.receipts??[]){if(row.operation!==inputs.policy?.operation)continue;if(!groups.has(row.attempt))groups.set(row.attempt,[]);const history=groups.get(row.attempt);if(!history.some(r=>r.id===row.id))history.push(clone(row));}return [...groups].map(([attempt,history])=>{history.sort((a,b)=>a.sequence-b.sequence);return {attempt,history,validated:valid,latest:valid?clone(history.at(-1)):null};});}
/** Preserve the raw diagnostic; specialized labels reflect its exact runtime message. */
export function presentDiagnostic(diagnostic){if(!diagnostic)return null;const message=diagnostic.message??'';const kind=message==='Schedule has dependency cycle'?'schedule-cycle':message==='Missing job dependency'?'schedule-dangling':diagnostic.code??'input';return {kind,label:kind==='schedule-cycle'?'Dependency cycle':kind==='schedule-dangling'?'Missing job dependency':kind,raw:clone({code:diagnostic.code,path:diagnostic.path,nodeId:diagnostic.nodeId,message})};}
export function detailedSummary(recipeId,record){const run=occurrence(record);if(!run)return 'Run a comparison to calculate a result.';if(run.status!=='completed'){const d=presentDiagnostic(run.diagnostic);return 'Evaluation failed: '+(d?.label??'unknown')+'. '+(d?.raw.message??'');}const o=run.outputs,t=id=>value(record,id);switch(recipeId){case 'needs-triage':{const selection=t('food');return selection?.found?'Selected '+selection.value.id+' with restoration '+selection.value.restoration+'; proposal '+o[0].status+'. Hunger is a positive guard.':'No fresh edible candidate; proposal '+o[0].status+'.';}case 'gather-readiness':return 'Observation age '+t('age')+' ticks; ready='+t('ready')+'. '+(gatherFailures(record).length?'Failed checks: '+gatherFailures(record).map(x=>x.label).join(', ')+'.':'All six supplied checks and time guards pass.')+' Proposal '+o[0].status+'.';case 'trade-preview':return 'Quantity '+o[0].quantity+'; predicted proceeds '+o[0].predictedProceeds+'. '+(tradeFailures(record).length?'Failed guards: '+tradeFailures(record).map(x=>x.label).join(', ')+'.':'All quote guards pass.');case 'receipt-reconciliation':return o[0].confirmedUnits+' confirmed units; '+o[0].attempts+' attempts; state '+o[0].state+'. Retry advice '+(o[0].mayRetry?'permitted':'withheld')+'.';case 'confirmed-checkpoints':return 'Haul '+o[0].haul+'; arrival '+o[0].arrival+'; completion '+o[1]+'.';case 'evidence-ledger':return o[0].state+': '+o[0].support+' supporting, '+o[0].refute+' refuting, '+o[0].sources+' source labels. '+o[0].used.length+' used; '+o[0].skipped.length+' skipped records.';default:return null;}}
/** Every preset starts from the real frozen recipe/story baseline and is detached.
* kind describes the teaching case, not a promise about every trace value. */
export function scenarioPresets(recipe){const b=baseline(recipe),make=(id,label,kind,fn,explanation)=>({id,label,kind,inputs:fn(clone(b)),explanation});const cases=[make('baseline','Baseline','baseline',x=>x,'Original supplied snapshot.'),{id:'change',label:'Change the result',kind:'change',inputs:changed(recipe),explanation:'Compare the recorded trace and outputs with baseline.'}];const add=(label,fn,explanation)=>cases.push(make('invariance',label,'invariance',fn,explanation));switch(recipe.id){case 'water-total':add('Same total, different readings',x=>({...x,readings:[4]}),'Total stays four liters; measurement count changes.');break;case 'route-preview':add('Close the unused road',x=>({...x,blocked:['B']}),'The A–C–D route stays available.');break;case 'craft-quote':add('Add unused stone',x=>({...x,inventory:[...x.inventory,{id:'stone',amount:100}]}),'Only ore and wood constrain the quote.');break;case 'confirmed-checkpoints':add('Another agreeing witness',x=>({...x,records:[...x.records,{...x.records[0],id:'extra-haul',source:'extra-source'}]}),'Checkpoint states stay supported; provenance gains a record.');break;case 'evidence-ledger':add('Repeat the same source support',x=>({...x,records:[...x.records,{...x.records[0],id:'extra-support'}]}),'Support counts the source once; used provenance grows.');break;case 'trade-preview':add('Add another offer',x=>({...x,offers:[...x.offers,{...x.offers[0],id:'stall',price:1}]}),'The recipe selects only the ID offer.');break;case 'needs-triage':add('Increase hunger',x=>({...x,hunger:100}),'Positive hunger permits selection but does not rank foods.');break;case 'receipt-reconciliation':add('Unrelated operation acknowledgement',x=>({...x,receipts:[...x.receipts,{id:'other',operation:'walk',attempt:'walk1',sequence:1,status:'confirmed',units:1}]}),'Valid foreign operation rows do not affect craft advice.');break;case 'gather-readiness':add('Widen the age limit',x=>({...x,maxAge:3}),'The observation already passed; proposal stays the same.');break;case 'work-schedule':add('Relax the deadline',x=>({...x,deadline:6}),'Job intervals stay fixed and the deadline predicate still holds.');cases.push(make('deadline','Tighter deadline','change',x=>({...x,deadline:4}),'Schedule intervals stay fixed while the verdict changes.'));break;}const edge=(id,label,fn,explanation,status='completed')=>cases.push({...make(id,label,'boundary',fn,explanation),expectedStatus:status});
switch(recipe.id){
case 'water-total':edge('decimal','Decimal readings',x=>({...x,readings:[.1,.2]}),'JavaScript floating point returns 0.30000000000000004, not an exact decimal rational.');edge('empty','No readings',x=>({...x,readings:[]}), 'No readings totals zero and counts zero; two zero readings total zero but count two.');edge('zeros','Two zero readings',x=>({...x,readings:[0,0]}),'The total is zero but the count is two.');break;
case 'route-preview':edge('stale','Open route, stale snapshot',x=>({...x,current:false}),'A path can exist while a caller-supplied freshness flag blocks its walking proposal.');edge('blocked','Block the goal',x=>({...x,blocked:['D']}),'No route to a blocked goal; the proposal is skipped.');break;
case 'craft-quote':edge('scarce','One ore remaining',x=>({...x,inventory:x.inventory.map(i=>({...i,amount:i.id==='ore'?1:i.amount}))}),'One ore cannot make the required two-ore batch.');edge('full','Enough for all four batches',x=>({...x,inventory:x.inventory.map(i=>({...i,amount:8})),freeCapacity:8}),'All four requested batches fit; the quote still reserves nothing.');edge('zero','Request zero batches',x=>({...x,requested:0}),'Zero batches fully meets a zero request. This Boolean is a mathematical comparison, not completed work.');break;
case 'needs-triage':edge('stale','Best food, stale need',x=>({...x,current:false}),'Selection still finds a food, but the current-snapshot guard skips its proposal.');edge('zero-food','Zero-restoration candidate',x=>({...x,foods:[{id:'zero',edible:true,fresh:true,restoration:0}]}),'The retained recipe admits zero restoration. Try the separately authored positive-restoration rule below.');edge('none','No eligible food',x=>({...x,foods:[]}),'No candidate means no eating proposal.');break;
case 'trade-preview':edge('price','Higher unit price',x=>({...x,offers:x.offers.map(o=>({...o,price:4.5}))}),'Two units at 4.5 predict nine symbolic crystal units.');edge('short-stock','Stock cannot fill request',x=>({...x,quantity:4,allowance:4,offers:x.offers.map(o=>({...o,available:3}))}),'This recipe quotes the full request or zero; it does not partially fill.');break;
case 'gather-readiness':edge('owner','Owner permission denied',x=>({...x,checks:x.checks.map((c,i)=>i===4?false:c)}),'The named owner-permission check blocks the collection proposal.');edge('future','Future observation',x=>({...x,observedAt:13}),'A future-dated observation fails even if its clamped age is zero.');break;
case 'evidence-ledger':edge('independent','Different source denial',x=>({...x,records:[...x.records,{...x.records[0],id:'different-denial',source:'another-label',value:false}]}),'Conflict remains visible, but the supplied source count is two; labels still do not authenticate independence.');break;
case 'confirmed-checkpoints':edge('testimony','Arrival as testimony',x=>({...x,records:x.records.map(r=>({...r,kind:r.claim==='arrival'?'testimony':r.kind}))}),'This checkpoint rule requires observation reports. Testimony does not confirm arrival.');break;
case 'receipt-reconciliation':edge('failed-attempt','Failed second attempt',x=>({...x,receipts:[...x.receipts,{id:'failed',operation:'craft',attempt:'attempt2',sequence:1,status:'failed',units:0}]}),'A failed attempt differs from an unknown one: retry advice can remain permitted.');edge('latest','Pending then confirmed',x=>({...x,receipts:[...x.receipts,{id:'pending',operation:'craft',attempt:'attempt2',sequence:1,status:'pending',units:0},{id:'confirmed',operation:'craft',attempt:'attempt2',sequence:2,status:'confirmed',units:2}]}),'The later sequence confirms the second attempt; history remains visible beneath the chart.');break;
case 'work-schedule':edge('cycle','Dependency cycle',x=>({...x,jobs:[{id:'a',depends:['b'],duration:1},{id:'b',depends:['a'],duration:1}]}),'The runtime refuses a cyclic schedule with no published outputs or effects. Its raw code remains refinement.','failed');edge('dangling','Missing dependency',x=>({...x,jobs:[{id:'a',depends:['missing'],duration:1}]}),'A missing dependency has a distinct message and presentation label; the retained raw code is refinement.','failed');break;
}return cases;}