:green_heart: Fix typol

:green_heart: Fix typol

jsoniq version "1.0";

(:
 : Copyright 2012-2014 28msec Inc.
 :)
(:~
 :
This module provides functionality for automatically fetching entities, etc, from the
 : appropriate profile.

 :
 : @author Ghislain Fourny
 :
 :)
module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

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

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

import module namespace seq = "http://zorba.io/modules/sequence";

(:~
 :
Return latest filings of entities and fiscal periods.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $entities a sequence of entity objects.
 : @param $fiscal-periods a set of fiscal periods such as "Q1", "Q2", "Q3", "Q4", "FY"
 :
 : @return the latest archives for given entities and fiscal periods.
 :)
declare function multiplexer:latest-filings(
  $profile-name as string,
  $entities as object*,
  $fiscal-periods as string*) as object*
{
  switch($profile-name)
  case "sec" return fiscal-core:latest-filings($entities, $fiscal-periods)
  case "japan" return
    japan:latest-filings($entities, $fiscal-periods)
  default return
    ()
};

(:~
 :
Retrieves entities depending on the profile.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $eid a sequence of EIDs.
 : @param $cik a sequence of profile-specific CIKs.
 : @param $tag a sequence of tags (e.g., indices, ALL, ...).
 : @param $ticker a sequence of stock exchange tickers.
 : @param $sic a sequence of industry group SIC codes.
 : @param $aid a sequence of archive IDs.
 : @param $options optional settings such as full-text-offset or full-text-limit
 :
 : @return the entities retrieved according to the profile specified.
 :)
declare %an:nondeterministic function multiplexer:entities(
  $profile-name as string,
  $eid as string*,
  $cik as string*,
  $tag as string*,
  $ticker as string*,
  $sic as string*,
  $full-text as string?,
  $language as string,
  $aid as string*,
  $options as object?) as object*
{
  switch($profile-name)
  case "sec" return
    for $entity in companies:companies(
      $cik,
      $tag,
      $ticker,
      $sic,
      $eid,
      $aid)
    order by $entity.Profiles.SEC.CompanyName
    return $entity
  case "japan" return
    japan:entities(
      $cik, $ticker,
      $eid, $tag,
      $full-text,
      $language,
      $aid,
      $options)
  default return
    entities:entities($eid)
};

(:~
 :
Retrieves archives depending on the profile.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $entities a sequence of entities or EIDs.
 : @param $fiscalPeriod a sequence of fiscal periods (Q1, Q2, Q3, Q4, FY).
 : @param $fiscalYear a sequence of fiscal years.
 : @param $filingKind a sequence of filing kinds to filter for.
 : @param $aid a sequence of AIDs.
 :
 : @return the archives retrieved according to the profile specified.
 :)
declare function multiplexer:filings(
  $profile-name as string,
  $entities as item*,
  $fiscalPeriod as string*,
  $fiscalYear as integer*,
  $filingKind as string*,
  $aid as string*) as object*
{
  switch($profile-name)
  case "sec" return fiscal-core:filings(
      $entities,
      $fiscalPeriod,
      $fiscalYear,
      $aid)
  case "japan" return japan:filings(
      $entities,
      $fiscalYear,
      $fiscalPeriod,
      $filingKind,
      $aid)
  default return (
      archives:archives($aid),
      archives:archives-for-entities($entities))
};

(:~
  :
Retrieves components depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  : searched.
  : @param $options additional options. among which

  :
MetadataOnly: to only get Archive and Role information.

  : </lu>
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the components retrieved according to the profile specified.
:)
declare function multiplexer:components(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $options as object?) as object*
{
  switch($profile-name)
  case "sec" return sec-networks:components(
    $archive,
    $cid,
    $concept,
    $disclosure,
    $role,
    $full-text-label)
  default return
    let $role as string* := if(empty($role))
                            then $components:ALL-ROLES
                            else $role
    let $archive as item* := if(empty($archive))
                             then $components:ALL-ARCHIVES
                             else $archive
    let $concept as item* := if(empty($concept))
                             then $components:ALL-CONCEPTS
                             else $concept
    let $exact-label as item* := if(empty($exact-label))
                            then $components:ALL-LABELS
                            else $exact-label
    return components:components-for(
      $archive,
      $role,
      $concept,
      $exact-label,
      {|
        $options,
        {
          ExtraFilter: {
           "Profiles" : { "FSA" : { "Disclosure" : [ $disclosure ]
          } } }
        }[$profile-name eq "japan" and exists($disclosure)]
      |}
    )
};

