:pencil: Add guard for unit fixing to restrict to SEC.

jsoniq version "1.0";

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace conversion = "http://28.io/modules/xbrl/conversion";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace labels = "http://28.io/modules/xbrl/labels";

import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

import module namespace config = "http://apps.28.io/config";
import module namespace session = "http://apps.28.io/session";
import module namespace api = "http://apps.28.io/api";

declare option rest:response "first-item";

(: Query parameters :)
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $rollup             as string* external;
declare  %rest:case-insensitive                 variable $map                as string? external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $labels             as boolean external := false;
declare  %rest:case-insensitive                 variable $merge              as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities as object* := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $components-not-found as boolean :=
  exists(($archives, $cid, $reportElement, $disclosure, $networkIdentifier, $label)) and empty($components)

let $component as object? := if($merge) then components:merge($components) else $components[1]
let $cid as string? := string-join($components ! components:cid($$), "--")
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

(: Fact resolution :)
let $facts :=
    if (exists($rollup) and $profile-name eq "sec")
         then
             let $calc-network := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
             let $hc := hypercubes:hypercubes-for-components($component)[]
             let $hc := hypercubes:modify-hypercube($hc, {
                 "sec:FiscalYear" : { Type: "integer", Default: null },
                 "sec:FiscalPeriod" : { Type: "string", Default: null },
                 "sec:FiscalPeriodType" : { Type: "string", Default: null }
             })
             let $options as object? := if(exists($rules)) then { Rules: [ $rules ] } else ()
             let $p := hypercubes:populate-networks-with-facts($calc-network, $hc, $archives, $options)
             let $map := concept-maps:concept-maps($map)
             let $concepts :=
                if (not $map instance of null)
                then
                    for $d in $rollup[]
                    return
                        descendant-objects($p)[$$.Name = ($map.Trees[])[$$.Name eq $d].To[].Name][1].To[].Name
                else
                    for $d in $rollup[]
                    return ($d, keys(descendant-objects($p)[$$.Name eq $d].To))
             return sec:facts-for-archives-and-concepts($archives, $concepts, { Hypercube: $hc })
         else components:facts(
            $component,
            {|
                {
                    FilterOverride : {
                        "sec:FiscalPeriod" : { Type: "string", Default: null },
                        "sec:FiscalYear" : { Type: "string", Default: null },
                        "sec:FiscalPeriodType" : { Type: "string", Default: null }
                    }
                }[$profile-name eq "sec"],
                { Validate: $validate },
                { Rules : [ $rules ] }[exists($rules)]
            |}
        )

let $facts :=
    if(not $labels)
    then $facts
    else
        (: if labels are requested by the labels=true parameter then also add labels for concepts :)
        let $concepts as object* := $component.Concepts[]
        for $fact in $facts
        let $labels := labels:labels-for-facts(
          $fact,
          $labels:STANDARD_LABEL_ROLE,
          $language,
          $concepts,
          $entities,
          ()
        )
        return
            {|
                trim($fact, "Labels"),
                { Labels : $labels }
            |}
let $facts :=
  if($profile-name eq "sec")
  then api:normalize-facts($facts)
  else $facts

let $result :=
  if($profile-name eq "sec")
  then {
    CIK : entities:eid($entities),
    EntityRegistrantName : $entities.Profiles.SEC.CompanyName,
    TableName : components:hypercubes($component),
    Label : $component.Label,
    AccessionNumber : $component.Archive,
    FormType : $archives.Profiles.SEC.FormType,
    FiscalPeriod : $archives.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
    FiscalYear : $archives.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
    AcceptanceDatetime : ($archives ! filings:acceptance-dateTimes($$)),
    NetworkIdentifier: $component.Role,
    Disclosure : $component.Profiles.SEC.Disclosure,
    FactTable : [ $facts ]
  } else {
    Archive : $component.Archive,
    Role: $component.Role,
    TableName : keys($component.Hypercubes),
    Label : $component.Label,
    FactTable : [ $facts ]
  }

let $comment := {
    NumFacts : count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}

let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return <FactTable entityRegistrantName="{$res.EntityRegistrantName}"
            cik="{$res.CIK}"
            tableName="{$res.TableName}"
            label="{$res.Label}"
            accessionNumber="{$res.AccessionNumber}"
            networkIdentifier="{$res.NetworkIdentifier}"
            formType="{$res.FormType}"
            fiscalPeriod="{$res.FiscalPeriod}"
            fiscalYear="{$res.FiscalYear}"
            acceptanceDatetime="{$res.AcceptanceDatetime}"
            disclosure="{$res.Disclosure}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
        default return <FactTable
            tableName="{$res.TableName}"
            label="{$res.Label}"
            archive="{$res.Archive}"
            role="{$res.Role}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        string-join(conversion:facts-to-csv($res.FactTable[], { Caller: "Component"}))
    }
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("components")
       default return api:serialize($result, $comment, $serializers, $format, "facttable-" || $cid)

jsoniq version "1.0";

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace conversion = "http://28.io/modules/xbrl/conversion";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace labels = "http://28.io/modules/xbrl/labels";

import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

import module namespace config = "http://apps.28.io/config";
import module namespace session = "http://apps.28.io/session";
import module namespace api = "http://apps.28.io/api";

declare option rest:response "first-item";

(: Query parameters :)
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $rollup             as string* external;
declare  %rest:case-insensitive                 variable $map                as string? external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $labels             as boolean external := false;
declare  %rest:case-insensitive                 variable $merge              as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities as object* := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $components-not-found as boolean :=
  exists(($archives, $cid, $reportElement, $disclosure, $networkIdentifier, $label)) and empty($components)

let $component as object? := if($merge) then components:merge($components) else $components[1]
let $cid as string? := string-join($components ! components:cid($$), "--")
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

(: Fact resolution :)
let $facts :=
    if (exists($rollup) and $profile-name eq "sec")
         then
             let $calc-network := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
             let $hc := hypercubes:hypercubes-for-components($component)[]
             let $hc := hypercubes:modify-hypercube($hc, {
                 "sec:FiscalYear" : { Type: "integer", Default: null },
                 "sec:FiscalPeriod" : { Type: "string", Default: null },
                 "sec:FiscalPeriodType" : { Type: "string", Default: null }
             })
             let $options as object? := if(exists($rules)) then { Rules: [ $rules ] } else ()
             let $p := hypercubes:populate-networks-with-facts($calc-network, $hc, $archives, $options)
             let $map := concept-maps:concept-maps($map)
             let $concepts :=
                if (not $map instance of null)
                then
                    for $d in $rollup[]
                    return
                        descendant-objects($p)[$$.Name = ($map.Trees[])[$$.Name eq $d].To[].Name][1].To[].Name
                else
                    for $d in $rollup[]
                    return ($d, keys(descendant-objects($p)[$$.Name eq $d].To))
             return sec:facts-for-archives-and-concepts($archives, $concepts, { Hypercube: $hc })
         else components:facts(
            $component,
            {|
                {
                    FilterOverride : {
                        "sec:FiscalPeriod" : { Type: "string", Default: null },
                        "sec:FiscalYear" : { Type: "string", Default: null },
                        "sec:FiscalPeriodType" : { Type: "string", Default: null }
                    }
                }[$profile-name eq "sec"],
                { Validate: $validate },
                { Rules : [ $rules ] }[exists($rules)]
            |}
        )

let $facts :=
    if(not $labels)
    then $facts
    else
        (: if labels are requested by the labels=true parameter then also add labels for concepts :)
        let $concepts as object* := $component.Concepts[]
        for $fact in $facts
        let $labels := labels:labels-for-facts(
          $fact,
          $labels:STANDARD_LABEL_ROLE,
          $language,
          $concepts,
          $entities,
          ()
        )
        return
            {|
                trim($fact, "Labels"),
                { Labels : $labels }
            |}
let $facts := api:normalize-facts($facts)

let $result :=
  if($profile-name eq "sec")
  then {
    CIK : entities:eid($entities),
    EntityRegistrantName : $entities.Profiles.SEC.CompanyName,
    TableName : components:hypercubes($component),
    Label : $component.Label,
    AccessionNumber : $component.Archive,
    FormType : $archives.Profiles.SEC.FormType,
    FiscalPeriod : $archives.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
    FiscalYear : $archives.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
    AcceptanceDatetime : ($archives ! filings:acceptance-dateTimes($$)),
    NetworkIdentifier: $component.Role,
    Disclosure : $component.Profiles.SEC.Disclosure,
    FactTable : [ $facts ]
  } else {
    Archive : $component.Archive,
    Role: $component.Role,
    TableName : keys($component.Hypercubes),
    Label : $component.Label,
    FactTable : [ $facts ]
  }

let $comment := {
    NumFacts : count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}

let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return <FactTable entityRegistrantName="{$res.EntityRegistrantName}"
            cik="{$res.CIK}"
            tableName="{$res.TableName}"
            label="{$res.Label}"
            accessionNumber="{$res.AccessionNumber}"
            networkIdentifier="{$res.NetworkIdentifier}"
            formType="{$res.FormType}"
            fiscalPeriod="{$res.FiscalPeriod}"
            fiscalYear="{$res.FiscalYear}"
            acceptanceDatetime="{$res.AcceptanceDatetime}"
            disclosure="{$res.Disclosure}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
        default return <FactTable
            tableName="{$res.TableName}"
            label="{$res.Label}"
            archive="{$res.Archive}"
            role="{$res.Role}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        string-join(conversion:facts-to-csv($res.FactTable[], { Caller: "Component"}))
    }
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("components")
       default return api:serialize($result, $comment, $serializers, $format, "facttable-" || $cid)


