Fixed wrapper

Fixed wrapper

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

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

(: Indexes declarations :)
declare variable $mw:ARCHIVES-ID as string := "_id_";
declare variable $mw:ARCHIVES-ENTITY as string := "Entity_hashed";

declare variable $mw:COMPONENTS-ID as string := "_id_";
declare variable $mw:COMPONENTS-ARCHIVE as string := "Archive_hashed";
declare variable $mw:COMPONENTS-PROFILE_SEC_DISCLOSURE as string := "Profiles.SEC.Disclosure_hashed";
declare variable $mw:COMPONENTS-ROLE as string := "Role_hashed";

declare variable $mw:CONCEPTS-ID as string := "_id_";
declare variable $mw:CONCEPTS-ARCHIVE-ROLE-NAME as string := "Archive_1_Role_1_Name_1";
declare variable $mw:CONCEPTS-NAME-ARCHIVE as string := "Name_1_Archive_1";

declare variable $mw:ENTITIES-ID as string := "_id_";
declare variable $mw:ENTITIES-PROFILES_SEC_COMPANYTYPE as string := "Profiles.SEC.CompanyType_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_SIC as string := "Profiles.SEC.SIC_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_SECTOR as string := "Profiles.SEC.Sector_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_TAGS as string := "Profiles.SEC.Tags_1";
declare variable $mw:ENTITIES-PROFILES_SEC_TICKERS as string := "Profiles.SEC.Tickers_1";

declare variable $mw:FACTS-ID as string := "_id_";
declare variable $mw:FACTS-AXCONCEPT_AXENTITY_ASFY_ASFP as string := "Aspects.xbrl:Concept_1_Aspects.xbrl:Entity_1_Aspects.sec:FiscalYear_1_Aspects.sec:FiscalPeriod_1";
declare variable $mw:FACTS-ASARCHIVE_AXCONCEPT as string := "Aspects.sec:Archive_1_Aspects.xbrl:Concept_1";

declare variable $mw:FOOTNOTES-ID as string := "_id_";
declare variable $mw:FOOTNOTES-ARCHIVE as string := "Archive_hashed";
declare variable $mw:FOOTNOTES-FACTS-LANG as string := "Facts._id_1_Lang_1";

declare variable $mw:ALL-INDEXES as object :=
{
  "archives" : [ $mw:ARCHIVES-ID, $mw:ARCHIVES-ENTITY ],
  "components": [ $mw:COMPONENTS-ID, $mw:COMPONENTS-ARCHIVE, $mw:COMPONENTS-PROFILE_SEC_DISCLOSURE, $mw:COMPONENTS-ROLE ],
  "concepts": [ $mw:CONCEPTS-ID, $mw:CONCEPTS-ARCHIVE-ROLE-NAME, $mw:CONCEPTS-NAME-ARCHIVE ],
  "entities": [ $mw:ENTITIES-ID, $mw:ENTITIES-PROFILES_SEC_COMPANYTYPE, $mw:ENTITIES-PROFILES_SEC_SIC,
               $mw:ENTITIES-PROFILES_SEC_SECTOR, $mw:ENTITIES-PROFILES_SEC_TAGS, $mw:ENTITIES-PROFILES_SEC_TICKERS ],
  "facts": [ $mw:FACTS-ID, $mw:FACTS-AXCONCEPT_AXENTITY_ASFY_ASFP, $mw:FACTS-ASARCHIVE_AXCONCEPT ],
  "footnotes": [ $mw:FOOTNOTES-ID, $mw:FOOTNOTES-ARCHIVE, $mw:FOOTNOTES-FACTS-LANG ]
};

declare %an:strictlydeterministic function mw:connection() as anyURI
{
  let $credentials :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return if (empty($credentials))
             then error(QName("mw:CONNECTION-FAILED"), "no xbrl MongoDB configured")
             else $credentials
  return
    try {
      mongo:connect($credentials)
    } catch mongo:* {
      error(QName("mw:CONNECTION-FAILED"), $err:description)
    }
};

declare function mw:find($collection as string, $query as object) as object()*
{
  let $conn := mw:connection()
  return mongo:find($conn, $collection, mw:hint($collection, $query))
};

declare function mw:find($collection as string, $query as object, $projection as object) as object()*
{
  let $conn := mw:connection()
  return mongo:find($conn, $collection, mw:hint($collection, $query), $projection)
};

declare function mw:run-cmd-deterministic($command as object) as object*
{
  let $conn := mw:connection()
  return mongo:run-cmd-deterministic($conn, $command)
};

declare %private function mw:hint($collection as string, $query as object) as object
{
  switch($collection)
    case "archives"
      return mw:archives-hint($query)
    case "components"
      return mw:components-hint($query)
    case "concepts"
      return mw:concepts-hint($query)
    case "entities"
      return mw:entities-hint($query)
    case "facts"
      return mw:facts-hint($query)
    case "footnotes"
      return mw:footnotes-hint($query)
    default
      return $query
};

declare %private function mw:archives-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Entity")))
      return { "$query": $query, "$hint": "Entity_hashed" }
    default
      return $query
};

declare %private function mw:components-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_hashed" }
    case (exists($query("Profiles.SEC.Disclosure")))
      return { "$query": $query, "$hint": "Profiles.SEC.Disclosure_hashed" }
    case (exists($query("Role")))
      return { "$query": $query, "$hint": "Role_hashed" }
    default
      return $query
};

declare %private function mw:concepts-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")) and exists($query("Role")))
      return { "$query": $query, "$hint": "Archive_1_Role_1_Name_1" }
    case (exists($query("Archive")) and exists($query("Name")))
      return { "$query": $query, "$hint": "Name_1_Archive_1" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_1_Role_1_Name_1" }
    default
      return $query
};

