More idiomatic `top`

More idiomatic `top`

module {
    name: "prelude",
    description: "Flow control services",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    }
};

########################################################################
# Stolen from SNOBOL: ABORT and FENCE
########################################################################

# Breaks out the current filter
# For constructs like:
#   try (A | possible abortion | B)
def abort: #:: a => !
    error("~!~")
;

# One way pass. Usage:
#   try (A | fence | B)
def fence: #:: a => a!
    (. , abort)
;

# Catch helpers. Usage:
#   try (...) catch _abort_(result)
#   . as $_ | try (A | possible abortion | B) catch _abort_($_)
def _abort_(result): #:: string| => a!
    if . == "~!~" then result else error(.) end
;
#   try (...) catch _abort_
def _abort_: #:: string| => @!
    if . == "~!~" then empty else error(.) end
;

########################################################################
# Variations on some JQ primitives
########################################################################

# Reverse of `isempty`
def nonempty(stream): #:: a|(a->*b) => boolean
#   (label $xit | stream | (true , break$xit)) // false
    first((stream|true) , false)
;

# all(stream; .)
def every(stream): #:: a|(a->*boolean) => boolean
    isempty(stream and empty)
;

# any(stream; .)
def some(stream): #:: a|(a->*boolean) => boolean
    nonempty(stream or empty)
;

# Enhanced `select`
def select(predicate; action): #:: a|(a->?boolean;a->*b) => *b
    if predicate then action else empty end
;

# Do `stream` produces?
def top(stream): #:: a|(a->*b) => ?a
#   if nonempty(stream) then . else empty end
    first(. as $t | stream | $t)
;

# Complement for `top`
def bot(stream): #:: a|(a->*b) => ?a
    if isempty(stream) then . else empty end
;

# Complement of `select`
def reject(predicate): #:: a|(a->?boolean) => ?a
    if predicate then empty else . end
;

# Strong select
def guard(predicate): #:: a|(a->?boolean) => a!
    if predicate//null then . else abort end
;

########################################################################
# Assertions
########################################################################

def assert(predicate; $location; $message): #:: a|(a->boolean;LOCATION;string) => a!
    if predicate//null then .
    else
        $location
        | error("Assertion failed: "+$message+", file \(.file), line \(.line)")
    end
;

def assert(predicate; $message): #:: a|(a->boolean;string) => a!
    if predicate//null then .
    else
        error("Assertion failed: "+$message)
    end
;

########################################################################
# Recursion schemata
########################################################################

# Additions to builtin recurse, while, until...

# Generate ℕ
def seq: #:: => *number
    0|recurse(.+1) # seq(1)
;
def seq($a): #:: (number) => *number
    $a|recurse(.+1) # seq($a)
;
def seq($a; $d): #:: (number;$number) => *number
    $a|recurse(.+$d) # seq($a; $d)
;

#
# Iteration
#

# f|recurse(f)
def chain(f): #:: a|(a->*a) => *a
    def r: ., (f|r);
    f|r
;

# g⁰ g¹ g² g³ g⁴ g⁵ g⁶ g⁷ g⁸ g⁹…
# Breadth-first search
def iterate(init; g): #:: a|(a->*b;b->*b) => *b
    def r:
         .[] , (map(g) | select(length > 0; r))
    ;
    [init] | r
;
def iterate(g): #:: a|(a->*a) => +a
    iterate(.; g)
;

#
# Fold/unfold family of patterns
#

#def fold(f; $a; g): #:: x|([a,b]->a;a;x->*b) => a
#    reduce g as $b
#        ($a; [.,$b]|f)
#;

#def scan(f; g): #:: x|([a,b]->a;x->*b) => *a
#    foreach g as $b
#        (.; [.,$b]|f; .)
#;
#def scan(f; $a; g): #:: x|([a,b]->a;a;x->*b) => *a
#    $a|scan(f; g)
#;

# Fold _opposite_
def unfold(f): #:: a|(a->[b,a]) => *b
    def r: f as [$b,$a] | $b , ($a|r);
    r
;

########################################################################
# Better versions for builtins
########################################################################

# Renamed map_values
def mapval(f): #:: <a>|(a->*b) => <b>
    .[] |= f
;

# map and add in one pass (catenable: x+x)
def mapadd(f): #:: <a>|(a->*b) => ?b
    reduce (.[] | f) as $x
        (null; . + $x)
    // empty
;

# Split string, map characters (length one strings) and concat results
def mapstr(f): #:: string|(char->*char) => string
    reduce ((./"")[] | f) as $s
        (""; . + $s)
;

# Variation on `with_entries`
#
# PAIR: {"name":string, "value":value}
#
def mapobj(filter): #:: object|(PAIR->PAIR) => object
    reduce (keys_unsorted[] as $k
            | {name: $k, value: .[$k]}
            | filter
            | {(.name): .value})
        as $pair ({}; . + $pair)
;

# Does not diverge with empty parameter
def repeat(g): #:: a|(a->*b) => *b
    def r: g , r;
    top(g) | r
;

#def any(generator; condition): false==isempty(generator | condition or empty);
#def any: false==isempty(.[] or empty);
#def any(condition): false==isempty(.[] | condition or empty);
#def all(generator; condition): isempty(generator | condition and empty);
#def all: isempty(.[] and empty);
#def all(condition): isempty(.[] | condition and empty);
#def range($init; $upto; $by):
#    select($by != 0)
#    | label $xit
#    | if $by > 0
#      then $init|recurse(.+$by) | if . >= $upto then break$xit end)
#      else $init|recurse(.+$by) | if . <= $upto then break$xit end)
#      end
#    end
#;

# Generic swap
#def swap(p; q): p as $t | p=q | q=$t;

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

module {
    name: "prelude",
    description: "Flow control services",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    }
};

