Fixed omissions.

Fixed omissions.

jsoniq version "1.0";

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

import module namespace components = "http://xbrl.io/modules/bizql/components";
import module namespace concepts = "http://xbrl.io/modules/bizql/concepts";
import module namespace facts = "http://xbrl.io/modules/bizql/facts";
import module namespace hypercubes = "http://xbrl.io/modules/bizql/hypercubes";
import module namespace networks = "http://xbrl.io/modules/bizql/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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $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,
        $concepts,
        $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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $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,
          $concepts,
          $options)
      case "Aspect" return resolution:convert-aspect-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts,
          $options)
      case "ConceptRelationship" return resolution:convert-concept-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts)
      case "DimensionRelationship" return resolution:convert-dimension-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts)
      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 $concepts the concept objects (for inferring the labels)
 : @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*,
    $concepts as object*,
    $label-role as string,
    $options as object?) as string*
{
    let $components-with-the-language-as-default :=
        $components[not $$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $components-with-a-different-default-language :=
        $components[$$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $all-hypercubes as object* := hypercubes:hypercubes-for-components($components-with-the-language-as-default)
    let $local-metadata as object* :=
        if ($concept-names instance of string*)
        then descendant-objects($all-hypercubes.Aspects)[$$.Name = $concept-names]
        else ()
    let $labels-from-local-metadata as string* := $local-metadata.Label
    let $labels-from-concepts-collection as string* :=
          for $concept-name in $concept-names
          return concepts:labels-for-components(
                $concept-name,
                $components-with-a-different-default-language,
                $label-role,
                $options.Language,
                $concepts,
                ())
    return ($labels-from-local-metadata, $labels-from-concepts-collection)
 };

(:~
 : 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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $options as object?) as object*
{
    let $main-members := values(values($definition-node.AspectRulesSet))

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

    let $children := resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $concepts,
        $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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $options as object?) as object
{
    let $children :=  resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $concepts,
        $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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @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?,
    $concepts 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, $concepts)
    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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:expand-concept-network(
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $concepts as object*) as object*
{
    let $concept := $network.Name
    let $default-hypercube := hypercubes:hypercubes-for-components($components, "xbrl:DefaultHypercube")
    let $concept-metadata := $default-hypercube.Aspects."xbrl:Concept".Domains."xbrl:ConceptDomain".Members.$concept[1]
    let $label :=
        resolution:labels(
            $concept,
            $components,
            $concepts,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            ())
    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,
                        $concepts
                    )
        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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @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?,
    $concepts 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, $concepts)
    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?,
    $concepts as object*) as object
{
    let $value := $network.Name
    let $label :=
        resolution:labels(
            $value,
            $components,
            $concepts,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            ())
    return
    {|
        {
            Labels: [ $label],
            ConstraintSets: {
                "" : { $dimension : $value }
            }
        },
        let $sub-networks := values($network.Members)
        let $converted-subnetworks :=
            for $sub-network in $sub-networks
            return resolution:expand-dimension-network(
                        $dimension,
                        $sub-network,
                        $components,
                        $parent-child-order,
                        $concepts
                )
        let $roll-up :={
            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*
{
    let $default-hypercube := hypercubes:hypercubes-for-components($components, "xbrl:DefaultHypercube")
    return descendant-objects($default-hypercube.Aspects)[$$.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 $components-with-a-different-default-language :=
        $components[$$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $concepts := concepts:concepts-for-components(
        $concepts:ALL_CONCEPT_NAMES,
        $components-with-a-different-default-language)
    return
    {
        ModelKind: "StructuralModel",
        TableSetLabels: $definition-model.Labels,
        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,
                                                                $concepts,
                                                                $options)
                        ]
                    }
                |}
            }
        ],
        GlobalConstraintSet: $definition-model.TableFilters,
        GlobalConstraintLabels: {|
            for $dimension in keys($definition-model.TableFilters)
            let $value := $definition-model.TableFilters.$dimension
            return { $dimension: {
                    DimensionLabels: [ resolution:metadata($components, $dimension).Label, $dimension ],
                    ValueLabels: [ if($value instance of string) then resolution:metadata($components, $value).Label else (), $value ]
                }
            }
        |},
        DimensionDefaults: {|
            let $hypercubes as object* :=
                if(exists($options.Hypercube))
                then $options.Hypercube
                else values($components.Hypercubes)
            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://xbrl.io/modules/bizql/resolution";

import module namespace components = "http://xbrl.io/modules/bizql/components";
import module namespace concepts = "http://xbrl.io/modules/bizql/concepts";
import module namespace facts = "http://xbrl.io/modules/bizql/facts";
import module namespace hypercubes = "http://xbrl.io/modules/bizql/hypercubes";
import module namespace networks = "http://xbrl.io/modules/bizql/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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $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,
        $concepts,
        $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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $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,
          $concepts,
          $options)
      case "Aspect" return resolution:convert-aspect-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts,
          $options)
      case "ConceptRelationship" return resolution:convert-concept-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts)
      case "DimensionRelationship" return resolution:convert-dimension-relationship-node(
          $definition-node,
          $components,
          $parent-child-order,
          $concepts)
      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 $concepts the concept objects (for inferring the labels)
 : @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*,
    $concepts as object*,
    $label-role as string,
    $options as object?) as string*
{
    let $components-with-the-language-as-default :=
        $components[not $$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $components-with-a-different-default-language :=
        $components[$$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $all-hypercubes as object* := hypercubes:hypercubes-for-components($components-with-the-language-as-default)
    let $local-metadata as object* :=
        if ($concept-names instance of string*)
        then descendant-objects($all-hypercubes.Aspects)[$$.Name = $concept-names]
        else ()
    let $labels-from-local-metadata as string* := $local-metadata.Label
    let $labels-from-concepts-collection as string* :=
          for $concept-name in $concept-names
          return concepts:labels-for-components(
                $concept-name,
                $components-with-a-different-default-language,
                $label-role,
                $options.Language,
                $concepts,
                ())
    return ($labels-from-local-metadata, $labels-from-concepts-collection)
 };

(:~
 : 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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $options as object?) as object*
{
    let $main-members := values(values($definition-node.AspectRulesSet))

    let $labels := (
        $definition-node.Labels[],
        resolution:labels(
            $main-members,
            $components,
            $concepts,
            $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 $concepts the concept objects (for inferring the labels)
 : @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?,
    $concepts as object*,
    $options as object?) as object
{
    let $children :=  resolution:convert-definition-nodes(
        $definition-node.Children[],
        $components,
        $parent-child-order,
        $concepts,
        $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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @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?,
    $concepts 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, $concepts)
    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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @return the converted structural nodes.
 :)
declare %private function resolution:expand-concept-network(
    $network as object,
    $components as object*,
    $parent-child-order as string?,
    $concepts as object*) as object*
{
    let $concept := $network.Name
    let $default-hypercube := hypercubes:hypercubes-for-components($components, "xbrl:DefaultHypercube")
    let $concept-metadata := $default-hypercube.Aspects."xbrl:Concept".Domains."xbrl:ConceptDomain".Members.$concept[1]
    let $label :=
        resolution:labels(
            $concept,
            $components,
            $concepts,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            ())
    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
                    )
        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"
 : @param $concepts the concept objects (for inferring the labels)
 :
 : @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?,
    $concepts 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, $concepts)
    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?,
    $concepts as object*) as object
{
    let $value := $network.Name
    let $label :=
        resolution:labels(
            $value,
            $components,
            $concepts,
            ($network.PreferredLabelRole, $concepts:STANDARD_LABEL_ROLE)[1],
            ())
    return
    {|
        {
            Labels: [ $label],
            ConstraintSets: {
                "" : { $dimension : $value }
            }
        },
        let $sub-networks := values($network.Members)
        let $converted-subnetworks :=
            for $sub-network in $sub-networks
            return resolution:expand-dimension-network(
                        $dimension,
                        $sub-network,
                        $components,
                        $parent-child-order
                )
        let $roll-up :={
            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*
{
    let $default-hypercube := hypercubes:hypercubes-for-components($components, "xbrl:DefaultHypercube")
    return descendant-objects($default-hypercube.Aspects)[$$.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 $components-with-a-different-default-language :=
        $components[$$.$components:DEFAULT-LANGUAGE ne $options.Language]
    let $concepts := concepts:concepts-for-components(
        $concepts:ALL_CONCEPT_NAMES,
        $components-with-a-different-default-language)
    return
    {
        ModelKind: "StructuralModel",
        TableSetLabels: $definition-model.Labels,
        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,
                                                                $concepts,
                                                                $options)
                        ]
                    }
                |}
            }
        ],
        GlobalConstraintSet: $definition-model.TableFilters,
        GlobalConstraintLabels: {|
            for $dimension in keys($definition-model.TableFilters)
            let $value := $definition-model.TableFilters.$dimension
            return { $dimension: {
                    DimensionLabels: [ resolution:metadata($components, $dimension).Label, $dimension ],
                    ValueLabels: [ if($value instance of string) then resolution:metadata($components, $value).Label else (), $value ]
                }
            }
        |},
        DimensionDefaults: {|
            let $hypercubes as object* :=
                if(exists($options.Hypercube))
                then $options.Hypercube
                else values($components.Hypercubes)
            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 filings.jq

Update filings.jq


jq: Rename conflicts filter to conflict & keep props [ci skip] (#1326)

- Singular, same as most of our filters
- Keeping only path/time is easy to do by hand
- Doing it by default sometimes makes the filter useless

# Custom jq filters for log analysis.
# See doc/developer/log_analysis.md to get started.

# Remove fields that are almost never used while debugging.
# To be used in other filters.
def clean:
  del(.hostname) |
  del(.name) |
  del(.pid) |
  del(.v);

# Filter by log level:
#
#     yarn jq error path/to/logs*
#     yarn jq warn path/to/logs*
#
# To get strictly warnings (without errors):
#
#     yarn jq warn_strict path/to/logs*
#
def error_level: 50;
def is_error: .level >= error_level;
def error: clean | select(is_error);
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def warn: clean | select(is_warn);
def warn_strict: clean | select(is_warn_strict);
def info_level: 30;
def is_info: .level >= info_level;
def info: clean | select(is_info);
def debug_level: 20;
def is_debug: .level >= debug_level;
def debug: clean | select(is_debug);

# Components:
def component(pattern): select(.component | test(pattern));
def App: component("^App$");
def Chokidar: component("^Chokidar|chokidar$");
def Fs: component("^Fs|FS$");
def LocalAnalysis: component("^LocalAnalysis|local/analysis$");
def LocalChange: component("^LocalChange|local/change$");
def LocalWatcher: component("^LocalWatcher$");
def LocalWriter: component("^LocalWriter$");
def Merge: component("^Merge$");
def Metadata: component("^Metadata$");
def Perfs: component("^Perfs|PerfReports$");
def Pouch: component("^Pouch$");
def Prep: component("^Prep$");
def RemoteCozy: component("^RemoteCozy$");
def RemoteUserActionRequired: component("^RemoteUserActionRequired|remote/user_action_required$");
def RemoteWarningPoller: component("^RemoteWarningPoller$");
def RemoteWatcher: component("^RemoteWatcher$");
def RemoteWriter: component("^RemoteWriter$");
def Sync: component("^Sync$");

def Local: select(.component | test("^local|chokidar"; "i"));
def Remote: select(.component | test("^remote"; "i"));

# Exclude "up-to-date" messages
#
#     yarn jq nouptodate path/to/logs*
#
def nouptodate: select(.msg | test("up[- ]to[- ]date") | not);

# Exclude Proxy stuff:
#
#     yarn jq noproxy path/to/logs*
#
def noproxy: select(.component | test("GUI:proxy") | not);

# Find conflicts:
#
#     yarn jq -c conflict path/to/logs*
#
def is_conflict: .msg == "resolveConflictAsync";
def conflict: clean | select(is_conflict);

# Non-issue filters (so we can ignore them when looking for real issues):
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Find main issues:
#
#     yarn jq issues path/to/logs*
#
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def issues: clean | select(is_issue);

# Filter by path (should handle moves, conflict renaming...):
#
#     yarn jq 'path("foo/bar")' path/to/logs*
#     yarn jq 'path("foo\\\\bar")' path/to/logs*
#
# FIXME: Should not output the same line twice when both path & oldpath match.
def path(pattern):
  clean | select(
    (
      (.path // (.doc | .path) // (.change | .doc | .path) // (.idConflict | .newDoc | .path)),
      (.oldpath // (.was | .path) // (.idConflict | .existingDoc | .path)),
      ""
    )
      | strings
      | test(pattern)
  );

# Include/exclude GUI stuff:
#
#    yarn jq gui path/to/logs*
#    yarn jq no_gui path/to/logs*
#
def is_gui: .component | test("GUI");
def gui: select(is_gui);
def no_gui: select(is_gui | not);

# To make `mocha` component messages more visible in test logs:
#
#    yarn jq -c 'debug|short|mocha' debug.log
#
# Or to completely ignore them:
#
#    yarn jq -c no_mocha debug.log
#
# Please note the `mocha` filter should always be the last one since it
# converts mocha log entries to strings.
def is_mocha: .component == "mocha";
def mocha: if is_mocha then .msg | gsub("\\n+"; "") else . end;
def no_mocha: select(is_mocha | not);

# Find the OS / app versions:
#
#     yarn jq client path/to/logs*
#
def client:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Filter by file extension:
#
#    yarn jq 'ext("png")' path/to/logs*
#
# You can use/define additional filters for common extensions:
#
#    yarn jq xls path/to/logs*
#
def is_ext(x): .path | test("\\." + x);
def ext(x): select(is_ext(x));
def no_ext(x): select(is_ext(x) | not);
def xls: ext("xls");
def no_xls: no_ext("xls");
def txt: ext("txt");
def no_txt: no_ext("txt");

# Filter by msg pattern:
#
#    yarn jq 'msg("422")' path/to/logs*
#    yarn jq 'msg("offline$")' path/to/logs*
#
def msg(pattern):
  select(.msg | test(pattern));

# Filter by time pattern:
#
#    yarn jq 'time("2018-04-14T22:34:25.453Z")' path/to/logs*
#    yarn jq 'time("2018-04-14")' path/to/logs*
#    yarn jq 'time("T22:34")' path/to/logs*
#    yarn jq 'before("2018-04-14T22:34:25.453Z")' path/to/logs* // inclusive
#    yarn jq 'after("2018-04-14T22:34:25.453Z")' path/to/logs* // inclusive
#
def time(pattern):
  select(.time | test(pattern));
def before(time): select(.time <= time);
def after(time): select(.time >= time);

# Drop time properties
def notime: del(.time);

# Filter inode number(s):
#
#    yarn jq 'ino(7852458)' path/to/logs*
#    yarn jq 'ino(7852458; 464672)' path/to/logs*
#
def ino(n): select((.ino // (.doc|.ino) // (.change|.doc|.ino)) as $ino | [n] | flatten | map(. == $ino) | any);

# Filter things with an attached doc and return it:
#
#    yarn jq 'Pouch|doc' path/to/logs*
#
def doc:
  select(.doc) | .doc;

# Get a global overview of another filter:
#
#    yarn -s jq -c 'select(...)|short' path/to/logs*
#
def short:
  {time,component,msg,path,oldpath}
    | if .oldpath then . else del(.oldpath) end
    ;

# FIXME: Find a way to make aggregation work, e.g.:
#
#     yarn jq 'issues|.msg' | yarn jq:frequencies
#
# We should be able to use --slurp in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );

# Custom jq filters for log analysis.
# See doc/developer/log_analysis.md to get started.

# Remove fields that are almost never used while debugging.
# To be used in other filters.
def clean:
  del(.hostname) |
  del(.name) |
  del(.pid) |
  del(.v);

# Filter by log level:
#
#     yarn jq error path/to/logs*
#     yarn jq warn path/to/logs*
#
# To get strictly warnings (without errors):
#
#     yarn jq warn_strict path/to/logs*
#
def error_level: 50;
def is_error: .level >= error_level;
def error: clean | select(is_error);
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def warn: clean | select(is_warn);
def warn_strict: clean | select(is_warn_strict);
def info_level: 30;
def is_info: .level >= info_level;
def info: clean | select(is_info);
def debug_level: 20;
def is_debug: .level >= debug_level;
def debug: clean | select(is_debug);

# Components:
def component(pattern): select(.component | test(pattern));
def App: component("^App$");
def Chokidar: component("^Chokidar|chokidar$");
def Fs: component("^Fs|FS$");
def LocalAnalysis: component("^LocalAnalysis|local/analysis$");
def LocalChange: component("^LocalChange|local/change$");
def LocalWatcher: component("^LocalWatcher$");
def LocalWriter: component("^LocalWriter$");
def Merge: component("^Merge$");
def Metadata: component("^Metadata$");
def Perfs: component("^Perfs|PerfReports$");
def Pouch: component("^Pouch$");
def Prep: component("^Prep$");
def RemoteCozy: component("^RemoteCozy$");
def RemoteUserActionRequired: component("^RemoteUserActionRequired|remote/user_action_required$");
def RemoteWarningPoller: component("^RemoteWarningPoller$");
def RemoteWatcher: component("^RemoteWatcher$");
def RemoteWriter: component("^RemoteWriter$");
def Sync: component("^Sync$");

def Local: select(.component | test("^local|chokidar"; "i"));
def Remote: select(.component | test("^remote"; "i"));

# Exclude "up-to-date" messages
#
#     yarn jq nouptodate path/to/logs*
#
def nouptodate: select(.msg | test("up[- ]to[- ]date") | not);

# Exclude Proxy stuff:
#
#     yarn jq noproxy path/to/logs*
#
def noproxy: select(.component | test("GUI:proxy") | not);

# Find conflicts:
#
#     yarn jq -c conflicts path/to/logs*
#
def is_conflict: .msg == "resolveConflictAsync";
def conflicts: clean | select(is_conflict) | {time,path};

# Non-issue filters (so we can ignore them when looking for real issues):
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Find main issues:
#
#     yarn jq issues path/to/logs*
#
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def issues: clean | select(is_issue);

# Filter by path (should handle moves, conflict renaming...):
#
#     yarn jq 'path("foo/bar")' path/to/logs*
#     yarn jq 'path("foo\\\\bar")' path/to/logs*
#
# FIXME: Should not output the same line twice when both path & oldpath match.
def path(pattern):
  clean | select(
    (
      (.path // (.doc | .path) // (.change | .doc | .path) // (.idConflict | .newDoc | .path)),
      (.oldpath // (.was | .path) // (.idConflict | .existingDoc | .path)),
      ""
    )
      | strings
      | test(pattern)
  );

# Include/exclude GUI stuff:
#
#    yarn jq gui path/to/logs*
#    yarn jq no_gui path/to/logs*
#
def is_gui: .component | test("GUI");
def gui: select(is_gui);
def no_gui: select(is_gui | not);

# To make `mocha` component messages more visible in test logs:
#
#    yarn jq -c 'debug|short|mocha' debug.log
#
# Or to completely ignore them:
#
#    yarn jq -c no_mocha debug.log
#
# Please note the `mocha` filter should always be the last one since it
# converts mocha log entries to strings.
def is_mocha: .component == "mocha";
def mocha: if is_mocha then .msg | gsub("\\n+"; "") else . end;
def no_mocha: select(is_mocha | not);

# Find the OS / app versions:
#
#     yarn jq client path/to/logs*
#
def client:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Filter by file extension:
#
#    yarn jq 'ext("png")' path/to/logs*
#
# You can use/define additional filters for common extensions:
#
#    yarn jq xls path/to/logs*
#
def is_ext(x): .path | test("\\." + x);
def ext(x): select(is_ext(x));
def no_ext(x): select(is_ext(x) | not);
def xls: ext("xls");
def no_xls: no_ext("xls");
def txt: ext("txt");
def no_txt: no_ext("txt");

# Filter by msg pattern:
#
#    yarn jq 'msg("422")' path/to/logs*
#    yarn jq 'msg("offline$")' path/to/logs*
#
def msg(pattern):
  select(.msg | test(pattern));

# Filter by time pattern:
#
#    yarn jq 'time("2018-04-14T22:34:25.453Z")' path/to/logs*
#    yarn jq 'time("2018-04-14")' path/to/logs*
#    yarn jq 'time("T22:34")' path/to/logs*
#    yarn jq 'before("2018-04-14T22:34:25.453Z")' path/to/logs* // inclusive
#    yarn jq 'after("2018-04-14T22:34:25.453Z")' path/to/logs* // inclusive
#
def time(pattern):
  select(.time | test(pattern));
def before(time): select(.time <= time);
def after(time): select(.time >= time);

# Drop time properties
def notime: del(.time);

# Filter inode number(s):
#
#    yarn jq 'ino(7852458)' path/to/logs*
#    yarn jq 'ino(7852458; 464672)' path/to/logs*
#
def ino(n): select((.ino // (.doc|.ino) // (.change|.doc|.ino)) as $ino | [n] | flatten | map(. == $ino) | any);

# Filter things with an attached doc and return it:
#
#    yarn jq 'Pouch|doc' path/to/logs*
#
def doc:
  select(.doc) | .doc;

# Get a global overview of another filter:
#
#    yarn -s jq -c 'select(...)|short' path/to/logs*
#
def short:
  {time,component,msg,path,oldpath}
    | if .oldpath then . else del(.oldpath) end
    ;

# FIXME: Find a way to make aggregation work, e.g.:
#
#     yarn jq 'issues|.msg' | yarn jq:frequencies
#
# We should be able to use --slurp in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );


Two more.

Two more.

jsoniq version "1.0";

import module namespace components = "http://xbrl.io/modules/bizql/components";
import module namespace archives = "http://xbrl.io/modules/bizql/archives";
import module namespace filings = "http://xbrl.io/modules/bizql/profiles/sec/filings";
import module namespace fiscal = "http://xbrl.io/modules/bizql/profiles/sec/fiscal/core";
import module namespace entities = "http://xbrl.io/modules/bizql/entities";
import module namespace companies = "http://xbrl.io/modules/bizql/profiles/sec/companies";

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

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

import module namespace request = "http://www.28msec.com/modules/http-request";
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, $level as integer)
{
    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)
{
    ((session:comment("xml")),
    <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.Children[], 0)
        }</Network>
    </Component>)

};

declare function local:to-csv-rec($objects, $level as integer)
{
    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)
{
    let $lines := local:to-csv-rec($model.ModelStructure.Children[], 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

declare function local:enrich-json-rec($objects, $level as integer)
{
    for $object in $objects
    return
        copy $o := $object
        modify (
            if (exists($o.Children)) then delete json $o("Children") else (),
            insert json { Level : $level } into $o,
            if ($o.Kind eq "Domain") then replace value of json $o("Kind") with "Member" else ()
        )
        return {|
            $o,
            let $children := local:enrich-json-rec($object.Children[], $level + 1)
            return if (exists($children)) then { Children : [ $children ] } else ()
        |}
};

declare function local:enrich-json($component)
{
    {
        ModelStructure : [ local:enrich-json-rec($component.ModelStructure, 0) ] ,
        CIK : $component.CIK,
        EntityRegistrantName : $component.EntityRegistrantName,
        Label : $component.Label,
        AccessionNumber : $component.AccessionNumber,
        TableName : $component.TableName,
        FormType : $component.FormType,
        FiscalPeriod : $component.FiscalPeriod,
        FiscalYear : $component.FiscalYear,
        AcceptanceDatetime : $component.AcceptanceDatetime,
        NetworkIdentifier: $component.NetworkIdentifier,
        Disclosure : $component.Disclosure
    }
};
declare function local:components-by-disclosures($disclosures, $aids)
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("components:CONNECTION-FAILED"), $err:description)
        }
    for $aid in $aids
    return
        mongo:find($conn, "components",
        {
            $components:ARCHIVE: $aid,
            "Profiles.SEC.Disclosure": { "$in" : [ $disclosures ] }
        })
};

declare function local:components-by-concepts($concepts, $aids)
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("components:CONNECTION-FAILED"), $err:description)
        }
    let $ids := mongo:find($conn, "concepts",
        {|
            (
                { "Name" : { "$in" : [ $concepts ] } },
                { "Archive" : { "$in" : [ $aids ] } }
            )
        |}).Component
    return components:components($ids)
};

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

let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y cast as integer
                            else ()
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then ("Q1", "Q2", "Q3", "FY")
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $archives    := (
                        local:filings($ciks, $tags, $tickers, $sics, $fiscalPeriods, $fiscalYears),
                        archives:archives($aids)
                    )
let $cid         := request:param-values("cid")
let $concepts    := distinct-values(request:param-values("concept"))
let $disclosures := request:param-values("disclosure")
let $components  := if (exists($cid))
                    then components:components($cid)
                    else if (exists($concepts) or exists($disclosures))
                    then (local:components-by-concepts($concepts, $archives._id), local:components-by-disclosures($disclosures, $archives._id))
                    else components:components-for-archives($archives)
let $component := $components[1] (: only one for know :)
let $archive   := archives:archives($component.Archive)
let $entity    := entities:entities($archives.Entity)
return
     if (session:only-dow30($entity) or session:valid())
     then {
        let $model := {|
                    { CIK : $entity._id },
                    { EntityRegistrantName : $entity.Profiles.SEC.CompanyName },
                    { ModelStructure : sec-networks:model-structures($component) },
                    { TableName : sec-networks:tables($component, {IncludeImpliedTable: true}).Name },
                    { 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 }
        |}
        return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                local:to-xml($model)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=modelstructure-" || $cid || ".csv");
                local:to-csv($model)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=modelstructure-" || $cid || ".csv");
                local:to-csv($model)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    local:enrich-json($model),
                    session:comment("json")
                |}
            }
     } else {
        response:status-code(401);
        session:error("accessing fact table for an entity that is not in the DOW30", $format)
     }

jsoniq version "1.0";

import module namespace components = "http://xbrl.io/modules/bizql/components";
import module namespace archives = "http://xbrl.io/modules/bizql/archives";
import module namespace filings = "http://xbrl.io/modules/bizql/profiles/sec/filings";
import module namespace fiscal = "http://xbrl.io/modules/bizql/profiles/sec/fiscal/core";
import module namespace entities = "http://xbrl.io/modules/bizql/entities";
import module namespace companies = "http://xbrl.io/modules/bizql/profiles/sec/companies";

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

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

import module namespace request = "http://www.28msec.com/modules/http-request";
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, $level as integer)
{
    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)
{
    ((session:comment("xml")),
    <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.Children[], 0)
        }</Network>
    </Component>)

};