Big simplification for `span`

Big simplification for `span`

module {
    name: "word/scanner",
    description: "Icon style scanner operations on words",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    }
};

########################################################################
# Types used in declarations:
#   WORD:       [a]^string
#   SYMBOL:     singleton WORD
#   POS:        number
#   TEST:       SYMBOL->boolean

########################################################################
# Match one symbol

# Do satisfies the symbol at `.` in `$w` the predicate `t`?
def meets($w; t): #:: POS|(WORD;TEST) => ?POS
    select(0 <= . and . < ($w|length))
    | select($w[.:.+1] | t)
    | .+1
;

# Icon `any`
def any($w; $wset): #:: POS|(WORD;WORD;POS) => ?POS
    meets($w; inside($wset))
;

# Icon `notany`
def notany($w; $wset): #:: POS|(WORD;WORD) => ?POS
    meets($w; false==inside($wset))
;

########################################################################
# upto
########################################################################

# Positions for all symbols in `$w[.:j]` satisfying `t`
def locus($w; t; $j): #:: POS|(WORD;TEST;POS) => *POS
    . as $i
    | select(0 <= $i and $i < $j and $j <= ($w|length))
    | range($i;$j) as $k
    | select($w[$k:$k+1] | t)
    | $k
;

# Positions for all symbols in `$w[.:]` satisfying `t`
def locus($w; t): #:: POS|(WORD;TEST) => *POS
    locus($w; t; $w|length)
;

# Icon `upto`
def upto($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *POS
    locus($w; inside($wset); $j)
;
def upto($w; $wset): #:: POS|(WORD;WORD) => *POS
    locus($w; inside($wset); $w|length)
;

def upto_c($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *POS
    locus($w; false==inside($wset); $j)
;
def upto_c($w; $wset): #:: POS|(WORD;WORD) => *POS
    locus($w; false==inside($wset); $w|length)
;

########################################################################
# many
########################################################################

# Generalized Icon `many`, SNOBOL `SPAN`, C `strspn`, Haskell `span`
def span($w; t; $j): #:: POS|(WORD;TEST;POS) => ?POS
    . as $i
    | select(0 <= $i and $i < $j)
    | last(meets($w; t) | recurse(meets($w; t)))
      // empty
;
def span($w; t): #:: POS|(WORD;TEST) => ?POS
    span($w; t; $w|length)
;

# Icon `many`
def many($w; $wset; $j): #:: POS|(WORD;WORD;POS;POS) => ?POS
    span($w; inside($wset); $j)
;
def many($w; $wset): #:: POS|(WORD;WORD) => ?POS
    span($w; inside($wset); $w|length)
;

# Complementary of `many`
def many_c($w; $wset; $j): #:: POS|(WORD;WORD;POS;POS) => ?POS
    span($w; false==inside($wset); $j)
;
def many_c($w; $wset): #:: POS|(WORD;WORD) => ?POS
    span($w; false==inside($wset); $w|length)
;

########################################################################
# Match/find word(s)
########################################################################

# Matches u at the begining of w? (Icon `match`)
def match($w; $u; $j): #:: POS|(WORD;WORD;POS)=> ?POS
    select(0 <= . and . < $j)
    | ($u|length) as $n
    | select(.+$n <= $j and $w[.:.+$n] == $u)
    | .+$n
;
def match($w; $u): #:: POS|(WORD;WORD)=> ?POS
    match($w; $u; $w|length)
;

# Global search factor (Icon `find`)
def find($w; $u; $j): #:: POS|(WORD;WORD;POS) => *POS
    select(0 <= . and . < $j and $j <= ($w|length))
    | $w[.:$j] | indices($u)[]
;
def find($w; $u): #:: POS|(WORD;WORD) => *POS
    find($w; $u; $w|length)
;

########################################################################
# Tokenize words
########################################################################

# Produce tokens delimited by `$wset` symbols
def tokens($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *WORD
    def r:
        first(upto_c($w; $wset; $j))    # [delimiters]*(?=[^delimiters])
        | . as $i
        | many_c($w; $wset; $j)         # [^delimiters]+
        | $w[$i:.], r
    ;
    r
;
def tokens($w; $wset): #:: POS|(WORD;WORD) => *WORD
    tokens($w; $wset; $w|length)
;

# Produce tokens consisting in `$wset` symbols
def tokens_c($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *WORD
    def r:
        first(upto($w; $wset; $j))  # [^consisting]*(?=[consisting])
        | . as $i
        | many($w; $wset; $j)       # [consisting]+
        | $w[$i:.], r
    ;
    r
;
def tokens_c($w; $wset): #:: POS|(WORD;WORD) => *WORD
    tokens_c($w; $wset; $w|length)
;

# vim:ai:sw=4:ts=4:et:syntax=jq

module {
    name: "word/scanner",
    description: "Icon style scanner operations on words",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    }
};

########################################################################
# Types used in declarations:
#   WORD:       [a]^string
#   SYMBOL:     singleton WORD
#   POS:        number
#   TEST:       SYMBOL->boolean

########################################################################
# Match one symbol

# Do satisfies the symbol at `.` in `$w` the predicate `t`?
def meets($w; t): #:: POS|(WORD;TEST) => ?POS
    select(0 <= . and . < ($w|length))
    | select($w[.:.+1] | t)
    | .+1
;

# Icon `any`
def any($w; $wset): #:: POS|(WORD;WORD;POS) => ?POS
    meets($w; inside($wset))
;

# Icon `notany`
def notany($w; $wset): #:: POS|(WORD;WORD) => ?POS
    meets($w; false==inside($wset))
;

########################################################################
# upto
########################################################################

# Positions for all symbols in `$w[.:j]` satisfying `t`
def locus($w; t; $j): #:: POS|(WORD;TEST;POS) => *POS
    . as $i
    | select(0 <= $i and $i < $j and $j <= ($w|length))
    | range($i;$j) as $k
    | select($w[$k:$k+1] | t)
    | $k
;

# Positions for all symbols in `$w[.:]` satisfying `t`
def locus($w; t): #:: POS|(WORD;TEST) => *POS
    locus($w; t; $w|length)
;

# Icon `upto`
def upto($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *POS
    locus($w; inside($wset); $j)
;
def upto($w; $wset): #:: POS|(WORD;WORD) => *POS
    locus($w; inside($wset); $w|length)
;

def upto_c($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *POS
    locus($w; false==inside($wset); $j)
;
def upto_c($w; $wset): #:: POS|(WORD;WORD) => *POS
    locus($w; false==inside($wset); $w|length)
;

########################################################################
# many
########################################################################

# Generalized Icon `many`, SNOBOL `SPAN`, C `strspn`, Haskell `span`
def span($w; t; $j): #:: POS|(WORD;TEST;POS) => ?POS
    . as $i
    | select(0 <= $i and $i < $j)
    | last(meets($w; t; $j) | recurse(meets($w; t; $j)))
      // empty
#   | label $pipe
#   # for $k=$i to $j+1 (off-value used as a flag)
#   | range($i; $j+1) as $k
#   | if $k == $j           # if past end, all matched
#     then $k , break$pipe  # then return $k
#     elif $w[$k:$k+1] | t  # if match at $k
#     then empty            # then continue loop
#     elif $k > $i          # if moved at least one forward
#     then $k , break$pipe  # then return $k
#     else break$pipe       # abort, none match!
#     end
;
def span($w; t): #:: POS|(WORD;TEST) => ?POS
    span($w; t; $w|length)
;

# Icon `many`
def many($w; $wset; $j): #:: POS|(WORD;WORD;POS;POS) => ?POS
    span($w; inside($wset); $j)
;
def many($w; $wset): #:: POS|(WORD;WORD) => ?POS
    span($w; inside($wset); $w|length)
;

# Complementary of `many`
def many_c($w; $wset; $j): #:: POS|(WORD;WORD;POS;POS) => ?POS
    span($w; false==inside($wset); $j)
;
def many_c($w; $wset): #:: POS|(WORD;WORD) => ?POS
    span($w; false==inside($wset); $w|length)
;

########################################################################
# Match/find word(s)
########################################################################

# Matches u at the begining of w? (Icon `match`)
def match($w; $u; $j): #:: POS|(WORD;WORD;POS)=> ?POS
    select(0 <= . and . < $j)
    | ($u|length) as $n
    | select(.+$n <= $j and $w[.:.+$n] == $u)
    | .+$n
;
def match($w; $u): #:: POS|(WORD;WORD)=> ?POS
    match($w; $u; $w|length)
;

# Global search factor (Icon `find`)
def find($w; $u; $j): #:: POS|(WORD;WORD;POS) => *POS
    select(0 <= . and . < $j and $j <= ($w|length))
    | $w[.:$j] | indices($u)[]
;
def find($w; $u): #:: POS|(WORD;WORD) => *POS
    find($w; $u; $w|length)
;

########################################################################
# Tokenize words
########################################################################

# Produce tokens delimited by `$wset` symbols
def tokens($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *WORD
    def r:
        first(upto_c($w; $wset; $j))    # [delimiters]*(?=[^delimiters])
        | . as $i
        | many_c($w; $wset; $j)         # [^delimiters]+
        | $w[$i:.], r
    ;
    r
;
def tokens($w; $wset): #:: POS|(WORD;WORD) => *WORD
    tokens($w; $wset; $w|length)
;

# Produce tokens consisting in `$wset` symbols
def tokens_c($w; $wset; $j): #:: POS|(WORD;WORD;POS) => *WORD
    def r:
        first(upto($w; $wset; $j))  # [^consisting]*(?=[consisting])
        | . as $i
        | many($w; $wset; $j)       # [consisting]+
        | $w[$i:.], r
    ;
    r
;
def tokens_c($w; $wset): #:: POS|(WORD;WORD) => *WORD
    tokens_c($w; $wset; $w|length)
;

# vim:ai:sw=4:ts=4:et:syntax=jq


Further simplified.

Further simplified.

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/test";

import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token         as string? external;
declare  %rest:env                              variable $request-uri   as string  external;
declare  %rest:case-insensitive                 variable $format        as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cik           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker        as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear    as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $aid           as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)

(: Object resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

let $summaries :=
    switch($profile-name)
    case "sec" return
        for $f in filings:summaries($archives)
        order by $f.Accepted descending
        return $f
    case "japan" return
      for $a in $archives
      order by $a.Profiles.JAPAN.SubmissionDate descending
      return project($a, ("_id", "Entity", "Profiles")))
    default return $archives

let $summaries :=
  switch($profile-name)
  case "sec" return
    for $archive in $summaries
    return {|
      project($archive, "AccessionNumber"),
      {
        Components: backend:url("components",
          {
              aid: encode-for-uri($archive.AccessionNumber),
              format: $format,
              profile-name: $profile-name
          }, true)
      },
      trim($archive, "AccessionNumber")
    |}
  default return
    for $archive in $summaries
    return {|
      project($archive, "_id"),
      {
        Components: backend:url("components",
          {
              aid: encode-for-uri($archive._id),
              format: $format,
              profile-name: $profile-name
          }, true)
      },
      trim($archive, "_id")
    |}

let $result := { "Archives" : [ $summaries ] }
let $comment :=
{
    NumArchives: count($summaries),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return
            <Filings>{
                filings:summaries-to-xml($res.Archives[])
            }</Filings>
        default return
            <Archives>{
                for $a in $res.Archives[]
                return <Archive>
                    <AID>{$a._id}</AID>
                    <Entity>{$a.Entity}</Entity>
                </Archive>
            }
            </Archives>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return
            string-join(filings:summaries-to-csv($res.Archives[]))
        default return api:json-to-csv($res.Archives[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "filings")
return api:check-and-return-results($token, $results, $format)

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/test";

import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token         as string? external;
declare  %rest:env                              variable $request-uri   as string  external;
declare  %rest:case-insensitive                 variable $format        as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cik           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker        as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear    as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $aid           as string* external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)

(: Object resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

let $summaries :=
    switch($profile-name)
    case "sec" return
        for $f in filings:summaries($archives)
        order by $f.Accepted descending
        return $f
    case "japan" return
      for $a in $archives
      order by $a.Profiles.JAPAN.DocumentFiscalYearFocus descending, $a.Profiles.JAPAN.DocumentFiscalPeriodFocus
      return api:flatten-json-object(project($a, ("_id", "Entity", "Profiles")))
    default return
        for $a in $archives
        return {
            AID: $a._id,
            Entity: $a.Entity
        }
let $summaries :=
  switch($profile-name)
  case "sec" return
    for $archive in $summaries
    return {|
      project($archive, "AccessionNumber"),
      {
        Components: backend:url("components",
          {
              aid: encode-for-uri($archive.AccessionNumber),
              format: $format,
              profile-name: $profile-name
          }, true)
      },
      trim($archive, "AccessionNumber")
    |}
  case "japan" return
    for $archive in $summaries
    return {|
      project($archive, "_id"),
      {
        Components: backend:url("components",
          {
              aid: encode-for-uri($archive._id),
              format: $format,
              profile-name: $profile-name
          }, true)
      },
      trim($archive, "_id")
    |}
  default return
    for $archive in $summaries
    return {|
      $archive,
      {
        Components: backend:url("components",
          {
              aid: encode-for-uri($archive.AID),
              format: $format,
              profile-name: $profile-name
          }, true)
      }
    |}

let $result := { "Archives" : [ $summaries ] }
let $comment :=
{
    NumArchives: count($summaries),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return
            <Filings>{
                filings:summaries-to-xml($res.Archives[])
            }</Filings>
        default return
            <Archives>{
                for $a in $res.Archives[]
                return <Archive>
                    <AID>{$a.AID}</AID>
                    <Entity>{$a.Entity}</Entity>
                </Archive>
            }
            </Archives>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return
            string-join(filings:summaries-to-csv($res.Archives[]))
        default return api:json-to-csv($res.Archives[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "filings")
return api:check-and-return-results($token, $results, $format)


Removing DOW30 tokenless behavior.

Removing DOW30 tokenless behavior.

jsoniq version "1.0";

module namespace session = "http://apps.28.io/session";

import module namespace random = "http://zorba.io/modules/random";
import module namespace user = "http://apps.28.io/user";
import module namespace api = "http://apps.28.io/api";
import module namespace req = "http://www.28msec.com/modules/http-request";
import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace cred = "http://www.28msec.com/modules/credentials";

declare variable $session:tokens := "Tokens";
declare variable $session:VALID-TOKEN := "[a-z0-9\\-]+";
declare variable $session:ACCESS-ALLOWED := 0;
declare variable $session:ACCESS-DENIED := 1;
declare variable $session:ACCESS-AUTH-REQUIRED := 2;

declare variable $session:comment := "Generated by 28.io, for more information see http://www.28.io/xbrl (" || fn:current-dateTime() || ")";

declare function session:comment($format as string) as item*
{
    switch ($format)
    case "json" return { "Comment" : $session:comment }
    case "xml" return
        (
            comment { $session:comment },
            comment { req:server-name() || req:uri() }
        )
    default return error()
};

declare function session:comment($format as string, $stats as object)
  as item*
{
    switch ($format)
    case "json" return
        {|
            session:comment($format),
            { "Statistics" : $stats }
        |}
    case "xml" return
        (
            session:comment($format),
            comment { "Statistics: " || string-join(for $k in keys($stats) return concat ($k, ": ", $stats($k)), ", ") }
        )
    default return error()
};

declare function session:connect() as xs:anyURI
{
    let $cred := cred:credentials("MongoDB", "xbrl")
    return mongo:connect($cred)
};

declare function session:num-facts() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "facts")
};

declare function session:num-archives() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "archives")
};

declare function session:num-components() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "components")
};

declare function session:num-concepts() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "concepts")
};

declare function session:num-entities() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "entities")
};