########################################################################
# Stolen from SNOBOL: ABORT and FENCE
########################################################################

# Breaks out the current filter
# For constructs like:
#   try (A | possible abortion | B)
def abort: #:: a => !
    error("~!~")
;

# One way pass. Usage:
#   try (A | fence | B)
def fence: #:: a => a!
    (. , abort)
;

# Catch helpers. Usage:
#   try (...) catch _abort_(result)
#   . as $_ | try (A | possible abortion | B) catch _abort_($_)
def _abort_(result): #:: string| => a!
    if . == "~!~" then result else error(.) end
;
#   try (...) catch _abort_
def _abort_: #:: string| => @!
    if . == "~!~" then empty else error(.) end
;

########################################################################
# Variations on some JQ primitives
########################################################################

# Reverse of `isempty`
def nonempty(stream): #:: a|(a->*b) => boolean
    (label $xit | stream | (true , break$xit))
    // false
;

# all(stream; .)
def every(stream): #:: a|(a->*boolean) => boolean
    isempty(stream and empty)
;

# any(stream; .)
def some(stream): #:: a|(a->*boolean) => boolean
    nonempty(stream or empty)
;

# Enhanced `select`
def select(predicate; action): #:: a|(a->?boolean;a->*b) => *b
    if predicate then action else empty end
;

# Do `stream` produces?
def top(stream): #:: a|(a->*b) => ?a
    if nonempty(stream) then . else empty end
;

# Complement for `top`
def bot(stream): #:: a|(a->*b) => ?a
    if isempty(stream) then . else empty end
;

# Complement of `select`
def reject(predicate): #:: a|(a->?boolean) => ?a
    if predicate then empty else . end
;

# Strong select
def guard(predicate): #:: a|(a->?boolean) => a!
    if predicate//null then . else abort end
;

########################################################################
# Assertions
########################################################################

def assert(predicate; $location; $message): #:: a|(a->boolean;LOCATION;string) => a!
    if predicate//null then .
    else
        $location
        | error("Assertion failed: "+$message+", file \(.file), line \(.line)")
    end
;

def assert(predicate; $message): #:: a|(a->boolean;string) => a!
    if predicate//null then .
    else
        error("Assertion failed: "+$message)
    end
;

########################################################################
# Recursion schemata
########################################################################

# Additions to builtin recurse, while, until...

# Generate ℕ
def seq: #:: => *number
    0|recurse(.+1) # seq(1)
;
def seq($a): #:: (number) => *number
    $a|recurse(.+1) # seq($a)
;
def seq($a; $d): #:: (number;$number) => *number
    $a|recurse(.+$d) # seq($a; $d)
;

#
# Iteration
#

# f|recurse(f)
def chain(f): #:: a|(a->*a) => *a
    def r: ., (f|r);
    f|r
;

# g⁰ g¹ g² g³ g⁴ g⁵ g⁶ g⁷ g⁸ g⁹…
# Breadth-first search
def iterate(init; g): #:: a|(a->*b;b->*b) => *b
    def r:
         .[] , (map(g) | select(length > 0; r))
    ;
    [init] | r
;
def iterate(g): #:: a|(a->*a) => +a
    iterate(.; g)
;

#
# Fold/unfold family of patterns
#

#def fold(f; $a; g): #:: x|([a,b]->a;a;x->*b) => a
#    reduce g as $b
#        ($a; [.,$b]|f)
#;

#def scan(f; g): #:: x|([a,b]->a;x->*b) => *a
#    foreach g as $b
#        (.; [.,$b]|f; .)
#;
#def scan(f; $a; g): #:: x|([a,b]->a;a;x->*b) => *a
#    $a|scan(f; g)
#;

# Fold _opposite_
def unfold(f): #:: a|(a->[b,a]) => *b
    def r: f as [$b,$a] | $b , ($a|r);
    r
;

########################################################################
# Better versions for builtins
########################################################################

# Renamed map_values
def mapval(f): #:: <a>|(a->*b) => <b>
    .[] |= f
;

# map and add in one pass (catenable: x+x)
def mapadd(f): #:: <a>|(a->*b) => ?b
    reduce (.[] | f) as $x
        (null; . + $x)
    // empty
;

# Split string, map characters (length one strings) and concat results
def mapstr(f): #:: string|(char->*char) => string
    reduce ((./"")[] | f) as $s
        (""; . + $s)
;

# Variation on `with_entries`
#
# PAIR: {"name":string, "value":value}
#
def mapobj(filter): #:: object|(PAIR->PAIR) => object
    reduce (keys_unsorted[] as $k
            | {name: $k, value: .[$k]}
            | filter
            | {(.name): .value})
        as $pair ({}; . + $pair)
;

# Does not diverge with empty parameter
def repeat(g): #:: a|(a->*b) => *b
    def r: g , r;
    top(g) | r
;

#def any(generator; condition): false==isempty(generator | condition or empty);
#def any: false==isempty(.[] or empty);
#def any(condition): false==isempty(.[] | condition or empty);
#def all(generator; condition): isempty(generator | condition and empty);
#def all: isempty(.[] and empty);
#def all(condition): isempty(.[] | condition and empty);
#def range($init; $upto; $by):
#    select($by != 0)
#    | label $xit
#    | if $by > 0
#      then $init|recurse(.+$by) | if . >= $upto then break$xit end)
#      else $init|recurse(.+$by) | if . <= $upto then break$xit end)
#      end
#    end
#;

# Generic swap
#def swap(p; q): p as $t | p=q | q=$t;

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


fixed report-elements.jq

fixed report-elements.jq

import module namespace companies = "http://xbrl.io/modules/bizql/profiles/sec/companies";
import module namespace sec-fiscal = "http://xbrl.io/modules/bizql/profiles/sec/fiscal/core";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace session = "http://apps.28.io/session";
import module namespace archives = "http://xbrl.io/modules/bizql/archives";
import module namespace entities = "http://xbrl.io/modules/bizql/entities";