declare function local:to-csv-rec($objects, $level as integer)
{
    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)
{
    let $lines := local:to-csv-rec($model.ModelStructure.Children[], 0)
    return
        if (exists($lines))
        then string-join(csv:serialize($lines, { serialize-null-as : "" }))
        else ""
};

declare function local:enrich-json-rec($objects, $level as integer)
{
    for $object in $objects
    return
        copy $o := $object
        modify (
            if (exists($o.Children)) then delete json $o("Children") else (),
            insert json { Level : $level } into $o,
            if ($o.Kind eq "Domain") then replace value of json $o("Kind") with "Member" else ()
        )
        return {|
            $o,
            let $children := local:enrich-json-rec($object.Children[], $level + 1)
            return if (exists($children)) then { Children : [ $children ] } else ()
        |}
};

declare function local:enrich-json($component)
{
    {
        ModelStructure : [ local:enrich-json-rec($component.ModelStructure.Children[], 0) ] ,
        CIK : $component.CIK,
        EntityRegistrantName : $component.EntityRegistrantName,
        Label : $component.Label,
        AccessionNumber : $component.AccessionNumber,
        TableName : $component.TableName,
        FormType : $component.FormType,
        FiscalPeriod : $component.FiscalPeriod,
        FiscalYear : $component.FiscalYear,
        AcceptanceDatetime : $component.AcceptanceDatetime,
        NetworkIdentifier: $component.NetworkIdentifier,
        Disclosure : $component.Disclosure
    }
};
declare function local:components-by-disclosures($disclosures, $aids)
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("components:CONNECTION-FAILED"), $err:description)
        }
    for $aid in $aids
    return
        mongo:find($conn, "components",
        {
            $components:ARCHIVE: $aid,
            "Profiles.SEC.Disclosure": { "$in" : [ $disclosures ] }
        })
};