(:~
  :
Retrieves one component depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  : @param $hypercubes a list of hypercubes to filter.
  : @param $merge true to merge all found components, false to pick one.
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the component retrieved according to the profile specified.
:)
declare function multiplexer:component(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $hypercubes as string*,
  $merge as boolean) as object?
{
  let $components := multiplexer:components(
      $profile-name,
      $archive,
      $cid,
      $concept,
      $disclosure,
      $role,
      $exact-label,
      $full-text-label)
  let $components := for $c in $components
                     order by $c.Archive, $c.Role
                     return $c
  let $components :=
    if(empty($hypercubes))
    then $components
    else
      for $c in $components
      return if(every $h in keys($c.Hypercubes) satisfies $h = $hypercubes)
             then $c
             else {|
               trim($c, "Hypercubes"),
               {
                 Hypercubes: project($c.Hypercubes, $hypercubes)
               }
             |}
  return switch(true)
         case empty($components)
             return ()
         case count($components) eq 1 and count(keys($components.Hypercubes)) eq 1
             return $components
         case $merge
             return components:merge($components)
         default
             return $components[1]
};

(:~
  :
Retrieves one hypercube from a component.

  :
  : @param $component a component.
  : @param $hypercubes a list of hypercubes.
  :
  : @return a hypercube name.
  :)
declare function multiplexer:hypercube(
  $component as object?,
  $hypercubes as string*
) as string?
{
  let $component-hypercubes as string* :=
      hypercubes:hypercubes-for-components($component).Name
  return $component-hypercubes[empty($hypercubes) or $$ = $hypercubes][1]
};

(:~
  :
Retrieves all hypercubes from components.

  :
  : @param $component a sequence of components.
  : @param $hypercubes a list of hypercubes to filer.
  :
  : @return a sequence of hypercube names.
  :)
declare function multiplexer:hypercubes(
  $components as object*,
  $hypercubes as string*
) as string*
{
  let $component-hypercubes as string* :=
      hypercubes:hypercubes-for-components($components).Name
  return distinct-values($component-hypercubes
      [empty($hypercubes) or $$ = $hypercubes])
};

(:~
  :
Retrieves components depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the components retrieved according to the profile specified.
:)
declare function multiplexer:components(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*) as object*
{
  multiplexer:components(
    $profile-name,
    $archive,
    $cid,
    $concept,
    $disclosure,
    $role,
    $exact-label,
    $full-text-label,
    ())
};

declare function multiplexer:concepts(
  $profile-name as string,
  $archive as object*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $report as object?,
  $onlyNames as boolean) as object*
{
  switch(true)
  case exists($report)
    return
    let $map as object? := reports:concept-map($report)
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($concept) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $c as string in $concept[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $c]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* :=
        seq:value-except($concept, $mapped-names)
    let $results-not-computed-by-maps as object* :=
        if(exists($concepts-not-computable-by-maps))
        then
          multiplexer:concepts(
            $profile-name,
            $archive,
            $concepts-not-computable-by-maps,
            $disclosure,
            $role,
            $exact-label,
            $full-text-label,
            (),
            $onlyNames
          )
        else ()
    let $results-computed-by-maps as object* :=
        let $all-results as object* := multiplexer:concepts(
          $profile-name,
          $archive,
          $mapped-names,
          $disclosure,
          $role,
          $exact-label,
          $full-text-label,
          (),
          $onlyNames
        )
        for $c as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $c.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $n
            where $n eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return ($results-not-computed-by-maps, $results-computed-by-maps)

  case empty($disclosure) and empty($report) and empty($full-text-label)
    return
    let $role as string* := if(empty($role))
                            then $concepts:ANY_COMPONENT_LINK_ROLE
                            else $role
    let $concept as item* := if(empty($concept))
                             then $concepts:ALL_CONCEPT_NAMES
                             else $concept
    let $exact-label as item* := if(empty($exact-label))
                            then $concepts:ALL_CONCEPT_LABELS
                            else $exact-label
    return concepts:concepts(
      $concept,
      $archive,
      $role,
      $exact-label,
      { OnlyNames: $onlyNames}
    )
  default return
    let $components as object* :=
      multiplexer:components(
        $profile-name,
        $archive,
        (),
        $concept,
        $disclosure,
        $role,
        $exact-label,
        $full-text-label,
        { MetadataOnly: true })
    let $exact-label as item* := if(empty($exact-label))
                            then $concepts:ALL_CONCEPT_LABELS
                            else $exact-label
    let $concept as item* := if(empty($concept))
                             then $concepts:ALL_CONCEPT_NAMES
                             else $concept
    return concepts:concepts-for-components(
      $concept,
      $exact-label,
      $components,
      { OnlyNames: $onlyNames})
};