declare %private function mw:entities-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Profiles.SEC.CompanyType")))
      return { "$query": $query, "$hint": "Profiles.SEC.CompanyType_hashed" }
    case (exists($query("Profiles.SEC.SIC")))
      return { "$query": $query, "$hint": "Profiles.SEC.SIC_hashed" }
    case (exists($query("Profiles.SEC.Sector")))
      return { "$query": $query, "$hint": "Profiles.SEC.Sector_hashed" }
    case (exists($query("Profiles.SEC.Tags")))
      return { "$query": $query, "$hint": "Profiles.SEC.Tags_1" }
    case (exists($query("Profiles.SEC.Tickers")))
      return { "$query": $query, "$hint": "Profiles.SEC.Tickers_1" }
    default
      return $query
};

declare %private function mw:facts-hint($query as object) as object
{
    switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Aspects.xbrl:Concept")) and
          exists($query("Aspects.xbrl:Entity")) and
          exists($query("Aspects.sec:FiscalYear")) and
          exists($query("Aspects.sec:FiscalPeriod")))
      return { "$query": $query, "$hint": "Aspects.xbrl:Concept_1_Aspects.xbrl:Entity_1_Aspects.sec:FiscalYear_1_Aspects.sec:FiscalPeriod_1" }
    case (exists($query("Aspects.sec:Archive")) and exists($query("Aspects.xbrl:Concept")))
      return { "$query": $query, "$hint": "Aspects.sec:Archive_1_Aspects.xbrl:Concept_1" }
    default
      return $query
};

declare %private function mw:footnotes-hint($query as object) as object
{
    switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_hashed" }
    case (exists($query("Facts._id")) and exists($query("Lang")))
      return { "$query": $query, "$hint": "Facts._id_1_Lang_1" }
    default
      return $query
};

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

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

(: Indexes declarations :)
declare variable $mw:ARCHIVES-ID as string := "_id_";
declare variable $mw:ARCHIVES-ENTITY as string := "Entity_hashed";

declare variable $mw:COMPONENTS-ID as string := "_id_";
declare variable $mw:COMPONENTS-ARCHIVE as string := "Archive_hashed";
declare variable $mw:COMPONENTS-PROFILE_SEC_DISCLOSURE as string := "Profiles.SEC.Disclosure_hashed";
declare variable $mw:COMPONENTS-ROLE as string := "Role_hashed";

declare variable $mw:CONCEPTS-ID as string := "_id_";
declare variable $mw:CONCEPTS-ARCHIVE-ROLE-NAME as string := "Archive_1_Role_1_Name_1";
declare variable $mw:CONCEPTS-NAME-ARCHIVE as string := "Name_1_Archive_1";

declare variable $mw:ENTITIES-ID as string := "_id_";
declare variable $mw:ENTITIES-PROFILES_SEC_COMPANYTYPE as string := "Profiles.SEC.CompanyType_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_SIC as string := "Profiles.SEC.SIC_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_SECTOR as string := "Profiles.SEC.Sector_hashed";
declare variable $mw:ENTITIES-PROFILES_SEC_TAGS as string := "Profiles.SEC.Tags_1";
declare variable $mw:ENTITIES-PROFILES_SEC_TICKERS as string := "Profiles.SEC.Tickers_1";

declare variable $mw:FACTS-ID as string := "_id_";
declare variable $mw:FACTS-AXCONCEPT_AXENTITY_ASFY_ASFP as object := "Aspects.xbrl:Concept_1_Aspects.xbrl:Entity_1_Aspects.sec:FiscalYear_1_Aspects.sec:FiscalPeriod_1";
declare variable $mw:FACTS-ASARCHIVE_AXCONCEPT as object := "Aspects.sec:Archive_1_Aspects.xbrl:Concept_1";

declare variable $mw:FOOTNOTES-ID as string := "_id_";
declare variable $mw:FOOTNOTES-ARCHIVE as string := "Archive_hashed";
declare variable $mw:FOOTNOTES-FACTS-LANG as string := "Facts._id_1_Lang_1";

declare variable $mw:ALL-INDEXES :=
{
  "archives" : [ $mw:ARCHIVES-ID, $mw:ARCHIVES-ENTITY ],
  "components": [ $mw:COMPONENTS-ID, $mw:COMPONENTS-ARCHIVE, $mw:COMPONENTS-PROFILE_SEC_DISCLOSURE, $mw:COMPONENTS-ROLE ],
  "concepts": [ $mw:CONCEPTS-ID, $mw:CONCEPTS-ARCHIVE-ROLE-NAME, $mw:CONCEPTS-NAME-ARCHIVE ],
  "entities": [ $mw:ENTITIES-ID, $mw:ENTITIES-PROFILES_SEC_COMPANYTYPE, $mw:ENTITIES-PROFILES_SEC_SIC,
               $mw:ENTITIES-PROFILES_SEC_SECTOR, $mw:ENTITIES-PROFILES_SEC_TAGS, $mw:ENTITIES-PROFILES_SEC_TICKERS ],
  "facts": [ $mw:FACTS-ID, $mw:FACTS-AXCONCEPT_AXENTITY_ASFY_ASFP, $mw:FACTS-ASARCHIVE_AXCONCEPT ],
  "footnotes": [ $mw:FOOTNOTES-ID, $mw:FOOTNOTES-ARCHIVE, $mw:FOOTNOTES-FACTS-LANG ]
};