import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace credentials = "http://www.28msec.com/modules/credentials";

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

declare namespace concepts = "http://www.28msec.com/modules/bizql/concepts";

declare function local:to-csv($concepts as item*, $onlyNames as boolean) as string
{
    if ($onlyNames)
    then
        string-join(("Name", $concepts), "
")
    else
        string-join(csv:serialize($concepts, { serialize-null-as : "" }))
};

declare function local:to-xml($concepts as item*, $onlyNames as boolean) as element()*
{
    for $c in $concepts
    return
        <ReportElement>{
             if ($onlyNames)
             then
                <Name>{$c}</Name>
             else (
                <Name>{$c.Name}</Name>,
                <Component>{$c.Component}</Component>,
                <Archive>{$c.Archive}</Archive>
             )
        }</ReportElement>
};

declare function local:filings(
    $ciks as string*,
    $tags as string*,
    $tickers as string*,
    $sics as string*,
    $fp as string*,
    $fy as string*) as object*
{
    let $entities := (
        companies:companies($ciks),
        companies:companies-for-tags($tags),
        companies:companies-for-tickers($tickers),
        companies:companies-for-SIC($sics)
    )
    for $entity in $entities
    for $fy in distinct-values(
                for $fy in $fy
                return
                    switch ($fy)
                    case "LATEST" return
                        for $p in $fp
                        return
                            if ($p eq "FY")
                            then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                            else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
                    case "ALL" return (2010 to 2015) (: temporary solution until next release :)
                    default return $fy
                )
    for $fp in $fp
    return sec-fiscal:filings-for-entities-and-fiscal-periods-and-years($entity, $fp, $fy ! ($$ cast as integer))
};

declare function local:concepts-for-archives($aids as string*) as object*
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("concepts:CONNECTION-FAILED"), $err:description)
        }
    return
        mongo:find($conn, "concepts",
        {
            "Archive": { "$in" : [ $aids ] }
        })
};

let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y
                            else ()
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then ("Q1", "Q2", "Q3", "Q4", "FY")
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $archives    := (
                        local:filings($ciks, $tags, $tickers, $sics, $fiscalPeriods, $fiscalYears),
                        archives:archives($aids)
                    )
let $entities    := entities:entities($archives.Entity)
let $onlyNames   := let $o := request:param-values("onlyNames")[1] return if (exists($o)) then ($o cast as boolean) else false
return
    if (session:only-dow30($entities) or session:valid())
        then {
            let $concepts := let $concepts := local:concepts-for-archives($archives._id)
                             return if ($onlyNames) then distinct-values($concepts.Name) else $concepts
            return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                (session:comment("xml"),
                <ReportElements>{
                    local:to-xml($concepts, $onlyNames)
                }</ReportElements>)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=facts.csv");
                local:to-csv($concepts, $onlyNames)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                local:to-csv($concepts, $onlyNames)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    session:comment("json"),
                    { ReportElements : [ $concepts ] }
                |}
            }
        }
        else {
            response:status-code(401);
            session:error("accessing filings of an entity that is not in the DOW30", $format)
        }

import module namespace companies = "http://xbrl.io/modules/bizql/profiles/sec/companies";
import module namespace sec-fiscal = "http://xbrl.io/modules/bizql/profiles/sec/fiscal/core";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace session = "http://apps.28.io/session";
import module namespace archives = "http://xbrl.io/modules/bizql/archives";
import module namespace entities = "http://xbrl.io/modules/bizql/entities";

import module namespace mongo = "http://www.28msec.com/modules/mongodb";
import module namespace credentials = "http://www.28msec.com/modules/credentials";

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

declare namespace concepts = "http://www.28msec.com/modules/bizql/concepts";

declare function local:to-csv($concepts as item*, $onlyNames as boolean) as string
{
    if ($onlyNames)
    then
        string-join(("Name", $concepts), "
")
    else
        string-join(csv:serialize($concepts, { serialize-null-as : "" }))
};

declare function local:to-xml($concepts as item*, $onlyNames as boolean) as element()*
{
    for $c in $concepts
    return
        <ReportElement>{
             if ($onlyNames)
             then
                <Name>{$c}</Name>
             else (
                <Name>{$c.Name}</Name>,
                <Component>{$c.Component}</Component>,
                <Archive>{$c.Archive}</Archive>
             )
        }</ReportElement>
};

declare function local:filings(
    $ciks as string*,
    $tags as string*,
    $tickers as string*,
    $sics as string*,
    $fp as string*,
    $fy as string*) as object*
{
    let $entities := (
        companies:companies($ciks),
        companies:companies-for-tags($tags),
        companies:companies-for-tickers($tickers),
        companies:companies-for-SIC($sics)
    )
    for $entity in $entities
    for $fy in distinct-values(
                for $fy in $fy
                return
                    switch ($fy)
                    case "LATEST" return
                        for $p in $fp
                        return
                            if ($p eq "FY")
                            then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                            else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
                    case "ALL" return (2010 to 2015) (: temporary solution until next release :)
                    default return $fy
                )
    for $fp in $fp
    return sec-fiscal:filings-for-entities-and-fiscal-periods-and-years($entity, $fp, $fy ! ($$ cast as integer))
};

declare function local:concepts-for-archives($aids as string*) as object*
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("concepts:CONNECTION-FAILED"), $err:description)
        }
    return
        mongo:find($conn, "concepts",
        {
            "Archive": { "$in" : [ $aids ] }
        })
};

let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y cast as integer
                            else ()
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then ("Q1", "Q2", "Q3", "Q4", "FY")
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $archives    := (
                        local:filings($ciks, $tags, $tickers, $sics, $fiscalPeriods, $fiscalYears),
                        archives:archives($aids)
                    )
