Update facts.jq

Update facts.jq

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

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

declare %an:nondeterministic function local:test-empty($params as object) as item
{
    let $endpoint := "facts"
    let $res as object := test:invoke-raw($endpoint, $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res
    }
};

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

declare %an:nondeterministic function local:test-labels() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw($endpoint, $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "Accession Number,Concept,Entity,Period,Fiscal Period,Fiscal Year,Accepted,Legal Entity,Unit,Value,Decimals",
        "0000021344-13-000050,\"Cash and Cash Equivalents, at Carrying Value\",COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,11118000000,-6",
        "0000021344-13-000050,Assets,COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,89432000000,-6"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res
    }
};

declare %an:nondeterministic function local:test-labels-aids() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: "disc:AdvertisingCostsPolicyTextBlock",
        map: "Disclosures",
        format: "json",
        fiscalYear: 2014,
        fiscalPeriod: "FY",
        labels: true,
        aid: "0000858877-14-000029"
    }
    let $res := test:invoke($endpoint, $params)
    let $actual := [
            for $labels in $res[2].FactTable[].Labels
            return (keys($labels) ! $labels.$$)
        ]
    let $expected := [ "Advertising Costs, Policy", "Default Legal Entity", "Accession Number", "Concept", "Entity", "CISCO SYSTEMS, INC.", "Period", "Fiscal Period", "Fiscal Year", "Accepted", "Unit", "Legal Entity" ]
    let $status as integer := $res[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

local:check({
    cocacola: local:test-facttable(468, {
        ticker:"ko"
    }),
    cocacola-all: local:test-facttable(468, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-instant: local:test-facttable(163, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant" ]
    }),
    cocacola-ytd: local:test-facttable(305, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "YTD" ]
    }),
    cocacola-qtd: local:test-facttable(90, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "Q2" ],
        fiscalPeriodType: [ "QTD" ]
    }),
    cocacolaCSVLabels: local:test-labels(),
    ciscoLabelsByAid: local:test-labels-aids(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-empty({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    }),
    quartersfact: local:test-facttable(0, { (: this fact is not reported in the raw XML filing :)
        aid: "0000051143-13-000007",
        concept: "disc:AssetRetirementObligationsPolicy",
        fiscalPeriod: "Q3",
        fiscalYear: "2013",
        map: "Disclosures"
    }),
    quartersfact2: local:test-facttable(1, {
        aid: "0001193125-12-207154",
        concept: "disc:CashFlowOperatingCapitalTableTextBlock",
        fiscalPeriod: "Q1",
        fiscalYear: "2012",
        map: "Disclosures"
    }),
    generic: local:test-facttable(2, {
        "xbrl:Entity":"http://www.sec.gov/CIK%200000021344",
        "sec:Accepted":"20140227132423",
        concept:"us-gaap:Assets",
        profile-name: "generic"
    })
})

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

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

declare %an:nondeterministic function local:test-empty($params as object) as item
{
    let $endpoint := "facts"
    let $res as object := test:invoke-raw($endpoint, $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res
    }
};

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

declare %an:nondeterministic function local:test-labels() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw($endpoint, $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "Accession Number,Concept,Entity,Period,Fiscal Period,Fiscal Year,Accepted,Legal Entity,Unit,Value,Decimals",
        "0000021344-13-000050,\"Cash and Cash Equivalents, at Carrying Value\",COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,11118000000,-6",
        "0000021344-13-000050,Assets,COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,89432000000,-6"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        url: test:url($endpoint, $params),
        unexpectedResponse: $res
    }
};

declare %an:nondeterministic function local:test-labels-aids() as item
{
    let $endpoint := "facts"
    let $params := {
        concept: "disc:AdvertisingCostsPolicyTextBlock",
        map: "Disclosures",
        format: "json",
        fiscalYear: 2014,
        fiscalPeriod: "FY",
        labels: true,
        aid: "0000858877-14-000029"
    }
    let $res := test:invoke($endpoint, $params)
    let $actual := [
            for $labels in $res[2].FactTable[].Labels
            return (keys($labels) ! $labels.$$)
        ]
    let $expected := [ "Advertising Costs, Policy", "Default Legal Entity", "Accession Number", "Concept", "Entity", "CISCO SYSTEMS, INC.", "Period", "Fiscal Period", "Fiscal Year", "Accepted", "Unit", "Legal Entity" ]
    let $status as integer := $res[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

local:check({
    cocacola: local:test-facttable(468, {
        ticker:"ko"
    }),
    cocacola-all: local:test-facttable(468, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant", "YTD", "QTD" ]
    }),
    cocacola-instant: local:test-facttable(163, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "instant" ]
    }),
    cocacola-ytd: local:test-facttable(305, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "FY" ],
        fiscalPeriodType: [ "YTD" ]
    }),
    cocacola-qtd: local:test-facttable(0, {
        ticker:"ko",
        fiscalYear: 2013,
        fiscalPeriod: [ "Q2" ],
        fiscalPeriodType: [ "QTD" ]
    }),
    cocacolaCSVLabels: local:test-labels(),
    ciscoLabelsByAid: local:test-labels-aids(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-empty({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    }),
    quartersfact: local:test-facttable(0, { (: this fact is not reported in the raw XML filing :)
        aid: "0000051143-13-000007",
        concept: "disc:AssetRetirementObligationsPolicy",
        fiscalPeriod: "Q3",
        fiscalYear: "2013",
        map: "Disclosures"
    }),
    quartersfact2: local:test-facttable(1, {
        aid: "0001193125-12-207154",
        concept: "disc:CashFlowOperatingCapitalTableTextBlock",
        fiscalPeriod: "Q1",
        fiscalYear: "2012",
        map: "Disclosures"
    }),
    generic: local:test-facttable(2, {
        "xbrl:Entity":"http://www.sec.gov/CIK%200000021344",
        "sec:Accepted":"20140227132423",
        concept:"us-gaap:Assets",
        profile-name: "generic"
    })
})