declare %an:strictlydeterministic function mw:connection() as anyURI
{
  let $credentials :=
      let $credentials := credentials:credentials("MongoDB", "xbrl")
      return if (empty($credentials))
             then error(QName("mw:CONNECTION-FAILED"), "no xbrl MongoDB configured")
             else $credentials
  return
    try {
      mongo:connect($credentials)
    } catch mongo:* {
      error(QName("mw:CONNECTION-FAILED"), $err:description)
    }
};

declare function mw:find($collection as string, $query as object) as object()*
{
  let $conn := mw:connection()
  return mongo:find($conn, $collection, mw:hint($collection, $query))
};

declare function mw:find($collection as string, $query as object, $projection as object) as object()*
{
  let $conn := mw:connection()
  return mongo:find($conn, $collection, mw:hint($collection, $query), $projection)
};

declare function mw:run-cmd-deterministic($command as object) as object*
{
  let $conn := mw:connection()
  return mongo:run-cmd-deterministic($conn, $command)
};

declare %private function mw:hint($collection as string, $query as object) as object
{
  switch($collection)
    case "archives"
      return mw:archives-hint($query)
    case "components"
      return mw:components-hint($query)
    case "concepts"
      return mw:concepts-hint($query)
    case "entities"
      return mw:entities-hint($query)
    case "facts"
      return mw:facts-hint($query)
    case "footnotes"
      return mw:footnotes-hint($query)
    default
      return $query
};

declare %private function mw:archives-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Entity")))
      return { "$query": $query, "$hint": "Entity_hashed" }
    default
      return $query
};

declare %private function mw:components-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_hashed" }
    case (exists($query("Profiles.SEC.Disclosure")))
      return { "$query": $query, "$hint": "Profiles.SEC.Disclosure_hashed" }
    case (exists($query("Role")))
      return { "$query": $query, "$hint": "Role_hashed" }
    default
      return $query
};

declare %private function mw:concepts-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")) and exists($query("Role")))
      return { "$query": $query, "$hint": "Archive_1_Role_1_Name_1" }
    case (exists($query("Archive")) and exists($query("Name")))
      return { "$query": $query, "$hint": "Name_1_Archive_1" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_1_Role_1_Name_1" }
    default
      return $query
};

declare %private function mw:entities-hint($query as object) as object
{
  switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Profiles.SEC.CompanyType")))
      return { "$query": $query, "$hint": "Profiles.SEC.CompanyType_hashed" }
    case (exists($query("Profiles.SEC.SIC")))
      return { "$query": $query, "$hint": "Profiles.SEC.SIC_hashed" }
    case (exists($query("Profiles.SEC.Sector")))
      return { "$query": $query, "$hint": "Profiles.SEC.Sector_hashed" }
    case (exists($query("Profiles.SEC.Tags")))
      return { "$query": $query, "$hint": "Profiles.SEC.Tags_1" }
    case (exists($query("Profiles.SEC.Tickers")))
      return { "$query": $query, "$hint": "Profiles.SEC.Tickers_1" }
    default
      return $query
};

declare %private function mw:facts-hint($query as object) as object
{
    switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Aspects.xbrl:Concept")) and
          exists($query("Aspects.xbrl:Entity")) and
          exists($query("Aspects.sec:FiscalYear")) and
          exists($query("Aspects.sec:FiscalPeriod")))
      return { "$query": $query, "$hint": "Aspects.xbrl:Concept_1_Aspects.xbrl:Entity_1_Aspects.sec:FiscalYear_1_Aspects.sec:FiscalPeriod_1" }
    case (exists($query("Aspects.sec:Archive")) and exists($query("Aspects.xbrl:Concept")))
      return { "$query": $query, "$hint": "Aspects.sec:Archive_1_Aspects.xbrl:Concept_1" }
    default
      return $query
};

declare %private function mw:footnotes-hint($query as object) as object
{
    switch (true)
    case (exists($query("_id")))
      return { "$query": $query, "$hint": "_id_" }
    case (exists($query("Archive")))
      return { "$query": $query, "$hint": "Archive_hashed" }
    case (exists($query("Facts._id")) and exists($query("Lang")))
      return { "$query": $query, "$hint": "Facts._id_1_Lang_1" }
    default
      return $query
};


:bug: throw an error if an entity id is not found.

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

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

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

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

 :
 : @author Ghislain Fourny
 :
 :)

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

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

         let $entities := entities:entities($eid)
         let $idsNotFound :=
           for $id in $eid
           let $entity := $entities[$$._id eq $id]
           where empty($entity)
           return $id
         return
           if(empty($idsNotFound))
           then $entities
           else error(xs:QName("japan:ENTITY_NOT_FOUND"),
                      string-join($idsNotFound ! ("'" || $$ || "'"), ",") || ": Entities not found.")
       )
       group by $entity._id
       return $entity[1]
    default return ()
};

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

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

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

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

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

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

 :
 : @author Ghislain Fourny
 :
 :)

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

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

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

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


:fire: Remove useless functions.

:fire: Remove useless functions.

jsoniq version "1.0";