jsoniq version "1.0";

(:
 : Copyright 2012-2014 28msec Inc.
 :)
(:~
 :
This module provides functionality for automatically fetching entities, etc, from the
 : appropriate profile.

 :
 : @author Ghislain Fourny
 :
 :)
module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

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

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

import module namespace seq = "http://zorba.io/modules/sequence";

(:~
 :
Return latest filings of entities and fiscal periods.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $entities a sequence of entity objects.
 : @param $fiscal-periods a set of fiscal periods such as "Q1", "Q2", "Q3", "Q4", "FY"
 :
 : @return the latest archives for given entities and fiscal periods.
 :)
declare function multiplexer:latest-filings(
  $profile-name as string,
  $entities as object*,
  $fiscal-periods as string*) as object*
{
  switch($profile-name)
  case "sec" return fiscal-core:latest-filings($entities, $fiscal-periods)
  case "japan" return
    japan:latest-filings($entities, $fiscal-periods)
  default return
    ()
};

(:~
 :
Retrieves entities depending on the profile.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $eid a sequence of EIDs.
 : @param $cik a sequence of profile-specific CIKs.
 : @param $tag a sequence of tags (e.g., indices, ALL, ...).
 : @param $ticker a sequence of stock exchange tickers.
 : @param $sic a sequence of industry group SIC codes.
 : @param $aid a sequence of archive IDs.
 : @param $options optional settings such as full-text-offset or full-text-limit
 :
 : @return the entities retrieved according to the profile specified.
 :)
declare %an:nondeterministic function multiplexer:entities(
  $profile-name as string,
  $eid as string*,
  $cik as string*,
  $tag as string*,
  $ticker as string*,
  $sic as string*,
  $full-text as string?,
  $language as string,
  $aid as string*,
  $options as object?) as object*
{
  switch($profile-name)
  case "sec" return
    for $entity in companies:companies(
      $cik,
      $tag,
      $ticker,
      $sic,
      $eid,
      $aid)
    order by $entity.Profiles.SEC.CompanyName
    return $entity
  case "japan" return
    japan:entities(
      $cik, $ticker,
      $eid, $tag,
      $full-text,
      $language,
      $aid,
      $options)
  default return
    entities:entities($eid)
};

(:~
 :
Retrieves archives depending on the profile.

 :
 : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
 : @param $entities a sequence of entities or EIDs.
 : @param $fiscalPeriod a sequence of fiscal periods (Q1, Q2, Q3, Q4, FY).
 : @param $fiscalYear a sequence of fiscal years.
 : @param $filingKind a sequence of filing kinds to filter for.
 : @param $aid a sequence of AIDs.
 :
 : @return the archives retrieved according to the profile specified.
 :)
declare function multiplexer:filings(
  $profile-name as string,
  $entities as item*,
  $fiscalPeriod as string*,
  $fiscalYear as integer*,
  $filingKind as string*,
  $aid as string*) as object*
{
  switch($profile-name)
  case "sec" return fiscal-core:filings(
      $entities,
      $fiscalPeriod,
      $fiscalYear,
      $aid)
  case "japan" return japan:filings(
      $entities,
      $fiscalYear,
      $fiscalPeriod,
      $filingKind,
      $aid)
  default return (
      archives:archives($aid),
      archives:archives-for-entities($entities))
};