added test for additional labels

added test for additional labels

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

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facts.jq?_method=POST" || $params).body.content).FactTable[])
    return if ($actual eq $expected) then true else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

declare %an:nondeterministic function local:test-empty($params as string) as item
{
    let $res as object := http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facts.jq?_method=POST" || $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        unexpectedResponse: $res
    }
};

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

declare %an:nondeterministic function local:test-labels() as item
{
    let $res as object := http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facts.jq?_method=POST&concept=fac:Assets&concept=us-gaap:CashAndCashEquivalentsAtCarryingValue&report=FundamentalAccountingConcepts&format=csv&ticker=ko&fiscalYear=2013&fiscalPeriod=Q3&labels=true")
    let $actual := $res.body.content
    let $expectedLines := (
        "Archive,Concept,Entity,Period,Fiscal Period,Fiscal Year,Accepted,Legal Entity,Value,Decimals,EntityRegistrantName,Unit",
        "0000021344-13-000050,\"Cash and Cash Equivalents, at Carrying Value\",COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,11118000000,-6,COCA COLA CO,iso4217:USD",
        "0000021344-13-000050,Assets,COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,89432000000,-6,COCA COLA CO,iso4217:USD"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        unexpectedResponse: $res
    }
};

local:check({
    cocacola: local:test-facttable(468, "&ticker=ko"),
    cocacolaCSVLabels: local:test-labels(),
    tickerconcept: local:test-facttable(1, "&ticker=ko&concept=us-gaap:Assets"),
    tickerfyfprole: local:test-facttable(1, "&ticker=ko&fiscalYear=2012&fiscalPeriod=Q1&concept=us-gaap:Assets"),
    tagconcept: local:test-facttable(30, "&tag=DOW30&concept=us-gaap:Assets"),
    tagfyfprole: local:test-facttable(30, "&tag=DOW30&fiscalYear=2012&fiscalPeriod=Q1&concept=us-gaap:Assets"),
    testNoEntity4Sic: local:test-empty("&concept=fac:Assets&map=FundamentalAccountingConcepts&format=csv&sic=5052&fiscalYear=2010&fiscalPeriod=Q3")
})

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

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as string) as atomic
{
    let $actual as integer := count(parse-json(http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facts.jq?_method=POST" || $params).body.content).FactTable[])
    return if ($actual eq $expected) then true else "false [Actual="||$actual||", Expected="||$expected ||"]"
};

declare %an:nondeterministic function local:test-empty($params as string) as item
{
    let $res as object := http-client:get("http://" || request:server-name() || ":" || request:server-port() || "/v1/_queries/public/api/facts.jq?_method=POST" || $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        unexpectedResponse: $res
    }
};

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

local:check({
    cocacola: local:test-facttable(468, "&ticker=ko"),
    tickerconcept: local:test-facttable(1, "&ticker=ko&concept=us-gaap:Assets"),
    tickerfyfprole: local:test-facttable(1, "&ticker=ko&fiscalYear=2012&fiscalPeriod=Q1&concept=us-gaap:Assets"),
    tagconcept: local:test-facttable(30, "&tag=DOW30&concept=us-gaap:Assets"),
    tagfyfprole: local:test-facttable(30, "&tag=DOW30&fiscalYear=2012&fiscalPeriod=Q1&concept=us-gaap:Assets"),
    testNoEntity4Sic: local:test-empty("&concept=fac:Assets&map=FundamentalAccountingConcepts&format=csv&sic=5052&fiscalYear=2010&fiscalPeriod=Q3")
})


fixed issue 368 response shouldn't be 400 in case there is no entity for a SIC

import module namespace util = "http://secxbrl.info/modules/util";
import module namespace session = "http://apps.28.io/session";

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

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

declare function local:contains-aspect($name as string) as boolean
{
  some $x in (request:param-names() ! starts-with($$, $name))
  satisfies $x
};

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

session:audit-call();

(: Query parameters :)
let $format as string?         := request:param-values("format")
let $ciks as string*           := distinct-values(request:param-values("cik"))
let $tags as string*           := distinct-values(request:param-values("tag"))
let $tickers as string*        := distinct-values(request:param-values("ticker"))
let $sics as string*           := distinct-values(request:param-values("sic"))
let $fiscalYears as string*    := distinct-values(request:param-values("fiscalYear", "LATEST"))
let $fiscalPeriods as string*  := distinct-values(request:param-values("fiscalPeriod", "FY"))
let $aids as string*           := distinct-values(request:param-values("aid"))
let $map as string?            := request:param-values("map") (: Backwards compatibility :)
let $rules as string?          := request:param-values("rule") (: Backwards compatibility :)
let $report as string?         := request:param-values("report")
let $validate as string        := request:param-values("validate", "false")

(: Post-processing :)
let $format as string? := (: backwards compatibility, to be deprecated  :)
    lower-case(($format, substring-after(request:path(), ".jq."))[1])
let $tags as string* := (: backwards compatibility, to be deprecated :)
    distinct-values($tags ! upper-case($$))
let $fiscalYears as integer* :=
    for $fy in $fiscalYears ! upper-case($$)
    return switch($fy)
           case "LATEST" return $fiscal-core:LATEST_FISCAL_YEAR
           case "ALL" return $fiscal-core:ALL_FISCAL_YEARS
           default return if($fy castable as integer) then integer($fy) else ()
let $fiscalPeriods as string* :=
    for $fp in $fiscalPeriods ! upper-case($$)
    return switch($fp)
           case "ALL" return $fiscal-core:ALL_FISCAL_PERIODS
           default return $fp
let $validate as boolean := $validate = "true"

(: Object resolution :)
let $entities as object* :=
    companies:companies(
        $ciks,
        $tags,
        $tickers,
        $sics)
let $archives as object* := fiscal-core:filings(
    $entities,
    $fiscalPeriods,
    $fiscalYears,
    $aids)
let $entities := entities:entities($archives.Entity)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rules as item* :=
    if(exists($report))
    then reports:rules($report)
    else $rules

let $hypercube := local:hypercube($entities, $fiscalPeriods, $fiscalYears, $aids)

let $facts :=
    if(empty($archives))
    then ()
    else
        for $fact in sec:facts-for(
            {|
                {
                    Hypercube : $hypercube,
                    Validate: $validate
                },
                { "ConceptMaps" : $map }[exists($map)],
                { "Rules" : [ $rules ] }[exists($rules)]
            |}
        )
        return {|
            $fact,
            { "EntityRegistrantName" : $entities[$$._id eq $fact.Aspects."xbrl:Entity"].Profiles.SEC.CompanyName}
        |}

let $facts := util: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]
    }
}

