• Jump To … +
    server.coffee src/actionknob.coffee src/autosem.coffee src/bitbucket_kba.coffee src/browserlog.coffee src/datareduction.coffee src/dci.coffee src/dciknob.coffee src/deeseeeye.coffee src/dnd.coffee src/doof.coffee src/formurla-mngr.coffee src/fractalpanel.coffee src/fractalpanel_test.coffee src/front.coffee src/ingestor.coffee src/kbabitbucket.coffee src/knobctrl.coffee src/lib_test.coffee src/nanoclock.coffee src/noodb.coffee src/noodbabstract.coffee src/noodbbrowser.coffee src/noodbbrowser_test.coffee src/noodbsec.coffee src/noorauth.coffee src/noorplugin.coffee src/noorquery.coffee src/noorvm.coffee src/noorwrite.coffee src/quadparser.coffee src/quadparsern3.coffee src/rbac.coffee src/reactor.coffee src/rebase.coffee src/rsrcidx.coffee src/sandboxactions.coffee src/screen_ctx.coffee src/spogi.coffee src/tabular_widget.coffee src/visctrl.coffee src/voicesknob.coffee src/whowhen.coffee src/xsd2native.coffee
  • ¶

    Docs

    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))

  • ¶

    Observations

    1. If there is only one person mentioned then there is only need for one aggregation function.
    2. If there is more than one person (explicitly or implicitly, ie ‘everyone’ counts as more than one) but they all occupy the same equivalence class then there is a need for two and only two aggregation functions
      -- one to apply to each individual
         and one to apply across the participating individuals.
      
    3. If there are multiple equivalence classes then there is a need for an aggregation function across them and a need for aggregation functions within them.

    Historical

    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.

      • (n)scalar http://goo.gl/rgMpvY a variable quantity which cannot be resolved into components
      • (n)compound http://goo.gl/mysg5F a whole formed by a union of two or more elements or parts
      • (adj) linear
      • (adj) numeric
      • (adj) non-numeric
      • (adj) sortable

    Problems:

    • This job is not done until it is possible to have a single DataReducer be shared across the set of criteria which are using it, so a single instance can serve an entire visualization which just happens to use the same DataReducer for all “criteria” Q. How do we prevent “stuffing the ballot box” intentionally or otherwise? For example what if some person has provided multiple allegations through time – either because their opinion has changed for some reason or they are attempting to game the system by “stuffing the ballot box” with either the same equivalent values? A. We could have two kinds of aggregation function:
      • within_person_aggregator (WPA): for all the allegations from one person
      • equivalent_person_aggregator (EPA): after the WPA across an equivalency class
      • across_class_aggregator (ACA): after the EPA across equivalency classes This strategy clarifies that the simple “Weighted()” strategy above is really just one of many possible ACAs.
    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:

    1. they are explicitly included in the HeedSpec @bob
    2. the author of the spogi matches ‘me’ me
    3. implicitly included everyone
    4. implicitly included then explicitly excluded everyone~@bob
    5. included via membership in a group my.rel(‘friend’)
    6. excluded via membership in a group everyone~my.rel(‘friend’)
    7. implicitly included but explicitly included in a lesser equivalency everyone|@bob
        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