let $entities    := entities:entities($archives.Entity)
let $onlyNames   := let $o := request:param-values("onlyNames")[1] return if (exists($o)) then ($o cast as boolean) else false
return
    if (session:only-dow30($entities) or session:valid())
        then {
            let $concepts := let $concepts := local:concepts-for-archives($archives._id)
                             return if ($onlyNames) then distinct-values($concepts.Name) else $concepts
            return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                (session:comment("xml"),
                <ReportElements>{
                    local:to-xml($concepts, $onlyNames)
                }</ReportElements>)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=facts.csv");
                local:to-csv($concepts, $onlyNames)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                local:to-csv($concepts, $onlyNames)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    session:comment("json"),
                    { ReportElements : [ $concepts ] }
                |}
            }
        }
        else {
            response:status-code(401);
            session:error("accessing filings of an entity that is not in the DOW30", $format)
        }


:pencil: Change to 302

:pencil: Change to 302

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

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

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";
import module namespace request = "http://www.28msec.com/modules/http-request";

declare option rest:response "first-item";

(: 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 $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 $disclosure         as string* external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $eliminate          as boolean external := false;
declare  %rest:case-insensitive                 variable $merge              as boolean external := false;
declare  %rest:case-insensitive                 variable $elimination-threshold as integer external := 0;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";
declare  %rest:case-insensitive                 variable $debug              as boolean external := false;

(: 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 := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic,
  (),
  (),
  (),
  ())

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

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 $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? := switch(true)
                              case empty($components) return ()
                              case $merge return components:merge($components)
                              default return $components[1]
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

return if(empty($component)) then {
    { status: 404 },
    session:error("component not found", "json")
} else
(: Fact resolution :)
let $definition-model :=
  switch($profile-name)
  case "sec" return sec-networks:standard-definition-models-for-components($component, {Language: $language})
  case "japan" return components:standard-definition-models-for-components(
  $component,
  {
    Language: $language,
    HideDimensions: [ "fsa:Submitted", "fsa:FiscalPeriod", "fsa:FiscalPeriodType", "fsa:FiscalYear" ]
  }
  )
default return components:standard-definition-models-for-components($component, {Language: $language})
let $spreadsheet as object? :=
    components:spreadsheet(
        $component,
        {|
            {
                FlattenRows: true,
                Eliminate: $eliminate,
                EliminationThreshold: double($elimination-threshold) div 100,
                Validate: $validate,
                DefinitionModel: $definition-model,
                Language: $language,
                Debug: $debug
            },
            if(exists($rules))
            then { Rules : [ $rules ] }
            else ()
        |})

let $results :=
        if($format eq "html")
        then {
          {
            status: 302,
            content-type: "text/html",
            serialization: { "method" : "html" },
            headers: { "Location" : "http://rendering.secxbrl.info/#?url=" || encode-for-uri("http://" || request:server-name() || ":" || request:server-port() || replace(request:uri(), "format=html", "format=json")) }
          },
          <html/>
        }
        else
        {
            { serialization: { method: "json", indent : true } },
            $spreadsheet
        }
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("component")
       default return api:check-and-return-results($token, $results, $format)

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

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

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";
import module namespace request = "http://www.28msec.com/modules/http-request";

declare option rest:response "first-item";

(: 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 $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 $disclosure         as string* external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $eliminate          as boolean external := false;
declare  %rest:case-insensitive                 variable $merge              as boolean external := false;
declare  %rest:case-insensitive                 variable $elimination-threshold as integer external := 0;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $language           as string  external := "en-US";
declare  %rest:case-insensitive                 variable $debug              as boolean external := false;

(: 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 := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic,
  (),
  (),
  (),
  ())

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

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 $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? := switch(true)
                              case empty($components) return ()
                              case $merge return components:merge($components)
                              default return $components[1]
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

return if(empty($component)) then {
    { status: 404 },
    session:error("component not found", "json")
} else
(: Fact resolution :)
let $definition-model :=
  switch($profile-name)
  case "sec" return sec-networks:standard-definition-models-for-components($component, {Language: $language})
  case "japan" return components:standard-definition-models-for-components(
  $component,
  {
    Language: $language,
    HideDimensions: [ "fsa:Submitted", "fsa:FiscalPeriod", "fsa:FiscalPeriodType", "fsa:FiscalYear" ]
  }
  )
default return components:standard-definition-models-for-components($component, {Language: $language})
let $spreadsheet as object? :=
    components:spreadsheet(
        $component,
        {|
            {
                FlattenRows: true,
                Eliminate: $eliminate,
                EliminationThreshold: double($elimination-threshold) div 100,
                Validate: $validate,
                DefinitionModel: $definition-model,
                Language: $language,
                Debug: $debug
            },
            if(exists($rules))
            then { Rules : [ $rules ] }
            else ()
        |})

let $results :=
        if($format eq "html")
        then {
          {
            status: 301,
            content-type: "text/html",
            serialization: { "method" : "html" },
            headers: { "Location" : "http://rendering.secxbrl.info/#?url=" || encode-for-uri("http://" || request:server-name() || ":" || request:server-port() || replace(request:uri(), "format=html", "format=json")) }
          },
          <html/>
        }
        else
        {
            { serialization: { method: "json", indent : true } },
            $spreadsheet
        }
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("component")
       default return api:check-and-return-results($token, $results, $format)


Little changes

Little changes

module {
    name: "choice",
    description: "Combinatorial generators",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    },
    description: "Non-deterministic choice generators"
};

include "fadado.github.io/prelude";
import "fadado.github.io/generator/stream" as stream;
import "fadado.github.io/generator/chance" as chance;

########################################################################
# Private auxiliar filters

# Pick one element, any
def _pick: #:: [a]| => [a]
    .[0:1]
;

# What's left after picking one element
def _left: #:: [a]| => [a]
    .[1:]
;

# Pick the element at $i
def _pick($i): #:: [a]|(number) => [a]
    .[$i:$i+1]
;

# What's left after picking the element at $i
def _left($i): #:: [a]|(number) => [a]
    del(.[$i])
;

########################################################################
# Sub-sets
########################################################################

# Size (n-1) subsets
#
def subsets_1: #:: [a]| => *[a]
    if length == 0  # none to pick?
    then empty      # then fail
    else
        # either one picked with what's left with one removed
        _pick + (_left|subsets_1)
        # or  what's left after picking one
        , _left
    end
;

# Size k subsets
# Combinations
#
def combinations($k): #:: [a]|(number) => *[a]
    def _combs($k):
        if $k == 0
        then []
        elif length == 0 # none to pick?
        then empty       # then fail (no results for k > n)
        else
            # either _pick one and add to what's left combinations
            _pick + (_left|_combs($k-1))
            # or what's left combined
            , (_left|_combs($k))
        end
    ;
    select(0 <= $k and $k <= length) # not defined for all $k
    | _combs($k)
;

# All subsets, stable
#
def powerset: #:: [a]| => *[a]
    combinations(range(0; 1+length))
;

# All subsets, unstable output
#
def subsets: #:: [a]| => *[a]
    if length == 0
    then []
    else
        # or this one added to what's left subsets
        _pick + (_left|subsets)
        # either what's left after picking one,
        , (_left|subsets)
    end
;

########################################################################
# Sub-sequences
########################################################################

# Permutations
#
def permutations: #:: [a]| => *[a]
    def choose: range(0; length);
    #
    if length == 0
    then []
    else
        # choose one and add to what's left permuted
        choose as $i
        | _pick($i) + (_left($i)|permutations)
    end
;

# Partial permutations
# Variations
#
def permutations($k): #:: [a]|(number) => *[a]
    def _perm($k):
        def choose: # empty if none to choose
            range(0; length)
        ;
        if $k == 1
        then
            #choose as $i | _pick($i)
            .[] | [.]
        else
            # choose one and add to what's left permuted
            choose as $i
            | _pick($i) + (_left($i)|_perm($k-1))
        end
    ;
    select(0 <= $k and $k <= length) # not defined for all $k
    | if $k == 0 then [] else _perm($k) end
;

# All sizes permutations
#
def subseqs: #:: [a]| => *[a]
    permutations(range(0; 1+length))
;

########################################################################
# Multi-sets
########################################################################

# Size k multisets
# Combinations with reposition
#
def mulsets($k): #:: [a]|(number) => *[a]
    def _mulset($k):
        if $k == 0
        then []
        elif length == 0 # none to pick?
        then empty       # then fail
        else
            # either _pick one and add to other multisets minus one
            _pick + _mulset($k-1)
            # or what's left multisets
            , (_left|_mulset($k))
        end
    ;
    select(0 <= $k) # not defined for all $k
    | _mulset($k)
;

# Infinite multisets from a set
#
def mulsets: #:: [a]| => *[a]
    mulsets(range(0; infinite))
;

########################################################################
# Multi-sequences
########################################################################

# Cartesian product
#
def product: #:: [[a]]| => *[a]
#   if length == 0
#   then []
#   else
#       .[0][] as $x
#       | (.[1:]|product) as $y
#       | [$x] + $y
#   end
    def _product:
        if length == 1
        then
            .[0][] | [.]
        else
            .[0][] as $x
            | [$x] + (.[1:]|_product)
        end
    ;
    if length == 0 then [] else _product end
;
def product($a): [$a]|product;
def product($a;$b): [$a,$b]|product;
def product($a;$b;$c): [$a,$b,$c]|product;
def product($a;$b;$c;$d): [$a,$b,$c,$d]|product;
def product($a;$b;$c;$d;$e): [$a,$b,$c,$d,$e]|product;

# Generates Σ⁰ or Σ¹ or … or Σⁿ
#
def powers($n): #:: [a]|(number) => *[a]
#   . as Σ
    select(0 <= $n) # not defined for negative $k
    | if length == 0 or $n == 0
      then []
      elif $n == 1
      then .
      else
        . as $set
        | [range($n) | $set]
        | product
      end
;

# Generates Σ⁺
#
def kplus: #:: [a]| => *[a]
#   . as Σ
    def r:
        # Ordered version for:
        #   def k: [], (.[]|[.]) + k;
        (.[]|[.])           # either wrap each symbol
        ,                   # or
        r as $w             # for each word in previous level
        | .[] as $symbol    # for each symbol in alphabet
        | $w|.[length]=$symbol  # make new word
    ;
    if length == 0 then empty else r end
;

# Generates Σ*
#
def kstar: #:: [a]| => +[a]
#   . as Σ
    [] , kplus
;

########################################################################

# Size n words over an alphabet Σ
# Powers exponent n of Σ
# Permutations (variations) with reposition
#
def words($n): #:: [a]|(number) => *[a]
#   . as Σ
    powers($n)
;

# Infinite words over an alphabet Σ
# Infinite tuples from a set
# All sizes permutations (variations) with reposition
#
def words: #:: [a]| => +[a]
#   . as Σ
    [] , kplus
;

########################################################################

# TODO...
# Language product?
def lproduct: #:: [[a]]| => *[a]
    def _product:
        if length == 1
        then
            .[0][]
        else
            .[0][] as $x
            | $x + (.[1:]|_product)
        end
    ;
    if length == 0 then [] else _product end
    # L · {ε} = L       L · [[]] = L
    # L · ∅ = ∅         L · [] = []
;

########################################################################
# Constricted permutations
########################################################################

# Circular permutations (necklaces)
#
def cicles: #:: [a]| => *[a]
    _pick as $first
    | _left|permutations
    | $first + .
;

# Derangements
#
def derangement: #:: [a]| => *[a]
    def choose($i): #:: [a]|(number) => [[number,a]]
        range(length) as $j
        | select(.[$j][0] != $i)
        | [$j, .[$j][1]]
    ;
    def _derange($i): #:: [a]|(number) => *[a]
        if length == 0
        then []
        else
            # choose one valid for this "column"
            # and add to what's left after removing this one deranged
            choose($i) as [$j, $x]
            | [$x] + (_left($j)|_derange($i+1))
        end
    ;
    select(length >= 2) # no derangements for less than 2 elements
    # . (dot) for _derange has still available enumerated elements
    | [range(length) as $i | [$i,.[$i]]]
    | _derange(0)
;

########################################################################
# Random choice
########################################################################

# Shuffle array contents.
#
def shuffle($seed): #:: [a]|(number) => [a]
    # Swaps two array positions
    def swap($i; $j):
        when($i != $j;
             .[$i] as $t | .[$i]=.[$j] | .[$j]=$t)
    ;
    . as $array
    | length as $len
    | [stream::take($len; chance::rand($seed))] as $r
    # https://en.wikipedia.org/wiki/Fisher-Yates_shuffle
    # To shuffle an array a of n elements (indices 0..n-1)
    # for i from n−1 downto 1 do
    | reduce ($len - range(1; $len)) as $i
        ($array;
         # j ← random integer such that 0 ≤ j ≤ i
         ($r[$i] % ($i+1)) as $j
         # exchange a[j] and a[i]
         | swap($i; $j))
;
def shuffle: #:: [a]| => [a]
    shuffle(chance::randomize)
;

# Choose in order k random elements from the input array.
#
def take($k; $seed): #:: [a]|(number;number) => *a
    # Print in order k random elements from A[1]..A[n]
    # for (i=1; n>0; i++)
    #     if (rand() < k/n--) {
    #         print A[i]
    #         k--
    #     }
    def _take($a; $r; $m):
        def t($n; $k):
            if $n < 1
            then emtpy
            else
                ($m-$n) as $i
                | if $r[$i] < ($k/$n)
                then $a[$i] , t($n-1; $k-1)
                else t($n-1; $k)
                end
            end
        ;
        t($m; $k)
    ;
    . as $a
    | length as $len
    | [stream::take($len; chance::rnd($seed))] as $r
    | _take($a; $r; $len)
;

def take($k): #:: [a]|(number) => *a
    take($k; chance::randomize)
;

########################################################################
# Multi-set permutations (naïve implementation)
#
#def arrangement: #:: [a]| => *[a]
#    [permutations]
#    | unique[]
#;

# Multi-set combinations (naïve implementation)
#
#def disposition: #:: [a]| => *[a]
#    [powerset]
#    | unique
#    | sort_by(length)[]
#;

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

module {
    name: "choice",
    description: "Combinatorial generators",
    namespace: "fadado.github.io",
    author: {
        name: "Joan Josep Ordinas Rosa",
        email: "jordinas@gmail.com"
    },
    description: "Non-deterministic choice generators"
};

include "fadado.github.io/prelude";
import "fadado.github.io/generator/stream" as stream;
import "fadado.github.io/generator/chance" as chance;

########################################################################
# Private auxiliar filters

# Pick one element, any
def _pick: #:: [a]| => [a]
    .[0:1]
;

# What's left after picking one element
def _left: #:: [a]| => [a]
    .[1:]
;

# Pick the element at $i
def _pick($i): #:: [a]|(number) => [a]
    .[$i:$i+1]
;

# What's left after picking the element at $i
def _left($i): #:: [a]|(number) => [a]
    del(.[$i])
;

########################################################################
# Sub-sets
########################################################################

# Size (n-1) subsets
#
def subsets_1: #:: [a]| => *[a]
    if length == 0  # none to pick?
    then empty      # then fail
    else
        # either one picked with what's left with one removed
        _pick + (_left|subsets_1)
        # or  what's left after picking one
        , _left
    end
;

# Size k subsets
# Combinations
#
def combinations($k): #:: [a]|(number) => *[a]
    def _combs($k):
        if $k == 0
        then []
        elif length == 0 # none to pick?
        then empty       # then fail (no results for k > n)
        else
            # either _pick one and add to what's left combinations
            _pick + (_left|_combs($k-1))
            # or what's left combined
            , (_left|_combs($k))
        end
    ;
    select(0 <= $k and $k <= length) # not defined for all $k
    | _combs($k)
;

# All subsets, stable
#
def powerset: #:: [a]| => *[a]
    combinations(range(0; 1+length))
;

# All subsets, unstable output
#
def subsets: #:: [a]| => *[a]
    if length == 0
    then []
    else
        # or this one added to what's left subsets
        _pick + (_left|subsets)
        # either what's left after picking one,
        , (_left|subsets)
    end
;

########################################################################
# Sub-sequences
########################################################################

# Permutations
#
def permutations: #:: [a]| => *[a]
    def choose: range(0; length);
    #
    if length == 0
    then []
    else
        # choose one and add to what's left permuted
        choose as $i
        | _pick($i) + (_left($i)|permutations)
    end
;

# Partial permutations
# Variations
#
def permutations($k): #:: [a]|(number) => *[a]
    def _perm($k):
        def choose: # empty if none to choose
            range(0; length)
        ;
        if $k == 1
        then
            #choose as $i | _pick($i)
            .[] | [.]
        else
            # choose one and add to what's left permuted
            choose as $i
            | _pick($i) + (_left($i)|_perm($k-1))
        end
    ;
    select(0 <= $k and $k <= length) # not defined for all $k
    | if $k == 0 then [] else _perm($k) end
;

# All sizes permutations
#
def subseqs: #:: [a]| => *[a]
    permutations(range(0; 1+length))
;

########################################################################
# Multi-sets
########################################################################

# Size k multisets
# Combinations with reposition
#
def mulsets($k): #:: [a]|(number) => *[a]
    def _mulset($k):
        if $k == 0
        then []
        elif length == 0 # none to pick?
        then empty       # then fail
        else
            # either _pick one and add to other multisets minus one
            _pick + _mulset($k-1)
            # or what's left multisets
            , (_left|_mulset($k))
        end
    ;
    select(0 <= $k) # not defined for all $k
    | _mulset($k)
;

# Infinite multisets from a set
#
def mulsets: #:: [a]| => *[a]
    mulsets(range(0; infinite))
;

########################################################################
# Multi-sequences
########################################################################

# Cartesian product
#
def product: #:: [[a]]| => *[a]
#   if length == 0
#   then []
#   else
#       .[0][] as $x
#       | (.[1:]|product) as $y
#       | [$x] + $y
#   end
    def _product:
        if length == 1
        then
            .[0][] | [.]
        else
            .[0][] as $x
            | [$x] + (.[1:]|_product)
        end
    ;
    if length == 0 then [] else _product end
;

# Size n words over an alphabet Σ
# Powers exponent n of Σ
# Permutations (variations) with reposition
#
# Generates Σ⁰ or Σ¹ or … or Σⁿ
def powers($n): #:: [a]|(number) => *[a]
    select(0 <= $n) # not defined for negative $k
    | if length == 0 or $n == 0
      then []
      elif $n == 1
      then .
      else
        . as $set
        | [range($n) | $set]
        | product
      end
;

# Infinite words over an alphabet Σ
# Infinite tuples from a set
# All sizes permutations (variations) with reposition
#
# Generates Σ⁺
def kplus: #:: [a]| => *[a]
#   . as Σ
    def r:
        # Ordered version for:
        #   def k: [], (.[]|[.]) + k;
        (.[]|[.])           # either wrap each symbol
        ,                   # or
        r as $w             # for each word in previous level
        | .[] as $symbol    # for each symbol in alphabet
        | $w|.[length]=$symbol  # make new word
    ;
    if length == 0 then empty else r end
;

# Generates Σ*
def kstar: #:: [a]| => +[a]
#   . as Σ
    [] , kplus
;

# Generates Σ⁰ or Σ¹ or … or Σⁿ
def words($n): #:: [a]|(number) => *[a]
    powers($n)
;

# Generates Σ*
def words: #:: [a]| => +[a]
#   . as Σ
    [] , kplus
;

# TODO...
# Language product?
def lproduct: #:: [[a]]| => *[a]
    def _product:
        if length == 1
        then
            .[0][]
        else
            .[0][] as $x
            | $x + (.[1:]|_product)
        end
    ;
    if length == 0 then [] else _product end
    # L · {ε} = L       L · [[]] = L
    # L · ∅ = ∅         L · [] = []
;

########################################################################
# Constricted permutations
########################################################################

# Circular permutations (necklaces)
#
def cicles: #:: [a]| => *[a]
    _pick as $first
    | _left|permutations
    | $first + .
;

# Derangements
#
def derangement: #:: [a]| => *[a]
    def choose($i): #:: [a]|(number) => [[number,a]]
        range(length) as $j
        | select(.[$j][0] != $i)
        | [$j, .[$j][1]]
    ;
    def _derange($i): #:: [a]|(number) => *[a]
        if length == 0
        then []
        else
            # choose one valid for this "column"
            # and add to what's left after removing this one deranged
            choose($i) as [$j, $x]
            | [$x] + (_left($j)|_derange($i+1))
        end
    ;
    select(length >= 2) # no derangements for less than 2 elements
    # . (dot) for _derange has still available enumerated elements
    | [range(length) as $i | [$i,.[$i]]]
    | _derange(0)
;

########################################################################
# Random choice
########################################################################

# Shuffle array contents.
#
def shuffle($seed): #:: [a]|(number) => [a]
    # Swaps two array positions
    def swap($i; $j):
        when($i != $j;
             .[$i] as $t | .[$i]=.[$j] | .[$j]=$t)
    ;
    . as $array
    | length as $len
    | [stream::take($len; chance::rand($seed))] as $r
    # https://en.wikipedia.org/wiki/Fisher-Yates_shuffle
    # To shuffle an array a of n elements (indices 0..n-1)
    # for i from n−1 downto 1 do
    | reduce ($len - range(1; $len)) as $i
        ($array;
         # j ← random integer such that 0 ≤ j ≤ i
         ($r[$i] % ($i+1)) as $j
         # exchange a[j] and a[i]
         | swap($i; $j))
;
def shuffle: #:: [a]| => [a]
    shuffle(chance::randomize)
;

# Choose in order k random elements from the input array.
#
def take($k; $seed): #:: [a]|(number;number) => *a
    # Print in order k random elements from A[1]..A[n]
    # for (i=1; n>0; i++)
    #     if (rand() < k/n--) {
    #         print A[i]
    #         k--
    #     }
    def _take($a; $r; $m):
        def t($n; $k):
            if $n < 1
            then emtpy
            else
                ($m-$n) as $i
                | if $r[$i] < ($k/$n)
                then $a[$i] , t($n-1; $k-1)
                else t($n-1; $k)
                end
            end
        ;
        t($m; $k)
    ;
    . as $a
    | length as $len
    | [stream::take($len; chance::rnd($seed))] as $r
    | _take($a; $r; $len)
;

def take($k): #:: [a]|(number) => *a
    take($k; chance::randomize)
;

########################################################################
# Multi-set permutations (naïve implementation)
#
#def arrangement: #:: [a]| => *[a]
#    [permutations]
#    | unique[]
#;

# Multi-set combinations (naïve implementation)
#
#def disposition: #:: [a]| => *[a]
#    [powerset]
#    | unique
#    | sort_by(length)[]
#;

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


:fix: Fix mongo wrapper module URL.

:fix: Fix mongo wrapper module URL.

jsoniq version "1.0";
module namespace japan = "http://28.io/modules/xbrl/profiles/japan/core";

import module namespace mw = "http://28.io/modules/xbrl/mongo-wrapper";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace entities = "http://28.io/modules/xbrl/entities";

(:
 : Copyright 2012-2015 28msec Inc.
 :)
(:~
 :
This module provides functionality for querying against the Japan profile.

 :
 : @author Ghislain Fourny
 :
 :)

(:~
 :
Retrieves the entities with the given identifiers.

 :
 : @param $cik a sequence of shortcut codes, like E01225.
 : @param $eid a sequence of raw EIDs.
 : @param $tag a sequence of tags (e.g., indices).
 :
 : @return the entities that match those identifiers.
 :)
declare function japan:entities(
    $cik as string*,
    $eid as string*,
    $tag as string*
) as object*
{
  let $eid := distinct-values((
    $cik ! ("http://info.edinet-fsa.go.jp " || $$ || "-000"),
    $eid
  ))
  return
    switch(true)
    case $tag = "ALL" return entities:entities()
    case exists(($eid, $tag)) return
       for $entity in (
         for $tag in $tag
         return mw:find($entities:col, { "Profiles.FSA.Tags" : $tag }),
         entities:entities($eid)
       )
       group by $entity._id
       return $entity[1]
    default return ()
};

(:~
 :
Retrieves the filings with the given characteristics.

 :
 : @param $entities a sequence of entities or EIDs.
 : @param $fiscalYear a sequence of fiscal years.
 : @param $fiscalPeriod a sequence of fiscal periods.
 : @param $aid a sequence of AIDs.
 :
 : @return the filings that match those criteria.
 :)
declare function japan:filings(
    $entities as item*,
    $fiscalYear as integer*,
    $fiscalPeriod as string*,
    $aid as string*) as object*
{
    archives:archives($aid),
    if($fiscalYear = 1)
    then
        for $a as object in archives:archives-for-entities($entities)
        where (empty($fiscalPeriod) or ($fiscalPeriod = "ALL") or $a.Profiles.FSA.DocumentFiscalPeriodFocus = $fiscalPeriod)
        group by $a.Entity
        return
            for $filing in $a
            group by $fy := $filing.Profiles.FSA.DocumentFiscalYearFocus
            order by $fy descending
            count $i where $i eq 1
            return $filing
    else
        for $a as object in archives:archives-for-entities($entities)
        where (empty($fiscalYear) or
               $fiscalYear = 0 or
               $fiscalYear = $a.Profiles.FSA.DocumentFiscalYearFocus)
               and (empty($fiscalPeriod) or ($fiscalPeriod = "ALL") or $a.Profiles.FSA.DocumentFiscalPeriodFocus = $fiscalPeriod)
        return $a
};

jsoniq version "1.0";
module namespace japan = "http://28.io/modules/xbrl/profiles/japan/core";

import module namespace mw = "http://28.io/modules/xbrl/profiles/japan/mongo-wrapper";

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace entities = "http://28.io/modules/xbrl/entities";

(:
 : Copyright 2012-2015 28msec Inc.
 :)
(:~
 :
This module provides functionality for querying against the Japan profile.

 :
 : @author Ghislain Fourny
 :
 :)

(:~
 :
Retrieves the entities with the given identifiers.

 :
 : @param $cik a sequence of shortcut codes, like E01225.
 : @param $eid a sequence of raw EIDs.
 : @param $tag a sequence of tags (e.g., indices).
 :
 : @return the entities that match those identifiers.
 :)
declare function japan:entities(
    $cik as string*,
    $eid as string*,
    $tag as string*
) as object*
{
  let $eid := distinct-values((
    $cik ! ("http://info.edinet-fsa.go.jp " || $$ || "-000"),
    $eid
  ))
  return
    switch(true)
    case $tag = "ALL" return entities:entities()
    case exists(($eid, $tag)) return
       for $entity in (
         for $tag in $tag
         return mw:find($entities:col, { "Profiles.FSA.Tags" : $tag }),
         entities:entities($eid)
       )
       group by $entity._id
       return $entity[1]
    default return ()
};

(:~
 :
Retrieves the filings with the given characteristics.

 :
 : @param $entities a sequence of entities or EIDs.
 : @param $fiscalYear a sequence of fiscal years.
 : @param $fiscalPeriod a sequence of fiscal periods.
 : @param $aid a sequence of AIDs.
 :
 : @return the filings that match those criteria.
 :)
declare function japan:filings(
    $entities as item*,
    $fiscalYear as integer*,
    $fiscalPeriod as string*,
    $aid as string*) as object*
{
    archives:archives($aid),
    if($fiscalYear = 1)
    then
        for $a as object in archives:archives-for-entities($entities)
        where (empty($fiscalPeriod) or ($fiscalPeriod = "ALL") or $a.Profiles.FSA.DocumentFiscalPeriodFocus = $fiscalPeriod)
        group by $a.Entity
        return
            for $filing in $a
            group by $fy := $filing.Profiles.FSA.DocumentFiscalYearFocus
            order by $fy descending
            count $i where $i eq 1
            return $filing
    else
        for $a as object in archives:archives-for-entities($entities)
        where (empty($fiscalYear) or
               $fiscalYear = 0 or
               $fiscalYear = $a.Profiles.FSA.DocumentFiscalYearFocus)
               and (empty($fiscalPeriod) or ($fiscalPeriod = "ALL") or $a.Profiles.FSA.DocumentFiscalPeriodFocus = $fiscalPeriod)
        return $a
};