return
    if(empty($archives) and (not(empty($aids)) or not(empty($ciks))))
    then {
        response:status-code(404);
        session:error("entities or archives not found (valid parameters: cik, ticker, tag, sic, aid)", $format)
    }
    else let $results := util:serialize($result, $comment, $serializers, $format, "facts")
         return util:check-and-return-results($entities, $results, $format)

import module namespace util = "http://secxbrl.info/modules/util";
import module namespace session = "http://apps.28.io/session";

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

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

declare function local:contains-aspect($name as string) as boolean
{
  some $x in (request:param-names() ! starts-with($$, $name))
  satisfies $x
};

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

session:audit-call();

(: Query parameters :)
let $format as string?         := request:param-values("format")
let $ciks as string*           := distinct-values(request:param-values("cik"))
let $tags as string*           := distinct-values(request:param-values("tag"))
let $tickers as string*        := distinct-values(request:param-values("ticker"))
let $sics as string*           := distinct-values(request:param-values("sic"))
let $fiscalYears as string*    := distinct-values(request:param-values("fiscalYear", "LATEST"))
let $fiscalPeriods as string*  := distinct-values(request:param-values("fiscalPeriod", "FY"))
let $aids as string*           := distinct-values(request:param-values("aid"))
let $map as string?            := request:param-values("map") (: Backwards compatibility :)
let $rules as string?          := request:param-values("rule") (: Backwards compatibility :)
let $report as string?         := request:param-values("report")
let $validate as string        := request:param-values("validate", "false")

(: Post-processing :)
let $format as string? := (: backwards compatibility, to be deprecated  :)
    lower-case(($format, substring-after(request:path(), ".jq."))[1])
let $tags as string* := (: backwards compatibility, to be deprecated :)
    distinct-values($tags ! upper-case($$))
let $fiscalYears as integer* :=
    for $fy in $fiscalYears ! upper-case($$)
    return switch($fy)
           case "LATEST" return $fiscal-core:LATEST_FISCAL_YEAR
           case "ALL" return $fiscal-core:ALL_FISCAL_YEARS
           default return if($fy castable as integer) then integer($fy) else ()
let $fiscalPeriods as string* :=
    for $fp in $fiscalPeriods ! upper-case($$)
    return switch($fp)
           case "ALL" return $fiscal-core:ALL_FISCAL_PERIODS
           default return $fp
let $validate as boolean := $validate = "true"

(: Object resolution :)
let $entities as object* :=
    companies:companies(
        $ciks,
        $tags,
        $tickers,
        $sics)
let $archives as object* := fiscal-core:filings(
    $entities,
    $fiscalPeriods,
    $fiscalYears,
    $aids)
let $entities := entities:entities($archives.Entity)
let $report as object? := reports:reports($report)
let $map as item* :=
    if(exists($report))
    then reports:concept-map($report)
    else $map
let $rules as item* :=
    if(exists($report))
    then reports:rules($report)
    else $rules

let $hypercube := local:hypercube($entities, $fiscalPeriods, $fiscalYears, $aids)

let $facts :=
    if(empty($archives))
    then ()
    else
        for $fact in sec:facts-for(
            {|
                {
                    Hypercube : $hypercube,
                    Validate: $validate
                },
                { "ConceptMaps" : $map }[exists($map)],
                { "Rules" : [ $rules ] }[exists($rules)]
            |}
        )
        return {|
            $fact,
            { "EntityRegistrantName" : $entities[$$._id eq $fact.Aspects."xbrl:Entity"].Profiles.SEC.CompanyName}
        |}

let $facts := util: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" })
    }
}

return
    if(empty($archives))
    then {
        response:status-code(400);
        session:error("entities or archives not found (valid parameters: cik, ticker, tag, sic, aid)", $format)
    }
    else let $results := util:serialize($result, $comment, $serializers, $format, "facts")
         return util:check-and-return-results($entities, $results, $format)


Fixed member filtering.

Fixed member filtering.

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

import module namespace hypercubes = "http://xbrl.io/modules/bizql/hypercubes";
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-csv($o as object*) as string?
{
    if (exists($o)) (: bug in csv:serialize :)
    then
    string-join(
        csv:serialize(
            for $o in $o
            let $a := $o.Aspects
            return {|
                for $k in keys($a)
                return { $k : $a.$k },
                { "Value" : $o.Value },
                if (exists($o."Unit"))
                then { "Unit" :  $o."Unit" }
                else (),
                if (exists($o.Decimals))
                then { "Decimals" : $o.Decimals }
                else(),
                { "EntityRegistrantName" : $o.EntityRegistrantName },
                if (exists($o.ReportedConcept))
                then { "ReportedConcept" : $o.ReportedConcept }
                else ()
            |},
            { serialize-null-as : "" }
        )
    )
    else ()
};