declare %an:sequential function session:start($user-id as xs:string, $expiration-date as xs:dateTime)
as string
{
    session:cleanup();

    variable $token := random:uuid();
    variable $session :=
      {
        "_id"           : $token,
        user-id         : $user-id,
        expiration-date : $expiration-date
      };
    db:insert($session:tokens, $session);

    $token
};

declare function session:get($token as string)
as string
{
    api:validate-regexp("token", $token, $session:VALID-TOKEN);

    variable $session := find($session:tokens, { "_id" : $token });

    if (exists($session))
    then
        if ($session.expiration-date gt current-dateTime())
        then $session.user-id
        else fn:error(xs:QName("session:expired"), "Your session has expired at " || $session.expiration-date )
    else fn:error(xs:QName("session:expired"), "Your session is not valid")
};

declare %an:sequential function session:terminate($token as string)
as empty-sequence()
{
    api:validate-regexp("token", $token, $session:VALID-TOKEN);

    variable $session := find($session:tokens, { "_id" : $token });

    if (exists($session))
    then db:delete($session);
    else ();
};

declare %an:sequential function session:cleanup()
as empty-sequence()
{
    for $session in collection($session:tokens)
    where $session.expiration-date le current-dateTime()
    return db:delete($session);
};

declare function session:ensure-valid($token as string)
as string
{
    session:get($token) (: validates token :)
};

declare function session:is-valid($token as string?)
as boolean
{
    if (exists($token))
    then try {{ session:get($token); true }} catch * {{ false }} (: validates token :)
    else false
};

declare function session:ensure-right($token as string, $right-id as string)
as string
{
    variable $user-id := session:get($token); (: validates token :)
    if (user:is-authorized($user-id, $right-id))
    then $user-id
    else fn:error(xs:QName("session:missing-authorization"), "User does not have required right " || $right-id)
};

declare function session:has-right($token as string?, $right-id as string)
as boolean
{
    variable $user-id := session:get($token); (: validates token :)
    if (user:is-authorized($user-id, $right-id))
    then true
    else false
};

declare function session:has-access($token as string?, $right-id as string)
as integer
{
	try {{
	    if (exists($token))
	    then
	    {
	      if (session:has-right($token, $right-id))
	      then $session:ACCESS-ALLOWED
	      else $session:ACCESS-DENIED
	    }
	    else $session:ACCESS-AUTH-REQUIRED
	}}
	catch * {{
	    $session:ACCESS-AUTH-REQUIRED
	}}
};

