fixed help

fixed help

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

import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace session = "http://apps.28.io/session";
import module namespace csv = "http://zorba.io/modules/json-csv";

declare function local:to-xml($f as object*, $total as boolean) as element()*
{
    for $f in $f
    return
        <Fact>{
            if ($total) then <CIKS>{$f.CIKS[] ! <CIK>{$$}</CIK>}</CIKS> else <CIK>{$f.CIK}</CIK>,
            if ($total) then <CompanyNames>{ $f.CompanyNames[] ! <CompanyName>{$$}</CompanyName>}</CompanyNames> else <CompanyName>{$f.CompanyName}</CompanyName>,
            <Period>{$f.Period}</Period>,
            <Year>{$f.Year}</Year>,
            <Concept>{$f.Concept}</Concept>,
            if ($total) then <Types>{$f.Types[] ! <Type>{$$}</Type>}</Types> else <Type>{$f.Type}</Type>,
            if ($total) then <Units>{$f.Units[] ! <Unit>{$$}</Unit>}</Units> else <Unit>{$f.Unit}</Unit>,
            <Value>{$f.Value}</Value>
        }</Fact>
};

declare function local:facts($entities, $period, $year, $concepts, $map)
{
    for $entity in $entities
    let $year := if (exists($year))
                 then $year cast as integer
                 else if ($period = "FY")
                      then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                      else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
    for $concept in $concepts
    let $facts := for $fact in
                    if (exists($map) and $map eq "FundamentalAccountingConcepts")
                    then sec-fiscal:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
                        $entity, $concept, $period, $year, { "concept-maps" : $map })
                    else sec-fiscal:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
                        $entity, $concept, $period, $year)
                    order by $fact.Profiles.SEC.Fiscal.Acceptance descending
                    group by $fact.Aspects."xbrl:Entity", $fact.Profiles.SEC.Fiscal.Acceptance, $fact.Profiles.SEC.Fiscal.Period
                  return
                {|
                    {
                        CIK: $entity._id,
                        CompanyName : $entity.Profiles.SEC.CompanyName,
                        Period : $fact.Profiles.SEC.Fiscal.Period,
                        Year : $fact.Profiles.SEC.Fiscal.Year,
                        Concept: $concept,
                        Type: $fact[1].Type,
                        Unit: $fact[1].Aspects."xbrl:Unit",
                        Decimals: $fact[1].Decimals,
                        Value: $fact[1].Value
                    },
                    if (exists($map) and $map eq "FundamentalAccountingConcepts")
                    then {
                        Map: $map,
                        OriginalConcept: $fact.AuditTrails[].Data.OriginalConcept
                    } else ()
                |}
    return $facts
};

let $format   := lower-case(substring-after(request:path(), ".jq.")) (: text, xml, or json (default) :)
let $ciks     := distinct-values(companies:eid(request:param-values("cik")))
let $indexes  := request:param-values("index") ! upper-case($$) (: DOW30, SP500, FORTUNE100 :)
let $tickers  := request:param-values("ticker")
let $sics     := request:param-values("sic")
let $period   := request:param-values("period", "FY")[1]
let $year     := request:param-values("year")[1]
let $concepts := request:param-values("concept")
let $map      := request:param-values("map")[1]
let $total    := let $total := request:param-values("total")[1] return if (exists($total)) then $total cast as boolean else false
let $period   := switch($period)
                 case "Q1" return ("Q1","YTD1")
                 case "Q2" return ("Q2","YTD2")
                 case "Q3" return ("Q3","YTD3")
                 default return ("Q4","FY")
return
  if (empty($concepts))
  then {
    response:status-code(400);
    session:error("missing parameter(s) (concept)", $format)
  } else
    let $entities := (companies:companies($ciks), companies:companies-for-tags($indexes), companies:companies-for-tickers($tickers), companies:companies-for-SIC($sics))
    return switch(true)
      case empty($entities) return {
        response:status-code(404);
        session:error("entity not found (valid parameters: cik, ticker, index, sic)", $format)
      }
      case not (session:only-dow30($entities) or session:valid()) return {
        response:status-code(401);
        session:error("accessing facts of an entity that is not in the DOW30", $format)
      }
      default return
        let $facts := local:facts($entities, $period, $year, $concepts, $map)
        let $facts :=
            if ($total)
            then
                for $fact in $facts
                group by $fact.Concept
                return {
                    CIKS: [ distinct-values($fact.CIK) ],
                    CompanyNames : [ distinct-values($fact.CompanyName) ],
                    Period : $fact.Period[1],
                    Year : $fact.Year[1],
                    Concept: $fact.Concept[1],
                    Types: [ distinct-values($fact.Type) ],
                    Units: [ distinct-values($fact.Unit) ],
                    Value: sum($fact.Value)
                }
            else $facts
        return
            switch ($format)
            case "text" return
                if ($total)
                then {
                    response:status-code(400);
                    session:error("csv representation not available if using the total parameter", $format)
                } else string-join(csv:serialize($facts))
            case "xml" return <Facts>{local:to-xml($facts, $total)}</Facts>
            default return [ $facts ]

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