declare function local:to-xml($o as object*)
{
    (session:comment("xml"),
    <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
        for $o in $o
        let $a := $o.Aspects
        return
            <Fact>{
                <Aspects>{
                    for $k in keys($a)
                    return
                        <Aspect>
                            <Name>{$k}</Name>
                            <Value>{$a.$k}</Value>
                        </Aspect>
                }</Aspects>,
                <Value>{$o.Value}</Value>,
                <Type>{$o.Type}</Type>,
                if (exists($o.Unit))
                then <Unit>{$o.Unit}</Unit>
                else(),
                if (exists($o.Decimals))
                then <Decimals>{$o.Decimals}</Decimals>
                else (),
                <EntityRegistrantName>{$o.EntityRegistrantName}</EntityRegistrantName>,
                if (exists($o.ReportedConcept))
                then <ReportedConcept>{$o."ReportedConcept"}</ReportedConcept>
                else ()
            }</Fact>
    }</FactTable>)
};

declare function local:facts(
    $entities,
    $archives,
    $fiscalPeriods,
    $fiscalYears,
    $concepts,
    $dimensions,
    $map)
{
    for $entity in $entities
    (: compute latest reported fiscalYear if none is specified :)
    let $years  :=  distinct-values(
                        for $f in $fiscalYears
                        return
                            switch ($f)
                            case "LATEST" return
                                for $p in $fiscalPeriods
                                return
                                    if ($p eq "FY")
                                    then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                                    else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
                            case "ALL" return ()
                            default return $f
                    )
    let $fiscalPeriods := distinct-values(
                            $fiscalPeriods !
                                (switch($$)
                                case "Q1" return ("Q1","YTD1")
                                case "Q2" return ("Q2","YTD2")
                                case "Q3" return ("Q3","YTD3")
                                default return ("Q4","FY")))
    for $concept in $concepts
    let $aspects :=
        {|
            { "xbrl:Concept" : $concept },
            { "xbrl:Entity" : $entity._id },
            (: This is because of a bug that will be fixed later (hypercube members do not get considered) :)
            for $d in values($dimensions)
            let $members := descendant-objects(values($d)).Name
            where exists($members)
            return { $d.Name: [ $members ] }
        |}
    let $hypercube := copy $h := hypercubes:dimensionless-hypercube()
                      modify (
                          insert json {|
                            "dei:LegalEntityAxis" ! { $$ : { Name : $$, Default : "sec:DefaultLegalEntity" } }
                          |} into $h.Aspects,
                          for $d in $dimensions[not values($$).Name = "dei:LegalEntityAxis"]
                          return insert json $d into $h.Aspects
                      )
                      return $h
    let $options :=
            {|
                if (exists($map))
                then { "concept-maps" : $map }
                else (),
                { Hypercube : $hypercube }
            |}
    return
    for $fact in
        for $f in
            (
                sec-fiscal:facts-for-aspects-and-fiscal-periods-and-years(
                    $aspects, $fiscalPeriods, $years, $options)
            )
        order by $f.Profiles.SEC.Fiscal.Acceptance descending
        group by $f.Profiles.SEC.Fiscal.Year,
                 $f.Profiles.SEC.Fiscal.Period,
                 $f.Profiles.SEC.Fiscal.Acceptance
        return $f[1]
    return {|
        { Aspects : {|
            for $a in keys($fact.Aspects)
            return { $a : $fact.Aspects.$a },
            { "bizql:FiscalPeriod" : $fact.Profiles.SEC.Fiscal.Period },
            { "bizql:FiscalYear" : $fact.Profiles.SEC.Fiscal.Year }
        |} },
        { Type: $fact.Type },
        if (exists($fact.Aspects."xbrl:Unit"))
        then { Unit: $fact.Aspects."xbrl:Unit" }
        else  (),
        if (exists($fact.Decimals))
        then { Decimals: $fact.Decimals }
        else (),
        { Value: $fact.Value },
        { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
        if (exists($map))
        then { "ReportedConcept" : $fact.AuditTrails[].Data.OriginalConcept[1] }
        else ()
    |}
};

let $format      := lower-case(request:param-values("format")[1])

(: choose
    1. entities (using ciks, tags, tickers, sics), FY, and FP ) or
    2. accession numbers
:)
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y cast as integer
                            else ()
                    )
let $fiscalPeriods := let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (lower-case($fp) eq "all")
                        then ("Q1", "Q2", "Q3", "FY")
                        else $fp
let $aids := request:param-values("aid")
let $dimensions :=  for $p in request:param-names()
                    where contains($p, ":")
                    group by $dimension-name := if(ends-with(lower-case($p), ":default"))
                                                then substring-before($p, ":default")
                                                else $p
                    let $default := $p = $dimension-name || ":default"
                    let $all := (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
                    return
                    {
                       $dimension-name : {|
                            { Name : $dimension-name },
                            if ($default)
                            then { Default : request:param-values($dimension-name || ":default")[1] }
                            else (),
                            if ($all)
                            then ()
                            else {
                                Domains : {
                                    "sec:ImplicitDomain" : {
                                        Name: "sec:ImplicitDomain",
                                        Members: {|
                                            (request:param-values($dimension-name)) ! { $$ : { Name: $$ } }
                                        |}
                                    }
                                }
                            }
                        |}
                    }
let $concepts := request:param-values("concept")
let $map      := request:param-values("map")[1]
return
  if (empty($concepts))
  then {
    response:status-code(400);
    session:error("concept: missing parameter", $format)
  } else
    let $archives := (
        archives:archives($aids)
    )
    let $entities := (
        companies:companies($ciks),
        companies:companies-for-tags($tags),
        companies:companies-for-tickers($tickers),
        companies:companies-for-SIC($sics),
        if (exists($archives)) then companies:companies($archives.Entity) else ()
    )
    return switch(true)
      case empty($entities) and empty($archives) return {
        response:status-code(404);
        session:error("entities or archives not found (valid parameters: cik, ticker, tag, sic, aid)", $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, $archives,
            $fiscalPeriods, $fiscalYears,
            $concepts, $dimensions, $map)
        return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                local:to-xml($facts)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=facts.csv");
                local:to-csv($facts)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                local:to-csv($facts)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    { NetworkIdentifier : "http://bizql.io/facts" },
                    { TableName : "xbrl:Facts" },
                    { FactTable : [ $facts ] },
                    session:comment("json")
                |}
            }

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