declare function session:error($msg as string, $format as string?) as item
{
	switch ($format)
    case "xml" return <error><message>{$msg}</message></error>
	case "text" or "csv" or "excel" return $msg
    default return
	{
		"success" : false,
		"description" : $msg
	}
};

declare %an:sequential function session:audit-call($token as string?) as empty-sequence()
{
    let $dist-aspects := [ "xbrl:Concept", "xbrl:Entity", "xbrl:Period" ]
    let $facts := {
        KeyAspects : $dist-aspects,
        Aspects : {
            "xbrl:Concept" : "secxbrl:ClientIP",
            "xbrl:Entity" : if (session:is-valid($token)) then user:get-by-id(session:get($token)).email else "Anonymous",
            "xbrl:Period" : string(fn:current-dateTime()),
            "xbrl:Unit" : "xbrl:NonNumeric",
            "secxbrl:Query" : req:path()
        },
        Type : "NonNumericValue",
        Value : (req:header-value("X-FORWARDED-FOR"), req:remote-addr())[1]
    }
    return db:insert("audit", $facts);
};

jsoniq version "1.0";

module namespace session = "http://apps.28.io/session";

import module namespace random = "http://zorba.io/modules/random";
import module namespace user = "http://apps.28.io/user";
import module namespace api = "http://apps.28.io/api";
import module namespace req = "http://www.28msec.com/modules/http-request";
import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace cred = "http://www.28msec.com/modules/credentials";

declare variable $session:tokens := "Tokens";
declare variable $session:VALID-TOKEN := "[a-z0-9\\-]+";
declare variable $session:ACCESS-ALLOWED := 0;
declare variable $session:ACCESS-DENIED := 1;
declare variable $session:ACCESS-AUTH-REQUIRED := 2;

declare variable $session:comment := "Generated by 28.io, for more information see http://www.28.io/xbrl (" || fn:current-dateTime() || ")";

declare function session:comment($format as string) as item*
{
    switch ($format)
    case "json" return { "Comment" : $session:comment }
    case "xml" return
        (
            comment { $session:comment },
            comment { req:server-name() || req:uri() }
        )
    default return error()
};

declare function session:comment($format as string, $stats as object)
  as item*
{
    switch ($format)
    case "json" return
        {|
            session:comment($format),
            { "Statistics" : $stats }
        |}
    case "xml" return
        (
            session:comment($format),
            comment { "Statistics: " || string-join(for $k in keys($stats) return concat ($k, ": ", $stats($k)), ", ") }
        )
    default return error()
};

declare function session:connect() as xs:anyURI
{
    let $cred := cred:credentials("MongoDB", "xbrl")
    return mongo:connect($cred)
};

declare function session:num-facts() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "facts")
};

declare function session:num-archives() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "archives")
};

declare function session:num-components() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "components")
};

declare function session:num-concepts() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "concepts")
};

declare function session:num-entities() as xs:integer
{
    let $conn := session:connect()
    return mongo:count($conn, "entities")
};

declare %an:sequential function session:start($user-id as xs:string, $expiration-date as xs:dateTime)
as string
{
    session:cleanup();

    variable $token := random:uuid();
    variable $session :=
      {
        "_id"           : $token,
        user-id         : $user-id,
        expiration-date : $expiration-date
      };
    db:insert($session:tokens, $session);

    $token
};

declare function session:get($token as string)
as string
{
    api:validate-regexp("token", $token, $session:VALID-TOKEN);

    variable $session := find($session:tokens, { "_id" : $token });

    if (exists($session))
    then
        if ($session.expiration-date gt current-dateTime())
        then $session.user-id
        else fn:error(xs:QName("session:expired"), "Your session has expired at " || $session.expiration-date )
    else fn:error(xs:QName("session:expired"), "Your session is not valid")
};

declare %an:sequential function session:terminate($token as string)
as empty-sequence()
{
    api:validate-regexp("token", $token, $session:VALID-TOKEN);

    variable $session := find($session:tokens, { "_id" : $token });

    if (exists($session))
    then db:delete($session);
    else ();
};

declare %an:sequential function session:cleanup()
as empty-sequence()
{
    for $session in collection($session:tokens)
    where $session.expiration-date le current-dateTime()
    return db:delete($session);
};

declare function session:ensure-valid($token as string)
as string
{
    session:get($token) (: validates token :)
};

declare function session:is-valid($token as string?)
as boolean
{
    if (exists($token))
    then try {{ session:get($token); true }} catch * {{ false }} (: validates token :)
    else false
};

declare function session:ensure-right($token as string, $right-id as string)
as string
{
    variable $user-id := session:get($token); (: validates token :)
    if (user:is-authorized($user-id, $right-id))
    then $user-id
    else fn:error(xs:QName("session:missing-authorization"), "User does not have required right " || $right-id)
};

declare function session:has-right($token as string?, $right-id as string)
as boolean
{
    variable $user-id := session:get($token); (: validates token :)
    if (user:is-authorized($user-id, $right-id))
    then true
    else false
};

declare function session:has-access($token as string?, $entities as object*, $right-id as string)
as integer
{
    if (session:only-dow30($entities))
    then $session:ACCESS-ALLOWED
    else
        try {{
            if (exists($token))
            then
            {
              if (session:has-right($token, $right-id))
              then $session:ACCESS-ALLOWED
              else $session:ACCESS-DENIED
            }
            else $session:ACCESS-AUTH-REQUIRED
        }}
        catch * {{
            $session:ACCESS-AUTH-REQUIRED
        }}
};

declare %private function session:only-dow30($entities as object*)
as boolean
{
    count(
        for $e in $entities
        where count($e.Profiles.SEC.Tags[]) gt 0 and $e.Profiles.SEC.Tags[] = "DOW30"
        return $e) eq count($entities)
};

declare function session:error($msg as string, $format as string?) as item
{
	switch ($format)
    case "xml" return <error><message>{$msg}</message></error>
	case "text" or "csv" or "excel" return $msg
    default return
	{
		"success" : false,
		"description" : $msg
	}
};

declare %an:sequential function session:audit-call($token as string?) as empty-sequence()
{
    let $dist-aspects := [ "xbrl:Concept", "xbrl:Entity", "xbrl:Period" ]
    let $facts := {
        KeyAspects : $dist-aspects,
        Aspects : {
            "xbrl:Concept" : "secxbrl:ClientIP",
            "xbrl:Entity" : if (session:is-valid($token)) then user:get-by-id(session:get($token)).email else "Anonymous",
            "xbrl:Period" : string(fn:current-dateTime()),
            "xbrl:Unit" : "xbrl:NonNumeric",
            "secxbrl:Query" : req:path()
        },
        Type : "NonNumericValue",
        Value : (req:header-value("X-FORWARDED-FOR"), req:remote-addr())[1]
    }
    return db:insert("audit", $facts);
};


added 'as string'

added 'as string'

jsoniq version "1.0";

module namespace conversion = "http://28.io/modules/xbrl/conversion";

import module namespace csv = "http://zorba.io/modules/json-csv";

declare %private variable $conversion:STANDARD_LABELS :=
    {
        "sec:Archive" : "Archive",
        "xbrl:Concept" : "Concept",
        "xbrl:Entity" : "Entity",
        "xbrl:Period" : "Period",
        "sec:FiscalPeriod" : "Fiscal Period",
        "sec:FiscalYear" : "Fiscal Year",
        "sec:Accepted" : "Accepted",
        "xbrl:Unit": "Unit",
        "dei:LegalEntityAxis": "Legal Entity"
    };

declare %private function conversion:aspect-label(
    $aspect as string) as string
{
    switch(true)
    case exists($conversion:STANDARD_LABELS.$aspect)
      return $conversion:STANDARD_LABELS.$aspect
    default return $aspect
};

declare %private function conversion:aspect-value-or-label(
    $aspect as string,
    $aspect-value as atomic,
    $labels as object?) as atomic
{
    switch(true)
    case ($aspect eq "xbrl:Entity" and starts-with($aspect-value, "http://www.sec.gov/CIK "))
      return substring-after($aspect-value, "http://www.sec.gov/CIK ")
    case ($aspect eq "xbrl:Unit" and starts-with($aspect-value, "iso4217:"))
      return substring-after($aspect-value, "iso4217:")
    case ($aspect-value instance of string and exists($labels.($aspect-value)))
      return $labels.($aspect-value)
    default return $aspect-value
};

declare function conversion:facts-to-csv(
    $facts as object*,
    $options as object?) as string?
{
    let $projection :=
        if($options.Caller eq "Report")
        then ("Value",
              "Unit",
              "Decimals",
              "EntityRegistrantName",
              "ReportedConcept")
        else ("Unit",
              "Value",
              "Type",
              "Decimals")
    let $use-labels as boolean := exists($facts.Labels)
    return if (exists($facts)) (: bug in csv:serialize :)
           then string-join(
                csv:serialize(
                    for $fact in $facts
                    return
                        {|
                            if($use-labels)
                            then
                                for $aspect as string in keys($fact.Aspects)
                                let $aspect-label := conversion:aspect-label($aspect)
                                let $aspect-value := conversion:aspect-value-or-label($aspect, $fact.Aspects.($aspect), $fact.Labels)
                                return
                                    {
                                        $aspect-label : $aspect-value
                                    }
                            else
                                $fact.Aspects,
                            project($fact, $projection)
                        |},
                    { serialize-null-as : "" }
                )
            )
            else ()
};