import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace session = "http://apps.28.io/session";
import module namespace csv = "http://zorba.io/modules/json-csv";

declare function local:to-xml($f as object*, $total as boolean) as element()*
{
    for $f in $f
    return
        <Fact>{
            if ($total) then <CIKS>{$f.CIKS[] ! <CIK>{$$}</CIK>}</CIKS> else <CIK>{$f.CIK}</CIK>,
            if ($total) then <CompanyNames>{ $f.CompanyNames[] ! <CompanyName>{$$}</CompanyName>}</CompanyNames> else <CompanyName>{$f.CompanyName}</CompanyName>,
            <Period>{$f.Period}</Period>,
            <Year>{$f.Year}</Year>,
            <Concept>{$f.Concept}</Concept>,
            if ($total) then <Types>{$f.Types[] ! <Type>{$$}</Type>}</Types> else <Type>{$f.Type}</Type>,
            if ($total) then <Units>{$f.Units[] ! <Unit>{$$}</Unit>}</Units> else <Unit>{$f.Unit}</Unit>,
            <Value>{$f.Value}</Value>
        }</Fact>
};

declare function local:facts($entities, $period, $year, $concepts, $map)
{
    for $entity in $entities
    let $year := if (exists($year))
                 then $year cast as integer
                 else if ($period = "FY")
                      then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                      else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
    for $concept in $concepts
    let $facts := for $fact in
                    if (exists($map) and $map eq "FundamentalAccountingConcepts")
                    then sec-fiscal:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
                        $entity, $concept, $period, $year, { "concept-maps" : $map })
                    else sec-fiscal:facts-for-entities-and-concepts-and-fiscal-periods-and-years(
                        $entity, $concept, $period, $year)
                    order by $fact.Profiles.SEC.Fiscal.Acceptance descending
                    group by $fact.Aspects."xbrl:Entity", $fact.Profiles.SEC.Fiscal.Acceptance, $fact.Profiles.SEC.Fiscal.Period
                  return
                {|
                    {
                        CIK: $entity._id,
                        CompanyName : $entity.Profiles.SEC.CompanyName,
                        Period : $fact.Profiles.SEC.Fiscal.Period,
                        Year : $fact.Profiles.SEC.Fiscal.Year,
                        Concept: $concept,
                        Type: $fact[1].Type,
                        Unit: $fact[1].Aspects."xbrl:Unit",
                        Decimals: $fact[1].Decimals,
                        Value: $fact[1].Value
                    },
                    if (exists($map) and $map eq "FundamentalAccountingConcepts")
                    then {
                        Map: $map,
                        OriginalConcept: $fact.AuditTrails[].Data.OriginalConcept
                    } else ()
                |}
    return $facts
};

let $format   := lower-case(substring-after(request:path(), ".jq.")) (: text, xml, or json (default) :)
let $ciks     := distinct-values(companies:eid(request:param-values("cik")))
let $indexes  := request:param-values("index") ! upper-case($$) (: DOW30, SP500, FORTUNE100 :)
let $tickers  := request:param-values("ticker")
let $sics     := request:param-values("sic")
let $period   := request:param-values("period", "FY")[1]
let $year     := request:param-values("year")[1]
let $concepts := request:param-values("concept")
let $map      := request:param-values("map")[1]
let $total    := let $total := request:param-values("total")[1] return if (exists($total)) then $total cast as boolean else false
let $period   := switch($period)
                 case "Q1" return ("Q1","YTD1")
                 case "Q2" return ("Q2","YTD2")
                 case "Q3" return ("Q3","YTD3")
                 default return ("Q4","FY")