import module namespace hypercubes = "http://xbrl.io/modules/bizql/hypercubes";
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-csv($o as object*) as string?
{
    if (exists($o)) (: bug in csv:serialize :)
    then
    string-join(
        csv:serialize(
            for $o in $o
            let $a := $o.Aspects
            return {|
                for $k in keys($a)
                return { $k : $a.$k },
                { "Value" : $o.Value },
                if (exists($o."Unit"))
                then { "Unit" :  $o."Unit" }
                else (),
                if (exists($o.Decimals))
                then { "Decimals" : $o.Decimals }
                else(),
                { "EntityRegistrantName" : $o.EntityRegistrantName },
                if (exists($o.ReportedConcept))
                then { "ReportedConcept" : $o.ReportedConcept }
                else ()
            |},
            { serialize-null-as : "" }
        )
    )
    else ()
};

declare function local:to-xml($o as object*)
{
    (session:comment("xml"),
    <FactTable NetworkIdentifier="http://bizql.io/facts"
            TableName="xbrl:Facts">{
        for $o in $o
        let $a := $o.Aspects
        return
            <Fact>{
                <Aspects>{
                    for $k in keys($a)
                    return
                        <Aspect>
                            <Name>{$k}</Name>
                            <Value>{$a.$k}</Value>
                        </Aspect>
                }</Aspects>,
                <Value>{$o.Value}</Value>,
                <Type>{$o.Type}</Type>,
                if (exists($o.Unit))
                then <Unit>{$o.Unit}</Unit>
                else(),
                if (exists($o.Decimals))
                then <Decimals>{$o.Decimals}</Decimals>
                else (),
                <EntityRegistrantName>{$o.EntityRegistrantName}</EntityRegistrantName>,
                if (exists($o.ReportedConcept))
                then <ReportedConcept>{$o."ReportedConcept"}</ReportedConcept>
                else ()
            }</Fact>
    }</FactTable>)
};

declare function local:facts(
    $entities,
    $archives,
    $fiscalPeriods,
    $fiscalYears,
    $concepts,
    $dimensions,
    $map)
{
    for $entity in $entities
    (: compute latest reported fiscalYear if none is specified :)
    let $years  :=  distinct-values(
                        for $f in $fiscalYears
                        return
                            switch ($f)
                            case "LATEST" return
                                for $p in $fiscalPeriods
                                return
                                    if ($p eq "FY")
                                    then sec-fiscal:latest-reported-fiscal-period($entity, "10-K").year
                                    else sec-fiscal:latest-reported-fiscal-period($entity, "10-Q").year
                            case "ALL" return ()
                            default return $f
                    )
    let $fiscalPeriods := distinct-values(
                            $fiscalPeriods !
                                (switch($$)
                                case "Q1" return ("Q1","YTD1")
                                case "Q2" return ("Q2","YTD2")
                                case "Q3" return ("Q3","YTD3")
                                default return ("Q4","FY")))
    for $concept in $concepts
    let $aspects :=
        {|
            { "xbrl:Concept" : $concept },
            { "xbrl:Entity" : $entity._id }
        |}
    let $hypercube := copy $h := hypercubes:dimensionless-hypercube()
                      modify (
                          insert json {|
                            "dei:LegalEntityAxis" ! { $$ : { Name : $$, Default : "sec:DefaultLegalEntity" } }
                          |} into $h.Aspects,
                          for $d in $dimensions
                          return insert json $d into $h.Aspects
                      )
                      return $h
    let $options :=
            {|
                if (exists($map))
                then { "concept-maps" : $map }
                else (),
                { Hypercube : $hypercube }
            |}
    return
    for $fact in
        for $f in
            (
                sec-fiscal:facts-for-aspects-and-fiscal-periods-and-years(
                    $aspects, $fiscalPeriods, $years, $options)
            )
        order by $f.Profiles.SEC.Fiscal.Acceptance descending
        group by $f.Profiles.SEC.Fiscal.Year,
                 $f.Profiles.SEC.Fiscal.Period,
                 $f.Profiles.SEC.Fiscal.Acceptance
        return $f[1]
    return {|
        { Aspects : {|
            for $a in keys($fact.Aspects)
            return { $a : $fact.Aspects.$a },
            { "bizql:FiscalPeriod" : $fact.Profiles.SEC.Fiscal.Period },
            { "bizql:FiscalYear" : $fact.Profiles.SEC.Fiscal.Year }
        |} },
        { Type: $fact.Type },
        if (exists($fact.Aspects."xbrl:Unit"))
        then { Unit: $fact.Aspects."xbrl:Unit" }
        else  (),
        if (exists($fact.Decimals))
        then { Decimals: $fact.Decimals }
        else (),
        { Value: $fact.Value },
        { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
        if (exists($map))
        then { "ReportedConcept" : $fact.AuditTrails[].Data.OriginalConcept[1] }
        else ()
    |}
};

let $format      := lower-case(request:param-values("format")[1])

(: choose
    1. entities (using ciks, tags, tickers, sics), FY, and FP ) or
    2. accession numbers
:)
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        for $y in request:param-values("fiscalYear", "LATEST")
                        return
                            if ($y eq "LATEST" or $y eq "ALL")
                            then $y
                            else if ($y castable as integer)
                            then $y cast as integer
                            else ()
                    )
let $fiscalPeriods := let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (lower-case($fp) eq "all")
                        then ("Q1", "Q2", "Q3", "FY")
                        else $fp
let $aids := request:param-values("aid")
let $dimensions :=  for $p in request:param-names()
                    where contains($p, ":")
                    group by $dimension-name := if(ends-with(lower-case($p), ":default"))
                                                then substring-before($p, ":default")
                                                else $p
                    let $default := $p = $dimension-name || ":default"
                    let $all := (request:param-values($dimension-name) ! upper-case($$)) = "ALL"
                    return
                    {
                       $dimension-name : {|
                            { Name : $dimension-name },
                            if ($default)
                            then { Default : request:param-values($dimension-name || ":default")[1] }
                            else (),
                            if ($all)
                            then ()
                            else {
                                Domains : {
                                    Name: "sec:ImplicitDomain",
                                    Members: {|
                                        (request:param-values($dimension-name)) ! { $$ : { Name: $$ } }
                                    |}
                                }
                            }
                        |}
                    }
