EXPORTS_OR_THIS = (exports ? this)
DataReducers: Min(@alice) the minimum value from @alice etc: Min|Max|Average|Earliest|Latest
Latest(everyone) the most recent expressed value from anyone
Min(@alice) or Max(everyone) The lowest value alice has ever expressed or the highest value anyone else has.
Min(@alice,@bob) or Min(@carol) or Max(everyone) The lowest value either alice or bob have expressed or the lowest value carol has expressed or the highest value anyone else has expressed.
Min( Latest(@alice,@bob) or Latest(@carol) ) The lesser of either alice or bob’s most recently asserted value OR the most recently asserted value by carol.
Avg( Min( Latest(@alice or @bob)) or Max( Earliest(@carol or @dan) ) ) We consider the latest value from alice and the latest from bob. We consider the earliest from carol and the earliest from dan. Return the average of the minimum of the latest from alice OR the latest from bob and the maximum of the latest from carol OR the latest from dan.
Notice that this is as deep as a DataReducer can get.
Why?
Alice and Bob are an "equivilence class" -- meaning that evaluations
from either of them are treated as being interchangeable.
Avg( 6Min(@alice) or 4Min(@bob) or Min(@carol) ) Avg( Min(6@alice or 6@bob or @carol) ) Return a value which weighs the minimum value from alice as 5 times the minimum value from bob which is 3 times the minimum value from carol as the unit value. eg Min(@alice) is 10 Min(@bob) is 20 Min(@carol) is 25 Return (610 + 420 + 25)/(6+4+1) ==> (60 + 80 + 25) / 11 ==> 15
Q1. Or is each tier (equivalence class) N-times greater than the next?
return (6*4*10 + 4*20 + 25)/((6*4)+4+1) ==> (240 + 80 + 25)/29 => 345/29 => 11.89
The advantage of this approach is that one can keep adding equivalence classes
and not have to 'rebalance the weightings'. Rather, the spirit of successively
dominating equivalenct classes seems to be more along the lines of multiplicative
weighting than additive weighting -- if that makes any sense.
Latest(Min(@alice,@bob or @carol)) # ‘or’ replaces ‘>’ to mean “dominates”
Latest(Min(@alice,@bob,@carol) or Max(everyone))
-- one to apply to each individual
and one to apply across the participating individuals.
A DataReduction is a ‘reduced data set’ after WhoToHeed and Aggregation and Weighting.
Another way to think of this name is the product of “data reduction” process. Perhaps it should be called KnowledgeReduction. What Nooron needs is a StreamKnowledgeReducer which processes a stream of spogi and produces a changing “reduced value”.
WhoToHeed() eg: me, ‘betty’, ‘bob’ > ‘alice’, ‘cathy’ > everyone - ‘goofy’
Where each set of sources delimited by a > is an “equivalency class” meaning that if an allegation is provided by anybody in that set (eg “‘alice’, ‘cathy’”) then they dominate any “lesser” sources but because they are equivalent should have their alleged values aggregated
Aggregation()
Aggregation techniques are either Electors or Digestors. Electors elect one of the things which go in as the source of the output value.
suitable for: both object-values and literal-values
eg: First(), Latest(), Mode()
Digestors digest all of the things which go in to produce an output value.
suitable for: a subset of literal-values
They are suitable for numeric/linear/sortable data like numbers and datetimes.
eg: Average() [aka Mean()], Max(), Min()
problems: some literals can not be meaningfully Averaged, eg strings
Problem: all of the examples above retain the same range and units but Sum() and other aggregation functions like StdDev(), Variance() have different units and ranges, so though they are required perhaps those are best dealt with by “Functional Predicates” (aka “Computed Columns”).
Weighting() Weight([5,2,1] # a list of vote multipliers for the equivalency classes eg: “me/betty/bob allegations are worth 5x, alice/cathy are 2x from everyone” Problem: there are plausibly multiple algorithms Priority: Lower than simple Aggregation and even “Computed Columns”
There are various terms for different kinds of data that might be aggregated.
Problems:
EXPORTS_OR_THIS = (exports ? this)
set a flag to indicate support for ES6-style Unicode support in RegExp
try new RegExp('','u') and UNICODE_REGEXP = true catch e then UNICODE_REGEXP = false
JSON_pps = (obj) ->
JSON.stringify(obj, null, 4)
JSON_pp = (obj) ->
console.log(JSON_pps(obj))
equiv is an integer indicating which “equivalence class” the author of the spogi was found to be in. If equiv is 0 that means they were either explicitly ignored (eg “ - ‘bob’”) or not included (for example by there being no everyone term). Examples: WhoToHeed(“‘alice’ > ‘bob’ > everyone - ‘cathy’”).heed(‘cathy’) ==> 0 WhoToHeed(“‘alice’ > ‘bob’ > everyone - ‘cathy’”).heed(‘alice’) ==> 1 WhoToHeed(“‘alice’ > ‘bob’ > everyone - ‘cathy’”).heed(‘bob’) ==> 2 WhoToHeed(“‘alice’ > ‘bob’ > everyone - ‘cathy’”).heed(‘newton’) ==> 3 WhoToHeed(“‘alice’ > ‘bob’”).heed(‘cathy’) ==> 0
GENERIC = {}
Provide a way to override the per-type (WPA|WCA|ACA) versions of Aggregator classes
WPA = WCA = ACA = GENERIC
class DataReducer
@docs = "A DataReducer generically works across subjects and/or predicates"
constructor: (dr_spec_or_ast, @me_is) ->
dr_spec_or_ast ?= “Latest(Latest(Latest(everyone)))”
discr_pth = (not window? and '../lib/' or '') + 'discriminator'
DiscriminatorReader = require(discr_pth).DiscriminatorReader
if typeof dr_spec_or_ast is 'string'
@dd_spec = dr_spec_or_ast
@dd_ast = new DiscriminatorReader(dr_spec_or_ast).ast.prog[0]
else
if typeof dr_spec_or_ast is 'object'
@dd_ast = dr_spec_or_ast
else
throw new Error("new DataReducer() was passed neither a string nor an AST")
@build_from_ast(@dd_ast)
build_from_ast: (ast) ->
if not (ast.type is 'call')
console.log("ast:",JSON_pps(ast))
throw new Error("build_from_ast() did not receive a well-formed Discriminator")
@u_to_aggregator = {}
@root = {}
@make_branches(@root, ast)
if ast.method?
@re_predicates = ast.method.args.slice() # shallow copy
else
@re_predicates = []
@re_predicates = [true]
make_branches: (parent, ast) ->
switch ast.type
when 'call'
this_node =
fn_name: ast.func.value
up: (not window?) and 'parent' or parent # to avoid circularity for JSON.stringify
if not parent.kids?
parent.kids = []
parent.kids.push(this_node)
for arg in ast.args
if arg.type in ['atuser','genuser']
this_node.heed = ast.args # bail and send whole heed AST
break
@make_branches(this_node, arg)
when ‘atuser’ user = arg.value @u_to_aggregator[user] = parent when ‘genuser’ switch ast.value when ‘everyone’ @u_to_aggregator[EVERYONE] = parent when ‘others’ @u_to_aggregator[OTHERS] = parent when ‘me’ @u_to_aggregator[ME] = parent
spawn: (s_p_key) ->
return new DataReduction(@)
See http://shapecatcher.com/ to find unicode by drawing them. http://shapecatcher.com/unicode/block/Arrows
symbol_to_name:
"↡":
name: "Min"
hex: '21A1'
"↟":
name: "Max"
hex: '219F'
"⏦":
name: "Average"
hex: '23E6'
"↺":
name: "Earliest"
hex: '21BA'
"↻":
name: "Latest"
hex: '21BB'
"∀":
name: "Earliest"
hex: '2200'
support_symbolic_aggr_names: UNICODE_REGEXP
eg_src: "avg(max(latest(@alice|everyone~@bob)),earliest(me)).re(naughty,nice)"
eg_json: [] # see docs/branched_and_complex_discriminator.json
class GenericAggregator
constructor: () ->
@members = {}
@value = null
init_reduction: () ->
console.log @constructor.name,"init_reduction() should be removed"
init_member: (member_id) ->
@members[member_id] =
id: member_id
ensure_member: (member_id) ->
console.log “ensuring member”, member_id
return @members[member_id] or @init_member(member_id)
cascade: (spogi, from_inner) ->
call the HeedElector and recursively to the final aggregator
rollup = @
member = @consume(spogi, rollup, from_inner)
if not member
return member
if @outer_aggr?
return @outer_aggr.cascade(spogi, member)
return member
consume: (spogi, rollup, member) ->
Return either false or the member whose aggregation was affected by the spogi
@caused_promotion(spogi, rollup, member)
caused_promotion: (spogi, rollup, member) ->
if not rollup.value? or @should_promote(spogi, rollup, member)
@promote_member(spogi, rollup, member)
return @
return false
promote_spogi: (spogi, rollup, member) ->
rollup.spogi_prior = rollup.spogi
rollup.spogi = member.spogi
promote_value: (spogi, rollup, member) ->
rollup.value_prior = rollup.value
rollup.value = member.value
should_promote: (spogi, rollup, member) ->
console.log @constructor.name,"should_elect() must be implemented"
class DataReduction
constructor: (@data_reducer) ->
the @members become the equivalency classes the member_id is a 1-based integer index
@user_to_aggr = {}
@value = null
@compile(@data_reducer.root.kids[0], @)
cascade: (spogi, inner_aggr) ->
@spogi = inner_aggr.spogi
@value = inner_aggr.value
return true
return @spogi
ReductionDispatcher.compile()
compile: (abstract_branch, outer_aggr) ->
if abstract_branch.fn_name
this_aggr = new GENERIC[abstract_branch.fn_name]()
if outer_aggr? and outer_aggr.constructor.name isnt 'DataReducer'
this_aggr.outer_aggr = outer_aggr
if abstract_branch.kids?
for abstract_kid in abstract_branch.kids
@compile(abstract_kid, this_aggr)
if abstract_branch.heed?
tiers = WhoToHeed.build_tiers_from_args(
abstract_branch.heed, @data_reducer.me_is)
he = new HeedElector()
he.who_to_heed = new WhoToHeed(tiers, @data_reducer.me_is)
he.test_for_domination = true
he.outer_aggr = this_aggr
for incl_u of tiers.incl_all # will be userid OR true (for everyone)
if not @user_to_aggr[incl_u]?
@user_to_aggr[incl_u] = []
if incl_u is true
throw new Error("incl_u should never be true, rather _everyone, _others, _notme or @WHATEVER")
@user_to_aggr[incl_u].push(he)
get_aggregators_for_user: (u_key) ->
retlist = []. # TODO handle _other correctly
concat(@user_to_aggr[u_key] or []).
concat(@user_to_aggr[EVERYONE] or [])
return retlist if false
me_is = @data_reducer.me_is
if not me_is?
return []
throw new Error(“me_is should be defined”)
if not @user_to_aggr[u_key]? # ie u_key is not mentioned explicitly
retlist = retlist.concat(@user_to_aggr[OTHERS] or [])
if u_key isnt me_is # if u_key isnt the current user they are OTHERS
retlist = retlist.concat(@user_to_aggr[NOTME] or [])
return Array.from(new Set(retlist)) # uniqueify
consume_spogi: (spogi) ->
u_key = spogi.ww().user_symbol
Iterate through the possibly multiple entry points in the aggregation tree matching u_key (via explicit include, everyone, me or others)
last_truthy_result = false
for aggr, idx in @get_aggregators_for_user(u_key)
spogi_or_false = aggr.cascade(spogi)
if spogi_or_false
last_truthy_result = spogi_or_false
return false OR the LAST truthy_result because it accumulates all changes
return last_truthy_result
class HeedElector extends GenericAggregator # TODO rename to ReductionDispatcher
Expose the GenericAggregator interface but just process WhoToHeed In other words, this is the innermost aggregation method and its sole purpose is to determine whether the spogi should even be processed based on WhoToHeed logic.
Actually, “sole purpose” is wrong. If ACA is happening then the a check for whether equiv_class_index1 is_dominated by a prior value should occur. If ACA IS NOT happening then there is no need for is_dominated testing.
consume: (spogi, rollup_IGNORED, member_IGNORED) ->
rollup and member are ignored because this is the first consume() called
usym = spogi.ww().user_symbol
if not usym
throw new Error("expect user_symbol to be truthy", usym)
equiv_class_index1 = @who_to_heed.heed(usym)
if equiv_class_index1 is 0 or @is_dominated(equiv_class_index1)
return false
member = @ensure_member(equiv_class_index1)
member.spogi = spogi
member.value = spogi.o.getNativeValue()
member.outer_aggr = @outer_aggr @value = member.value @spogi = spogi
return member
is_dominated: (equiv_class_index1) ->
if not @test_for_domination
return false
by_class = equiv_class_index1 - 1
while by_class > 0
if @members[by_class]?
return true
by_class = by_class - 1
return false
class GenericElector extends GenericAggregator
promote_member: (spogi, rollup, member) ->
@promote_spogi(spogi, rollup, member)
@promote_value(spogi, rollup, member)
class GenericDigestor extends GenericAggregator
promote_member: (spogi, rollup, member) ->
@promote_value(spogi, rollup, member)
class GENERIC.Latest extends GenericElector
should_promote: (spogi, rollup, member) ->
return rollup.spogi.ww().cmp(member.spogi.ww()) < 0
GENERIC['↻'] = GENERIC.Latest
class GENERIC.Earliest extends GenericElector
should_promote: (spogi, rollup, member) ->
return rollup.spogi.ww().cmp(member.spogi.ww()) > 0
GENERIC['↺'] = GENERIC.Earliest
class GENERIC.Max extends GenericDigestor # TODO make this an Elector or document why not
should_promote: (spogi, rollup, member) ->
return rollup.value < member.value
GENERIC['↟'] = GENERIC.Max
class GENERIC.Min extends GenericDigestor # TODO make this an Elector or document why not
should_promote: (spogi, rollup, member) ->
debugger
return rollup.value > member.value
GENERIC['↡'] = GENERIC.Min
class GENERIC.Every extends GenericElector
should_promote: (spogi, rollup, member) ->
return true
GENERIC['∀'] = GENERIC.Every
class GENERIC.Average extends GenericDigestor
should_promote: (spogi, rollup, member) ->
return true
promote_value: (spogi, rollup, member) ->
rollup.value_prior = rollup.value or 0
if not rollup.count?
rollup.count = 0
count_prior = rollup.count
rollup.count = rollup.count + 1
rollup.value = (count_prior * rollup.value_prior + member.value) / rollup.count
TODO get Distinct working class GenericAccumulator extends GenericAggregator @docs = “An Accumulator ‘collects’ the members into a Set”
class GENERIC.Distinct extends GenericAccumulator @docs = “Superset ‘collects’ all the distinct values” should_promote: (spogi, rollup, member) -> return true promote_value: (spogi, rollup, member) ->
WhoToHeed() use cases: me Just heed me me, personA Just heed me and personA me > personA Heed me over personA (ie my evaluations instead of theirs) (me, personA) > personB Heed me and personA over personB me > (personA, personB) Heed me over personA and personB me > (personA, personB) > everyone Heed me over personA and personB and them over everyone else me > everyone > (personA, personB) Heed me over everyone and everyone over personA and personB me > everyone Heed me over everyone everyone Heed everyone everyone - personA Heed everyone except personA everyone - (personA, personB) Heed everyone except personA and personB me > (tom, bob) > (everyone - (personA, personB)) > personC > personA - personB Problems: How to represent everyone? 1 How to represent exclusion? who_to_heed: include: true # everyone exclude: new Set([‘tom’]) chain: include: new Set([‘tom’]) exclude: new Set([‘bob’])
# Notes:
# if chain then exclude membership does not mean reject, yet
How to represent named groups of people? (the admins, my friends, black hats) How to represent people playing roles? (owner, member, creator, admin)
EVERYONE = '_everyone'
OTHERS = '_others'
NOTME = '_notme'
ME = '_me'
class WhoToHeed
@build_tiers_from_args: (args_ast, me_is) ->
Build a tree structure with nodes like: node: include: [true] # or [alice, bob, ladygaga] exclude: [alice, bob, ladygaga] chain: # another node
tiers =
incl_all:{}
excl_all:{}
current_tier = tiers
for arg in args_ast
if not current_tier.include?
current_tier.include = []
incl_or_excl = current_tier.include
incl_or_excl_all = tiers.incl_all
if arg.type in ['atuser','genuser']
if arg.type is 'atuser'
val_to_push = arg.value
else # arg.type is 'genuser' ie everyone|me|others|notme
if arg.value is 'me'
val_to_push = me_is
else
val_to_push = '_' + arg.value
incl_or_excl.push(val_to_push)
incl_or_excl_all[val_to_push] = true #current_tier
if arg.type is 'punc'
if arg.value is '~'
if not current_tier.exclude?
current_tier.exclude = []
incl_or_excl = current_tier.exclude
incl_or_excl_all = tiers.excl_all
if arg.value is '|'
current_tier.chain = {}
current_tier = current_tier.chain
return tiers
constructor: (tier, me_or_top) ->
Pass me_is as the second argument to the top of a WhoToHeed tree otherwise pass the top as the second argument.
if typeof me_or_top is 'string'
this IS the top of a WhoToHeed tree
@me_is = me_or_top
@top = @
@tier = tier
else
@top = me_or_top
if tier is 'everyone'
tier = 1
tier ?= 0 # default to heeding no one if nothing is said to the contrary
if typeof tier is 'number'
@force = tier
return
if tier.exclude?
@exclude = new Set(tier.exclude)
if tier.include?
@include = new Set(tier.include)
if tier.chain?
@chain = new WhoToHeed(tier.chain, @.top)
heed: (who, otherwise, equiv) ->
Return either 0 or the 1-based index of the “Heeding equivalency class”
if @force?
return @force
if equiv?
equiv = equiv + 1
else
equiv = 1
me_is = @top.me_is
if not me_is?
throw new Error("@top.me_is must be defined")
if @exclude?
if @exclude.has(true) # true means EVERYONE
throw new Error("user refs should never be true, rather _everyone, _others, _notme")
if @exclude.has(who)
if @chain?
return @chain.heed(who, 0, equiv)
else
return 0
if (@exclude.has(OTHERS) and
(not @top.tier.excl_all[me_is]) and (not @top.tier.incl_all[me_is]))
return 0
if @include?
if @include.has(who) # explicit inclusion dominates chained exclusion
return equiv
if @include.has(true) # true means EVERYONE
throw new Error("user refs should never be true, rather _everyone, _others, _notme or @WHATEVER!!!")
if @include.has(EVERYONE)
otherwise = equiv
if not @me_is? throw new Error(“@me_is must be defined”) else console.log “me_is:”, @me_is
if who isnt me_is and @include.has(NOTME)
return equiv
if (@include.has(OTHERS) and
(not @top.tier.excl_all[me_is]) and (not @top.tier.incl_all[me_is]))
otherwise = equiv
if @chain?
return @chain.heed(who, otherwise, equiv)
if otherwise?
return otherwise
return equiv
expect = (val) ->
to:
equal: (check, msg) ->
if check isnt val
console.log "expected #{val} to equal #{check}", msg or ""
debugger
else
console.log "."
DataReduction_suite = (noodb) ->
if not noodb?
NooDB = require((not window? and '../lib/' or '') + 'noodb').NooDB
Log = require("log")
log = new Log('ERROR')
ARGS = {verbosity: 0, log: log}
noodb = new NooDB("test/test_noodb.nq", "http://example.com/", ARGS)
window.IN_JIG = true
jig = new ReductionJig(noodb, "Latest(everyone)", 'bob')
redox = jig.reduction
aggr_chains = redox.get_aggregators_for_user('ann')
expect(jig.do({s:'_:gilligan', p:'_:iq', o:75, g:'_:minnow'})).to.equal(75)
expect(jig.do({o:76})).to.equal(76)
expect(jig.do({o:77})).to.equal(77)
jig = new ReductionJig(noodb, "Min(Latest(everyone))", 'bob')
expect(jig.do({s:'_:gilligan', p:'_:iq', o:75, g:'_:minnow'})).to.equal(75)
expect(jig.do({o:76})).to.equal(75)
expect(jig.do({o:74})).to.equal(74)
jig = new ReductionJig(noodb, "Average(Latest(everyone))", 'bob')
redox = jig.reduction
heed_elector = redox.get_aggregators_for_user('ann')[0]
expect(heed_elector.outer_aggr.constructor.name).to.equal('Latest')
expect(heed_elector.outer_aggr.outer_aggr.constructor.name).to.equal('Average')
expect(jig.do({s:'_:ginger', p:'_:iq', o:120, g:'_:minnow'})).to.equal(120)
expect(jig.do({o:60})).to.equal((120+60)/2) # 180/2 => 90
expect(jig.do({o:180})).to.equal((120+60+180)/3) # 360/3 => 120
expect(jig.do({o:40})).to.equal((120+60+180+40)/4) # 400/4 => 100
console.log "SUCCESS"
expect(1).to.equal(2, 'PROOF THAT THIS GHETTO expect() WORKS')
Discriminator_suite = (noodb) ->
console.clear()
if not noodb?
NooDB = require((not window? and '../lib/' or '') + 'noodb').NooDB
Log = require("log")
log = new Log('ERROR')
ARGS = {verbosity: 0, log: log}
noodb = new NooDB("test/test_noodb.nq", "http://example.com/", ARGS)
alice = {user_symbol: 'alice', session_no: 1}
bob = {user_symbol: 'bob', session_no: 1}
carol = {user_symbol: 'carol', session_no: 1}
dan = {user_symbol: 'dan', session_no: 1}
eve = {user_symbol: 'eve', session_no: 1}
fred = {user_symbol: 'fred', session_no: 1}
window.IN_JIG = false
if true
jig = new DiscriminatorJig(noodb, "Every(Latest(Every(@alice,@bob|@carol,@dan)))", 'bob')
initial_spogi = {s:'_:gilligan', p:'iq', o:75, g:'_:minnow', who:fred}
xtnd = _.extend
expect(jig.disc(initial_spogi)).to.equal(null)
expect(jig.disc({o:120, who:dan})).to.equal(120)
expect(jig.disc({o:130, who:bob})).to.equal(130)
expect(jig.disc({o:120, who:carol})).to.equal(null, "should be dominated by bob")
expect(jig.disc({o:140, who:alice})).to.equal(140, "alice should replace bob")
if true
jig = new DiscriminatorJig(noodb, "Min(Latest(me),Latest(others))", 'bob')
initial_spogi = {s:'_:gilligan', p:'iq', o:75, g:'_:minnow', who:alice}
xtnd = _.extend
expect(jig.disc(initial_spogi)).to.equal(75, "alice's value")
expect(jig.disc(xtnd(initial_spogi,{o:74, who:bob}))).to.equal(74,
"bob's latest is lower")
expect(jig.disc(xtnd(initial_spogi,{o:76, who:bob}))).to.equal(null,
"bob's latest is more than alice's so show hers")
if true
jig = new ReductionJig(noodb, "Latest(notme)", 'bob')
first_spogi = {s:'_:doc', p:'_:iq', o:140, g:'_:minnow', who:bob}
expect(jig.do(first_spogi)).to.equal(null)
expect(jig.do({o:120, who:carol})).to.equal(120)
expect(jig.do({o:145, who:bob})).to.equal(120)
expect(jig.do({o:135, who:alice})).to.equal(135)
if true
jig = new ReductionJig(noodb, "Latest(everyone~@alice)", 'bob')
first_spogi = {s:'_:doc', p:'_:iq', o:140, g:'_:minnow', who:bob}
expect(jig.do(first_spogi)).to.equal(140)
expect(jig.do({o:120, who:carol})).to.equal(120)
expect(jig.do({o:145, who:dan})).to.equal(145)
expect(jig.do({o:135, who:alice})).to.equal(145)
if true
jig = new DiscriminatorJig(noodb, "Latest(everyone).re(iq)", 'bob')
expect(jig.disc({s:'_:gilligan', p:'iq', o:75, g:'_:minnow'})).to.equal(75)
expect(jig.disc({o:76})).to.equal(76)
expect(jig.disc({o:77})).to.equal(77)
expect(jig.disc({p:'lb', o:98})).to.equal(null)
if true
jig = new DiscriminatorJig(noodb, "Average(Latest(everyone))", 'bob')
gilligan_iq = {s:'_:gilligan', p:'iq', o:120, g:'_:minnow'}
skipper_iq = {s:'_:skipper', p:'iq', o:150, g:'_:minnow'}
xtnd = _.extendOwn
expect(jig.disc(gilligan_iq)).to.equal(120)
expect(jig.disc(skipper_iq)).to.equal(150)
expect(jig.disc(xtnd(gilligan_iq,{o:60}))).to.equal((120+60)/2) # 90
expect(jig.disc(xtnd(skipper_iq,{o:50}))).to.equal((150+50)/2) # 100
expect(jig.disc(xtnd(gilligan_iq,{o:180}))).to.equal((120+60+180)/3) # 120
expect(jig.disc(xtnd(skipper_iq,{o:130}))).to.equal((150+50+130)/3) # 110
expect(jig.disc(xtnd(gilligan_iq,{o:40}))).to.equal((120+60+180+40)/4) # 100
expect(jig.disc(xtnd(skipper_iq,{o:82}))).to.equal((150+50+130+82)/4) # 103 = 412/
console.log "SUCCESS"
expect(1).to.equal(2, 'PROOF THAT THIS GHETTO expect() WORKS')
WhoToHeed_suite = () -> # same as in test/test_noodb.coffee but here for debugging
no_one = new WhoToHeed()
expect(no_one.heed('bob')).to.equal(0)
everyone = new WhoToHeed
include: [true]
expect(everyone.heed('bob')).to.equal(1)
force_everyone = new WhoToHeed(1)
expect(force_everyone.heed('bob')).to.equal(1)
force_nobody = new WhoToHeed(0)
expect(force_nobody.heed('bob')).to.equal(0)
not_bob = new WhoToHeed
include: [true]
exclude: ['bob']
expect(not_bob.heed('alice')).to.equal(1)
expect(not_bob.heed('bob')).to.equal(0)
not_bert_and_annie_last = new WhoToHeed
include: [true]
exclude: ['bert','annie']
chain:
include: ['annie']
expect(not_bert_and_annie_last.heed('bert')).to.equal(0, "looking for bert")
expect(not_bert_and_annie_last.heed('annie')).to.equal(2, "looking for annie")
everyone_but_bob = new WhoToHeed
include: [true]
chain:
exclude: ['bob']
expect(everyone_but_bob.heed('bob')).to.equal(0)
expect(everyone_but_bob.heed('alice')).to.equal(1)
msg = "This intentionally broken test shows that expect() works"
expect(everyone_but_bob.heed('alice')).to.equal(false, msg)
WhoToHeed_suite()
class Discriminator
@docs = "A Discriminator spawns a PredicateReducer per unique Subject-Criterion pairing"
constructor: (discriminator_src, @me_is) ->
@criterion_reducer_mapping = {}
@default_reducer = null
if not DiscriminatorReader?
discr_pth = (not window? and '../lib/' or '') + 'discriminator'
DiscriminatorReader = require(discr_pth).DiscriminatorReader
@discr_ast = new DiscriminatorReader(discriminator_src)
@populate_criterion_reducer_mapping_from_discr_ast()
@s_p_to_reduction = {}
populate_criterion_reducer_mapping_from_discr_ast: () ->
@tree = {}
if not (@discr_ast? and @discr_ast.ast and @discr_ast.ast.prog)
throw new Error("no Discriminator was parsed")
predicate_reducer_ASTs = @discr_ast.ast.prog
last_data_reducer = predicate_reducer_ASTs[predicate_reducer_ASTs.length - 1]
for predicate_reducer_AST in predicate_reducer_ASTs
new_pred_red = new DataReducer(predicate_reducer_AST, @me_is)
for p_key in new_pred_red.re_predicates
old_pred_red = @criterion_reducer_mapping[p_key]
@criterion_reducer_mapping[p_key] = new_pred_red
if new_pred_red.re_predicates.length is 0
@default_reducer = new_pred_red
discriminate: (spogi, noodb) ->
returns this spogi OR a synthetic one (if the product of digestion) OR null
p_key = spogi.p.key()
reduction = @get_reduction_for_spogi(spogi)
if reduction? and reduction.consume_spogi(spogi)
if it returns null the spogi did not affect the summary
return @get_real_or_synthetic(reduction, spogi, noodb)
return null
get_reduction_for_spogi: (spogi) ->
The goal of this method is to return a reduction tuned to the subject, the predicate and the current user (see @me_is)
There are many different ways a user (say @bob) can be processed:
s_key = spogi.s.key()
p_key = spogi.p.key()
s_p_key = s_key + " " + p_key
reduction = @s_p_to_reduction[s_p_key]
if not reduction
reducer = @get_reducer_for_p(p_key)
reduction = reducer? and reducer.spawn(s_p_key) or null
@s_p_to_reduction[s_p_key] = reduction
return reduction
get_reducer_for_p: (p_key) ->
retval = @criterion_reducer_mapping[p_key]
if not retval?
console.log “using @default_reducer for #{p_key}”, @default_reducer
return @default_reducer
return retval or @default_reducer
get_real_or_synthetic: (reduction, spogi, noodb) ->
Return Elected spogis or synthesize a spogi
ret_spogi = reduction.spogi # only produced by GenericElector subclasses
if not ret_spogi? # so if no ret_spogi let us synthesize one
sess =
TODO Examine how to make this a reference to the Discriminator on the theory that the Discriminator is the “Agent” responsible for creating the synthetic value.
user_symbol: 'synthetic'
session_no: 1 # TODO examine this for improvement
s_key = spogi.s.key()
p_key = spogi.p.key()
g_key = spogi.g.key()
if window?
ret_spogi = noodb.allege_local(s_key, p_key, reduction.value, g_key, sess)
else
ret_spogi = noodb.allege(s_key, p_key, reduction.value, g_key, sess)
return ret_spogi
class ReductionJig
doc: ".do() returns the current value of a DataReduction starting with null. It is assumed that all spogi changes sent to .do() are for the same S_P pair."
constructor: (@noodb, @discr_form, @me_is) ->
@reducer = new DataReducer(discr_form, @me_is)
@reduction = @reducer.spawn()
do: (new_args) ->
@args ?= {}
for k,v of new_args
@args[k] = v
a = @args
if window? # is this the browser environment? (as opposed to ther server)
a_spogi = @noodb.allege_local(a.s, a.p, a.o, a.g, a.who)
else
a_spogi = @noodb.allege(a.s, a.p, a.o, a.g, a.who)
if not @reduction.consume_spogi
console.log "@reduction", @reduction
debugger
@reduction.consume_spogi(a_spogi)
return @reduction.value
class DiscriminatorJig
doc: ".disc() runs .discriminate() returning a spogi IF THERE WAS CHANGE else null"
constructor: (@noodb, @discr_form, @me_is) ->
@discriminator = new Discriminator(discr_form, @me_is)
disc: (new_args) ->
@args ?= {}
for k,v of new_args
@args[k] = v
a = @args
if window?
a_spogi = @noodb.allege_local(a.s, a.p, a.o, a.g, a.who)
else
a_spogi = @noodb.allege(a.s, a.p, a.o, a.g, a.who)
onward = @discriminator.discriminate(a_spogi, @noodb)
if onward
return onward.o.getNativeValue()
return null
(exports ? this).WhoToHeed = WhoToHeed
(exports ? this).WhoToHeed_suite = WhoToHeed_suite
(exports ? this).DataReduction = DataReduction
(exports ? this).DataReduction_suite = DataReduction_suite
(exports ? this).DataReducer = DataReducer
(exports ? this).Discriminator = Discriminator
(exports ? this).ReductionJig = ReductionJig
(exports ? this).DiscriminatorJig = DiscriminatorJig
(exports ? this).Discriminator_suite = Discriminator_suite