declare function conversion:facts-to-xml(
    $facts as object*,
    $options as object?) as element()*
{
    for $fact in $facts
    let $aspects := $fact.Aspects
    return
        <Fact>{
            <Aspects>{
                for $aspect in keys($aspects)
                let $aspect-value := $aspects.$aspect
                return
                    <Aspect>
                        <Name>{$aspect}</Name>
                        <Value>{
                            if($fact.Labels and $aspect-value instance of string and exists($fact.Labels.($aspect-value)))
                            then attribute { "label" } { $fact.Labels.($aspect-value) }
                            else (),
                            $aspects.$aspect
                        }</Value>
                    </Aspect>
            }</Aspects>,
            if($options.Caller eq "Report")
            then (
                <Value>{$fact.Value}</Value>,
                <Type>{$fact.Type}</Type>,
                <Unit>{$fact.Unit}</Unit>[exists($fact.Unit)],
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)],
                <EntityRegistrantName>{$fact.EntityRegistrantName}</EntityRegistrantName>,
                conversion:audittrail-to-xml($fact.AuditTrails)[exists($fact.AuditTrails)]
            ) else (
                <Type>{$fact.Type}</Type>,
                <Value>{$fact.Value}</Value>,
                <Unit>{$fact.Unit}</Unit>[exists($fact.Unit)],
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)]
            )
        }</Fact>
};

declare function conversion:audittrail-to-xml($audit as item) as element()
{
    <AuditTrails>{
        for $a in flatten($audit)
        return (
            <Type>{$a.Type}</Type>,
            <Label>{$a.Label}</Label>,
            <Message>{$a.Message}</Message>,
            <Data>{
                if (exists($a.Data.OriginalConcept))
                then <OriginalConcept>{$a.Data.OriginalConcept}</OriginalConcept>
                else (),
                if (exists($a.Data.OutputConcept))
                then <OutputConcept>{$a.Data.OutputConcept}</OutputConcept>
                else (),
                if (exists($a.Data.AuditTrails))
                then conversion:audittrail-to-xml($a.Data.AuditTrails)
                else ()
            }</Data>
        )
    }</AuditTrails>
};

jsoniq version "1.0";

module namespace conversion = "http://28.io/modules/xbrl/conversion";

import module namespace csv = "http://zorba.io/modules/json-csv";

declare %private variable $conversion:STANDARD_LABELS :=
    {
        "sec:Archive" : "Archive",
        "xbrl:Concept" : "Concept",
        "xbrl:Entity" : "Entity",
        "xbrl:Period" : "Period",
        "sec:FiscalPeriod" : "Fiscal Period",
        "sec:FiscalYear" : "Fiscal Year",
        "sec:Accepted" : "Accepted",
        "xbrl:Unit": "Unit",
        "dei:LegalEntityAxis": "Legal Entity"
    };

declare %private function conversion:aspect-label(
    $aspect as string) as string
{
    switch(true)
    case exists($conversion:STANDARD_LABELS.$aspect)
      return $conversion:STANDARD_LABELS.$aspect
    default return $aspect
};

declare %private function conversion:aspect-value-or-label(
    $aspect as string,
    $aspect-value as atomic,
    $labels as object?) as atomic
{
    switch(true)
    case ($aspect eq "xbrl:Entity" and starts-with($aspect-value, "http://www.sec.gov/CIK "))
      return substring-after($aspect-value, "http://www.sec.gov/CIK ")
    case ($aspect eq "xbrl:Unit" and starts-with($aspect-value, "iso4217:"))
      return substring-after($aspect-value, "iso4217:")
    case ($aspect-value instance of string and exists($labels.($aspect-value)))
      return $labels.($aspect-value)
    default return $aspect-value
};

declare function conversion:facts-to-csv(
    $facts as object*,
    $options as object?) as string?
{
    let $projection :=
        if($options.Caller eq "Report")
        then ("Value",
              "Unit",
              "Decimals",
              "EntityRegistrantName",
              "ReportedConcept")
        else ("Unit",
              "Value",
              "Type",
              "Decimals")
    let $use-labels as boolean := exists($facts.Labels)
    return if (exists($facts)) (: bug in csv:serialize :)
           then string-join(
                csv:serialize(
                    for $fact in $facts
                    return
                        {|
                            if($use-labels)
                            then
                                for $aspect in keys($fact.Aspects)
                                let $aspect-label := conversion:aspect-label($aspect)
                                let $aspect-value := conversion:aspect-value-or-label($aspect, $fact.Aspects.($aspect), $fact.Labels)
                                return
                                    {
                                        $aspect-label : $aspect-value
                                    }
                            else
                                $fact.Aspects,
                            project($fact, $projection)
                        |},
                    { serialize-null-as : "" }
                )
            )
            else ()
};

declare function conversion:facts-to-xml(
    $facts as object*,
    $options as object?) as element()*
{
    for $fact in $facts
    let $aspects := $fact.Aspects
    return
        <Fact>{
            <Aspects>{
                for $aspect in keys($aspects)
                let $aspect-value := $aspects.$aspect
                return
                    <Aspect>
                        <Name>{$aspect}</Name>
                        <Value>{
                            if($fact.Labels and $aspect-value instance of string and exists($fact.Labels.($aspect-value)))
                            then attribute { "label" } { $fact.Labels.($aspect-value) }
                            else (),
                            $aspects.$aspect
                        }</Value>
                    </Aspect>
            }</Aspects>,
            if($options.Caller eq "Report")
            then (
                <Value>{$fact.Value}</Value>,
                <Type>{$fact.Type}</Type>,
                <Unit>{$fact.Unit}</Unit>[exists($fact.Unit)],
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)],
                <EntityRegistrantName>{$fact.EntityRegistrantName}</EntityRegistrantName>,
                conversion:audittrail-to-xml($fact.AuditTrails)[exists($fact.AuditTrails)]
            ) else (
                <Type>{$fact.Type}</Type>,
                <Value>{$fact.Value}</Value>,
                <Unit>{$fact.Unit}</Unit>[exists($fact.Unit)],
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)]
            )
        }</Fact>
};

declare function conversion:audittrail-to-xml($audit as item) as element()
{
    <AuditTrails>{
        for $a in flatten($audit)
        return (
            <Type>{$a.Type}</Type>,
            <Label>{$a.Label}</Label>,
            <Message>{$a.Message}</Message>,
            <Data>{
                if (exists($a.Data.OriginalConcept))
                then <OriginalConcept>{$a.Data.OriginalConcept}</OriginalConcept>
                else (),
                if (exists($a.Data.OutputConcept))
                then <OutputConcept>{$a.Data.OutputConcept}</OutputConcept>
                else (),
                if (exists($a.Data.AuditTrails))
                then conversion:audittrail-to-xml($a.Data.AuditTrails)
                else ()
            }</Data>
        )
    }</AuditTrails>
};


added concept metadata to bfi report.

added concept metadata to bfi report.

jsoniq version "1.0";
import module namespace http = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace export = "http://apps.28.io/reports-export";

declare %an:nondeterministic function local:concept($name as string, $label as string, $detailed as xs:boolean) as object{
  {|
      {
        "Name" : $name,
        "Label" : $label
      },
      {
        "PeriodType" : "instant",
        "Labels" : [ {
          "Role" : "http://www.xbrl.org/2003/role/label",
          "Language" : "en-us",
          "Value" : $label
        } ],
        "IsAbstract" : false,
        "DataType" : "xbrli:sharesItemType",
        "BaseType" : "xbrli:shares",
        "ClosestSchemaBuiltinType" : "xs:decimal"
      }[$detailed]
  |}
};

declare %an:nondeterministic function local:concepts($detailed as xs:boolean) as object*{
  local:concept("fac:CommonStockSharesAuthorized","Common Stock Shares (authorized)", $detailed),
  local:concept("fac:CommonStockSharesIssued","Common Stock Shares (issued)", $detailed),
  local:concept("fac:CommonStockSharesOutstanding","Common Stock Shares (outstanding)", $detailed)
};