let $concepts := request:param-values("concept")
let $map      := request:param-values("map")[1]
return
  if (empty($concepts))
  then {
    response:status-code(400);
    session:error("concept: missing parameter", $format)
  } else
    let $archives := (
        archives:archives($aids)
    )
    let $entities := (
        companies:companies($ciks),
        companies:companies-for-tags($tags),
        companies:companies-for-tickers($tickers),
        companies:companies-for-SIC($sics),
        if (exists($archives)) then companies:companies($archives.Entity) else ()
    )
    return switch(true)
      case empty($entities) and empty($archives) return {
        response:status-code(404);
        session:error("entities or archives not found (valid parameters: cik, ticker, tag, sic, aid)", $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, $archives,
            $fiscalPeriods, $fiscalYears,
            $concepts, $dimensions, $map)
        return
            switch ($format)
            case "xml" return {
                response:serialization-parameters({"omit-xml-declaration" : false, indent : true });
                local:to-xml($facts)
            }
            case "text" case "csv" return {
                response:content-type("text/csv");
                response:header("Content-Disposition", "attachment; filename=facts.csv");
                local:to-csv($facts)
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                local:to-csv($facts)
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    { NetworkIdentifier : "http://bizql.io/facts" },
                    { TableName : "xbrl:Facts" },
                    { FactTable : [ $facts ] },
                    session:comment("json")
                |}
            }


Added constantc

Added constantc

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

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";

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

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


Update facts.jq

Update facts.jq

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

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as object) as item
{
    let $request := test:invoke("facts", $params)
    let $actual as integer := count($request[2].FactTable[])
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status)
};

declare %an:nondeterministic function local:test-empty($params as object) as item
{
    let $res as object := test:invoke-raw("facts", $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        unexpectedResponse: $res
    }
};

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

declare %an:nondeterministic function local:test-labels() as item
{
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw("facts", $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "Accession Number,Concept,Entity,Period,Fiscal Period,Fiscal Year,Accepted,Legal Entity,Unit,Value,Decimals",
        "0000021344-13-000050,\"Cash and Cash Equivalents, at Carrying Value\",COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,11118000000,-6",
        "0000021344-13-000050,Assets,COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,89432000000,-6"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        unexpectedResponse: $res
    }
};

local:check({
    cocacola: local:test-facttable(468, {
        ticker:"ko"
    }),
    cocacolaCSVLabels: local:test-labels(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-empty({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    })
})

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

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as object) as item
{
    let $request := test:invoke("facts", $params)
    let $actual as integer := count($request[2].FactTable[])
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status)
};

declare %an:nondeterministic function local:test-empty($params as object) as item
{
    let $res as object := test:invoke-raw("facts", $params)
    return if($res.status eq 200 and $res.headers."Content-Length" eq "0") then true else {
        unexpectedResponse: $res
    }
};

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

declare %an:nondeterministic function local:test-labels() as item
{
    let $params := {
        concept: [ "fac:Assets", "us-gaap:CashAndCashEquivalentsAtCarryingValue" ],
        report: "FundamentalAccountingConcepts",
        format: "csv",
        ticker: "ko",
        fiscalYear: 2013,
        fiscalPeriod: "Q3",
        labels: true
    }
    let $res as object := test:invoke-raw("facts", $params)
    let $actual := $res.body.content
    let $expectedLines := (
        "Accession Number,Concept,Entity,Period,Fiscal Period,Fiscal Year,Accepted,Legal Entity,Unit,Value,Decimals,EntityRegistrantName",
        "0000021344-13-000050,\"Cash and Cash Equivalents, at Carrying Value\",COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,11118000000,-6,COCA COLA CO",
        "0000021344-13-000050,Assets,COCA COLA CO,2013-09-27,Q3,2013,20131024121047,Default Legal Entity,USD,89432000000,-6,COCA COLA CO"
    )
    return if($res.status eq 200 and (every $line in $expectedLines satisfies contains($actual,$line))) then true else {
        unexpectedResponse: $res
    }
};

local:check({
    cocacola: local:test-facttable(468, {
        ticker:"ko"
    }),
    cocacolaCSVLabels: local:test-labels(),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-empty({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    })
})


:bug: Filtering concepts.

:bug: Filtering concepts.

jsoniq version "1.0";

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