(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for querying XBRL Models of financial reports submitted to the SEC.
 :

 :
 :
Standard $options Parameter

 :
 :
Most functions in the BizQL package allow an additional $options
 :    parameter. The options parameter is a JSON object which is defined in the documentation
 :    of the facts module.

 :
 :
In addition to the option fields defined in the
 :    facts module the following
 :    fields can be used in the options of a function in the SEC profile:

 :
 :

 :
HideAmendedFacts: if set to true (default behavior) all facts that have been
 :     amended are not included in the result.

 :
IncludeImpliedTable: set to true in order to output the implied table if
 :     there is no Table (default is false).

 :

 :
 : @author Charles Hoffman
 : @author Matthias Brantner
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace report-schemas = "http://28.io/modules/xbrl/report-schemas";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Scheme for the SEC entity identifiers (i.e. http://www.sec.gov/CIK)
 :)
declare variable $sec:CIK as xs:string := "http://www.sec.gov/CIK";

(:~
 : Joker value for all archives or all concepts.
 :)
declare variable $sec:ALL_OF_THEM as boolean := true;

(:~
 : All prefixes pre-declared in SEC's EDGAR.
 :)
declare variable $sec:EDGAR_PREFIXES :=
  ( "xbrli", "link", "xl", "xlink", "xbrldt", "xbrldi", "nonnum",
    "num", "ref", "us-gaap", "dei", "us-types", "invest", "country",
    "currency", "exch", "naics", "sic", "stpr", "rr", "rr-ent",
    "rr-cal", "rr-def", "rr-pre" );

(:~
 : All SEC characteristics that are always there with their defaults
 :)
declare variable $sec:DEFAULTS as object := {
    "dei:LegalEntityAxis" : {
      Default: "sec:DefaultLegalEntity",
      Domain: [ "sec:DefaultLegalEntity" ]
    },
    "sec:Accepted" : {
      Type: "xs:string"
    },
    "sec:FiscalYear" : {
      Type: "xs:integer"
    },
    "sec:FiscalPeriod" : {
      Type: "xs:string"
    }
  };

(:~
 :
Return all facts that match a given filter object optionally interpreted
 :    in the context of an optionally given hypercube.
 :    Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 : @param $options standard fact retrieving options.
 :
 : @error facts:INVALID-RULE-TYPE the type of a rule is not unknown/invalid
 : @error facts:RULE-EXECUTION-ERROR a rule raised an error whilst being executed
 : @error facts:FILTER-TOO-GENERIC The filter object must have at least one of the
 :        fields Archive, Aspects.xbrl:Concept, Aspects.xbrl:Period, or
 :        Aspects.xbrl:Entity.
 : @return all facts satisfying the filter and options.
 :)
declare function sec:facts-for(
  $options as object?) as object*
{
  let $facts := facts:facts-for($options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 :
 : @param $hypercube a hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-hypercube(
    $hypercube as object
)
as object*
{
  sec:facts-for({ Hypercube: $hypercube })
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 :
 : @param $hypercube a hypercube.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-hypercube(
    $hypercube as object,
    $options as object?
)
as object*
{
  let $options as object := facts:merge-objects(
    { Hypercube: $hypercube },
    $options,
    true
  )
  return sec:facts-for($options)
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*) as object*
{
  sec:facts-for-archives-and-concepts($archive_or_ids, $concepts, ())
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*,
    $options as object?) as object*
{
  let $options := if(empty($options.Hypercube))
                  then facts:merge-objects(
                      {
                          "Hypercube" : sec:dimensionless-hypercube(())
                      },
                      $options,
                      true)
                  else $options
  let $facts :=
    facts:facts-for-archives-and-concepts(
      $archive_or_ids,
      $concepts,
      $options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Filters a list of facts and returns only the latest amending facts.
 :    Accordingly, all amended facts are filtered out.

 :
 : @param $facts a list of facts.
 : @param $options optional parameters:
 :

 :
HideAmendedFacts (boolean, true by default): activates or deactivates amendment hiding. If false,
 :     the function will just return the input facts.

 :

 :
 : @return a sequence of facts without amended facts.
 :)
declare function sec:hide-amended-facts(
    $facts as object*,
    $options as object?
)
as object*
{
  let $hide-amended-facts as boolean := ($options.HideAmendedFacts, true)[1]
  return
    if(not $hide-amended-facts)
    then $facts
    else
      for $grouped-facts in $facts
      group by
        $entity := $grouped-facts.Aspects."xbrl:Entity",
        $fiscal-period := $grouped-facts.Aspects."sec:FiscalPeriod",
        $fiscal-year := $grouped-facts.Aspects."sec:FiscalYear"
      return
        if (empty($fiscal-period) or empty($fiscal-year) or $fiscal-period instance of null or $fiscal-year instance of null)
        then
          (: $grouped-facts is already grouped by entity.
             Now, group by period as a fallback. :)
          for $regrouped-facts in $grouped-facts
          group by $period := $regrouped-facts.Aspects."xbrl:Period"
          return
            let $latest-accepted :=
              max(distinct-values($regrouped-facts.Aspects."sec:Accepted"))
            return
              if (empty($latest-accepted))
              then $regrouped-facts
              else
                (: hide amended facts :)
                $regrouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]

        else
          let $latest-accepted :=
            max(distinct-values($grouped-facts.Aspects."sec:Accepted"))
          return
            if (empty($latest-accepted))
            then $grouped-facts
            else
              (: hide amended facts :)
              $grouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]
};

(:~
 :
Return all facts for concepts that are defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having one of the following prefixes:

 :
 :

 :
xbrli

 :
link

 :
xl

 :
xlink

 :
xbrldt

 :
xbrldi

 :
nonnum

 :
num

 :
ref

 :
us-gaap

 :
dei

 :
us-types

 :
invest

 :
country

 :
currency

 :
exch

 :
naics

 :
sic

 :
stpr

 :
rr

 :
rr-ent

 :
rr-cal

 :
rr-def

 :
rr-pre

 :

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having one of the above prefixes.
 :)
declare function sec:edgar-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: false
          }
        }
      }
    })
};

(:~
 :
Return all facts for concepts that are NOT defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having none of the prefixes listed under function
 : sec:edgar-facts-for-archives.

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having none of the common edgar prefixes.
 :)
declare function sec:extension-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: true
          }
        }
      }
    })
};