declare variable $id := "BasicFinancialInformation";
declare variable $report := parse-json(http:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/export/fac.jq?_method=POST").body.content);
declare variable $label := "Basic Financial Information";
declare variable $desc :=
  "This report extends the fundamental accounting concepts report. Documentation about the fundamental accounting concepts defined in this report can be found at http://fundamentalaccountingconcepts.wikispaces.com/home";
declare variable $role := "http://xbrl.io/basic-financial-information";
declare variable $additionalRules :=
  (
    {
        "Id" : "gi_CommonStockSharesOutstanding",
        "Type" : "xbrl28:formula",
        "Label" : "CommonStockSharesOutstanding imputation",
        "DependsOn" : [ "fac:Assets" ],
        "Formula" : "(: workaround for bug: https://github.com/28msec/xbrl-infosetgenerator/issues/7 :)
let $aligned-filter-no-fiscal as object :=
    copy $new := $aligned-filter
    modify (
	if($new.Aspects.\"sec:FiscalPeriodType\") then delete json $new.Aspects.\"sec:FiscalPeriodType\" else (),
    if($new.Aspects.\"sec:FiscalPeriod\") then delete json $new.Aspects.\"sec:FiscalPeriod\" else (),
    if($new.Aspects.\"sec:FiscalYear\") then delete json $new.Aspects.\"sec:FiscalYear\" else ()
  )
  return $new
let $AllEntityCommonStockSharesOutstanding as object* :=
	facts:facts-for-internal((
      \"dei:EntityCommonStockSharesOutstanding\"
    ), $hypercube, $aligned-filter-no-fiscal, $concept-maps, $rules, $cache, $options)
for $facts in (facts:facts-for-internal((
      \"fac:CommonStockSharesOutstanding\", \"fac:Assets\"
    ), $hypercube, $aligned-filter, $concept-maps, $rules, $cache, $options)
  )
let $aligned-period := ( facts:duration-for-fact($facts).End, facts:instant-for-fact($facts), \"forever\")[1]
group by $canonical-filter-string :=
  facts:canonical-grouping-key($facts, ($facts:CONCEPT, $facts:UNIT, $facts:PERIOD))
  , $aligned-period
let $archive as string := distinct-values($facts.$facts:ASPECTS.$facts:ARCHIVE)
let $warnings as string* := ()
let $CommonStockSharesOutstanding as object* := $facts[$$.$facts:ASPECTS.$facts:CONCEPT eq \"fac:CommonStockSharesOutstanding\"]
let $EntityCommonStockSharesOutstanding as object* := $AllEntityCommonStockSharesOutstanding[$$.$facts:ASPECTS.$facts:ARCHIVE eq $archive]
let $Assets as object* := $facts[$$.$facts:ASPECTS.$facts:CONCEPT eq \"fac:Assets\"]
let $warnings := ($warnings, if(count($Assets) gt 1)
                             then if(count(distinct-values($Assets.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept Assets.\"
                                  else \"Cell collision with consistent values for concept Assets.\"
                             else (),
                             if(count($CommonStockSharesOutstanding) gt 1)
                             then if(count(distinct-values($CommonStockSharesOutstanding.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept CommonStockSharesOutstanding.\"
                                  else \"Cell collision with consistent values for concept CommonStockSharesOutstanding.\"
                             else (),
							 if(count($EntityCommonStockSharesOutstanding) gt 1)
                             then if(count(distinct-values($EntityCommonStockSharesOutstanding.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept EntityCommonStockSharesOutstanding.\"
                                  else \"Cell collision with consistent values for concept EntityCommonStockSharesOutstanding.\"
                             else ())
let $CommonStockSharesOutstanding as object? := $CommonStockSharesOutstanding[1]
let $EntityCommonStockSharesOutstanding as object? := $EntityCommonStockSharesOutstanding[1]
let $Assets as object? := $Assets[1]
return
  switch (true)
  case exists($CommonStockSharesOutstanding) return $CommonStockSharesOutstanding
  case (exists($EntityCommonStockSharesOutstanding) and exists($Assets))
  return
    let $computed-value := rules:decimal-value($EntityCommonStockSharesOutstanding)
    let $audit-trail-message as string* :=
      rules:fact-trail({\"Aspects\": { \"xbrl:Unit\" : \"xbrli:shares\", \"xbrl:Concept\" : \"fac:CommonStockSharesOutstanding\" }, Value: $computed-value }) || \" = \" ||
         rules:fact-trail($EntityCommonStockSharesOutstanding, \"EntityCommonStockSharesOutstanding\") || \" [\" || (facts:duration-for-fact($EntityCommonStockSharesOutstanding).End, facts:instant-for-fact($EntityCommonStockSharesOutstanding)) || \"]\"
    let $audit-trail-message as string* := ($audit-trail-message, $warnings)
    let $source-facts as object* := ($EntityCommonStockSharesOutstanding)
    return
		copy $newfact :=
		  rules:create-computed-fact(
			$Assets,
			\"fac:CommonStockSharesOutstanding\",
			$computed-value,
			$rule,
			$audit-trail-message,
			$source-facts,
			$options)
		modify (
			if($newfact(\"Unit\"))
			then replace value of json $newfact(\"Unit\") with \"xbrli:shares\"
			else insert json { \"Unit\": \"xbrli:shares\" } into $newfact,
			if($newfact.$facts:ASPECTS.$facts:UNIT)
			then replace value of json $newfact.$facts:ASPECTS.$facts:UNIT with \"xbrli:shares\"
			else insert json { $facts:UNIT: \"xbrli:shares\" } into $newfact.$facts:ASPECTS
		  )
		return $newfact
  default return ()",
        "ComputableConcepts" : [ "fac:CommonStockSharesOutstanding" ]
      },
      {
          "Id" : "decimals_inf",
          "Type" : "xbrl28:formula",
          "Label" : "Align INF decimals",
          "Formula" : "for $fact in facts:facts-for-internal($concepts, $hypercube, $aligned-filter, $concept-maps, $rules, $cache, $options)
                             return
                               if(string($fact.Decimals) eq \"INF\")
                               then
                                 let $concept := $fact.Aspects.\"xbrl:Concept\"
                                 let $audit-trail :=
                                   {
                                     \"Type\" : \"xbrl28:decimals-inf-alignment\",
                                     \"Label\" : \"Aligning Decimals to 3 instead of INF\",
                                     \"Message\" : $concept || \" = 0\",
                                     \"Data\" : {
                                       \"OutputConcept\" : $concept
                                     }
                                   }
                                 return
                                 copy $f := $fact
                                 modify (
                                     replace value of json $f.Decimals with 3,
                                     if(exists($f.AuditTrails))
                                     then append json $audit-trail into $f.AuditTrails
                                     else insert json { AuditTrails: [ $audit-trail ] } into $f
                                 )
                                 return $f
                               else $fact"
        }
  );

(: Presentation :)
let $presentation := $report.Networks[][$$.ShortName eq "Presentation"]
let $genInfo := $presentation.Trees[][$$.Name eq "fac:FundamentalAccountingConceptsLineItems"].To[][$$.Name eq "fac:FundamentalAccountingConceptsHierarchy"].To[][$$.Name eq "fac:GeneralInformationHierarchy"]
return
  {
    replace value of json $presentation.LinkRole with $role;
    replace value of json $genInfo.To with [ ( $genInfo.To[], local:concepts(false) ) ];
  }

(: Hypercube :)
let $concepts := $report.Hypercubes."xbrl28:ImpliedTable".Aspects."xbrl:Concept"
return
  replace value of json $concepts.Members with [ $concepts.Members[], local:concepts(false) ];

(: ConceptMap :)
let $conceptMap := $report.Networks[][$$.ShortName eq "ConceptMap"]
let $mappings as object* :=
    (
      {
        "Name" : "fac:CommonStockSharesAuthorized",
        "Label" : "Common Stock Shares (authorized)",
        "To" : [
          {
            "Name" : "us-gaap:CommonStockSharesAuthorized"
          }
        ]
      },
      {
        "Name" : "fac:CommonStockSharesIssued",
        "Label" : "Common Stock Shares (issued)",
        "To" : [
          {
            "Name" : "us-gaap:CommonStockSharesIssued"
          }
        ]
      },
      {
        "Name" : "fac:CommonStockSharesOutstanding",
        "Label" : "Common Stock Shares (outstanding)",
        "To" : [
          {
            "Name" : "dei:EntityCommonStockSharesOutstanding",
            "Order" : 1
          },
          {
            "Name" : "us-gaap:CommonStockSharesOutstanding",
            "Order" : 2
          }
        ]
      }
  )
return
  {
    replace value of json $conceptMap.LinkRole with $role;
    replace value of json $conceptMap.Trees with [ $conceptMap.Trees[], $mappings ];
  }

(: DefinitionModel :)
replace value of json $report.DefinitionModels[][1].Breakdowns.y[][1].BreakdownTrees[][1].LinkRole with $role;

(: remove default_zero rule :)
replace value of json $report.Rules with [ $report.Rules[][$$.Id ne "default_zero"] ];

export:cleanup(
    { "_id": $id,
      Archive: null,
      Label: $label,
      Description: $desc,
      Prefix: "fac",
      Role: $role,
      Owner: "charlie@prudena.com",
      LastModified: string(current-dateTime()),
      ACL: [],
      Networks: $report.Networks,
      Hypercubes: $report.Hypercubes,
      DefinitionModels: $report.DefinitionModels,
      Rules: [ for $rule in ($report.Rules[], $additionalRules)
               order by $rule.ComputableConcepts[][1], $rule.Id descending empty least
               return $rule ],
      Concepts: [ $report.Concepts[], local:concepts(true) ],
      Filters: {
        cik: [  ],
        tag: [ "DOW30" ],
        fiscalYear : [ 2013 ],
        fiscalPeriod : [ "FY" ],
        sic : [  ]
      }
    }
)

jsoniq version "1.0";
import module namespace http = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace export = "http://apps.28.io/reports-export";

declare %an:nondeterministic function local:concepts() as object*{
  {
    "Name" : "fac:CommonStockSharesAuthorized",
    "Label" : "Common Stock Shares (authorized)"
  },
  {
    "Name" : "fac:CommonStockSharesIssued",
    "Label" : "Common Stock Shares (issued)"
  },
  {
    "Name" : "fac:CommonStockSharesOutstanding",
    "Label" : "Common Stock Shares (outstanding)"
  }
};

declare variable $id := "BasicFinancialInformation";
declare variable $report := parse-json(http:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/export/fac.jq?_method=POST").body.content);
declare variable $label := "Basic Financial Information";
declare variable $desc :=
  "This report extends the fundamental accounting concepts report. Documentation about the fundamental accounting concepts defined in this report can be found at http://fundamentalaccountingconcepts.wikispaces.com/home";
declare variable $role := "http://xbrl.io/basic-financial-information";
declare variable $additionalRules :=
  (
    {
        "Id" : "gi_CommonStockSharesOutstanding",
        "Type" : "xbrl28:formula",
        "Label" : "CommonStockSharesOutstanding imputation",
        "DependsOn" : [ "fac:Assets" ],
        "Formula" : "(: workaround for bug: https://github.com/28msec/xbrl-infosetgenerator/issues/7 :)
let $aligned-filter-no-fiscal as object :=
    copy $new := $aligned-filter
    modify (
	if($new.Aspects.\"sec:FiscalPeriodType\") then delete json $new.Aspects.\"sec:FiscalPeriodType\" else (),
    if($new.Aspects.\"sec:FiscalPeriod\") then delete json $new.Aspects.\"sec:FiscalPeriod\" else (),
    if($new.Aspects.\"sec:FiscalYear\") then delete json $new.Aspects.\"sec:FiscalYear\" else ()
  )
  return $new
let $AllEntityCommonStockSharesOutstanding as object* :=
	facts:facts-for-internal((
      \"dei:EntityCommonStockSharesOutstanding\"
    ), $hypercube, $aligned-filter-no-fiscal, $concept-maps, $rules, $cache, $options)
for $facts in (facts:facts-for-internal((
      \"fac:CommonStockSharesOutstanding\", \"fac:Assets\"
    ), $hypercube, $aligned-filter, $concept-maps, $rules, $cache, $options)
  )
let $aligned-period := ( facts:duration-for-fact($facts).End, facts:instant-for-fact($facts), \"forever\")[1]
group by $canonical-filter-string :=
  facts:canonical-grouping-key($facts, ($facts:CONCEPT, $facts:UNIT, $facts:PERIOD))
  , $aligned-period
let $archive as string := distinct-values($facts.$facts:ASPECTS.$facts:ARCHIVE)
let $warnings as string* := ()
let $CommonStockSharesOutstanding as object* := $facts[$$.$facts:ASPECTS.$facts:CONCEPT eq \"fac:CommonStockSharesOutstanding\"]
let $EntityCommonStockSharesOutstanding as object* := $AllEntityCommonStockSharesOutstanding[$$.$facts:ASPECTS.$facts:ARCHIVE eq $archive]
let $Assets as object* := $facts[$$.$facts:ASPECTS.$facts:CONCEPT eq \"fac:Assets\"]
let $warnings := ($warnings, if(count($Assets) gt 1)
                             then if(count(distinct-values($Assets.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept Assets.\"
                                  else \"Cell collision with consistent values for concept Assets.\"
                             else (),
                             if(count($CommonStockSharesOutstanding) gt 1)
                             then if(count(distinct-values($CommonStockSharesOutstanding.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept CommonStockSharesOutstanding.\"
                                  else \"Cell collision with consistent values for concept CommonStockSharesOutstanding.\"
                             else (),
							 if(count($EntityCommonStockSharesOutstanding) gt 1)
                             then if(count(distinct-values($EntityCommonStockSharesOutstanding.Value)) gt 1)
                                  then \"Cell collision with conflicting values for concept EntityCommonStockSharesOutstanding.\"
                                  else \"Cell collision with consistent values for concept EntityCommonStockSharesOutstanding.\"
                             else ())
let $CommonStockSharesOutstanding as object? := $CommonStockSharesOutstanding[1]
let $EntityCommonStockSharesOutstanding as object? := $EntityCommonStockSharesOutstanding[1]
let $Assets as object? := $Assets[1]
return
  switch (true)
  case exists($CommonStockSharesOutstanding) return $CommonStockSharesOutstanding
  case (exists($EntityCommonStockSharesOutstanding) and exists($Assets))
  return
    let $computed-value := rules:decimal-value($EntityCommonStockSharesOutstanding)
    let $audit-trail-message as string* :=
      rules:fact-trail({\"Aspects\": { \"xbrl:Unit\" : \"xbrli:shares\", \"xbrl:Concept\" : \"fac:CommonStockSharesOutstanding\" }, Value: $computed-value }) || \" = \" ||
         rules:fact-trail($EntityCommonStockSharesOutstanding, \"EntityCommonStockSharesOutstanding\") || \" [\" || (facts:duration-for-fact($EntityCommonStockSharesOutstanding).End, facts:instant-for-fact($EntityCommonStockSharesOutstanding)) || \"]\"
    let $audit-trail-message as string* := ($audit-trail-message, $warnings)
    let $source-facts as object* := ($EntityCommonStockSharesOutstanding)
    return
		copy $newfact :=
		  rules:create-computed-fact(
			$Assets,
			\"fac:CommonStockSharesOutstanding\",
			$computed-value,
			$rule,
			$audit-trail-message,
			$source-facts,
			$options)
		modify (
			if($newfact(\"Unit\"))
			then replace value of json $newfact(\"Unit\") with \"xbrli:shares\"
			else insert json { \"Unit\": \"xbrli:shares\" } into $newfact,
			if($newfact.$facts:ASPECTS.$facts:UNIT)
			then replace value of json $newfact.$facts:ASPECTS.$facts:UNIT with \"xbrli:shares\"
			else insert json { $facts:UNIT: \"xbrli:shares\" } into $newfact.$facts:ASPECTS
		  )
		return $newfact
  default return ()",
        "ComputableConcepts" : [ "fac:CommonStockSharesOutstanding" ]
      },
      {
          "Id" : "decimals_inf",
          "Type" : "xbrl28:formula",
          "Label" : "Align INF decimals",
          "Formula" : "for $fact in facts:facts-for-internal($concepts, $hypercube, $aligned-filter, $concept-maps, $rules, $cache, $options)
                             return
                               if(string($fact.Decimals) eq \"INF\")
                               then
                                 let $concept := $fact.Aspects.\"xbrl:Concept\"
                                 let $audit-trail :=
                                   {
                                     \"Type\" : \"xbrl28:decimals-inf-alignment\",
                                     \"Label\" : \"Aligning Decimals to 3 instead of INF\",
                                     \"Message\" : $concept || \" = 0\",
                                     \"Data\" : {
                                       \"OutputConcept\" : $concept
                                     }
                                   }
                                 return
                                 copy $f := $fact
                                 modify (
                                     replace value of json $f.Decimals with 3,
                                     if(exists($f.AuditTrails))
                                     then append json $audit-trail into $f.AuditTrails
                                     else insert json { AuditTrails: [ $audit-trail ] } into $f
                                 )
                                 return $f
                               else $fact"
        }
  );

(: Presentation :)
let $presentation := $report.Networks[][$$.ShortName eq "Presentation"]
let $genInfo := $presentation.Trees[][$$.Name eq "fac:FundamentalAccountingConceptsLineItems"].To[][$$.Name eq "fac:FundamentalAccountingConceptsHierarchy"].To[][$$.Name eq "fac:GeneralInformationHierarchy"]
return
  {
    replace value of json $presentation.LinkRole with $role;
    replace value of json $genInfo.To with [ ( $genInfo.To[], local:concepts() ) ];
  }

(: Hypercube :)
let $concepts := $report.Hypercubes."xbrl28:ImpliedTable".Aspects."xbrl:Concept"
return
  replace value of json $concepts.Members with [ $concepts.Members[], local:concepts() ];

(: ConceptMap :)
let $conceptMap := $report.Networks[][$$.ShortName eq "ConceptMap"]
let $mappings as object* :=
    (
      {
        "Name" : "fac:CommonStockSharesAuthorized",
        "Label" : "Common Stock Shares (authorized)",
        "To" : [
          {
            "Name" : "us-gaap:CommonStockSharesAuthorized"
          }
        ]
      },
      {
        "Name" : "fac:CommonStockSharesIssued",
        "Label" : "Common Stock Shares (issued)",
        "To" : [
          {
            "Name" : "us-gaap:CommonStockSharesIssued"
          }
        ]
      },
      {
        "Name" : "fac:CommonStockSharesOutstanding",
        "Label" : "Common Stock Shares (outstanding)",
        "To" : [
          {
            "Name" : "dei:EntityCommonStockSharesOutstanding",
            "Order" : 1
          },
          {
            "Name" : "us-gaap:CommonStockSharesOutstanding",
            "Order" : 2
          }
        ]
      }
  )
return
  {
    replace value of json $conceptMap.LinkRole with $role;
    replace value of json $conceptMap.Trees with [ $conceptMap.Trees[], $mappings ];
  }

(: DefinitionModel :)
replace value of json $report.DefinitionModels[][1].Breakdowns.y[][1].BreakdownTrees[][1].LinkRole with $role;

(: remove default_zero rule :)
replace value of json $report.Rules with [ $report.Rules[][$$.Id ne "default_zero"] ];

export:cleanup(
    { "_id": $id,
      Archive: null,
      Label: $label,
      Description: $desc,
      Prefix: "fac",
      Role: $role,
      Owner: "charlie@prudena.com",
      LastModified: string(current-dateTime()),
      ACL: [],
      Networks: $report.Networks,
      Hypercubes: $report.Hypercubes,
      DefinitionModels: $report.DefinitionModels,
      Rules: [ for $rule in ($report.Rules[], $additionalRules)
               order by $rule.ComputableConcepts[][1], $rule.Id descending empty least
               return $rule ],
      Concepts: [ $report.Concepts[], local:concepts() ],
      Filters: {
        cik: [  ],
        tag: [ "DOW30" ],
        fiscalYear : [ 2013 ],
        fiscalPeriod : [ "FY" ],
        sic : [  ]
      }
    }
)


Fixed missing import

Fixed missing import

import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace session = "http://apps.28.io/session";
import module namespace user = "http://apps.28.io/user";
import module namespace reports = "http://apps.28.io/reports";
import module namespace api = "http://apps.28.io/api";

import schema namespace mongos = "http://www.28msec.com/modules/mongodb/types";

declare  %rest:body-text                        variable $body                as string  external;
declare  %rest:case-insensitive                 variable $validation-only     as string? external := "false"; (: backward compatibility :)
declare  %rest:case-insensitive                 variable $public-read         as string? external := "false"; (: backward compatibility :)
declare  %rest:case-insensitive                 variable $private             as string? external := "false"; (: backward compatibility :)

try {

    (: ### INIT PARAMS :)
    let $validation-only as boolean := api:boolean($validation-only)
    let $public-read as boolean := api:boolean($public-read)
    let $private as boolean := api:boolean($private)

    let $authenticated-user := user:get-existing-by-id(session:validate())
    let $report as object? :=
        if(exists($body))
        then
            let $r := parse-json($body)
            return
                switch(true)
                case ($private) return reports:make-private-read($r)
                case ($public-read) return reports:make-public-read($r)
                default return $r
        else ()
    let $id as string? :=
        if(exists($report))
        then $report."_id"
        else ()
    let $existing-report as object? :=
        if(exists($id))
        then find("reports", { "_id" :  $id })
        else ()

    return
        switch (true)

        (: ### AUTHENTICATION :)
        case not(session:valid()) return {
            response:status-code(401);
            session:error("Unauthorized: Login required", "json")
        }

        (: ### AUTHORIZATION :)
        (: user authorized to validate report? :)
        case ($validation-only and not(session:valid("reports_validate")))

        (: user authorized to update report? :)
        case (exists($id) and exists($existing-report) and
            (not(session:valid("reports_edit")) or not(reports:has-report-access-permission($existing-report, $authenticated-user.email, "WRITE"))))

        (: user authorized to create a report? :)
        case (exists($id) and empty($existing-report) and
              not(session:valid("reports_create")))
        return {
            response:status-code(403);
            session:error("Forbidden: You are not authorized to access the requested resource", "json")
        }

        (: ### BAD REQUEST HANDLING :)
        case (empty($report))
        return {
            response:status-code(400);
            session:error("mandatory request content missing", "json")
        }

        case ((empty($id) or $id eq "") and exists($report))
        return {
            response:status-code(400);
            session:error("report: mandatory _id field missing or empty in report object", "json")
        }

        (: ### MAIN WORK :)
        (: report validation :)
        case ($validation-only)
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            [
                reports:validate($report)
            ]
        }

        (: update existing report :)
        case (exists($existing-report))
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            if (is-available-collection("reportcache"))
            then db:delete(find("reportcache", { "_id" : { "$regex" : mongos:regex("^" || $existing-report."_id" || ".*" ) }}));
            else create("reportcache");

            let $report := reports:validate-and-update-report-properties($report, $existing-report, $authenticated-user.email)
            return
                {
                    db:edit($existing-report,$report);
                    $report
                }
        }

        (: create new report :)
        default
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            if (is-available-collection("reportcache"))
            then db:delete(find("reportcache", { "_id" : { "$regex" : mongos:regex("^" || $existing-report."_id" || ".*" ) }}));
            else create("reportcache");

            let $report := reports:validate-and-update-report-properties($report, $existing-report, $authenticated-user.email)
            return
                {
                    db:insert("reports", $report);
                    $report
                }
        }
} catch reports:UNAUTHORIZED {
    {
        response:status-code(403);
        session:error("Access denied: " || $err:description, "json")
    }
} catch reports:CONFLICT {
    {
        response:status-code(409);
        session:error("Conflict: " || $err:description, "json")
    }
} catch session:expired {
    {
        response:status-code(401);
        session:error("Unauthorized: Login required (session expired)", "json")
    }
} catch api:missing-parameter {
    if(exists($err:value) and $err:value.parameter eq "token")
    then
    {
        response:status-code(401);
        session:error("Unauthorized: Login required (token missing)", "json")
    }
    else
    {
        response:status-code(400);
        session:error($err:description, "json")
    }
}

import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace session = "http://apps.28.io/session";
import module namespace user = "http://apps.28.io/user";
import module namespace reports = "http://apps.28.io/reports";

import schema namespace mongos = "http://www.28msec.com/modules/mongodb/types";

declare namespace api = "http://apps.28.io/api";

declare  %rest:body-text                        variable $body                as string  external;
declare  %rest:case-insensitive                 variable $validation-only     as string? external := "false"; (: backward compatibility :)
declare  %rest:case-insensitive                 variable $public-read         as string? external := "false"; (: backward compatibility :)
declare  %rest:case-insensitive                 variable $private             as string? external := "false"; (: backward compatibility :)

try {

    (: ### INIT PARAMS :)
    let $validation-only as boolean := api:boolean($validation-only)
    let $public-read as boolean := api:boolean($public-read)
    let $private as boolean := api:boolean($private)

    let $authenticated-user := user:get-existing-by-id(session:validate())
    let $report as object? :=
        if(exists($body))
        then
            let $r := parse-json($body)
            return
                switch(true)
                case ($private) return reports:make-private-read($r)
                case ($public-read) return reports:make-public-read($r)
                default return $r
        else ()
    let $id as string? :=
        if(exists($report))
        then $report."_id"
        else ()
    let $existing-report as object? :=
        if(exists($id))
        then find("reports", { "_id" :  $id })
        else ()

    return
        switch (true)

        (: ### AUTHENTICATION :)
        case not(session:valid()) return {
            response:status-code(401);
            session:error("Unauthorized: Login required", "json")
        }

        (: ### AUTHORIZATION :)
        (: user authorized to validate report? :)
        case ($validation-only and not(session:valid("reports_validate")))

        (: user authorized to update report? :)
        case (exists($id) and exists($existing-report) and
            (not(session:valid("reports_edit")) or not(reports:has-report-access-permission($existing-report, $authenticated-user.email, "WRITE"))))

        (: user authorized to create a report? :)
        case (exists($id) and empty($existing-report) and
              not(session:valid("reports_create")))
        return {
            response:status-code(403);
            session:error("Forbidden: You are not authorized to access the requested resource", "json")
        }

        (: ### BAD REQUEST HANDLING :)
        case (empty($report))
        return {
            response:status-code(400);
            session:error("mandatory request content missing", "json")
        }

        case ((empty($id) or $id eq "") and exists($report))
        return {
            response:status-code(400);
            session:error("report: mandatory _id field missing or empty in report object", "json")
        }

        (: ### MAIN WORK :)
        (: report validation :)
        case ($validation-only)
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            [
                reports:validate($report)
            ]
        }

        (: update existing report :)
        case (exists($existing-report))
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            if (is-available-collection("reportcache"))
            then db:delete(find("reportcache", { "_id" : { "$regex" : mongos:regex("^" || $existing-report."_id" || ".*" ) }}));
            else create("reportcache");

            let $report := reports:validate-and-update-report-properties($report, $existing-report, $authenticated-user.email)
            return
                {
                    db:edit($existing-report,$report);
                    $report
                }
        }

        (: create new report :)
        default
        return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});

            if (is-available-collection("reportcache"))
            then db:delete(find("reportcache", { "_id" : { "$regex" : mongos:regex("^" || $existing-report."_id" || ".*" ) }}));
            else create("reportcache");

            let $report := reports:validate-and-update-report-properties($report, $existing-report, $authenticated-user.email)
            return
                {
                    db:insert("reports", $report);
                    $report
                }
        }
} catch reports:UNAUTHORIZED {
    {
        response:status-code(403);
        session:error("Access denied: " || $err:description, "json")
    }
} catch reports:CONFLICT {
    {
        response:status-code(409);
        session:error("Conflict: " || $err:description, "json")
    }
} catch session:expired {
    {
        response:status-code(401);
        session:error("Unauthorized: Login required (session expired)", "json")
    }
} catch api:missing-parameter {
    if(exists($err:value) and $err:value.parameter eq "token")
    then
    {
        response:status-code(401);
        session:error("Unauthorized: Login required (token missing)", "json")
    }
    else
    {
        response:status-code(400);
        session:error($err:description, "json")
    }
}