(:~
  :
Retrieves components depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  : searched.
  : @param $options additional options. among which

  :
MetadataOnly: to only get Archive and Role information.

  : </lu>
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the components retrieved according to the profile specified.
:)
declare function multiplexer:components(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $options as object?) as object*
{
  switch($profile-name)
  case "sec" return sec-networks:components(
    $archive,
    $cid,
    $concept,
    $disclosure,
    $role,
    $full-text-label)
  default return
    let $role as string* := if(empty($role))
                            then $components:ALL-ROLES
                            else $role
    let $archive as item* := if(empty($archive))
                             then $components:ALL-ARCHIVES
                             else $archive
    let $concept as item* := if(empty($concept))
                             then $components:ALL-CONCEPTS
                             else $concept
    let $exact-label as item* := if(empty($exact-label))
                            then $components:ALL-LABELS
                            else $exact-label
    return components:components-for(
      $archive,
      $role,
      $concept,
      $exact-label,
      {|
        $options,
        {
          ExtraFilter: {
           "Profiles" : { "FSA" : { "Disclosure" : [ $disclosure ]
          } } }
        }[$profile-name eq "japan" and exists($disclosure)]
      |}
    )
};

(:~
  :
Retrieves one component depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  : @param $hypercubes a list of hypercubes to filter.
  : @param $merge true to merge all found components, false to pick one.
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the component retrieved according to the profile specified.
:)
declare function multiplexer:component(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $hypercubes as string*,
  $merge as boolean) as object?
{
  let $components := multiplexer:components(
      $profile-name,
      $archive,
      $cid,
      $concept,
      $disclosure,
      $role,
      $exact-label,
      $full-text-label)
  let $components := for $c in components
                     order by $c.Archive, $c.Role
                     return $c
  let $components :=
    if(empty($hypercubes))
    then $components
    else
      for $c in $components
      return if(every $h in keys($c.Hypercubes) satisfies $h = $hypercubes)
             then $c
             else {|
               trim($c, "Hypercubes"),
               {
                 Hypercubes: project($c.Hypercubes, $hypercubes)
               }
             |}
  return switch(true)
         case empty($components)
             return ()
         case count($components) eq 1 and count(keys($components.Hypercubes)) eq 1
             return $components
         case $merge
             return components:merge($components)
         default
             return $components[1]
};

(:~
  :
Retrieves one hypercube from a component.

  :
  : @param $component a component.
  : @param $hypercubes a list of hypercubes.
  :
  : @return a hypercube name.
  :)
declare function multiplexer:hypercube(
  $component as object?,
  $hypercubes as string*
) as string?
{
  let $component-hypercubes as string* :=
      hypercubes:hypercubes-for-components($component).Name
  return $component-hypercubes[empty($hypercubes) or $$ = $hypercubes][1]
};

(:~
  :
Retrieves all hypercubes from components.

  :
  : @param $component a sequence of components.
  : @param $hypercubes a list of hypercubes to filer.
  :
  : @return a sequence of hypercube names.
  :)
declare function multiplexer:hypercubes(
  $components as object*,
  $hypercubes as string*
) as string*
{
  let $component-hypercubes as string* :=
      hypercubes:hypercubes-for-components($components).Name
  return distinct-values($component-hypercubes
      [empty($hypercubes) or $$ = $hypercubes])
};

(:~
  :
Retrieves components depending on the profile.

  :
  : @param $profile-name the name of the profile (e.g., SEC, Japan, Generic).
  : @param $archives a sequence of archive objects.
  : @param $cid a sequence of CIDs.
  : @param $concept a sequence of report element names.
  : @param $disclosure a sequence of disclosure names.
  : @param $role a sequence of network identifiers.
  : @param $exact-label a sequence of labels for exact matches.
  : @param $full-text-label a sequence of labels for full-text search.
  :
  : @error multiplexer:ARCHIVE-MISSING if a AID is required but not provided.
  :
  : @return the components retrieved according to the profile specified.
:)
declare function multiplexer:components(
  $profile-name as string,
  $archive as object*,
  $cid as string*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*) as object*
{
  multiplexer:components(
    $profile-name,
    $archive,
    $cid,
    $concept,
    $disclosure,
    $role,
    $exact-label,
    $full-text-label,
    ())
};