return
  if (empty($concepts))
  then {
    response:status-code(400);
    session:error("missing parameter(s) (concept)", $format)
  } else
    let $entities := (companies:companies($ciks), companies:companies-for-tags($indexes), companies:companies-for-tickers($tickers), companies:companies-for-SIC($sics))
    return switch(true)
      case empty($entities) return {
        response:status-code(404);
        session:error("entity not found (valid parameters: cik, ticker, index)", $format)
      }
      case not (session:only-dow30($entities) or session:valid()) return {
        response:status-code(401);
        session:error("accessing facts of an entity that is not in the DOW30", $format)
      }
      default return
        let $facts := local:facts($entities, $period, $year, $concepts, $map)
        let $facts :=
            if ($total)
            then
                for $fact in $facts
                group by $fact.Concept
                return {
                    CIKS: [ distinct-values($fact.CIK) ],
                    CompanyNames : [ distinct-values($fact.CompanyName) ],
                    Period : $fact.Period[1],
                    Year : $fact.Year[1],
                    Concept: $fact.Concept[1],
                    Types: [ distinct-values($fact.Type) ],
                    Units: [ distinct-values($fact.Unit) ],
                    Value: sum($fact.Value)
                }
            else $facts
        return
            switch ($format)
            case "text" return
                if ($total)
                then {
                    response:status-code(400);
                    session:error("csv representation not available if using the total parameter", $format)
                } else string-join(csv:serialize($facts))
            case "xml" return <Facts>{local:to-xml($facts, $total)}</Facts>
            default return [ $facts ]


Update facts.jq

Update facts.jq

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

import module namespace conversion = "http://28.io/modules/xbrl/conversion";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace rules = "http://28.io/modules/xbrl/rules";

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

declare function local:contains-aspect($name as string) as boolean
{
  exists(request:param-names()[starts-with($$, $name)])
};