(:~
 :
Retrieves the document end date of a filing or a fact.

 :
 : @param $filing-fact-or-id a filing, a fact, or its id.
 :
 : @return the document end date (e.g., xs:date("2011-04-30")).
 :)
declare function sec:end-date(
    $filing-fact-or-id as item?) as xs:date?
{
  if (empty($filing-fact-or-id))
  then ()
  else
    typeswitch ($filing-fact-or-id)
    case object return
      switch (true)
      (: archive :)
      case exists($filing-fact-or-id.Profiles.SEC.DocumentPeriodEndDate)
        return xs:date($filing-fact-or-id.Profiles.SEC.DocumentPeriodEndDate)
      (: fact :)
      case exists($filing-fact-or-id.Profiles.SEC.DocEndDate)
        return xs:date($filing-fact-or-id.Profiles.SEC.DocEndDate)
      default return ()
    default return
      let $id as xs:string := string($filing-fact-or-id)
      return
        switch(true)
        (:
          "The first ten digits are the CIK of the entity that logged
           in to submit the filing. The next two digits define the
           current year. The final digits are assigned sequentially by
           CIK starting with one on the first business day of each year."

        http://www.sec.gov/info/edgar/edgarfm-vol2-v25.pdf :)
      case matches($id, "\\d{10}-\\d{2}-\\d{6}")
        return xs:date(archives:archives($id).Profiles.SEC.DocumentPeriodEndDate)
      default return xs:date(facts:facts($id).Profiles.SEC.DocEndDate)
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited, however
 :    you can restrict it using the options. If you need more
 :    XBRL dimensions, use the user-defined-hypercube function instead.

 :
In the SEC profile, there is an additional axis that is always here:
 : dei:LegalEntityAxis, with a default value sec:DefaultLegalEntity and a filtering
 : only accepting this default value.

 :
 : @return dimensionless hypercube instantiation.
 : @param $options: additional options among which:
 : - Concepts: an array of concept names to include in the hypercube.
 : - Periods: an array of periods to include in the hypercube.
 : - Entities: an array of EIDs to include in the hypercube.
 : - Units: an array of units to include in the hypercube.
 : - LegalEntities: an array of legal entities to include in the hypercube.
 :)
declare function sec:dimensionless-hypercube($options as object?) as object
{
  sec:user-defined-hypercube(
    {|
      if(exists($options.Concepts))
      then { "xbrl:Concept" : { Domain: $options.Concepts } }
      else (),
      if(exists($options.Entities))
      then { "xbrl:Entity" : { Domain: [ entities:eid($options.Entities[]) ] } }
      else (),
      if(exists($options.Periods))
      then { "xbrl:Period" : { Domain: $options.Periods } }
      else (),
      if(exists($options.Units))
      then { "xbrl:Unit" : { Domain: $options.Units } }
      else (),
      if(exists($options.LegalEntities))
      then { "dei:LegalEntityAxis" : { Domain: $options.LegalEntities } }
      else ()
    |}
  )
};

(:~
 :
Returns an instantiation of a Hypercube with no extra dimensions besides the legal entity axis.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The characteristic dei:LegalEntityAxis is also here. It has
 :    a default value of sec:DefaultLegalEntity and restricts the value
 :    space to this default value.

 :
 :
 : @return the hypercube instantiation.
 :)
declare function sec:user-defined-hypercube() as object
{
  sec:user-defined-hypercube(())
};

(:~
 :
Returns an instantiation of a Hypercube containing the specified dimensions.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The following dimensions are always present, but the values can be overriden:

 :
{
 :     "dei:LegalEntityAxis" : {
 :       Default: "sec:DefaultLegalEntity", -- overridable with HasDefault : false
 :       Domain: [ "sec:DefaultLegalEntity" ] -- overridable with Domain: null
 :     },
 :     "sec:Accepted" : {
 :       Type: "string"
 :     },
 :     "sec:FiscalYear" : {
 :       Type: "integer"
 :     },
 :     "sec:FiscalPeriod" : {
 :       Type: "string"
 :     }
 :    }

 :
 : @return the hypercube instantiation.
 : @param $dimensions
an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have four (optional) fields:

 :

 :
Type (string, absent by default): the name of a type (string, integer, ...). This creates a typed dimension (if absent, an explicit dimension).

 :
Default (a string, absent by default): must be either castable to Type, or null (explicit dimensions: must be a string with a member name, null not allowed).

 :
HasDefault (a boolean, true by default): if false, the dimension has no default even if Default is present (this includes the possibility to override predefined dimensions).

 :
Domain (null or an array of atomics, absent by default): restricts the domain of the dimension to the specified values, that must all be castable to Type or null (or all strings with member names for explicit dimensions). Don't forget to include the default value in there if any. If null is used instead of an array, this removes any predefined restrictions.
.
 :

 :)
declare function sec:user-defined-hypercube($dimensions as object?) as object
{
  let $dimensions := facts:merge-objects($dimensions, $sec:DEFAULTS, true)
  let $extended-dimensions := {|
    for $d in keys($dimensions)
    let $dimension-spec := $dimensions.$d

    let $dimension-default as atomic? :=
      if(not $dimension-spec.HasDefault eq false)
      then $dimension-spec.Default
      else ()

    let $dimension-domain as array? :=
        typeswitch($dimension-spec.Domain)
        case null return ()
        default return $dimension-spec.Domain

    return {
      $d : {|
        { Default : $dimension-default }[exists($dimension-default)],
        { Type : $dimension-spec.Type }[exists($dimension-spec.Type)],
        { Domain : $dimension-domain }[exists($dimension-domain)]
      |}
    }
  |}
  return hypercubes:user-defined-hypercube($extended-dimensions)
};

jsoniq version "1.0";

(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for querying XBRL Models of financial reports submitted to the SEC.
 :

 :
 :
Standard $options Parameter

 :
 :
Most functions in the BizQL package allow an additional $options
 :    parameter. The options parameter is a JSON object which is defined in the documentation
 :    of the facts module.

 :
 :
In addition to the option fields defined in the
 :    facts module the following
 :    fields can be used in the options of a function in the SEC profile:

 :
 :

 :
HideAmendedFacts: if set to true (default behavior) all facts that have been
 :     amended are not included in the result.

 :
IncludeImpliedTable: set to true in order to output the implied table if
 :     there is no Table (default is false).

 :

 :
 : @author Charles Hoffman
 : @author Matthias Brantner
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace facts = "http://28.io/modules/xbrl/facts";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace report-schemas = "http://28.io/modules/xbrl/report-schemas";

declare namespace ver = "http://zorba.io/options/versioning";
declare option ver:module-version "1.0";

(:~
 : Scheme for the SEC entity identifiers (i.e. http://www.sec.gov/CIK)
 :)
declare variable $sec:CIK as xs:string := "http://www.sec.gov/CIK";

(:~
 : Joker value for all archives or all concepts.
 :)
declare variable $sec:ALL_OF_THEM as boolean := true;

(:~
 : All prefixes pre-declared in SEC's EDGAR.
 :)
declare variable $sec:EDGAR_PREFIXES :=
  ( "xbrli", "link", "xl", "xlink", "xbrldt", "xbrldi", "nonnum",
    "num", "ref", "us-gaap", "dei", "us-types", "invest", "country",
    "currency", "exch", "naics", "sic", "stpr", "rr", "rr-ent",
    "rr-cal", "rr-def", "rr-pre" );

(:~
 : All SEC characteristics that are always there with their defaults
 :)
declare variable $sec:DEFAULTS as object := {
    "dei:LegalEntityAxis" : {
      Default: "sec:DefaultLegalEntity",
      Domain: [ "sec:DefaultLegalEntity" ]
    },
    "sec:Accepted" : {
      Type: "xs:string"
    },
    "sec:FiscalYear" : {
      Type: "xs:integer"
    },
    "sec:FiscalPeriod" : {
      Type: "xs:string"
    }
  };

(:~
 :
Return all facts that match a given filter object optionally interpreted
 :    in the context of an optionally given hypercube.
 :    Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 : @param $options standard fact retrieving options.
 :
 : @error facts:INVALID-RULE-TYPE the type of a rule is not unknown/invalid
 : @error facts:RULE-EXECUTION-ERROR a rule raised an error whilst being executed
 : @error facts:FILTER-TOO-GENERIC The filter object must have at least one of the
 :        fields Archive, Aspects.xbrl:Concept, Aspects.xbrl:Period, or
 :        Aspects.xbrl:Entity.
 : @return all facts satisfying the filter and options.
 :)
declare function sec:facts-for(
  $options as object?) as object*
{
  let $facts := facts:facts-for($options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 :
 : @param $hypercube a hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-hypercube(
    $hypercube as object
)
as object*
{
  sec:facts-for({ Hypercube: $hypercube })
};

(:~
 :
 :
Retrieves all facts in the supplied hypercube, across all archives.

 :
Default values will be populated automatically.

 :
Only returns the facts that match the fiscal focus of the archive they
 :    belong to.

 :
 :
 : @param $hypercube a hypercube.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-hypercube(
    $hypercube as object,
    $options as object?
)
as object*
{
  let $options as object := facts:merge-objects(
    { Hypercube: $hypercube },
    $options,
    true
  )
  return sec:facts-for($options)
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*) as object*
{
  sec:facts-for-archives-and-concepts($archive_or_ids, $concepts, ())
};

(:~
 :
Retrieves the facts associated with the supplied concepts
 : from the supplied archive, that match the fiscal focus of the archive.

 :
 : @deprecated use sec:facts-for#1 and sec:user-defined-hypercube#1 instead.
 :
 : @param $archive_or_ids a sequence of archives or their ids or $sec:ALL_OF_THEM for no filtering.
 : @param $concepts the concepts or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return the latest facts associated with the supplied concept.
 : @deprecated This function has been deprecated in favor of more generic
 :   functions like facts:facts-for and hypercubes:facts.
 :)
declare function sec:facts-for-archives-and-concepts(
    $archive_or_ids as item*,
    $concepts as item*,
    $options as object?) as object*
{
  let $options := if(empty($options.Hypercube))
                  then facts:merge-objects(
                      {
                          "Hypercube" : sec:dimensionless-hypercube(())
                      },
                      $options,
                      true)
                  else $options
  let $facts :=
    facts:facts-for-archives-and-concepts(
      $archive_or_ids,
      $concepts,
      $options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Filters a list of facts and returns only the latest amending facts.
 :    Accordingly, all amended facts are filtered out.

 :
 : @param $facts a list of facts.
 : @param $options optional parameters:
 :

 :
HideAmendedFacts (boolean, true by default): activates or deactivates amendment hiding. If false,
 :     the function will just return the input facts.

 :

 :
 : @return a sequence of facts without amended facts.
 :)
declare function sec:hide-amended-facts(
    $facts as object*,
    $options as object?
)
as object*
{
  let $hide-amended-facts as boolean := ($options.HideAmendedFacts, true)[1]
  return
    if(not $hide-amended-facts)
    then $facts
    else
      for $grouped-facts in $facts
      group by
        $entity := $grouped-facts.Aspects."xbrl:Entity",
        $fiscal-period := $grouped-facts.Aspects."sec:FiscalPeriod",
        $fiscal-year := $grouped-facts.Aspects."sec:FiscalYear"
      return
        if (empty($fiscal-period) or empty($fiscal-year) or $fiscal-period instance of null or $fiscal-year instance of null)
        then
          (: $grouped-facts is already grouped by entity.
             Now, group by period as a fallback. :)
          for $regrouped-facts in $grouped-facts
          group by $period := $regrouped-facts.Aspects."xbrl:Period"
          return
            let $latest-accepted :=
              max(distinct-values($regrouped-facts.Aspects."sec:Accepted"))
            return
              if (empty($latest-accepted))
              then $regrouped-facts
              else
                (: hide amended facts :)
                $regrouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]

        else
          let $latest-accepted :=
            max(distinct-values($grouped-facts.Aspects."sec:Accepted"))
          return
            if (empty($latest-accepted))
            then $grouped-facts
            else
              (: hide amended facts :)
              $grouped-facts[$$.Aspects."sec:Accepted" eq $latest-accepted]
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied schema and to the fiscal focus, and populates
 : them with the default dimension values when missing.

 :
 :
 : @param $schema a schema.
 : @param $archives a sequence of archives or their AIDs or $sec:ALL_OF_THEM for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-schema(
    $schema as item,
    $archives as item*
)
as object*
{
  sec:facts-for-schema($schema, $archives, ())
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied schema and to the fiscal focus, and populates
 : them with the default dimension values when missing.

 :
 :
 : @param $schema a schema.
 : @param $archives a sequence of archives or their AIDs or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:facts-for-schema(
    $schema as item,
    $archives as item*,
    $options as object?
)
as object*
{
  let $facts := report-schemas:facts($schema, $archives, $options)
  return sec:hide-amended-facts($facts, $options)
};

(:~
 :
 :
Retrieves all facts from the supplied archives, that are relevant to the
 : supplied schema, and populates them with the default dimension values
 : when missing.

 :
 :
 : @param $schema a report schema or its RID.
 : @param $archives a sequence of archives or their AIDs or $sec:ALL_OF_THEM for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:fact-table-for-schema(
  $schema as item,
  $archives as item*) as array
{
   let $schema := report-schemas:report-schemas($schema)
   let $facts := sec:facts-for-schema($schema, $archives)
   let $hypercube := hypercubes:hypercubes-for-components($schema)[1]
   return hypercubes:fact-table-for-hypercube-and-facts($hypercube, $facts)
};

(:~
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied schema. Default dimension values are added to the facts
 : when missing.

 :
 : @param $schema a report schema or its RID.
 : @param $archives a sequence of archives or their AIDs.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:populate-schema-with-facts(
  $schema as item,
  $archives as item*) as object*
{
  sec:populate-schema-with-facts($schema, $archives,())
};

(:~
 :
Populates a concept-tree network with all facts from the supplied archives,
 : that are relevant to the
 : supplied schema. Default dimension values are added to the facts
 : when missing.

 :
 : @param $schema a report schema or its RID.
 : @param $archives a sequence of archives or their AIDs or $sec:ALL_OF_THEM for no filtering.
 : @param $options standard SEC BizQL options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function sec:populate-schema-with-facts(
  $schema as item,
  $archives as item*,
  $options as object?) as object*
{
  let $networks :=
      networks:networks-for-components-and-short-names($schema, "Presentation")
  return hypercubes:populate-networks-with-retrieved-facts(
    $networks,
    sec:facts-for-schema($schema, $archives, $options)
  )
};

(:~
 :
Return all facts for concepts that are defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having one of the following prefixes:

 :
 :

 :
xbrli

 :
link

 :
xl

 :
xlink

 :
xbrldt

 :
xbrldi

 :
nonnum

 :
num

 :
ref

 :
us-gaap

 :
dei

 :
us-types

 :
invest

 :
country

 :
currency

 :
exch

 :
naics

 :
sic

 :
stpr

 :
rr

 :
rr-ent

 :
rr-cal

 :
rr-def

 :
rr-pre

 :

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having one of the above prefixes.
 :)
declare function sec:edgar-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: false
          }
        }
      }
    })
};

(:~
 :
Return all facts for concepts that are NOT defined by one of the common
 : edgar taxonomies. In particular, this function returns facts for concepts
 : having none of the prefixes listed under function
 : sec:edgar-facts-for-archives.

 :
 : @param $archive_or_ids a sequence of archive or archive IDs to filter.
 : @return all facts having none of the common edgar prefixes.
 :)
declare function sec:extension-facts-for-archives(
  $archive_or_ids as item*) as object*
{
  sec:facts-for-archives-and-concepts(
    $archive_or_ids,
    $sec:ALL_OF_THEM,
    {
      Filter: {
        Profiles: {
          SEC: {
            IsExtension: true
          }
        }
      }
    })
};

(:~
 :
Retrieves the document end date of a filing or a fact.

 :
 : @param $filing-fact-or-id a filing, a fact, or its id.
 :
 : @return the document end date (e.g., xs:date("2011-04-30")).
 :)
declare function sec:end-date(
    $filing-fact-or-id as item?) as xs:date?
{
  if (empty($filing-fact-or-id))
  then ()
  else
    typeswitch ($filing-fact-or-id)
    case object return
      switch (true)
      (: archive :)
      case exists($filing-fact-or-id.Profiles.SEC.DocumentPeriodEndDate)
        return xs:date($filing-fact-or-id.Profiles.SEC.DocumentPeriodEndDate)
      (: fact :)
      case exists($filing-fact-or-id.Profiles.SEC.DocEndDate)
        return xs:date($filing-fact-or-id.Profiles.SEC.DocEndDate)
      default return ()
    default return
      let $id as xs:string := string($filing-fact-or-id)
      return
        switch(true)
        (:
          "The first ten digits are the CIK of the entity that logged
           in to submit the filing. The next two digits define the
           current year. The final digits are assigned sequentially by
           CIK starting with one on the first business day of each year."

        http://www.sec.gov/info/edgar/edgarfm-vol2-v25.pdf :)
      case matches($id, "\\d{10}-\\d{2}-\\d{6}")
        return xs:date(archives:archives($id).Profiles.SEC.DocumentPeriodEndDate)
      default return xs:date(facts:facts($id).Profiles.SEC.DocEndDate)
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited, however
 :    you can restrict it using the options. If you need more
 :    XBRL dimensions, use the user-defined-hypercube function instead.

 :
In the SEC profile, there is an additional axis that is always here:
 : dei:LegalEntityAxis, with a default value sec:DefaultLegalEntity and a filtering
 : only accepting this default value.

 :
 : @return dimensionless hypercube instantiation.
 : @param $options: additional options among which:
 : - Concepts: an array of concept names to include in the hypercube.
 : - Periods: an array of periods to include in the hypercube.
 : - Entities: an array of EIDs to include in the hypercube.
 : - Units: an array of units to include in the hypercube.
 : - LegalEntities: an array of legal entities to include in the hypercube.
 :)
declare function sec:dimensionless-hypercube($options as object?) as object
{
  sec:user-defined-hypercube(
    {|
      if(exists($options.Concepts))
      then { "xbrl:Concept" : { Domain: $options.Concepts } }
      else (),
      if(exists($options.Entities))
      then { "xbrl:Entity" : { Domain: [ entities:eid($options.Entities[]) ] } }
      else (),
      if(exists($options.Periods))
      then { "xbrl:Period" : { Domain: $options.Periods } }
      else (),
      if(exists($options.Units))
      then { "xbrl:Unit" : { Domain: $options.Units } }
      else (),
      if(exists($options.LegalEntities))
      then { "dei:LegalEntityAxis" : { Domain: $options.LegalEntities } }
      else ()
    |}
  )
};

(:~
 :
Returns an instantiation of a Hypercube with no extra dimensions besides the legal entity axis.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The characteristic dei:LegalEntityAxis is also here. It has
 :    a default value of sec:DefaultLegalEntity and restricts the value
 :    space to this default value.

 :
 :
 : @return the hypercube instantiation.
 :)
declare function sec:user-defined-hypercube() as object
{
  sec:user-defined-hypercube(())
};

(:~
 :
Returns an instantiation of a Hypercube containing the specified dimensions.
 :    The four basic characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit)
 :    are always added, but can be overriden. For each of those included aspects the value space is,
 :    by default, not limited, and only xbrl:Unit has a default value.

 :
The following dimensions are always present, but the values can be overriden:

 :
{
 :     "dei:LegalEntityAxis" : {
 :       Default: "sec:DefaultLegalEntity", -- overridable with HasDefault : false
 :       Domain: [ "sec:DefaultLegalEntity" ] -- overridable with Domain: null
 :     },
 :     "sec:Accepted" : {
 :       Type: "string"
 :     },
 :     "sec:FiscalYear" : {
 :       Type: "integer"
 :     },
 :     "sec:FiscalPeriod" : {
 :       Type: "string"
 :     }
 :    }

 :
 : @return the hypercube instantiation.
 : @param $dimensions
an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have four (optional) fields:

 :

 :
Type (string, absent by default): the name of a type (string, integer, ...). This creates a typed dimension (if absent, an explicit dimension).

 :
Default (a string, absent by default): must be either castable to Type, or null (explicit dimensions: must be a string with a member name, null not allowed).

 :
HasDefault (a boolean, true by default): if false, the dimension has no default even if Default is present (this includes the possibility to override predefined dimensions).

 :
Domain (null or an array of atomics, absent by default): restricts the domain of the dimension to the specified values, that must all be castable to Type or null (or all strings with member names for explicit dimensions). Don't forget to include the default value in there if any. If null is used instead of an array, this removes any predefined restrictions.
.
 :

 :)
declare function sec:user-defined-hypercube($dimensions as object?) as object
{
  let $dimensions := facts:merge-objects($dimensions, $sec:DEFAULTS, true)
  let $extended-dimensions := {|
    for $d in keys($dimensions)
    let $dimension-spec := $dimensions.$d

    let $dimension-default as atomic? :=
      if(not $dimension-spec.HasDefault eq false)
      then $dimension-spec.Default
      else ()

    let $dimension-domain as array? :=
        typeswitch($dimension-spec.Domain)
        case null return ()
        default return $dimension-spec.Domain

    return {
      $d : {|
        { Default : $dimension-default }[exists($dimension-default)],
        { Type : $dimension-spec.Type }[exists($dimension-spec.Type)],
        { Domain : $dimension-domain }[exists($dimension-domain)]
      |}
    }
  |}
  return hypercubes:user-defined-hypercube($extended-dimensions)
};


:pencil: Add functions to multiplexer.

:pencil: Add functions to multiplexer.

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, 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" : { "$in" : [ $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 $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*,
  $merge as boolean) as object?
{
  let $components := multiplexer:components(
      $profile-name,
      $archive,
      $cid,
      $concept,
      $disclosure,
      $role,
      $exact-label,
      $full-text-label)
  return switch(true)
         case empty($components) return ()
         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 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 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, 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" : { "$in" : [ $disclosure ] }
          }
        }[$profile-name eq "japan" and exists($disclosure)]
      |}
    )
};

(:~
  :
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})
};