declare function multiplexer:concepts(
  $profile-name as string,
  $archive as object*,
  $concept as string*,
  $disclosure as string*,
  $role as string*,
  $exact-label as string*,
  $full-text-label as string*,
  $report as object?,
  $onlyNames as boolean) as object*
{
  switch(true)
  case exists($report)
    return
    let $map as object? := reports:concept-map($report)
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($concept) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $c as string in $concept[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $c]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* :=
        seq:value-except($concept, $mapped-names)
    let $results-not-computed-by-maps as object* :=
        if(exists($concepts-not-computable-by-maps))
        then
          multiplexer:concepts(
            $profile-name,
            $archive,
            $concepts-not-computable-by-maps,
            $disclosure,
            $role,
            $exact-label,
            $full-text-label,
            (),
            $onlyNames
          )
        else ()
    let $results-computed-by-maps as object* :=
        let $all-results as object* := multiplexer:concepts(
          $profile-name,
          $archive,
          $mapped-names,
          $disclosure,
          $role,
          $exact-label,
          $full-text-label,
          (),
          $onlyNames
        )
        for $c as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $c.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $n
            where $n eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return ($results-not-computed-by-maps, $results-computed-by-maps)

  case empty($disclosure) and empty($report) and empty($full-text-label)
    return
    let $role as string* := if(empty($role))
                            then $concepts:ANY_COMPONENT_LINK_ROLE
                            else $role
    let $concept as item* := if(empty($concept))
                             then $concepts:ALL_CONCEPT_NAMES
                             else $concept
    let $exact-label as item* := if(empty($exact-label))
                            then $concepts:ALL_CONCEPT_LABELS
                            else $exact-label
    return concepts:concepts(
      $concept,
      $archive,
      $role,
      $exact-label,
      { OnlyNames: $onlyNames}
    )
  default return
    let $components as object* :=
      multiplexer:components(
        $profile-name,
        $archive,
        (),
        $concept,
        $disclosure,
        $role,
        $exact-label,
        $full-text-label,
        { MetadataOnly: true })
    let $exact-label as item* := if(empty($exact-label))
                            then $concepts:ALL_CONCEPT_LABELS
                            else $exact-label
    let $concept as item* := if(empty($concept))
                             then $concepts:ALL_CONCEPT_NAMES
                             else $concept
    return concepts:concepts-for-components(
      $concept,
      $exact-label,
      $components,
      { OnlyNames: $onlyNames})
};


fixed missing whitespace

fixed missing whitespace

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

let $schema := parse-json(fetch:content("/private/ReportSchema.jq", "MODULE"))