declare function local:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function local:hypercube(
    $entities as object*,
    $fiscalPeriods as string*,
    $fiscalYears as integer*,
    $aids as string*) as object
{
    let $shortcut-hypercube-spec :=
    {|
        let $concepts := request:param-values("concept")
        return { "xbrl:Concept" : { Domain : [ $concepts ] } }[exists($concepts)],
        fiscal-core:filter-override(
            $entities,
            $fiscalYears,
            $fiscalPeriods,
            $aids)
    |}
    let $main-hypercube-spec :=
    {|
        {
            "dei:LegalEntityAxis" : {
                "Domain" : [ "sec:DefaultLegalEntity" ],
                "Default" : "sec:DefaultLegalEntity"
            }
        }[not local:contains-aspect("dei:LegalEntityAxis")],

        for $parameter in (
            request:param-names(),
            keys($shortcut-hypercube-spec),
            "sec:Accepted",
            "sec:FiscalPeriod",
            "sec:FiscalYear")
        where contains($parameter, ":")
        group by $dimension-name :=
            switch(true)
            case ends-with(lower-case($parameter), "::default")
                return substring-before($parameter, "::default")
            case ends-with(lower-case($parameter), ":default")
                return substring-before($parameter, ":default")
            case ends-with(lower-case($parameter), "::type")
                return substring-before($parameter, "::type")
            default
                return $parameter
        let $all as boolean :=
            (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
        let $user-defined-type as string? :=
            (request:param-values($dimension-name || "::type"), request:param-values($dimension-name || ":type"))[1]
        let $type as string? :=
            switch(true)
            case exists($user-defined-type)
                return $user-defined-type
            case exists($shortcut-hypercube-spec.$dimension-name.Type)
                return $shortcut-hypercube-spec.$dimension-name.Type
            default
                return ()
        let $values := (request:param-values($dimension-name), $shortcut-hypercube-spec.$dimension-name.Domain[])
        let $typed-values := if (exists($type)) then local:cast-sequence($values, $type) else $values
        let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
        let $default-value := (request:param-values($dimension-name || "::default"), request:param-values($dimension-name || ":default"))[1]
        let $typed-default-value := if (exists($type)) then local:cast-sequence($default-value, $type) else $default-value
        return
        {
            $dimension-name : {|
                { "Type" : $type }[exists($type)],
                { "Domain" : [ $typed-values ] }[(exists($typed-values) and not($all))],
                { "Default" : $typed-default-value }[$has-default]
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($main-hypercube-spec)
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token             as string? external;
declare  %rest:env                              variable $request-uri       as string  external;
declare  %rest:case-insensitive                 variable $format            as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear        as string* external := "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                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $validate          as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;

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 as object* :=
    companies:companies(
        $cik,
        $tag,
        $ticker,
        $sic)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $hypercube := local:hypercube($entities, $fiscalPeriod, $fiscalYear, $aid)

let $facts :=
  sec:facts-for(
    {|
      {
        Hypercube : $hypercube,
        Validate: $validate
      },
      { "ConceptMaps" : $map }[exists($map)],
      { "Rules" : [ $rule ] }[exists($rule)]
    |}
  )

let $facts := if(empty(($cik,$tag,$ticker,$sic)) or exists($entities)) then $facts else ()

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

let $result := {
    NetworkIdentifier : "http://bizql.io/facts",
    TableName : "xbrl:Facts",
    FactTable : [ $facts ]
}
let $comment :=
{
    NumFacts: count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node()* {
           <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
                conversion:facts-to-xml($res.FactTable[], { Caller: "Report"})
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        (conversion:facts-to-csv($res.FactTable[], { Caller: "Report" }), "")[1]
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "facts")
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 conversion = "http://28.io/modules/xbrl/conversion";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace rules = "http://28.io/modules/xbrl/rules";

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

declare function local:contains-aspect($name as string) as boolean
{
  exists(request:param-names()[starts-with($$, $name)])
};

declare function local:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function local:hypercube(
    $entities as object*,
    $fiscalPeriods as string*,
    $fiscalYears as integer*,
    $aids as string*) as object
{
    let $shortcut-hypercube-spec :=
    {|
        let $concepts := request:param-values("concept")
        return { "xbrl:Concept" : { Domain : [ $concepts ] } }[exists($concepts)],
        fiscal-core:filter-override(
            $entities,
            $fiscalYears,
            $fiscalPeriods,
            $aids)
    |}
    let $main-hypercube-spec :=
    {|
        {
            "dei:LegalEntityAxis" : {
                "Domain" : [ "sec:DefaultLegalEntity" ],
                "Default" : "sec:DefaultLegalEntity"
            }
        }[not local:contains-aspect("dei:LegalEntityAxis")],

        for $parameter in (
            request:param-names(),
            keys($shortcut-hypercube-spec),
            "sec:Accepted",
            "sec:FiscalPeriod",
            "sec:FiscalYear")
        where contains($parameter, ":")
        group by $dimension-name :=
            switch(true)
            case ends-with(lower-case($parameter), "::default")
                return substring-before($parameter, "::default")
            case ends-with(lower-case($parameter), ":default")
                return substring-before($parameter, ":default")
            case ends-with(lower-case($parameter), "::type")
                return substring-before($parameter, "::type")
            default
                return $parameter
        let $all as boolean :=
            (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
        let $user-defined-type as string? :=
            (request:param-values($dimension-name || "::type"), request:param-values($dimension-name || ":type"))[1]
        let $type as string? :=
            switch(true)
            case exists($user-defined-type)
                return $user-defined-type
            case exists($shortcut-hypercube-spec.$dimension-name.Type)
                return $shortcut-hypercube-spec.$dimension-name.Type
            default
                return ()
        let $values := (request:param-values($dimension-name), $shortcut-hypercube-spec.$dimension-name.Domain[])
        let $typed-values := if (exists($type)) then local:cast-sequence($values, $type) else $values
        let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
        let $default-value := (request:param-values($dimension-name || "::default"), request:param-values($dimension-name || ":default"))[1]
        let $typed-default-value := if (exists($type)) then local:cast-sequence($default-value, $type) else $default-value
        return
        {
            $dimension-name : {|
                { "Type" : $type }[exists($type)],
                { "Domain" : [ $typed-values ] }[(exists($typed-values) and not($all))],
                { "Default" : $typed-default-value }[$has-default]
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($main-hypercube-spec)
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token             as string? external;
declare  %rest:env                              variable $request-uri       as string  external;
declare  %rest:case-insensitive                 variable $format            as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear        as string* external := "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                 variable $map               as string? external;
declare  %rest:case-insensitive                 variable $rule              as string? external;
declare  %rest:case-insensitive                 variable $report            as string? external;
declare  %rest:case-insensitive                 variable $validate          as boolean external := false;
declare  %rest:case-insensitive                 variable $additional-rules  as string? external;

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 as object* :=
    companies:companies(
        $cik,
        $tag,
        $ticker,
        $sic)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rule as item* :=
    (
        if(exists($report))
        then reports:rules($report)
        else rules:rules($rule),
        if(exists($additional-rules))
        then rules:rules($additional-rules)
        else ()
    )

let $hypercube := local:hypercube($entities, $fiscalPeriod, $fiscalYear, $aid)

let $facts :=
  sec:facts-for(
    {|
      {
        Hypercube : $hypercube,
        Validate: $validate
      },
      { "ConceptMaps" : $map }[exists($map)],
      { "Rules" : [ $rule ] }[exists($rule)]
    |}
  )

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

let $result := {
    NetworkIdentifier : "http://bizql.io/facts",
    TableName : "xbrl:Facts",
    FactTable : [ $facts ]
}
let $comment :=
{
    NumFacts: count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}
let $serializers := {
    to-xml : function($res as object) as node()* {
           <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
                conversion:facts-to-xml($res.FactTable[], { Caller: "Report"})
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        (conversion:facts-to-csv($res.FactTable[], { Caller: "Report" }), "")[1]
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "facts")
return api:check-and-return-results($token, $results, $format)


Added two parameters for filtering languages and label roles.

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

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

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

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

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $language           as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $labelRole          as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

session:audit-call($token);

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

(: 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:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label,
      { LabelsOnly: true, ExactLabelMatches: true})

let $res as object* :=
  for $component in $components
  for $concept in $component.Concepts[]
  where empty($reportElement) or $concept.Name = $reportElement
  for $found-label in $concept.Labels[]
  where (empty($label) or $found-label.Value = $label) and
        (empty($language) or $found-label.Language = $language) and
        (empty($labelRole) or $found-label.Role = $labelRole)
  return {|
    { Archive: $component.Archive },
    { ComponentRole: $component.Role },
    { Concept: $concept.Name },
    $found-label
  |}

let $result := { Labels: [ $res ] }
let $comment :=
 {
    NumLabels : count($res),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <Labels>{
          api:json-to-xml($res.Labels[], "Label")
        }</Labels>
    },
    to-csv : function($res as object) as string {
        api:json-to-csv($res.Labels[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "labels")
return api:check-and-return-results($token, $results, $format)

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

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

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

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

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

session:audit-call($token);

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

(: 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:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label,
      { LabelsOnly: true, ExactLabelMatches: true})

let $res as object* :=
  for $component in $components
  for $concept in $component.Concepts[]
  where empty($reportElement) or $concept.Name = $reportElement
  for $found-label in $concept.Labels[]
  where empty($label) or $found-label.Value = $label
  return {|
    { Archive: $component.Archive },
    { ComponentRole: $component.Role },
    { Concept: $concept.Name },
    $found-label
  |}

let $result := { Labels: [ $res ] }
let $comment :=
 {
    NumLabels : count($res),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <Labels>{
          api:json-to-xml($res.Labels[], "Label")
        }</Labels>
    },
    to-csv : function($res as object) as string {
        api:json-to-csv($res.Labels[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "labels")
return api:check-and-return-results($token, $results, $format)


:bug: Fix stats.

:bug: Fix stats.

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

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

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

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

declare option rest:response "first-item";

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

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

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

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

(: Query parameters :)
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $hypercube          as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $count              as boolean external := false;
declare  %rest:case-insensitive                 variable $indent             as boolean external := false;

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

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

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

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

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

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

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

let $hypercubes as string* := multiplexer:hypercubes($components, $hypercube)

let $components-not-found as boolean :=
  exists(($archives, $cid, $reportElement, $disclosure, $networkIdentifier, $label, $hypercube)) and empty($hypercubes)

let $model-structures :=
    for $component in $components
    for $hypercube in $hypercubes[$$ = keys($component.Hypercubes)]
    return if($profile-name eq "sec")
    then {
        AccessionNumber : $component.Archive,
        NetworkIdentifier: $component.Role,
        TableName : $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        Hypercube: $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
let $result := { ModelStructures: [ if($count) then () else $model-structures ] }
let $comment := {
    NumComponents : count($model-structures),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("component")
       default return api:serialize($result, $comment, $serializers, $format, "components")

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

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

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

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

declare option rest:response "first-item";

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

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

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

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

(: Query parameters :)
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $hypercube          as string* external;
declare  %rest:case-insensitive                 variable $cid                as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
declare  %rest:case-insensitive                 variable $count              as boolean external := false;
declare  %rest:case-insensitive                 variable $indent             as boolean external := false;

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

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

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

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

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

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

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

let $hypercubes as string* := multiplexer:hypercubes($components, $hypercube)

let $components-not-found as boolean :=
  exists(($archives, $cid, $reportElement, $disclosure, $networkIdentifier, $label, $hypercube)) and empty($hypercubes)

let $model-structures :=
    for $component in $components
    for $hypercube in $hypercubes[$$ = keys($component.Hypercubes)]
    return if($profile-name eq "sec")
    then {
        AccessionNumber : $component.Archive,
        NetworkIdentifier: $component.Role,
        TableName : $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        Hypercube: $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
let $result := { ModelStructures: [ if($count) then () else $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("component")
       default return api:serialize($result, $comment, $serializers, $format, "components")


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

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

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