declare function local:components-by-concepts($concepts, $aids)
{
    let $conn :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return
        try {
            mongo:connect($credentials)
        } catch mongo:* {
            error(QName("components:CONNECTION-FAILED"), $err:description)
        }
    let $ids := mongo:find($conn, "concepts",
        {|
            (
                { "Name" : { "$in" : [ $concepts ] } },
                { "Archive" : { "$in" : [ $aids ] } }
            )
        |}).Component
    return components:components($ids)
};

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

let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y cast as integer
                            else ()
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then ("Q1", "Q2", "Q3", "FY")
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $archives    := (
                        local:filings($ciks, $tags, $tickers, $sics, $fiscalPeriods, $fiscalYears),
                        archives:archives($aids)
                    )
let $cid         := request:param-values("cid")
let $concepts    := distinct-values(request:param-values("concept"))
let $disclosures := request:param-values("disclosure")
let $components  := if (exists($cid))
                    then components:components($cid)
                    else if (exists($concepts) or exists($disclosures))
                    then (local:components-by-concepts($concepts, $archives._id), local:components-by-disclosures($disclosures, $archives._id))
                    else components:components-for-archives($archives)
let $component := $components[1] (: only one for know :)
let $archive   := archives:archives($component.Archive)
let $entity    := entities:entities($archives.Entity)
return
     if (session:only-dow30($entity) or session:valid())
     then {
        let $model := {|
                    { CIK : $entity._id },
                    { EntityRegistrantName : $entity.Profiles.SEC.CompanyName },
                    { ModelStructure : sec-networks:model-structures($component) },
                    { TableName : sec-networks:tables($component, {IncludeImpliedTable: true}).Name },
                    { 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 }
        |}
        return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                local:to-xml($model)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=modelstructure-" || $cid || ".csv");
                local:to-csv($model)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=modelstructure-" || $cid || ".csv");
                local:to-csv($model)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    local:enrich-json($model),
                    session:comment("json")
                |}
            }
     } else {
        response:status-code(401);
        session:error("accessing fact table for an entity that is not in the DOW30", $format)
     }


XQlint workaround

XQlint workaround

jsoniq version "1.0";

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

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

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

(: a short lived token used in a UI session - e.g. the accounts login for app.secxbrl.info :)
declare variable $session:TOKEN-TYPE-LOGIN := "ui-login";
(: a short or long lived token used in an application :)
declare variable $session:TOKEN-TYPE-APP := "app";

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

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

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

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

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

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

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

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

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

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

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

    $token
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

jsoniq version "1.0";

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

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

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

(: a short lived token used in a UI session - e.g. the accounts login for app.secxbrl.info :)
declare variable $session:TOKEN-TYPE-LOGIN := "ui-login";
(: a short or long lived token used in an application :)
declare variable $session:TOKEN-TYPE-APP := "app";

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

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

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

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

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

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

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

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

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

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

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

    $token
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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