(: ################
   for whom do you want to reset/init the FACs?
   ################ :)

let $user := "admin@28.io"
return

(: ################
   don't change anything below this line
   ################ :)

if($user eq "admin@28.io")
then
    replace value of json $schema."_id" with "FundamentalAccountingConcepts";
else
    {
        replace value of json $schema."_id" with $schema."_id" || substring-before($user, "@");
        delete json $schema.ACL;
    };

replace value of json $schema."Owner" with $user;

let $record := find("reports", { "_id" : $schema."_id" })
return
if(empty($record))
then insert("reports", $schema);
else edit($record, $schema);

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

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

let$schema := parse-json(fetch:content("/private/ReportSchema.jq", "MODULE"))

(: ################
   for whom do you want to reset/init the FACs?
   ################ :)

let $user := "admin@28.io"
return

(: ################
   don't change anything below this line
   ################ :)

if($user eq "admin@28.io")
then
    replace value of json $schema."_id" with "FundamentalAccountingConcepts";
else
    {
        replace value of json $schema."_id" with $schema."_id" || substring-before($user, "@");
        delete json $schema.ACL;
    };

replace value of json $schema."Owner" with $user;

let $record := find("reports", { "_id" : $schema."_id" })
return
if(empty($record))
then insert("reports", $schema);
else edit($record, $schema);

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


:walking: Merge back.

:walking: Merge back.

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

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

session:audit-call($token);

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

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

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

let $entities as object* :=
    ($entities[$$._id = $archives.Entity],
    let $not-found := $archives.Entity[not $entities._id = $$]
    where exists($not-found)
    return entities:entities($not-found))
let $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels($archives._id, $label[1])
        else local:concepts-for-archives($archives._id, $name, $map, { OnlyNames: $onlyNames })
let $result :=
  let $all-aids := $concepts.Archive
  let $roles := $concepts.Role
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      if ($onlyNames)
      then distinct-values($concepts.Name)
      else
        if($profile-name eq "sec")
        then
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $component.Concepts[]
                let $archive as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[$$._id eq $archive.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive._id,
                    CIK : $entity._id,
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members[$$.Name eq $original-name], ("Name", "Labels")),
                    $metadata
                |}
       else
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $component.Concepts[]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                let $concept-in-component := $members[$$.Name eq $original-name]
                where not $onlyTextBlocks or $concept-in-component.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ],
                      Facts: backend:url("facts", {|
                        {
                          "xbrl:Concept": $original-name,
                          aid: $archive,
                          format: $format,
                          profile-name: $profile-name,
                          token: $token
                        },
                        {
                          fiscalYear: "ALL",
                          fiscalPeriod: "ALL",
                          fiscalPeriodType: "ALL"
                        }[$profile-name eq "japan"]
                      |}, true)
                    },
                    trim($concept-in-component, ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml(trim($res.ReportElements[], "Labels"), "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          string-join(csv:serialize(trim($res.ReportElements[], "Labels"), { serialize-null-as : "" }))
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

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

session:audit-call($token);

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

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

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

let $entities as object* :=
    ($entities[$$._id = $archives.Entity],
    let $not-found := $archives.Entity[not $entities._id = $$]
    where exists($not-found)
    return entities:entities($not-found))
let $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels($archives._id, $label[1])
        else local:concepts-for-archives($archives._id, $name, $map, { OnlyNames: $onlyNames })
let $result :=
  let $all-aids := $concepts.Archive
  let $roles := $concepts.Role
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      if ($onlyNames)
      then distinct-values($concepts.Name)
      else
        if($profile-name eq "sec")
        then
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $component.Concepts[]
                let $archive as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[$$._id eq $archive.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive._id,
                    CIK : $entity._id,
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members[$$.Name eq $original-name], ("Name", "Labels")),
                    $metadata
                |}
       else
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $component.Concepts[]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                let $concept-in-component := $members[$$.Name eq $original-name]
                where not $onlyTextBlocks or $concept-in-component.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ],
                      Fact: backend:url("facts", {
                          "xbrl:Concept": $original-name,
                          aid: $archive,
                          fiscalYear: "ALL",
                          fiscalPeriod: "ALL",
                          fiscalPeriodType: "ALL",
                          format: $format,
                          profile-name: $profile-name,
                          token: $token
                          }, true)
                    },
                    trim($concept-in-component, ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml(trim($res.ReportElements[], "Labels"), "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          string-join(csv:serialize(trim($res.ReportElements[], "Labels"), { serialize-null-as : "" }))
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)


:bug: Fix serialization of report elements endpoint.

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

declare function local:to-csv($concepts as item*, $onlyNames as boolean) as string
{
};

declare function local:to-xml($concepts as item*, $onlyNames as boolean) as element()*
{

};

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

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

session:audit-call($token);

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

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

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

let $entities as object* :=
    ($entities[$$._id = $archives.Entity],
    let $not-found := $archives.Entity[not $entities._id = $$]
    where exists($not-found)
    return entities:entities($not-found))
let $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels($archives._id, $label[1])
        else local:concepts-for-archives($archives._id, $name, $map, { OnlyNames: $onlyNames })

let $result :=
    if($profile-name eq "sec")
    then {
        ReportElements : [
            if ($onlyNames)
            then distinct-values($concepts.Name)
            else
                let $all-aids := $concepts.Archive
                let $roles := $concepts.Role
                let $components := components:components-for-archives-and-roles($all-aids, $roles)
                return
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $components.Concepts[]
                let $archive as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[$$._id eq $archive.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive._id,
                    CIK : $entity._id,
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members[$$.Name eq $original-name], ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
    else {
        ReportElements : [
            if ($onlyNames)
            then distinct-values($concepts.Name)
            else
                let $all-aids := $concepts.Archive
                let $roles := $concepts.Role
                let $components := components:components-for-archives-and-roles($all-aids, $roles)
                return
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $components.Concepts[]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members[$$.Name eq $original-name][1], ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml(trim($res.ReportElements[], "Labels"), "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          string-join(csv:serialize(trim($res.ReportElements[], "Labels"), { serialize-null-as : "" }))
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

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

declare function local:to-xml($concepts as item*, $onlyNames as boolean) as element()*
{
    for $c in $concepts
    return
        <ReportElement>{
             if ($onlyNames)
             then
                <Name>{$c}</Name>
             else (
                <Name>{$c.Name}</Name>,
                <Label>{$c.Label}</Label>,
                <IsNillable>{$c.IsNillable}</IsNillable>,
                <IsAbstract>{$c.IsAbstract}</IsAbstract>,
                <PeriodType>{$c.PeriodType}</PeriodType>,
                <Balance>{$c.Balance}</Balance>,
                <SubstitutionGroup>{$c.SubstitutionGroup}</SubstitutionGroup>,
                <DataType>{$c.DataType}</DataType>,
                <BaseType>{$c.BaseType}</BaseType>,
                <ClosestSchemaBuiltinType>{$c.ClosestSchemaBuiltinType}</ClosestSchemaBuiltinType>,
                <IsTextBlock>{$c.IsTextBlock}</IsTextBlock>,
                <ComponentRole>{$c.ComponentRole}</ComponentRole>,
                <ComponentLabel>{$c.ComponentLabel}</ComponentLabel>,
                <AccessionNumber>{$c.AccessionNumber}</AccessionNumber>,
                <CIK>{$c.CIK}</CIK>,
                <EntityRegistrantName>{$c.EntityRegistrantName}</EntityRegistrantName>,
                <FiscalYear>{$c.FiscalYear}</FiscalYear>,
                <FiscalPeriod>{$c.FiscalPeriod}</FiscalPeriod>
             )
        }</ReportElement>
};

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

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

session:audit-call($token);

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

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

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

let $entities as object* :=
    ($entities[$$._id = $archives.Entity],
    let $not-found := $archives.Entity[not $entities._id = $$]
    where exists($not-found)
    return entities:entities($not-found))
let $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels($archives._id, $label[1])
        else local:concepts-for-archives($archives._id, $name, $map, { OnlyNames: $onlyNames })

let $result :=
    if($profile-name eq "sec")
    then {
        ReportElements : [
            if ($onlyNames)
            then distinct-values($concepts.Name)
            else
                let $all-aids := $concepts.Archive
                let $roles := $concepts.Role
                let $components := components:components-for-archives-and-roles($all-aids, $roles)
                return
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $components.Concepts[]
                let $archive as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[$$._id eq $archive.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive._id,
                    CIK : $entity._id,
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members.$original-name, ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
    else {
        ReportElements : [
            if ($onlyNames)
            then distinct-values($concepts.Name)
            else
                let $all-aids := $concepts.Archive
                let $roles := $concepts.Role
                let $components := components:components-for-archives-and-roles($all-aids, $roles)
                return
                for $concept in $concepts
                group by $archive := $concept.Archive,  $role := $concept.Role
                let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
                let $members as object* := $components.Concepts[]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: [
                        for $labelRole in keys($concept.Labels)
                        for $language in keys($concept.Labels.$labelRole)
                        return {
                          Role: $labelRole,
                          Language: $language,
                          Value: $concept.Labels.$labelRole.$language
                        }
                      ]
                    },
                    trim($members.$original-name, ("Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
                    local:to-xml($res.ReportElements[], $onlyNames)
                }</ReportElements>
    },
    to-csv : function($res as object) as string {
        local:to-csv($res.ReportElements[], $onlyNames)
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)