(:~
 :
This module provides functionality for exporting archives to the XBRL format.

 :
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace xbrl-export = "http://28.io/modules/xbrl/export";

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

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

declare variable $xbrl-export:serialization-parameters :=
    <serialization-parameters xmlns="http://www.w3.org/2010/xslt-xquery-serialization">
      <method value="xml"/>
      <indent value="yes"/>
      <encoding value="UTF-8"/>
    </serialization-parameters>;

declare %private function xbrl-export:comment($options as object?) as comment()
{
  comment { "
    Generated: " || current-dateTime() || " (" || credentials:credentials("MongoDB", "xbrl").db || ")
    "||($options.Comment, "")[1]||"
    Generated by 28msec (CellStore/28.io)
  " }
};

declare function xbrl-export:schema(
    $archive as object,
    $options as object?)
as object
{
  {|
    let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
    let $components as object* := components:components-for-archives($archive)
    let $prefixes := distinct-values($components.Concepts[].Name ! (tokenize($$, ":")[1]))
    return
    for $concepts as object in $components.Concepts[]
    where [not $concepts.Name = $concepts:BUILT_IN_REPORT_ELEMENTS]
    let $prefix as string* :=  $concepts.Name ! (tokenize($$, ":")[1])
    where not $prefix = ("sec", "xbrl", "xbrl28", "fsa")
    group by $prefix
    let $concepts :=
      for $concept in $concepts
      group by $concept.Name
      return $concept[1]
    let $namespace as string := ($archive.Namespaces.$prefix, "http://28.io/unknown" || $prefix)[1]
    return { $prefix: [

      xbrl-export:comment($options),

      <schema targetNamespace="{$namespace}"
          elementFormDefault="qualified"
          xmlns="http://www.w3.org/2001/XMLSchema"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          xmlns:link="http://www.xbrl.org/2003/linkbase"
          xmlns:xbrli="http://www.xbrl.org/2003/instance"
          xmlns:xbrldt="http://xbrl.org/2005/xbrldt"
          xmlns:nonnum="http://www.xbrl.org/dtr/type/non-numeric">
      {
          for $any-prefix in keys($archive.Namespaces)
          return namespace { $any-prefix } { $archive.Namespaces.$any-prefix },

          for $other-prefix as string in $prefixes
          where $other-prefix ne $prefix
          let $other-namespace as string :=
            ($archive.Namespaces.$other-prefix,
             "http://28.io/unknown" || $other-prefix)[1]
          return <import namespace="{other-namespace}"
                         schemaLocation="{$taxonomyName}-{$other-prefix}.xsd"/>,

          <import
              namespace="http://www.xbrl.org/2003/instance"
              schemaLocation="http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd"/>,
          <import
              namespace="http://xbrl.org/2005/xbrldt"
              schemaLocation="http://www.xbrl.org/2005/xbrldt-2005.xsd" />,
          <import
              namespace="http://www.xbrl.org/dtr/type/non-numeric"
              schemaLocation="http://www.xbrl.org/dtr/type/nonNumeric-2009-12-16.xsd" />,
          components:to-xbrl-annotation($components, $taxonomyName),

          for $concept as element() in concepts:to-xbrl($concepts)
          group by $name := $concept/@name
          return $concept[1]
      }
      </schema>

    ] }
  |}
};

declare function xbrl-export:labels(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      labels:to-xbrl($components.Concepts[], $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:presentation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $presentationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:PRESENTATION_NETWORK)
      where exists($presentationNetwork) and $component.Role ne null
      return networks:to-xbrl($presentationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:calculation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $calculationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
      where exists($calculationNetwork) and $component.Role ne null
      return networks:to-xbrl($calculationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:definition(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
      {
        for $ns in keys($archive.Namespaces)
        return namespace { $ns } { $archive.Namespaces.$ns },
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/all" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#all"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-domain"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/domain-member" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#domain-member"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#hypercube-dimension"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/notAll" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#notAll"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-default"/>,
        components:to-xbrl-roleRefs($components, $taxonomyName),
        for $component in $components
        for $hypercube in values($component.Hypercubes)
        where $component.Role ne null
        return networks:to-xbrl($hypercube, $component, $taxonomyName)
      }
    </linkbase>
  )
};

declare function xbrl-export:archive(
    $archive as item
) as item()*
{
  xbrl-export:archive($archive, {})
};

declare function xbrl-export:archive(
    $archive as item,
    $options as object?)
as item()*
{
  let $archive as object := archives:archives($archive)
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $schemas as object := {|
    let $schemas-by-prefix := xbrl-export:schema($archive, $options)
    for $prefix in keys($schemas-by-prefix)
    let $nodes as node()* := $schemas-by-prefix.$prefix[]
    return { $prefix: serialize($nodes, $xbrl-export:serialization-parameters) }
  |}
  let $labels as string := serialize(xbrl-export:labels($archive, $options), $xbrl-export:serialization-parameters)
  let $presentation as string := serialize(xbrl-export:presentation($archive, $options), $xbrl-export:serialization-parameters)
  let $calculation as string := serialize(xbrl-export:calculation($archive, $options), $xbrl-export:serialization-parameters)
  let $definition as string := serialize(xbrl-export:definition($archive, $options), $xbrl-export:serialization-parameters)
  let $archive as xs:base64Binary := archive:create(
    (
      { "type" : "directory", "name" : $taxonomyName },
      for $prefix as string in keys($schemas)
      return { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "-" || $prefix || ".xsd" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_lab.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_pre.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_cal.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_def.xml" }
    ),
    (
      values($schemas),
      $labels,
      $presentation,
      $calculation,
      $definition
    )
  )
  return
    (
      {
        "content-type": "application/zip",
        "serialization":
        {
          "method": "binary"
        },
        "headers": {
          "Pragma": "public",
          "Expires": "0",
          "Cache-Control": "must-revalidate, post-check=0, pre-check=0",
          "Content-Disposition": "attachment; filename='" || $taxonomyName || ".xbrlx';",
          "Content-Transfer-Encoding": "binary"
        }
      },
      $archive
    )
};

jsoniq version "1.0";

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

(:~
 :
This module provides functionality for exporting archives to the XBRL format.

 :
 : @author Dennis Knochenwefel
 : @author Ghislain Fourny
 :)
module namespace xbrl-export = "http://28.io/modules/xbrl/export";

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

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

declare variable $xbrl-export:serialization-parameters :=
    <serialization-parameters xmlns="http://www.w3.org/2010/xslt-xquery-serialization">
      <method value="xml"/>
      <indent value="yes"/>
      <encoding value="UTF-8"/>
    </serialization-parameters>;

declare %private function xbrl-export:comment($options as object?) as comment()
{
  comment { "
    Generated: " || current-dateTime() || " (" || credentials:credentials("MongoDB", "xbrl").db || ")
    "||($options.Comment, "")[1]||"
    Generated by 28msec (CellStore/28.io)
  " }
};

declare function xbrl-export:schema(
    $archive as object,
    $options as object?)
as object
{
  {|
    let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
    let $components as object* := components:components-for-archives($archive)
    let $concept-names as string* := distinct-values($components.Concepts[].Name[not $$ = $concepts:BUILT_IN_REPORT_ELEMENTS])
    let $prefixes as string* :=  distinct-values($concept-names ! (tokenize($$, ":")[1]))[not $$ = ("sec", "xbrl", "xbrl28", "fsa")]
    for $prefix as string in $prefixes
    let $namespace as string := ($archive.Namespaces.$prefix, "http://28.io/unknown" || $prefix)[1]
    return { $prefix: [
      xbrl-export:comment($options),
      <schema targetNamespace="{$namespace}"
          elementFormDefault="qualified"
          xmlns="http://www.w3.org/2001/XMLSchema"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          xmlns:link="http://www.xbrl.org/2003/linkbase"
          xmlns:xbrli="http://www.xbrl.org/2003/instance"
          xmlns:xbrldt="http://xbrl.org/2005/xbrldt"
          xmlns:nonnum="http://www.xbrl.org/dtr/type/non-numeric">
      {
          for $ns in keys($archive.Namespaces)
          return namespace { $ns } { $archive.Namespaces.$ns },
          for $p as string in $prefixes
          where $p ne $prefix
          let $n as string := ($archive.Namespaces.$p, "http://28.io/unknown" || $p)[1]
          return <import namespace="{$n}" schemaLocation="{$taxonomyName}-{$p}.xsd"/>,
          <import namespace="http://www.xbrl.org/2003/instance" schemaLocation="http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd"/>,
          <import namespace="http://xbrl.org/2005/xbrldt" schemaLocation="http://www.xbrl.org/2005/xbrldt-2005.xsd" />,
          <import namespace="http://www.xbrl.org/dtr/type/non-numeric" schemaLocation="http://www.xbrl.org/dtr/type/nonNumeric-2009-12-16.xsd" />,
          components:to-xbrl-annotation($components, $taxonomyName),
          for $concept as element() in concepts:to-xbrl($components.Concepts[])
          group by $name := $concept/@name
          return $concept[1]
      }
      </schema>
    ] }
  |}
};

declare function xbrl-export:labels(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      labels:to-xbrl($components.Concepts[], $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:presentation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $presentationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:PRESENTATION_NETWORK)
      where exists($presentationNetwork) and $component.Role ne null
      return networks:to-xbrl($presentationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:calculation(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
    {
      for $ns in keys($archive.Namespaces)
      return namespace { $ns } { $archive.Namespaces.$ns },
      components:to-xbrl-roleRefs($components, $taxonomyName),
      for $component in $components
      let $calculationNetwork as object? := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
      where exists($calculationNetwork) and $component.Role ne null
      return networks:to-xbrl($calculationNetwork, $component, $taxonomyName)
    }
    </linkbase>
  )
};

declare function xbrl-export:definition(
    $archive as object,
    $options as object?)
as node()*
{
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $components as object* := components:components-for-archives($archive)
  return (
    xbrl-export:comment($options),
    <linkbase
        xmlns="http://www.xbrl.org/2003/linkbase"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns:xbrli="http://www.xbrl.org/2003/instance"
        xsi:schemaLocation="http://www.xbrl.org/2003/linkbase http://www.xbrl.org/2003/xbrl-linkbase-2003-12-31.xsd">
      {
        for $ns in keys($archive.Namespaces)
        return namespace { $ns } { $archive.Namespaces.$ns },
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/all" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#all"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-domain" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-domain"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/domain-member" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#domain-member"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/hypercube-dimension" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#hypercube-dimension"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/notAll" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#notAll"/>,
        <arcroleRef arcroleURI="http://xbrl.org/int/dim/arcrole/dimension-default" xlink:type="simple" xlink:href="http://www.xbrl.org/2005/xbrldt-2005.xsd#dimension-default"/>,
        components:to-xbrl-roleRefs($components, $taxonomyName),
        for $component in $components
        for $hypercube in values($component.Hypercubes)
        where $component.Role ne null
        return networks:to-xbrl($hypercube, $component, $taxonomyName)
      }
    </linkbase>
  )
};

declare function xbrl-export:archive(
    $archive as item
) as item()*
{
  xbrl-export:archive($archive, {})
};

declare function xbrl-export:archive(
    $archive as item,
    $options as object?)
as item()*
{
  let $archive as object := archives:archives($archive)
  let $taxonomyName as string := ($options.TaxonomyName, string($archive._id))[1]
  let $schemas as object := {|
    let $schemas-by-prefix := xbrl-export:schema($archive, $options)
    for $prefix in keys($schemas-by-prefix)
    let $nodes as node()* := $schemas-by-prefix.$prefix[]
    return { $prefix: serialize($nodes, $xbrl-export:serialization-parameters) }
  |}
  let $labels as string := serialize(xbrl-export:labels($archive, $options), $xbrl-export:serialization-parameters)
  let $presentation as string := serialize(xbrl-export:presentation($archive, $options), $xbrl-export:serialization-parameters)
  let $calculation as string := serialize(xbrl-export:calculation($archive, $options), $xbrl-export:serialization-parameters)
  let $definition as string := serialize(xbrl-export:definition($archive, $options), $xbrl-export:serialization-parameters)
  let $archive as xs:base64Binary := archive:create(
    (
      { "type" : "directory", "name" : $taxonomyName },
      for $prefix as string in keys($schemas)
      return { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "-" || $prefix || ".xsd" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_lab.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_pre.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_cal.xml" },
      { "encoding" : "utf-8", "type" : "regular", "name" : $taxonomyName || "/" || $taxonomyName || "_def.xml" }
    ),
    (
      values($schemas),
      $labels,
      $presentation,
      $calculation,
      $definition
    )
  )
  return
    (
      {
        "content-type": "application/zip",
        "serialization":
        {
          "method": "binary"
        },
        "headers": {
          "Pragma": "public",
          "Expires": "0",
          "Cache-Control": "must-revalidate, post-check=0, pre-check=0",
          "Content-Disposition": "attachment; filename='" || $taxonomyName || ".xbrlx';",
          "Content-Transfer-Encoding": "binary"
        }
      },
      $archive
    )
};