Fixed entities test.

Fixed entities test.

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 test = "http://apps.28.io/test";

declare variable $local:expected as object :=
    {
      "dow30" : [ "http://www.sec.gov/CIK 0000066740", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000012927", "http://www.sec.gov/CIK 0000018230", "http://www.sec.gov/CIK 0000093410", "http://www.sec.gov/CIK 0000858877", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000030554", "http://www.sec.gov/CIK 0000034088", "http://www.sec.gov/CIK 0000040545", "http://www.sec.gov/CIK 0000886982", "http://www.sec.gov/CIK 0000354950", "http://www.sec.gov/CIK 0000050863", "http://www.sec.gov/CIK 0000051143", "http://www.sec.gov/CIK 0000200406", "http://www.sec.gov/CIK 0000019617", "http://www.sec.gov/CIK 0000063908", "http://www.sec.gov/CIK 0000789019", "http://www.sec.gov/CIK 0000310158", "http://www.sec.gov/CIK 0000320187", "http://www.sec.gov/CIK 0000078003", "http://www.sec.gov/CIK 0000080424", "http://www.sec.gov/CIK 0000086312", "http://www.sec.gov/CIK 0000101829", "http://www.sec.gov/CIK 0000731766", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001403161", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001001039" ],
      "generic" : [ "http://www.sec.gov/CIK 0000004962" ],
      "cik" : [ "http://www.sec.gov/CIK 0000004962" ],
      "ticker" : [ "http://www.sec.gov/CIK 0000104169" ],
      "ticker2" : [ "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000104169" ],
      "sic" : [ "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "sic-dow30": [ "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000732712" ],
      "mixed" : [ "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "mixed-dow30": [ "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0000104169" ]
    };

declare %an:nondeterministic function local:test-example1() as item
{
  let $expected :=
  [ {
    "_id" : "http://www.sec.gov/CIK 0000021344",
    "Profiles" : {
      "SEC" : {
        "Name" : "SEC",
        "CompanyName" : "COCA COLA CO",
        "CompanyType" : "Corporation",
        "CIK" : "0000021344",
        "SIC" : "2080",
        "SICDescription" : "BEVERAGES",
        "SICGroup" : 9,
        "Taxonomy" : "ci",
        "Sector" : "Food/Kindred Products",
        "Tickers" : [ "ko" ],
        "Tags" : [ "FORTUNE100", "DOW30", "RUSSELL1000", "SP500" ],
        "IsTrust" : false
      }
    },
    "Archives" : "http://" || request:server-name() || ":" || request:server-port() ||
      "/v1/_queries/public/api/filings.jq?_method=POST&cik=0000021344&fiscalYear=ALL&fiscalPeriod=ALL&format=html&profile-name=sec&token=foobar"
  } ]
  let $endpoint := "entities"
  let $params := {cik:"21344"}
  let $request := test:invoke($endpoint, $params)
  let $status as integer := $request[1]
  let $actual as array? := $request[2].Entities
  return test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-entities(
    $expected as array,
    $params as object) as item
{
    let $endpoint := "entities"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Entities[]._id ]
    let $status as integer := $request[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-entities-generic(
    $expected as array,
    $params as object) as item
{
    let $endpoint := "entities"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Entities[].EID ]
    let $status as integer := $request[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:sequential function local:check($o as object) as object
{
    if (not(every $k in (keys($o) ! $o.$$) satisfies ($k instance of boolean and $k)))
    then {
            response:status-code(500);
            $o
    } else
            $o
};

let $dow30 := test:is-dow30()
return local:check({
    dow30: local:test-entities($local:expected.dow30, {tag:"DOW30"}),
    generic: local:test-entities-generic($local:expected.generic, {eid:"http://www.sec.gov/CIK 0000004962", profile-name:"generic"}),
    cik: local:test-entities($local:expected.cik, {cik:"4962"}),
    ticker: local:test-entities($local:expected.ticker, {ticker:"wmt"}),
    ticker2: local:test-entities($local:expected.ticker2, {ticker:["wmt","ko"]}),
    sic: local:test-entities(if($dow30) then $local:expected.sic-dow30 else $local:expected.sic, {sic:"4813"}),
    mixed: local:test-entities(if($dow30) then $local:expected.mixed-dow30 else $local:expected.mixed, {cik:"4962", sic:"4813", ticker:[ "wmt", "ko" ]}),
    example1: local:test-example1()
})


:green_heart: Fix tests.

:green_heart: Fix tests.


Addressed comment.

Addressed comment.

jsoniq version "1.0";

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

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace networks = "http://28.io/modules/xbrl/networks";

(: TODOs
- Options for relationship networks (formulaAxis, depth)
:)

(:~
 : Converts a definition breakdown to a structural breakdown
 :
 : @param $breakdown a breandowk
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the option object
 :
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND if a definition node kind
 : is unrecognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if a root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if a root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted, structural breakdown.
 :)
declare %private function resolution:convert-breakdown(
    $breakdown as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $parent-child-order as string? := ($breakdown.ParentChildOrder, $parent-child-order)[1]

    let $labels as array := $breakdown.BreakdownLabels

    let $unbalanced-trees as object* := resolution:convert-definition-nodes(
        $breakdown.BreakdownTrees[],
        $components,
        $parent-child-order,
        $options
    )
    let $balanced-trees as object* := resolution:height-balance($unbalanced-trees)

    return {
        BreakdownLabels: $labels,
        BreakdownTrees: [ $balanced-trees ]
    }
};

(:~
 : Converts each definition node to one or more structural nodes
 :
 : @param $definition-nodes some definition nodes.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND if a definition node kind
 : is unrecognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if a root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if a root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:convert-definition-nodes(
    $definition-nodes as object*,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    for $definition-node as object in $definition-nodes
    let $parent-child-order as string? := ($definition-node.ParentChildOrder, $parent-child-order)[1]
    for $converted-node in
      switch($definition-node.Kind)
      case "Rule" return resolution:convert-rule-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "Aspect" return resolution:convert-aspect-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "ConceptRelationship" return resolution:convert-concept-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "DimensionRelationship" return resolution:convert-dimension-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      default return error(
          QName("resolution:UNKNOWN-DEFINITION-NODE-KIND"),
          "Unknown definition node kind: $definition-node.Kind")
    return if(exists($definition-node.Id))
           then copy $c := $converted-node
                modify for $node in ($c, descendant-objects($c).Children[])
                       return insert json { Id: $definition-node.Id } into $node
                return $c
           else $converted-node
};

(:~
 : Retrieves the labels from the components' cache (default language)
 : or from the remove collection cache.
 :
 : @param $concept-names a sequence of concept names.
 : @param $components a sequence of components
 : @param $label-role the desired label role
 : @param $options the options object
 :
 : @return the labels
 :)
declare %private function resolution:labels(
    $concept-names as string*,
    $components as object*,
    $label-role as string,
    $options as object?) as string*
{
  concepts:labels(
    $concept-names,
    $label-role,
    ($options.Language, "en")[1],
    $components.Concepts[],
    $options
  )
};

(:~
 : Converts a rule definition node to one or more structural nodes.
 : A rule node restricts a dimension to a value.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:convert-rule-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    let $main-members := values(values($definition-node.AspectRulesSet))

    let $labels := (
        $definition-node.Labels[],
        resolution:labels(
            $main-members,
            $components,
            $concepts:STANDARD_LABEL_ROLE,
            $options)
    )

    let $children := resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $options)
    let $roll-up := {
        Labels: [ ],
        ConstraintSets : {
            "": { }

        },
        RollUp: true
    }
    let $constraint-sets := {|
        for $tag in keys($definition-node.AspectRulesSet)
        return {
            $tag: {|
                for $aspect in keys($definition-node.AspectRulesSet.$tag)
                return {
                    $aspect: $definition-node.AspectRulesSet.$tag.$aspect
                }
            |}
        }
    |}
    let $merged-children :=
        for $child in $children
        return copy $c := $child
        modify for $tag in keys($c.ConstraintSets)
               return replace value of json $c.ConstraintSets.$tag with facts:merge-objects(
                    $c.ConstraintSets.$tag,
                    $constraint-sets.$tag,
                    true)
        return $c
    return switch(true)
           case not $definition-node.Abstract and $definition-node.Merge
                return error(QName("resolution:NON-ABSTRACT-MERGE-NODE"), "A definition rule node cannot be non-abstract and merged.")
           case $definition-node.Merge return $merged-children
           case $definition-node.Abstract return {|
                {
                    Labels: [  $labels ],
                    ConstraintSets: $constraint-sets
                },
                if(exists($definition-node.TagSelectors))
                then {
                    TagSelectors: $definition-node.TagSelectors
                }
                else (),
                if(exists($children)) then {
                    Children: [ $children ]
                }
                else ()
           |}
            default return {|
                {
                    Labels: [  $labels ],
                    ConstraintSets: $constraint-sets
                },
                if(exists($definition-node.TagSelectors))
                then {
                    TagSelectors: $definition-node.TagSelectors
                }
                else (),
                if(exists($children))
                then {
                    Children: switch(true)
                              case $parent-child-order eq "parentFirst" return [$roll-up, $children]
                              default return [$children, $roll-up]
                }
                else (),
                if($definition-node.IsTotal)
                then { IsTotal: true }
                else ()
            |}
};

(:~
 : Convert an aspect definition node to one or more structural nodes.
 : An aspect nodes will expand to all values found in the fact pool during
 : the layouting stage.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-aspect-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $children :=  resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $options)
    let $constrained-aspect := $definition-node.Aspect
    return {|
        {
            Open: true,
            Aspect: $constrained-aspect
        },
        if ($children)
        then { Children: [ $children ] }
        else ()
    |}
};

(:~
 : Convert a concept relationship definition node to one or more structural nodes.
 : A concept relationship node will automatically expand a network to a hierarchy
 : of structural nodes.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root concept
 : cannot be resolved.
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-concept-relationship-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $link-role := $definition-node.LinkRole
    let $link-name := $definition-node.LinkName
    let $arc-role := $definition-node.ArcRole
    let $arc-name := $definition-node.ArcName
    let $root := $definition-node.RelationshipSource
    let $network as object? := networks:networks-for-components($components)[
        $$.LinkRole eq $link-role and
        $$.LinkName eq $link-name and
        $$.ArcRole eq $arc-role and
        $$.ArcName eq $arc-name
    ]
    let $subnetwork := descendant-objects($network.Trees)[$$.Name eq $root]
    return if(exists($subnetwork))
    then resolution:expand-concept-network($subnetwork, $components, $parent-child-order, $options)
    else error(QName("resolution:UNRESOLVED-CONCEPT-RELATIONSHIP"), $root || ": The concept root could not be resolved.")
};

(:~
 : Helper, recursive function for resolution:convert-concept-relationship-node#3
 :
 : @param $network a network to expand.
 : @param $components a sequence of components.
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:expand-concept-network(
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    let $concept as string := $network.Name
    let $default-languages as string* := $components.$components:DEFAULT-LANGUAGE
    let $default-languages := if(exists($default-languages)) then $default-languages else "en-US"
    let $concept-metadata := ($components.Concepts[])[$$.Name eq $concept][1]
    let $label :=
        if(every $language in $default-languages satisfies $language eq $options.Language)
        then $network.Label
        else
        resolution:labels(
            $concept,
            $components,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            $options)
    return
    {|
        {
            Labels: [ $label ],
            ConstraintSets: {
                "" : { "xbrl:Concept" : $concept }
            }
        },

        switch($network.PreferredLabelRole)
        case "http://www.xbrl.org/2003/role/totalLabel"
        return {
            IsTotal: true
        }
        case "http://www.xbrl.org/2009/role/negatedLabel"
        return {
            IsNegated: true
        }
        case "http://www.xbrl.org/2009/role/negatedTerseLabel"
        return {
            IsNegated: true
        }
        case "http://www.xbrl.org/2003/role/periodStartLabel"
        return {
            TagSelectors: [ "table.periodStart" ]
        }
        case "http://www.xbrl.org/2003/role/periodEndLabel"
        return {
            TagSelectors: [ "table.periodEnd" ],
            IsTotal: true
        }
        default return (),

        let $sub-networks := (values($network.To), $network.To[])
        let $converted-subnetworks :=
            for $sub-network in $sub-networks
            let $order := $sub-network.Order
            order by $order
            return
                resolution:expand-concept-network(
                        $sub-network,
                        $components,
                        $parent-child-order,
                        $options
                    )
        let $roll-up :=
            if($concept-metadata.IsAbstract)
            then ()
            else {
                Labels: [],
                RollUp: true
            }
        where exists($converted-subnetworks)
        return {
            Children: [
                switch($parent-child-order)
                case "parentFirst" return ($roll-up, $converted-subnetworks)
                default return ($converted-subnetworks, $roll-up)
            ]
        }

    |}
};

(:~
 : Convert a dimension relationship definition node to one or more structural nodes.
 : A dimension relationship node will automatically expand a network to a hierarchy
 : of structural nodes.
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root member
 : cannot be resolved.
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-dimension-relationship-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $link-role := $definition-node.LinkRole
    let $dimension := $definition-node.Dimension
    let $root := $definition-node.RelationshipSource
    let $hypercubes as object* := hypercubes:hypercubes-for-components($components[$$.Role eq $link-role])
    let $subnetwork as object := descendant-objects($hypercubes.Aspects.$dimension)[$$.Name eq $root][1]
    return if(exists($subnetwork))
    then resolution:expand-dimension-network($dimension, $subnetwork, $components, $parent-child-order, $options)
    else error(QName("resolution:UNRESOLVED-DIMENSION-RELATIONSHIP"), $root || ": The dimension member root could not be resolved.")
};

(:
    Helper, recursive function for resolution:convert-dimension-relationship-node#3
:)
declare function resolution:expand-dimension-network(
    $dimension as string,
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $value as string := $network.Name
    let $label as string* :=
          if(not $options.Language != $components.$components:DEFAULT-LANGUAGE)
          then $network.Label
          else
          resolution:labels(
            $value,
            $components,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            $options)
    return
    {|
        {
            Labels: [ $label],
            ConstraintSets: {
                "" : { $dimension : $value }
            }
        },
        let $sub-networks as object* := $network.Members[]
        let $converted-subnetworks as object* :=
            for $sub-network in $sub-networks
            return resolution:expand-dimension-network(
                        $dimension,
                        $sub-network,
                        $components,
                        $parent-child-order,
                        $options
                )
        let $roll-up as object* :={
            Labels: [],
            RollUp: true
        }
        where exists($converted-subnetworks)
        return { Children: [ $converted-subnetworks, $roll-up ] }
    |}
};

(:
    Helper function that returns the depth of breakdown trees set.
:)
declare function resolution:depth($axis-breakdown-trees as object*) as integer
{
    switch(true)
    case empty($axis-breakdown-trees) return 0
    case empty($axis-breakdown-trees.Children[]) return 1
    default return max($axis-breakdown-trees.Children[] ! (1 + resolution:depth($$)))
};

(:~
 : Helper function that height-balances a breakdown trees set,
 : by adding roll-up nodes to get a uniform depth.
 :
 : @param $axis-breakdown-trees the breakdown trees for an axis
 :
 : @return the balanced breakdown trees.
 :)
declare function resolution:height-balance($axis-breakdown-trees as object*) as object*
{
    let $depth := resolution:depth($axis-breakdown-trees)
    return resolution:height-balance($axis-breakdown-trees, $depth)
};

(:~
 : Helper, recursive function for resolution:height-balance#1.
 :
 : @param $axis-breakdown-trees the breakdown trees for an axis
 : @parem the depth at which these trees should get balanced.
 :
 : @return the balanced breakdown trees.
 :)
declare function resolution:height-balance($axis-breakdown-trees as object*, $desired-depth as integer) as object*
{
  let $roll-up-cell := {
    CellLabels: [],
    ConstraintSets: {"" : {}},
    RollUp: true
  }
  return
    for $tree in $axis-breakdown-trees
    return switch(true)
           case $desired-depth gt 1 and empty($tree.Children)
           return copy $t := $tree
                  modify insert json {
                    Children: [
                      resolution:height-balance($roll-up-cell, $desired-depth - 1)
                    ]
                  } into $t
                  return $t
                case $desired-depth gt 1 and exists($tree.Children)
                  return copy $t := $tree
                         modify replace value of json $t.Children with [
                           $t.Children[] ! resolution:height-balance($$, $desired-depth - 1)
                         ]
                         return $t
                case $desired-depth eq 1 and empty($tree.Children)
                  return $tree
                default
                  return error(QName("resolution:UNFORESEEN-ERROR"), "An error occurred while height-balancing")
};

(:~
 : Retrieves the metadata for some members from a component.
 :
 : @param $components a sequence of components.
 : @param $member-names the names of the members for which to retrieve the metadata.
 :
 : @return the metadata for the supplied members.
 :)
declare %private function resolution:metadata(
    $components as object*,
    $member-names as string*) as object*
{
    ($components.Concepts[])[$$.Name = $member-names][1]
};

(:~
  Resolves a definition model to a structural model, given a component.

  @param $definition-model A definition mode, in 28msec's proprietary JSON format.
  @param $component A component, in 28msec's proprietary JSON format.

  @error resolution:UNFORESEEN-ERROR Something went wrong, contact us.
  @error resolution:UNKNOWN-DEFINITION-NODE-KIND A definition node was not recognized.

  @return a structural model
:)
declare function resolution:resolve(
    $definition-model as object,
    $components as object*) as object
{
    resolution:resolve($definition-model, $components, ())
};

(:~
 : Resolves a definition model to a structural model, given a component.
 :
 : @param $definition-model A definition mode, in 28msec's proprietary JSON format.
 : @param $component A component, in 28msec's proprietary JSON format.
 : @param $options Optional parameters among which
 : <ul>
 :   <li>Hypercube: override the components' hypercubes with the provided hypercube
 :      (this will only affect the default values used later in the layout pipeline).</li>
 :    <li>Language: override the components' default language in the rendering.</li>
 : </ul>
 :
 : @error resolution:UNFORESEEN-ERROR Something went wrong, contact us.
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND A definition node was not recognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if the root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return a structural model
 :)
declare function resolution:resolve(
    $definition-model as object,
    $components as object*,
    $options as object?) as object
{
    let $hypercubes as object* :=
        if(exists($options.Hypercube))
        then $options.Hypercube
        else values($components.Hypercubes)
    return {
        ModelKind: "StructuralModel",
        TableSetLabels: $definition-model.Labels,
        Component: {
            Role: $components.Role[1],
            Label: $components.Label[1]
        },
        TableSet : [
            {
                Breakdowns: {|
                    for $axis in keys($definition-model.Breakdowns)
                    return {
                        $axis: [
                            for $breakdown in $definition-model.Breakdowns.$axis[]
                            return resolution:convert-breakdown($breakdown,
                                                                $components,
                                                                $definition-model.ParentChildOrder,
                                                                $options)
                        ]
                    }
                |}
            }
        ],
        GlobalConstraintSet: $definition-model.TableFilters,
        GlobalConstraintLabels: {|
            for $dimension in keys($definition-model.TableFilters)
            let $dimension-labels as string* := (
                resolution:labels(
                  $dimension, $components, $concepts:STANDARD_LABEL_ROLE, $options),
                resolution:labels(
                  $dimension, $components, $concepts:VERBOSE_LABEL_ROLE, $options
                )
            )
            let $value := $definition-model.TableFilters.$dimension
            let $value-labels as string* :=
              if($value instance of string)
              then (
                resolution:labels(
                  $value, $components, $concepts:VERBOSE_LABEL_ROLE, $options),
                resolution:labels(
                  $value, $components, $concepts:STANDARD_LABEL_ROLE, $options)
              )
              else ()
            return ({ $dimension: $dimension-labels[1] }[exists($dimension-labels)],
                    { $value: $value-labels[1] }[exists($value-labels)]
                    )
        |},
        DimensionDefaults: {|
            for $dimension as string in keys($hypercubes.Aspects)
            let $hypercube-default as atomic? := $hypercubes.Aspects.$dimension.Default[1]
            where exists($hypercube-default)
            return {
                $dimension: $hypercube-default
            }
        |}
    }
};

jsoniq version "1.0";

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

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace networks = "http://28.io/modules/xbrl/networks";

(: TODOs
- Options for relationship networks (formulaAxis, depth)
:)

(:~
 : Converts a definition breakdown to a structural breakdown
 :
 : @param $breakdown a breandowk
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the option object
 :
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND if a definition node kind
 : is unrecognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if a root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if a root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted, structural breakdown.
 :)
declare %private function resolution:convert-breakdown(
    $breakdown as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $parent-child-order as string? := ($breakdown.ParentChildOrder, $parent-child-order)[1]

    let $labels as array := $breakdown.BreakdownLabels

    let $unbalanced-trees as object* := resolution:convert-definition-nodes(
        $breakdown.BreakdownTrees[],
        $components,
        $parent-child-order,
        $options
    )
    let $balanced-trees as object* := resolution:height-balance($unbalanced-trees)

    return {
        BreakdownLabels: $labels,
        BreakdownTrees: [ $balanced-trees ]
    }
};

(:~
 : Converts each definition node to one or more structural nodes
 :
 : @param $definition-nodes some definition nodes.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND if a definition node kind
 : is unrecognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if a root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if a root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:convert-definition-nodes(
    $definition-nodes as object*,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    for $definition-node as object in $definition-nodes
    let $parent-child-order as string? := ($definition-node.ParentChildOrder, $parent-child-order)[1]
    for $converted-node in
      switch($definition-node.Kind)
      case "Rule" return resolution:convert-rule-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "Aspect" return resolution:convert-aspect-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "ConceptRelationship" return resolution:convert-concept-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      case "DimensionRelationship" return resolution:convert-dimension-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $options)
      default return error(
          QName("resolution:UNKNOWN-DEFINITION-NODE-KIND"),
          "Unknown definition node kind: $definition-node.Kind")
    return if(exists($definition-node.Id))
           then copy $c := $converted-node
                modify for $node in ($c, descendant-objects($c).Children[])
                       return insert json { Id: $definition-node.Id } into $node
                return $c
           else $converted-node
};

(:~
 : Retrieves the labels from the components' cache (default language)
 : or from the remove collection cache.
 :
 : @param $concept-names a sequence of concept names.
 : @param $components a sequence of components
 : @param $label-role the desired label role
 : @param $options the options object
 :
 : @return the labels
 :)
declare %private function resolution:labels(
    $concept-names as string*,
    $components as object*,
    $label-role as string,
    $options as object?) as string*
{
  concepts:labels(
    $concept-names,
    $label-role,
    ($options.Language, "en")[1],
    $components.Concepts[],
    $options
  )
};

(:~
 : Converts a rule definition node to one or more structural nodes.
 : A rule node restricts a dimension to a value.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:convert-rule-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    let $main-members := values(values($definition-node.AspectRulesSet))

    let $labels := (
        $definition-node.Labels[],
        resolution:labels(
            $main-members,
            $components,
            $concepts:STANDARD_LABEL_ROLE,
            $options)
    )

    let $children := resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $options)
    let $roll-up := {
        Labels: [ ],
        ConstraintSets : {
            "": { }

        },
        RollUp: true
    }
    let $constraint-sets := {|
        for $tag in keys($definition-node.AspectRulesSet)
        return {
            $tag: {|
                for $aspect in keys($definition-node.AspectRulesSet.$tag)
                return {
                    $aspect: $definition-node.AspectRulesSet.$tag.$aspect
                }
            |}
        }
    |}
    let $merged-children :=
        for $child in $children
        return copy $c := $child
        modify for $tag in keys($c.ConstraintSets)
               return replace value of json $c.ConstraintSets.$tag with facts:merge-objects(
                    $c.ConstraintSets.$tag,
                    $constraint-sets.$tag,
                    true)
        return $c
    return switch(true)
           case not $definition-node.Abstract and $definition-node.Merge
                return error(QName("resolution:NON-ABSTRACT-MERGE-NODE"), "A definition rule node cannot be non-abstract and merged.")
           case $definition-node.Merge return $merged-children
           case $definition-node.Abstract return {|
                {
                    Labels: [  $labels ],
                    ConstraintSets: $constraint-sets
                },
                if(exists($definition-node.TagSelectors))
                then {
                    TagSelectors: $definition-node.TagSelectors
                }
                else (),
                if(exists($children)) then {
                    Children: [ $children ]
                }
                else ()
           |}
            default return {|
                {
                    Labels: [  $labels ],
                    ConstraintSets: $constraint-sets
                },
                if(exists($definition-node.TagSelectors))
                then {
                    TagSelectors: $definition-node.TagSelectors
                }
                else (),
                if(exists($children))
                then {
                    Children: switch(true)
                              case $parent-child-order eq "parentFirst" return [$roll-up, $children]
                              default return [$children, $roll-up]
                }
                else (),
                if($definition-node.IsTotal)
                then { IsTotal: true }
                else ()
            |}
};

(:~
 : Convert an aspect definition node to one or more structural nodes.
 : An aspect nodes will expand to all values found in the fact pool during
 : the layouting stage.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 : @param $options the options object
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-aspect-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $children :=  resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $options)
    let $constrained-aspect := $definition-node.Aspect
    return {|
        {
            Open: true,
            Aspect: $constrained-aspect
        },
        if ($children)
        then { Children: [ $children ] }
        else ()
    |}
};

(:~
 : Convert a concept relationship definition node to one or more structural nodes.
 : A concept relationship node will automatically expand a network to a hierarchy
 : of structural nodes.
 :
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root concept
 : cannot be resolved.
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-concept-relationship-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $link-role := $definition-node.LinkRole
    let $link-name := $definition-node.LinkName
    let $arc-role := $definition-node.ArcRole
    let $arc-name := $definition-node.ArcName
    let $root := $definition-node.RelationshipSource
    let $network as object? := networks:networks-for-components($components)[
        $$.LinkRole eq $link-role and
        $$.LinkName eq $link-name and
        $$.ArcRole eq $arc-role and
        $$.ArcName eq $arc-name
    ]
    let $subnetwork := descendant-objects($network.Trees)[$$.Name eq $root]
    return if(exists($subnetwork))
    then resolution:expand-concept-network($subnetwork, $components, $parent-child-order, $options)
    else error(QName("resolution:UNRESOLVED-CONCEPT-RELATIONSHIP"), $root || ": The concept root could not be resolved.")
};

(:~
 : Helper, recursive function for resolution:convert-concept-relationship-node#3
 :
 : @param $network a network to expand.
 : @param $components a sequence of components.
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:expand-concept-network(
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object*
{
    let $concept as string := $network.Name
    let $default-languages as string* := $components.$components:DEFAULT-LANGUAGE
    let $default-languages := if(exists($default-languages)) then $default-languages else "en-US"
    let $concept-metadata := ($components.Concepts[])[$$.Name eq $concept][1]
    let $label :=
        if(every $language in $default-languages satisfies $language eq $options.Language)
        then $network.Label
        else
        resolution:labels(
            $concept,
            $components,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            $options)
    return
    {|
        {
            Labels: [ $label ],
            ConstraintSets: {
                "" : { "xbrl:Concept" : $concept }
            }
        },

        switch($network.PreferredLabelRole)
        case "http://www.xbrl.org/2003/role/totalLabel"
        return {
            IsTotal: true
        }
        case "http://www.xbrl.org/2009/role/negatedLabel"
        return {
            IsNegated: true
        }
        case "http://www.xbrl.org/2009/role/negatedTerseLabel"
        return {
            IsNegated: true
        }
        case "http://www.xbrl.org/2003/role/periodStartLabel"
        return {
            TagSelectors: [ "table.periodStart" ]
        }
        case "http://www.xbrl.org/2003/role/periodEndLabel"
        return {
            TagSelectors: [ "table.periodEnd" ],
            IsTotal: true
        }
        default return (),

        let $sub-networks := (values($network.To), $network.To[])
        let $converted-subnetworks :=
            for $sub-network in $sub-networks
            let $order := $sub-network.Order
            order by $order
            return
                resolution:expand-concept-network(
                        $sub-network,
                        $components,
                        $parent-child-order,
                        $options
                    )
        let $roll-up :=
            if($concept-metadata.IsAbstract)
            then ()
            else {
                Labels: [],
                RollUp: true
            }
        where exists($converted-subnetworks)
        return {
            Children: [
                switch($parent-child-order)
                case "parentFirst" return ($roll-up, $converted-subnetworks)
                default return ($converted-subnetworks, $roll-up)
            ]
        }

    |}
};

(:~
 : Convert a dimension relationship definition node to one or more structural nodes.
 : A dimension relationship node will automatically expand a network to a hierarchy
 : of structural nodes.
 : @param $definition-node a definition node.
 : @param $components a sequence of components
 : @param $parent-child-order "parentFirst" or "childrenFirst"
 :
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root member
 : cannot be resolved.
 :
 : @return the converted structural node.
 :)
declare %private function resolution:convert-dimension-relationship-node(
    $definition-node as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $link-role := $definition-node.LinkRole
    let $dimension := $definition-node.Dimension
    let $root := $definition-node.RelationshipSource
    let $hypercubes as object* := hypercubes:hypercubes-for-components($components[$$.Role eq $link-role])
    let $subnetwork as object := descendant-objects($hypercubes.Aspects.$dimension)[$$.Name eq $root][1]
    return if(exists($subnetwork))
    then resolution:expand-dimension-network($dimension, $subnetwork, $components, $parent-child-order, $options)
    else error(QName("resolution:UNRESOLVED-DIMENSION-RELATIONSHIP"), $root || ": The dimension member root could not be resolved.")
};

(:
    Helper, recursive function for resolution:convert-dimension-relationship-node#3
:)
declare function resolution:expand-dimension-network(
    $dimension as string,
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $options as object?) as object
{
    let $value as string := $network.Name
    let $label as string* :=
          if(not $options.Language != $components.$components:DEFAULT-LANGUAGE)
          then $network.Label
          else
          resolution:labels(
            $value,
            $components,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            $options)
    return
    {|
        {
            Labels: [ $label],
            ConstraintSets: {
                "" : { $dimension : $value }
            }
        },
        let $sub-networks as object* := $network.Members[]
        let $converted-subnetworks as object* :=
            for $sub-network in $sub-networks
            return resolution:expand-dimension-network(
                        $dimension,
                        $sub-network,
                        $components,
                        $parent-child-order,
                        $options
                )
        let $roll-up as object* :={
            Labels: [],
            RollUp: true
        }
        where exists($converted-subnetworks)
        return { Children: [ $converted-subnetworks, $roll-up ] }
    |}
};

(:
    Helper function that returns the depth of breakdown trees set.
:)
declare function resolution:depth($axis-breakdown-trees as object*) as integer
{
    switch(true)
    case empty($axis-breakdown-trees) return 0
    case empty($axis-breakdown-trees.Children[]) return 1
    default return max($axis-breakdown-trees.Children[] ! (1 + resolution:depth($$)))
};

(:~
 : Helper function that height-balances a breakdown trees set,
 : by adding roll-up nodes to get a uniform depth.
 :
 : @param $axis-breakdown-trees the breakdown trees for an axis
 :
 : @return the balanced breakdown trees.
 :)
declare function resolution:height-balance($axis-breakdown-trees as object*) as object*
{
    let $depth := resolution:depth($axis-breakdown-trees)
    return resolution:height-balance($axis-breakdown-trees, $depth)
};

(:~
 : Helper, recursive function for resolution:height-balance#1.
 :
 : @param $axis-breakdown-trees the breakdown trees for an axis
 : @parem the depth at which these trees should get balanced.
 :
 : @return the balanced breakdown trees.
 :)
declare function resolution:height-balance($axis-breakdown-trees as object*, $desired-depth as integer) as object*
{
  let $roll-up-cell := {
    CellLabels: [],
    ConstraintSets: {"" : {}},
    RollUp: true
  }
  return
    for $tree in $axis-breakdown-trees
    return switch(true)
           case $desired-depth gt 1 and empty($tree.Children)
           return copy $t := $tree
                  modify insert json {
                    Children: [
                      resolution:height-balance($roll-up-cell, $desired-depth - 1)
                    ]
                  } into $t
                  return $t
                case $desired-depth gt 1 and exists($tree.Children)
                  return copy $t := $tree
                         modify replace value of json $t.Children with [
                           $t.Children[] ! resolution:height-balance($$, $desired-depth - 1)
                         ]
                         return $t
                case $desired-depth eq 1 and empty($tree.Children)
                  return $tree
                default
                  return error(QName("resolution:UNFORESEEN-ERROR"), "An error occurred while height-balancing")
};

(:~
 : Retrieves the metadata for some members from a component.
 :
 : @param $components a sequence of components.
 : @param $member-names the names of the members for which to retrieve the metadata.
 :
 : @return the metadata for the supplied members.
 :)
declare %private function resolution:metadata(
    $components as object*,
    $member-names as string*) as object*
{
    ($components.Concepts[])[$$.Name = $member-names][1]
};

(:~
  Resolves a definition model to a structural model, given a component.

  @param $definition-model A definition mode, in 28msec's proprietary JSON format.
  @param $component A component, in 28msec's proprietary JSON format.

  @error resolution:UNFORESEEN-ERROR Something went wrong, contact us.
  @error resolution:UNKNOWN-DEFINITION-NODE-KIND A definition node was not recognized.

  @return a structural model
:)
declare function resolution:resolve(
    $definition-model as object,
    $components as object*) as object
{
    resolution:resolve($definition-model, $components, ())
};

(:~
 : Resolves a definition model to a structural model, given a component.
 :
 : @param $definition-model A definition mode, in 28msec's proprietary JSON format.
 : @param $component A component, in 28msec's proprietary JSON format.
 : @param $options Optional parameters among which
 : <ul>
 :   <li>Hypercube: override the components' hypercubes with the provided hypercube
 :      (this will only affect the default values used later in the layout pipeline).</li>
 :    <li>Language: override the components' default language in the rendering.</li>
 : </ul>
 :
 : @error resolution:UNFORESEEN-ERROR Something went wrong, contact us.
 : @error resolution:UNKNOWN-DEFINITION-NODE-KIND A definition node was not recognized.
 : @error resolution:UNRESOLVED-CONCEPT-RELATIONSHIP if the root concept
 : cannot be resolved.
 : @error resolution:UNRESOLVED-DIMENSION-RELATIONSHIP if the root member
 : cannot be resolved.
 : @error resolution:NON-ABSTRACT-MERGE-NODE if a rule node is merge,
 : but non-abstract.
 :
 : @return a structural model
 :)
declare function resolution:resolve(
    $definition-model as object,
    $components as object*,
    $options as object?) as object
{
    let $hypercubes as object* :=
        if(exists($options.Hypercube))
        then $options.Hypercube
        else values($components.Hypercubes)
    return {
        ModelKind: "StructuralModel",
        TableSetLabels: $definition-model.Labels,
        Component: {
            Role: $components.Role[1],
            Label: $components.Label[1]
        },
        TableSet : [
            {
                Breakdowns: {|
                    for $axis in keys($definition-model.Breakdowns)
                    return {
                        $axis: [
                            for $breakdown in $definition-model.Breakdowns.$axis[]
                            return resolution:convert-breakdown($breakdown,
                                                                $components,
                                                                $definition-model.ParentChildOrder,
                                                                $options)
                        ]
                    }
                |}
            }
        ],
        GlobalConstraintSet: $definition-model.TableFilters,
        GlobalConstraintLabels: {|
            for $dimension in keys($definition-model.TableFilters)
            let $dimension-label as string* := (
                resolution:labels(
                  $dimension, $components, $concepts:STANDARD_LABEL_ROLE, $options),
                resolution:labels(
                  $dimension, $components, $concepts:VERBOSE_LABEL_ROLE, $options
                )
            )
            let $value := $definition-model.TableFilters.$dimension
            let $value-label as string* :=
              if($value instance of string)
              then (
                resolution:labels(
                  $value, $components, $concepts:VERBOSE_LABEL_ROLE, $options),
                resolution:labels(
                  $value, $components, $concepts:STANDARD_LABEL_ROLE, $options)
              )
              else ()
            return ({ $dimension: $dimension-label[1] }[exists($dimension-label)],
                    { $value: $value-label[1] }[exists($value-label)]
                    )
        |},
        DimensionDefaults: {|
            for $dimension as string in keys($hypercubes.Aspects)
            let $hypercube-default as atomic? := $hypercubes.Aspects.$dimension.Default[1]
            where exists($hypercube-default)
            return {
                $dimension: $hypercube-default
            }
        |}
    }
};


Update facttable-for-report.jq

Update facttable-for-report.jq

import module namespace archives = "http://28.io/modules/xbrl/archives";
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 reports = "http://28.io/modules/xbrl/reports";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace facts = "http://28.io/modules/xbrl/facts";

import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";
import module namespace fiscal-core = "http://28.io/modules/xbrl/profiles/sec/fiscal/core";

import module namespace response = "http://www.28msec.com/modules/http-response";

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

(: 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 $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;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid           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 $report        as string? external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := "sec";

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 :=
    for $entity in
        companies:companies(
            $cik,
            $tag,
            $ticker,
            $sic,
            $aid)
    order by $entity.Profiles.SEC.CompanyName
    return $entity
let $report-id as string? := $report
let $report as object? := reports:reports($report-id)

return
if(empty($report))
then
{
      response:status-code(404);
      session:error("report with id '" || $report-id || "' does not exist.", $format)
} else

    (: Fact resolution :)
    let $filter-override as object? := fiscal-core:filter-override(
        $entities,
        $fiscalYear,
        $fiscalPeriod,
        $aid)[$profile-name eq "sec"]
    let $facts as object* :=
        let $hypercube := hypercubes:hypercubes-for-components($report, "xbrl:DefaultHypercube")
        let $filtered-aspects := values($hypercube.Aspects)[exists(($$.Domains, $$.DomainRestriction))]
        return if(count($filtered-aspects) lt 2 and not exists(($filter-override)))
        then {
              response:status-code(403);
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        } else
            components:facts(
                    $report,
                    {|
                        { FilterOverride: $filter-override }[exists($filter-override)],
                        { Validate: $validate }
                    |}
                )

    let $concepts as object* :=
        reports:concepts($report)
    let $language as string := ( $report.$components:DEFAULT-LANGUAGE , $concepts:AMERICAN_ENGLISH )[1]
    let $role as string := ( $report.Role, $concepts:ANY_COMPONENT_LINK_ROLE )[1]
    let $facts :=
      if($profile-name eq "sec")
      then
        for $fact in $facts
        group by $archive := $fact.Aspects."sec:Archive"
        let $archive := archives:archives($archive)
        let $entity := entities:entities($archive.Entity)
        for $fact in $fact
        return
        {|
            trim($fact, ("Labels", "EntityRegistrantName")),
            { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
            if($labels)
            then
                let $labels as object? := facts:labels($fact, $role, $concepts:STANDARD_LABEL_ROLE, $language, $concepts, ())
                return
                    { Labels : $labels }
            else ()
        |}
      else
        for $fact in $facts
        let $labels-object as object? := facts:labels($fact, $role, $concepts:STANDARD_LABEL_ROLE, $language, $concepts, ())
        return
        {|
            trim($fact, "Labels"),
            { Labels : $labels-object }[exists($labels)]
        |}

    let $facts := api:normalize-facts($facts)

    let $results :=
        switch ($format)
        case "xml" return {
            response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
            session:comment("xml",
            {
                NumFacts : count($facts),
                TotalNumFacts: session:num-facts(),
                TotalNumArchives: session:num-archives(),
                TotalNumEntities: session:num-entities()
            }),
            <FactTable NetworkIdentifier="http://bizql.io/facttable-for-report"
                    TableName="xbrl:FactTableForReport">{
                conversion:facts-to-xml($facts, { Caller: "Report" })
            }</FactTable>
        }
        case "text" case "csv" return {
            response:content-type("text/csv");
            response:header("Content-Disposition", "attachment; filename=facts.csv");
            conversion:facts-to-csv($facts, { Caller: "Report"})
        }
        case "excel" return {
            response:content-type("application/vnd.ms-excel");
            response:header("Content-Disposition", "attachment; filename=fact.csv");
            conversion:facts-to-csv($facts, { Caller: "Report"})
        }
        default return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});
            {|
                { NetworkIdentifier : "http://secxbrl.info/facts" },
                { TableName : "xbrl:Facts" },
                { FactTable : [ $facts ] },
                session:comment("json", {
                        NumFacts : count($facts),
                        TotalNumFacts: session:num-facts(),
                        TotalNumArchives: session:num-archives(),
                        TotalNumEntities: session:num-entities()
                    })
            |}
        }
    return api:check-and-return-results($token, $results, $format)

import module namespace archives = "http://28.io/modules/xbrl/archives";
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 reports = "http://28.io/modules/xbrl/reports";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace facts = "http://28.io/modules/xbrl/facts";

import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";
import module namespace fiscal-core = "http://28.io/modules/xbrl/profiles/sec/fiscal/core";

import module namespace response = "http://www.28msec.com/modules/http-response";

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

(: 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 $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;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid           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 $report        as string? external;
declare  %rest:case-insensitive                 variable $profile-name  as string  external := "sec";

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 :=
    for $entity in
        companies:companies(
            $cik,
            $tag,
            $ticker,
            $sic,
            $aid)
    order by $entity.Profiles.SEC.CompanyName
    return $entity
let $report-id as string? := $report
let $report as object? := reports:reports($report-id)

return
if(empty($report))
then
{
      response:status-code(404);
      session:error("report with id '" || $report-id || "' does not exist.", $format)
} else

    (: Fact resolution :)
    let $filter-override as object? := fiscal-core:filter-override(
        $entities,
        $fiscalYear,
        $fiscalPeriod,
        $aid)[$profile-name eq "sec"]
    let $facts as object* :=
        let $hypercube := hypercubes:hypercubes-for-components($report, "xbrl:DefaultHypercube")
        let $filtered-aspects := values($hypercube.Aspects)[exists(($$.Domains, $$.DomainRestriction))]
        return if(count($filtered-aspects) lt 2 and not exists(($filter-override)))
        then {
              response:status-code(403);
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        } else
            components:facts(
                    $report,
                    {|
                        { FilterOverride: $filter-override }[exists($filter-override)],
                        { Validate: $validate }
                    |}
                )

    let $concepts as object* :=
        reports:concepts($report)
    let $language as string := ( $report.$components:DEFAULT-LANGUAGE , $concepts:AMERICAN_ENGLISH )[1]
    let $role as string := ( $report.Role, $concepts:ANY_COMPONENT_LINK_ROLE )[1]
    let $facts :=
      if($profile-name eq "sec")
      then
        for $fact in $facts
        group by $archive := $fact.Aspects."sec:Archive"
        let $archive := archives:archives($archive)
        let $entity := entities:entities($archive.Entity)
        for $fact in $fact
        return
        {|
            trim($fact, ("Labels", "EntityRegistrantName")),
            { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
            if($labels)
            then
                let $labels as object? := facts:labels($fact, $role, $concepts:STANDARD_LABEL_ROLE, $language, $concepts, ())
                return
                    { Labels : $labels }
            else ()
        |}
      else
        for $fact in $facts
        let $labels-object as object? := facts:labels($fact, $role, $concepts:STANDARD_LABEL_ROLE, $language, $concepts, ())
        return
        {|
            trim($fact, "Labels")
            { Labels : $labels-object }[exists($labels)]
        |}

    let $facts := api:normalize-facts($facts)

    let $results :=
        switch ($format)
        case "xml" return {
            response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
            session:comment("xml",
            {
                NumFacts : count($facts),
                TotalNumFacts: session:num-facts(),
                TotalNumArchives: session:num-archives(),
                TotalNumEntities: session:num-entities()
            }),
            <FactTable NetworkIdentifier="http://bizql.io/facttable-for-report"
                    TableName="xbrl:FactTableForReport">{
                conversion:facts-to-xml($facts, { Caller: "Report" })
            }</FactTable>
        }
        case "text" case "csv" return {
            response:content-type("text/csv");
            response:header("Content-Disposition", "attachment; filename=facts.csv");
            conversion:facts-to-csv($facts, { Caller: "Report"})
        }
        case "excel" return {
            response:content-type("application/vnd.ms-excel");
            response:header("Content-Disposition", "attachment; filename=fact.csv");
            conversion:facts-to-csv($facts, { Caller: "Report"})
        }
        default return {
            response:content-type("application/json");
            response:serialization-parameters({"indent" : true});
            {|
                { NetworkIdentifier : "http://secxbrl.info/facts" },
                { TableName : "xbrl:Facts" },
                { FactTable : [ $facts ] },
                session:comment("json", {
                        NumFacts : count($facts),
                        TotalNumFacts: session:num-facts(),
                        TotalNumArchives: session:num-archives(),
                        TotalNumEntities: session:num-entities()
                    })
            |}
        }
    return api:check-and-return-results($token, $results, $format)


:pencil: Fix component retrieval.

:pencil: Fix component retrieval.

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

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

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

import module namespace response = "http://www.28msec.com/modules/http-response";

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

declare function local:to-xml-rec($o as object*, $level as integer) as element()*
{
    for $o in $o
    return
        element { if ($o.Kind eq "Domain") then "Member" else $o.Kind } {
            attribute { "name" } { $o.Name },
            attribute { "label" } { $o.Label },
            if (exists($o.Balance)) then attribute { "balance" } { $o.Balance } else (),
            if (exists($o.DataType)) then attribute { "dataType" } { $o.DataType } else (),
            if (exists($o.BaseType)) then attribute { "baseDataType" } { $o.BaseType } else (),
            if (exists($o.IsAbstract)) then attribute { "abstract" } { $o.IsAbstract } else (),
            if (exists($o.PeriodType)) then attribute { "periodType" } { $o.PeriodType } else (),
            attribute { "level" } { $level },
            local:to-xml-rec($o.Children[], $level + 1)
        }
};

declare function local:to-xml($model as object) as node()*
{
    <Component>
        <Network entityRegistrantName="{$model.EntityRegistrantName}"
                 accessionNumber="{$model.AccessionNumber}"
                 tableName="{$model.TableName}"
                 cik="{$model.CIK}"
                 label="{$model.Label}"
                 networkIdentifier="{$model.NetworkIdentifier}"
                 formType="{$model.FormType}"
                 fiscalPeriod="{$model.FiscalPeriod}"
                 fiscalYear="{$model.FiscalYear}"
                 acceptanceDatetime="{$model.AcceptanceDatetime}"
                 disclosure="{$model.Disclosure}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</Network>
    </Component>
};

declare function local:to-csv-rec($objects as object*, $level as integer) as object*
{
    for $o in $objects
    let $object := {
        "Label" : $o.Label,
        "Name" : $o.Name,
        "ObjectClass" : if ($o.Kind eq "Domain") then "Member" else $o.Kind,
        "DataType" : $o.DataType,
        "BaseDataType" : $o.BaseType,
        "Balance" : $o.Balance,
        "Abstract" : $o.IsAbstract,
        "PeriodType" : $o.PeriodType,
        "Level" : $level
    }
    return (
        $object,
        local:to-csv-rec($o.Children[], $level + 1)
    )
};

declare function local:to-csv($model as object) as string
{
    let $lines := local:to-csv-rec($model.ModelStructure, 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

(: 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 := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
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 $role               as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      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 %rest:distinct  variable $label              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)
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 $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

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

let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ components:model-structures($component) ],
        TableName : components:hypercubes($component),
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
let $result := { ModelStructures: [ $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return if (exists($components))
    then api:serialize($result, $comment, $serializers, $format, "components")
    else {
        response:status-code(404);
        response:content-type("application/json");
        session:error("component not found", "json")
    }

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

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

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

import module namespace response = "http://www.28msec.com/modules/http-response";

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

declare function local:to-xml-rec($o as object*, $level as integer) as element()*
{
    for $o in $o
    return
        element { if ($o.Kind eq "Domain") then "Member" else $o.Kind } {
            attribute { "name" } { $o.Name },
            attribute { "label" } { $o.Label },
            if (exists($o.Balance)) then attribute { "balance" } { $o.Balance } else (),
            if (exists($o.DataType)) then attribute { "dataType" } { $o.DataType } else (),
            if (exists($o.BaseType)) then attribute { "baseDataType" } { $o.BaseType } else (),
            if (exists($o.IsAbstract)) then attribute { "abstract" } { $o.IsAbstract } else (),
            if (exists($o.PeriodType)) then attribute { "periodType" } { $o.PeriodType } else (),
            attribute { "level" } { $level },
            local:to-xml-rec($o.Children[], $level + 1)
        }
};

declare function local:to-xml($model as object) as node()*
{
    <Component>
        <Network entityRegistrantName="{$model.EntityRegistrantName}"
                 accessionNumber="{$model.AccessionNumber}"
                 tableName="{$model.TableName}"
                 cik="{$model.CIK}"
                 label="{$model.Label}"
                 networkIdentifier="{$model.NetworkIdentifier}"
                 formType="{$model.FormType}"
                 fiscalPeriod="{$model.FiscalPeriod}"
                 fiscalYear="{$model.FiscalYear}"
                 acceptanceDatetime="{$model.AcceptanceDatetime}"
                 disclosure="{$model.Disclosure}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</Network>
    </Component>
};

declare function local:to-csv-rec($objects as object*, $level as integer) as object*
{
    for $o in $objects
    let $object := {
        "Label" : $o.Label,
        "Name" : $o.Name,
        "ObjectClass" : if ($o.Kind eq "Domain") then "Member" else $o.Kind,
        "DataType" : $o.DataType,
        "BaseDataType" : $o.BaseType,
        "Balance" : $o.Balance,
        "Abstract" : $o.IsAbstract,
        "PeriodType" : $o.PeriodType,
        "Level" : $level
    }
    return (
        $object,
        local:to-csv-rec($o.Children[], $level + 1)
    )
};

declare function local:to-csv($model as object) as string
{
    let $lines := local:to-csv-rec($model.ModelStructure, 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

(: 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 := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
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                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      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 %rest:distinct  variable $label              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)
let $reportElement := ($reportElement, $concept)

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 $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $components  := sec-networks:components(
    $archives,
    $cid,
    $reportElement,
    $disclosure,
    $networkIdentifier,
    $label)
let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ components:model-structures($component) ],
        TableName : components:hypercubes($component),
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
let $result := { ModelStructures: [ $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return if (exists($components))
    then api:serialize($result, $comment, $serializers, $format, "components")
    else {
        response:status-code(404);
        response:content-type("application/json");
        session:error("component not found", "json")
    }


:green_heart: Fix warning.

:green_heart: Fix warning.

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

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

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 csv = "http://zorba.io/modules/json-csv";

declare option rest:response "first-item";

declare function local:to-xml-rec($o as object*, $level as integer) as element()*
{
    for $o in $o
    return
        element { if ($o.Kind eq "Domain") then "Member" else $o.Kind } {
            attribute { "name" } { $o.Name },
            attribute { "label" } { $o.Label },
            if (exists($o.Balance)) then attribute { "balance" } { $o.Balance } else (),
            if (exists($o.DataType)) then attribute { "dataType" } { $o.DataType } else (),
            if (exists($o.BaseType)) then attribute { "baseDataType" } { $o.BaseType } else (),
            if (exists($o.IsAbstract)) then attribute { "abstract" } { $o.IsAbstract } else (),
            if (exists($o.PeriodType)) then attribute { "periodType" } { $o.PeriodType } else (),
            attribute { "level" } { $level },
            local:to-xml-rec($o.Children[], $level + 1)
        }
};

declare function local:to-xml($model as object) as node()*
{
    <ModelStructures>
    {
      for $model in $model.ModelStructures[]
      return
        if($profile-name eq "sec")
        then
          <ModelStructure entityRegistrantName="{$model.EntityRegistrantName}"
                 accessionNumber="{$model.AccessionNumber}"
                 tableName="{$model.TableName}"
                 cik="{$model.CIK}"
                 label="{$model.Label}"
                 networkIdentifier="{$model.NetworkIdentifier}"
                 formType="{$model.FormType}"
                 fiscalPeriod="{$model.FiscalPeriod}"
                 fiscalYear="{$model.FiscalYear}"
                 acceptanceDatetime="{$model.AcceptanceDatetime}"
                 disclosure="{$model.Disclosure}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</ModelStructure>
        else
          <ModelStructure archive="{$model.Archive}"
                 role="{$model.Role}"
                 >{
            local:to-xml-rec($model.ModelStructure[], 0)
        }</ModelStructure>
    }
    </ModelStructures>
};

declare function local:to-csv-rec($model as object, $objects as object*, $level as integer) as object*
{
    for $o in $objects
    let $object := {|
      trim($model, "Label"),
      {
        "Label" : string-join((1 to 8 * $level) ! "\u00a0") || $o.Label,
        "Name" : $o.Name,
        "Kind" : $o.Kind,
        "DataType" : $o.DataType,
        "BaseDataType" : $o.BaseType,
        "Balance" : $o.Balance,
        "Abstract" : $o.IsAbstract,
        "PeriodType" : $o.PeriodType,
        "Level" : $level
      }
    |}
    return (
        $object,
        local:to-csv-rec($model, $o.Children[], $level + 1)
    )
};

declare function local:to-csv($model as object) as string
{
    let $lines :=
      for $model in $model.ModelStructures[]
      return local:to-csv-rec(trim($model, "ModelStructure"), $model.ModelStructure[], 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

(: 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 $role               as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      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 %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $count              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 $model-structures :=
    for $component in $components
    return if($profile-name eq "sec")
    then {
        AccessionNumber : $component.Archive,
        NetworkIdentifier: $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        ModelStructure : [ components:model-structures($component) ]
    }
let $result := { ModelStructures: [ if($count) then () else $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
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:serialize($result, $comment, $serializers, $format, "components")