:green_heart: Fix test

:green_heart: Fix test

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

if(not $config:content[] = ("EDINET-TDNET-big", "EDINET-TDNET-small"))
then true
else
test:check-all-success({
    example1: if($config:content[] = "EDINET-TDNET-big") then true
    else test:invoke-and-assert-deep-equal(
      "periods",
      {tag:"ALL"},
      function($b as item*) as item* { $b.Periods },
      test:get-expected-result("edinet/periods-expected1.jq")
    ),
    eid: if($config:content[] = "EDINET-TDNET-big") then true
    else test:invoke-and-assert-deep-equal(
      "periods",
      {eid:"http://disclosure.edinet-fsa.go.jp E02529-000"},
      function($b as item*) as item* { count($b.Periods[]) },
      10
    )
})

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

if(not $config:content[] = ("EDINET-TDNET-big", "EDINET-TDNET-small"))
then true
else
test:check-all-success({
    example1: if($config:content[] = "EDINET-TDNET-big") then true
    else test:invoke-and-assert-deep-equal(
      "periods",
      {tag:"ALL"},
      function($b as item*) as item* { $b.Periods },
      test:get-expected-result("edinet/periods-expected1.jq")
    ),
    eid: if($config:content[] = "EDINET-TDNET-big") then true
    else test:invoke-and-assert-deep-equal(
      "periods",
      {eid:"http://disclosure.edinet-fsa.go.jp%20E02529-000"},
      function($b as item*) as item* { count($b.Periods[]) },
      10
    )
})


:green_heart: Fix test.

:green_heart: Fix test.

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 schema = "http://zorba.io/modules/schema";
import module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";

declare function local:expected-OtherOperatingIncomeExpenses-ATnT-2013() as object {
    {
        "Aspects" : {
          "sec:Accepted" : "20140221162554",
          "xbrl28:Archive" : "0000732717-14-000010",
          "sec:FiscalPeriod" : "FY",
          "sec:FiscalYear" : 2013,
          "sec:FiscalPeriodType" : "YTD",
          "xbrl:Period" : "2013-01-01/2013-12-31",
          "xbrl:Entity" : "http://www.sec.gov/CIK 0000732717",
          "xbrl:Concept" : "fac:OtherOperatingIncomeExpenses",
          "dei:LegalEntityAxis" : "sec:DefaultLegalEntity",
          "sec:Archive" : "0000732717-14-000010"
        },
        "KeyAspects" : [ "sec:Accepted", "xbrl:Period", "xbrl:Entity", "xbrl:Concept", "xbrl:Unit" ],
        "Type" : "NumericValue",
        "Value" : 51464000000,
        "Decimals" : -6,
        "Concept" : {
          "Balance" : "CREDIT",
          "PeriodType" : "duration",
          "DataType" : "xbrli:monetaryItemType"
        },
        "AuditTrails" : [ {
          "Id" : "bf2d1587-491c-4492-b587-ddd00d762f2d",
          "Type" : "xbrl28:formula",
          "Label" : "Other Operating Income (Expenses)",
          "Message" : "fac:OtherOperatingIncomeExpenses[51,464,000,000 USD] = fac:OperatingIncomeLoss[30,479,000,000 USD] - fac:GrossProfit[77,288,000,000 USD] + fac:OperatingExpenses[98,273,000,000 USD]",
          "Data" : {
            "OutputConcept" : "fac:OtherOperatingIncomeExpenses"
          }
        } ],
        "xbrl28:Type" : "xbrl28:formula",
        "EntityRegistrantName" : "AT&T INC.",
        "Unit" : "iso4217:USD"
      }
};

declare function local:compare-audit-trails($expected as array?, $actual as array?) as object* {
    for $exp as object at $pos in $expected[]
    let $act as object? := $actual[][$pos]
    return
        if (exists($exp.Data.ValidatedFacts))
        then
            let $expM := copy $e := $exp modify ( delete json $e.Data.ValidatedFacts ) return $e
            let $actM := copy $a := $act modify ( delete json $a.Data.ValidatedFacts ) return $a
            return  (
                if(deep-equal($expM, $actM))
                then ()
                else
                    {
                        type: "unexpected-audittrails-value",
                        expected: $expM,
                        actual: $actM
                    },
                if(count($exp.Data.ValidatedFacts[]) eq count($act.Data.ValidatedFacts[]))
                then ()
                else
                    {
                        type: "number-of-validated-facts-differs",
                        expected: $exp,
                        actual: $act
                    }
            )
        else
            if(deep-equal($exp, $act))
            then ()
            else
                {
                    type: "unexpected-audittrails-value",
                    expected: $exp,
                    actual: $act
                }
};

declare function local:diff-facts($fact-expected as object, $fact-actual as object?) as object* {
    if(empty($fact-actual))
    then
        {
            type: "missing-fact-in-result"
        }
    else
        (
            for $key in distinct-values((keys($fact-expected), keys($fact-actual)))
            where not($key = ("Aspects", "KeyAspects", "Profiles", "AuditTrails"))
            let $exp-value := $fact-expected.$key
            let $act-value := $fact-actual.$key
            return
                if(deep-equal($exp-value, $act-value) or
                   (schema:schema-type($exp-value) eq schema:schema-type($act-value) and schema:schema-type($exp-value) eq xs:QName("xs:decimal") and fn:format-number(xs:decimal($exp-value),"#,##0.0000000000000") eq fn:format-number(xs:decimal($act-value),"#,##0.0000000000000"))
                )
                then ()
                else
                    {
                        type: "unexpected-fact-field-value",
                        field: $key,
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare aspects :)
            for $key in distinct-values((keys($fact-expected.Aspects), keys($fact-actual.Aspects)))
            let $exp-value := $fact-expected.Aspects.$key
            let $act-value := $fact-actual.Aspects.$key
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-aspect-value",
                        aspect: $key,
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare keyaspects :)
            let $exp-value := $fact-expected.KeyAspects
            let $act-value := $fact-actual.KeyAspects
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-keyaspects-value",
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare Profiles :)
            let $exp-value := $fact-expected.Profiles
            let $act-value := $fact-actual.Profiles
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-profiles-value",
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare Audittrails :)
            let $exp-value := $fact-expected.AuditTrails
            let $act-value := $fact-actual.AuditTrails
            return
                local:compare-audit-trails($exp-value, $act-value)
        )
};

declare function local:compare-fact-tables($fact-table-expected as object, $fact-table-actual as object) as object*{
    let $actual-facts as object+ := $fact-table-actual.FactTable[]
    let $expected-facts as object+ := $fact-table-expected.FactTable[]
    let $errors :=
        (
            for $exp in $expected-facts
            let $act := $actual-facts[$$.Aspects."xbrl:Concept" eq $exp.Aspects."xbrl:Concept"
                                      and $$.Aspects."xbrl:Entity" eq $exp.Aspects."xbrl:Entity"
                                      and $$.Aspects."xbrl:Period" eq $exp.Aspects."xbrl:Period"
                                      and $$.Aspects."xbrl28:Archive" eq $exp.Aspects."xbrl28:Archive"]
            let $errors as object* := local:diff-facts($exp, $act)
            return
                if(empty($errors))
                then ()
                else {
                    expectedFact: $exp,
                    actualFact: $act,
                    errors: [$errors]
                },

            for $act in $actual-facts
            let $exp := $expected-facts[$$.Aspects."xbrl:Concept" eq $act.Aspects."xbrl:Concept"
                                      and $$.Aspects."xbrl:Entity" eq $act.Aspects."xbrl:Entity"
                                      and $$.Aspects."xbrl:Period" eq $act.Aspects."xbrl:Period"
                                      and $$.Aspects."xbrl28:Archive" eq $act.Aspects."xbrl28:Archive"]
            where empty($exp)
            return
                {
                    unexpectedFact: $act
                }
        )
    return
        if(empty($errors))
        then ()
        else (
                {
                    expectedNumberOfFacts: count($expected-facts),
                    actualNumberOfFacts: count($actual-facts)
                },
                $errors
            )
};

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as object) as item
{
    let $endpoint := "facttable-for-report"
    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-facttable-fact($concept as string, $expected as object, $params as object) as item
{
    let $endpoint := "facttable-for-report"
    let $request := test:invoke($endpoint, $params)
    let $facts as object* := $request[2].FactTable[]
    let $actual := $facts[$$.Aspects."xbrl:Concept" eq $concept]
    let $diff :=
      for $f in $actual return local:diff-facts($expected, $f)
    return if (empty($diff)) then true else { url: test:url($endpoint, $params), factDiffErrors: [ $diff ], expectedFact: $expected, actualFact: $actual }
};

declare %an:nondeterministic function local:test-report-does-not-exist($params as object) as item
{
    let $endpoint := "facttable-for-report"
    let $request := test:invoke($endpoint, $params)
    let $status as integer := $request[1]
    return if ($status eq 404) then true else { url: test:url($endpoint, $params), unexpectedResponse: $request[2] }
};

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-values() as item*
{
    let $endpoint := "facttable-for-report"
    let $params := {ticker:"ko",fiscalYear:"2013",fiscalPeriod:"FY",fiscalPeriodType: ["instant", "YTD", "QTD"],report:"FundamentalAccountingConcepts"}
    let $request := test:invoke($endpoint, $params)
    let $actual as object := $request[2]
    let $expected := parse-json(
        http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                        "/test/secxbrl/facttable-for-report-expected1.jq").body.content)
    let $diff := local:compare-fact-tables($expected, $actual)
    return if (empty($diff)) then true else { url: test:url($endpoint, $params), factTableDiff: [ ({ params : $params },$diff) ], expectedFactTable: $expected, actualFactTable: $actual }
};

let $dow30 := test:is-dow30()
return local:check({
    cocacola: local:test-facttable(92, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2013",
        fiscalPeriod:"Q1"}),
    aid: local:test-facttable(if($dow30) then 0 else 92, {
        aid:"0001193125-14-157120",
        report:"FundamentalAccountingConcepts"}),
    aid2: local:test-facttable(
        (:
          its only 18 here because they filed a 10-Q with a fiscalPeriod focus of FY
          http://www.sec.gov/cgi-bin/viewer?action=view&cik=732717&accession_number=0000732717-14-000022&xbrl_type=v
        :)
        18, {
        aid:"0000732717-14-000022",
        report:"FundamentalAccountingConcepts"}),
    aid2-qtd: local:test-facttable(93, {
        aid:"0000732717-14-000022",
        report:"FundamentalAccountingConcepts",
        fiscalPeriodType: [ "instant", "QTD" ]
    }),
    tickerrole: local:test-facttable(92, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1"}),
    tickerfyfprole: local:test-facttable(238, {
        report:"FundamentalAccountingConcepts",
        ticker:["ko","wmt"],
        fiscalYear:"2013",
        fiscalPeriod:"FY",
        fiscalPeriodType:["instant", "QTD", "YTD"]
    }),
    bfiXOMUnits: local:test-facttable(468, {
        report:"supportBasicFinancialInformation",
        ticker:["xom"],
        fiscalYear:"ALL",
        fiscalPeriod:"FY",
        fiscalPeriodType:["instant", "YTD"]
    }),
    allvalues : local:test-values(),
    otheroperatingincometest: local:test-facttable-fact("fac:OtherOperatingIncomeExpenses",
                                                        local:expected-OtherOperatingIncomeExpenses-ATnT-2013(),
                                                        {
        report:"FundamentalAccountingConcepts",
        ticker:"t",
        fiscalYear:"2013",
        fiscalPeriod:["FY"]}),
    reportDoesntExist: local:test-report-does-not-exist({
        report:"report-not-found",
        ticker:"MSFT",
        fiscalYear:"ALL",
        fiscalPeriod:"FY"}),
    balanceSheet: test:invoke-and-assert-deep-equal(
          "facttable-for-report",
          {
              report: "BalanceSheetClassified",
              ticker: "ko",
              fiscalYear:"2014",
              fiscalPeriod:"FY",
              validate:"true"
          },
          function($res as item*) as item* { size($res.FactTable) },
          13
        )
})

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 schema = "http://zorba.io/modules/schema";
import module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";

declare function local:expected-OtherOperatingIncomeExpenses-ATnT-2013() as object {
    {
        "Aspects" : {
          "sec:Accepted" : "20140221162554",
          "xbrl28:Archive" : "0000732717-14-000010",
          "sec:FiscalPeriod" : "FY",
          "sec:FiscalYear" : 2013,
          "sec:FiscalPeriodType" : "YTD",
          "xbrl:Period" : "2013-01-01/2013-12-31",
          "xbrl:Entity" : "http://www.sec.gov/CIK 0000732717",
          "xbrl:Concept" : "fac:OtherOperatingIncomeExpenses",
          "dei:LegalEntityAxis" : "sec:DefaultLegalEntity",
          "sec:Archive" : "0000732717-14-000010"
        },
        "KeyAspects" : [ "sec:Accepted", "xbrl:Period", "xbrl:Entity", "xbrl:Concept", "xbrl:Unit" ],
        "Type" : "NumericValue",
        "Value" : 51464000000,
        "Decimals" : -6,
        "Concept" : {
          "Balance" : "CREDIT",
          "PeriodType" : "duration",
          "DataType" : "xbrli:monetaryItemType"
        },
        "AuditTrails" : [ {
          "Id" : "bf2d1587-491c-4492-b587-ddd00d762f2d",
          "Type" : "xbrl28:formula",
          "Label" : "Other Operating Income (Expenses)",
          "Message" : "fac:OtherOperatingIncomeExpenses[51,464,000,000 USD] = fac:OperatingIncomeLoss[30,479,000,000 USD] - fac:GrossProfit[77,288,000,000 USD] + fac:OperatingExpenses[98,273,000,000 USD]",
          "Data" : {
            "OutputConcept" : "fac:OtherOperatingIncomeExpenses"
          }
        } ],
        "xbrl28:Type" : "xbrl28:formula",
        "EntityRegistrantName" : "AT&T INC.",
        "Unit" : "iso4217:USD"
      }
};

declare function local:compare-audit-trails($expected as array?, $actual as array?) as object* {
    for $exp as object at $pos in $expected[]
    let $act as object? := $actual[][$pos]
    return
        if (exists($exp.Data.ValidatedFacts))
        then
            let $expM := copy $e := $exp modify ( delete json $e.Data.ValidatedFacts ) return $e
            let $actM := copy $a := $act modify ( delete json $a.Data.ValidatedFacts ) return $a
            return  (
                if(deep-equal($expM, $actM))
                then ()
                else
                    {
                        type: "unexpected-audittrails-value",
                        expected: $expM,
                        actual: $actM
                    },
                if(count($exp.Data.ValidatedFacts[]) eq count($act.Data.ValidatedFacts[]))
                then ()
                else
                    {
                        type: "number-of-validated-facts-differs",
                        expected: $exp,
                        actual: $act
                    }
            )
        else
            if(deep-equal($exp, $act))
            then ()
            else
                {
                    type: "unexpected-audittrails-value",
                    expected: $exp,
                    actual: $act
                }
};

declare function local:diff-facts($fact-expected as object, $fact-actual as object?) as object* {
    if(empty($fact-actual))
    then
        {
            type: "missing-fact-in-result"
        }
    else
        (
            for $key in distinct-values((keys($fact-expected), keys($fact-actual)))
            where not($key = ("Aspects", "KeyAspects", "Profiles", "AuditTrails"))
            let $exp-value := $fact-expected.$key
            let $act-value := $fact-actual.$key
            return
                if(deep-equal($exp-value, $act-value) or
                   (schema:schema-type($exp-value) eq schema:schema-type($act-value) and schema:schema-type($exp-value) eq xs:QName("xs:decimal") and fn:format-number(xs:decimal($exp-value),"#,##0.0000000000000") eq fn:format-number(xs:decimal($act-value),"#,##0.0000000000000"))
                )
                then ()
                else
                    {
                        type: "unexpected-fact-field-value",
                        field: $key,
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare aspects :)
            for $key in distinct-values((keys($fact-expected.Aspects), keys($fact-actual.Aspects)))
            let $exp-value := $fact-expected.Aspects.$key
            let $act-value := $fact-actual.Aspects.$key
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-aspect-value",
                        aspect: $key,
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare keyaspects :)
            let $exp-value := $fact-expected.KeyAspects
            let $act-value := $fact-actual.KeyAspects
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-keyaspects-value",
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare Profiles :)
            let $exp-value := $fact-expected.Profiles
            let $act-value := $fact-actual.Profiles
            return
                if(deep-equal($exp-value, $act-value))
                then ()
                else
                    {
                        type: "unexpected-profiles-value",
                        expected: $exp-value,
                        actual: $act-value
                    },
            (: compare Audittrails :)
            let $exp-value := $fact-expected.AuditTrails
            let $act-value := $fact-actual.AuditTrails
            return
                local:compare-audit-trails($exp-value, $act-value)
        )
};

declare function local:compare-fact-tables($fact-table-expected as object, $fact-table-actual as object) as object*{
    let $actual-facts as object+ := $fact-table-actual.FactTable[]
    let $expected-facts as object+ := $fact-table-expected.FactTable[]
    let $errors :=
        (
            for $exp in $expected-facts
            let $act := $actual-facts[$$.Aspects."xbrl:Concept" eq $exp.Aspects."xbrl:Concept"
                                      and $$.Aspects."xbrl:Entity" eq $exp.Aspects."xbrl:Entity"
                                      and $$.Aspects."xbrl:Period" eq $exp.Aspects."xbrl:Period"
                                      and $$.Aspects."xbrl28:Archive" eq $exp.Aspects."xbrl28:Archive"]
            let $errors as object* := local:diff-facts($exp, $act)
            return
                if(empty($errors))
                then ()
                else {
                    expectedFact: $exp,
                    actualFact: $act,
                    errors: [$errors]
                },

            for $act in $actual-facts
            let $exp := $expected-facts[$$.Aspects."xbrl:Concept" eq $act.Aspects."xbrl:Concept"
                                      and $$.Aspects."xbrl:Entity" eq $act.Aspects."xbrl:Entity"
                                      and $$.Aspects."xbrl:Period" eq $act.Aspects."xbrl:Period"
                                      and $$.Aspects."xbrl28:Archive" eq $act.Aspects."xbrl28:Archive"]
            where empty($exp)
            return
                {
                    unexpectedFact: $act
                }
        )
    return
        if(empty($errors))
        then ()
        else (
                {
                    expectedNumberOfFacts: count($expected-facts),
                    actualNumberOfFacts: count($actual-facts)
                },
                $errors
            )
};

declare %an:nondeterministic function local:test-facttable($expected as integer, $params as object) as item
{
    let $endpoint := "facttable-for-report"
    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-facttable-fact($concept as string, $expected as object, $params as object) as item
{
    let $endpoint := "facttable-for-report"
    let $request := test:invoke($endpoint, $params)
    let $facts as object* := $request[2].FactTable[]
    let $actual := $facts[$$.Aspects."xbrl:Concept" eq $concept]
    let $diff :=
      for $f in $actual return local:diff-facts($expected, $f)
    return if (empty($diff)) then true else { url: test:url($endpoint, $params), factDiffErrors: [ $diff ], expectedFact: $expected, actualFact: $actual }
};

declare %an:nondeterministic function local:test-report-does-not-exist($params as object) as item
{
    let $endpoint := "facttable-for-report"
    let $request := test:invoke($endpoint, $params)
    let $status as integer := $request[1]
    return if ($status eq 404) then true else { url: test:url($endpoint, $params), unexpectedResponse: $request[2] }
};

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-values() as item*
{
    let $endpoint := "facttable-for-report"
    let $params := {ticker:"ko",fiscalYear:"2013",fiscalPeriod:"FY",fiscalPeriodType: ["instant", "YTD", "QTD"],report:"FundamentalAccountingConcepts"}
    let $request := test:invoke($endpoint, $params)
    let $actual as object := $request[2]
    let $expected := parse-json(
        http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                        "/test/secxbrl/facttable-for-report-expected1.jq").body.content)
    let $diff := local:compare-fact-tables($expected, $actual)
    return if (empty($diff)) then true else { url: test:url($endpoint, $params), factTableDiff: [ ({ params : $params },$diff) ], expectedFactTable: $expected, actualFactTable: $actual }
};

let $dow30 := test:is-dow30()
return local:check({
    cocacola: local:test-facttable(92, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2013",
        fiscalPeriod:"Q1"}),
    aid: local:test-facttable(if($dow30) then 0 else 92, {
        aid:"0001193125-14-157120",
        report:"FundamentalAccountingConcepts"}),
    aid2: local:test-facttable(
        (:
          its only 18 here because they filed a 10-Q with a fiscalPeriod focus of FY
          http://www.sec.gov/cgi-bin/viewer?action=view&cik=732717&accession_number=0000732717-14-000022&xbrl_type=v
        :)
        18, {
        aid:"0000732717-14-000022",
        report:"FundamentalAccountingConcepts"}),
    aid2-qtd: local:test-facttable(93, {
        aid:"0000732717-14-000022",
        report:"FundamentalAccountingConcepts",
        fiscalPeriodType: [ "instant", "QTD" ]
    }),
    tickerrole: local:test-facttable(92, {
        report:"FundamentalAccountingConcepts",
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1"}),
    tickerfyfprole: local:test-facttable(238, {
        report:"FundamentalAccountingConcepts",
        ticker:["ko","wmt"],
        fiscalYear:"2013",
        fiscalPeriod:"FY",
        fiscalPeriodType:["instant", "QTD", "YTD"]
    }),
    bfiXOMUnits: local:test-facttable(468, {
        report:"supportBasicFinancialInformation",
        ticker:["xom"],
        fiscalYear:"ALL",
        fiscalPeriod:"FY",
        fiscalPeriodType:["instant", "YTD"]
    }),
    allvalues : local:test-values(),
    otheroperatingincometest: local:test-facttable-fact("fac:OtherOperatingIncomeExpenses",
                                                        local:expected-OtherOperatingIncomeExpenses-ATnT-2013(),
                                                        {
        report:"FundamentalAccountingConcepts",
        ticker:"t",
        fiscalYear:"2013",
        fiscalPeriod:["FY"]}),
    reportDoesntExist: local:test-report-does-not-exist({
        report:"report-not-found",
        ticker:"MSFT",
        fiscalYear:"ALL",
        fiscalPeriod:"FY"}),
    balanceSheet: test:invoke-and-assert-deep-equal(
          "facttable-for-report",
          {
              _id: "BalanceSheetClassified",
              ticker: "ko",
              fiscalYear:"2014",
              fiscalPeriod:"FY",
              validate:"true"
          },
          function($res as item*) as item* { size($res.FactTable) },
          90
        )
})


Update facttable-for-report.jq

Update facttable-for-report.jq

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

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

import module namespace 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 util = "http://secxbrl.info/modules/util";

declare function local:to-xml($o as object*) as node()*
{
    session:comment("xml",
        {
            NumFacts : count($o),
            TotalNumFacts: session:num-facts(),
            TotalNumArchives: session:num-archives(),
            TotalNumEntities: session:num-entities()
    }),
    <FactTable NetworkIdentifier="http://bizql.io/facttable-for-report"
            TableName="xbrl:FactTableForReport">{
        conversion:facts-to-xml($o, { Caller: "Report" })
    }</FactTable>
};

session:audit-call();

(: Query parameters :)
let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        request:param-values("fiscalYear")
                            [$$ = ("LATEST", "ALL") or $$ castable as integer]
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then $sec-fiscal:ALL_FISCAL_PERIODS
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $validate    := request:param-values("validate", "false")

(: Object resolution :)
let $entities := util:entities($ciks, $tags, $tickers, $sics, $aids)
let $report := request:param-values("report")
let $report := reports:reports($report)

(: Fact resolution :)
let $filter-override as object? :=
    util:filter-override($entities, $fiscalPeriods, $fiscalYears, $aids)
let $filtered-aspects :=
    let $report := reports:reports($report)
    let $hypercube := hypercubes:hypercubes-for-components($report, "xbrl:DefaultHypercube")
    return values($hypercube.Aspects)[exists(($$.Domains, $$.DomainRestriction))]
let $facts :=
    for $fact in (
        components2:facts(
            $report,
            {
                FilterOverride: $filter-override,
                Validate: boolean($validate eq "true")
            }
        )[exists($filter-override)],
        if(count($filtered-aspects) ge 2 and not exists($filter-override))
        then components2:facts(
            $report,
            {
                Validate: boolean($validate eq "true")
            }
        )
        else ()
    )
    group by $archive := $fact.Archive
    let $archive := archives:archives($archive)
    let $entity := entities:entities($archive.Entity)
    for $fact in $fact
    return
    {|
        project($fact, "Aspects"),
        { Type: $fact.Type },
        { Unit: $fact.Aspects."xbrl:Unit" }[exists($fact.Aspects."xbrl:Unit")],
        project($fact, ("Decimals", "Value")),
        { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
        project($fact, "AuditTrails")
    |}
let $results :=
        if(count($filtered-aspects) lt 2 and not exists($filter-override))
        then {
              response:status-code(403);
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        }
        else {
            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");
                conversion:facts-to-csv($facts, { Caller: "Report"})
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                conversion:facts-to-csv($facts, { Caller: "Report"})
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    { NetworkIdentifier : "http://secxbrl.info/facts" },
                    { TableName : "xbrl:Facts" },
                    { FactTable : [ $facts ] },
                    session:comment("json", {
                            NumFacts : count($facts),
                            TotalNumFacts: session:num-facts(),
                            TotalNumArchives: session:num-archives(),
                            TotalNumEntities: session:num-entities()
                        })
                |}
            }
        }
return
    util:check-and-return-results($entities, $results, $format)

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

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

import module namespace 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 util = "http://secxbrl.info/modules/util";

declare function local:to-xml($o as object*) as node()*
{
    session:comment("xml",
        {
            NumFacts : count($o),
            TotalNumFacts: session:num-facts(),
            TotalNumArchives: session:num-archives(),
            TotalNumEntities: session:num-entities()
    }),
    <FactTable NetworkIdentifier="http://bizql.io/facttable-for-report"
            TableName="xbrl:FactTableForReport">{
        conversion:facts-to-xml($o, { Caller: "Report" })
    }</FactTable>
};

session:audit-call();

(: Query parameters :)
let $format      := lower-case((request:param-values("format"), substring-after(request:path(), ".jq."))[1])
let $ciks        := distinct-values(companies:eid(request:param-values("cik")))
let $tags        := distinct-values(request:param-values("tag") ! upper-case($$))
let $tickers     := distinct-values(request:param-values("ticker"))
let $sics        := distinct-values(request:param-values("sic"))
let $fiscalYears := distinct-values(
                        request:param-values("fiscalYear", "LATEST")
                            [$$ = ("LATEST", "ALL") or $$ castable as integer]
                    )
let $fiscalPeriods := distinct-values(let $fp := request:param-values("fiscalPeriod", "FY")
                      return
                        if (($fp ! lower-case($$)) = "all")
                        then $sec-fiscal:ALL_FISCAL_PERIODS
                        else $fp)
let $aids        := archives:aid(request:param-values("aid"))
let $validate    := request:param-values("validate", "false")

(: Object resolution :)
let $entities := util:entities($ciks, $tags, $tickers, $sics, $aids)
let $report := request:param-values("report")
let $report := reports:reports($report)

(: Fact resolution :)
let $filter-override as object? :=
    util:filter-override($entities, $fiscalPeriods, $fiscalYears, $aids)
let $filtered-aspects :=
    let $report := reports:reports($report)
    let $hypercube := hypercubes:hypercubes-for-components($report, "xbrl:DefaultHypercube")
    return values($hypercube.Aspects)[exists(($$.Domains, $$.DomainRestriction))]
let $facts :=
    for $fact in (
        components2:facts(
            $report,
            {
                FilterOverride: $filter-override,
                Validate: boolean($validate eq "true")
            }
        )[exists($filter-override)],
        if(count($filtered-aspects) ge 2 and not exists($filter-override))
        then components2:facts(
            $report,
            {
                Validate: boolean($validate eq "true")
            }
        )
        else ()
    )
    group by $archive := $fact.Archive
    let $archive := archives:archives($archive)
    let $entity := entities:entities($archive.Entity)
    for $fact in $fact
    return
    {|
        project($fact, "Aspects"),
        { Type: $fact.Type },
        { Unit: $fact.Aspects."xbrl:Unit" }[exists($fact.Aspects."xbrl:Unit")],
        project($fact, ("Decimals", "Value")),
        { "EntityRegistrantName" : $entity.Profiles.SEC.CompanyName },
        project($fact, "AuditTrails")
    |}
let $results :=
        if(count($filtered-aspects) lt 2 and not exists($filter-override))
        then {
              response:status-code(403);
              session:error("The report filters are too weak, which leads to too big an output.", $format)
        }
        else {
            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");
                conversion:facts-to-csv($facts, { Caller: "Report"})
            }
            case "excel" return {
                response:content-type("application/vnd.ms-excel");
                response:header("Content-Disposition", "attachment; filename=fact.csv");
                conversion:facts-to-csv($facts, { Caller: "Report"})
            }
            default return {
                response:content-type("application/json");
                response:serialization-parameters({"indent" : true});
                {|
                    { NetworkIdentifier : "http://secxbrl.info/facts" },
                    { TableName : "xbrl:Facts" },
                    { FactTable : [ $facts ] },
                    session:comment("json", {
                            NumFacts : count($facts),
                            TotalNumFacts: session:num-facts(),
                            TotalNumArchives: session:num-archives(),
                            TotalNumEntities: session:num-entities()
                        })
                |}
            }
        }
return
    util:check-and-return-results($entities, $results, $format)


made dimension members distinct exclude hypercube domain from members in modify-hypercube

jsoniq version "1.0";
(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for manipulating hypercubes.

 :
 :
A hypercube provides a multi-dimensional structure to organize facts.

 :
 :
The dimensions of a hypercube correspond to XBRL aspects (concepts, entities, periods,
 : units, further XBRL dimensions). An XBRL hypercube only is made of XBRL dimensions, however
 : including the other aspects as well is very useful in the context of NOLAP, so that 28.io
 : does so.

 :
 :
28.io introduces the notion of default hypercube. A default hypercube is implicitly added
 : to each component, and only contains the concept, entity, period and unit aspects.
 : In other words, the default hypercube
 : of a component contains all these facts that do not have any XBRL dimensions.

 :
 :
With this module, you can retrieve all hypercubes contained in a component. You can
 : retrieve all facts contained in a hypercube (default dimension values are processed automatically),
 : either in raw form, or organized as a (2D) fact table. You can also populate a network (for example,
 : a presentation network) with the facts contained in a hypercube.

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

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

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

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:ALL_ARCHIVES as boolean := true;

(:~
 :
Retrieves all hypercubes in the supplied components.

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components($components as object*) as object*
{
  $components.Hypercubes ! values($$)
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*
) as object*
{
  $names ! $components.Hypercubes.($$)
};

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

 :
Default values will be populated automatically.

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

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

 :
Default values will be populated automatically.

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

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

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

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*,
    $options as object?
)
as item* (: object* :)
{
    if (exists($options.facts-for-archives-and-concepts))
    then
      let $options as object :=
        facts:merge-objects(
          { Hypercube: $hypercube },
          $options,
          true (: parameter has higher priority than option :)
        )
      let $facts-for-archives-and-concepts := $options.facts-for-archives-and-concepts
      let $concepts :=
          keys($hypercube.Aspects.$facts:CONCEPT.Domains."xbrl:ConceptDomain".Members)
      let $concepts := if (exists($concepts))
                   then $concepts
                   else $facts:ALL_OF_THEM
      return
        $facts-for-archives-and-concepts(
        $archives, $concepts, $options)
    else
      let $options as object :=
        facts:merge-objects(
          {
            Hypercube: $hypercube,
            Filter: { Archive: [ archives:aid($archives) ] }
          },
          $options,
          true (: parameter has higher priority than option :)
        )
      return facts:facts-for($options)
};

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*) as array
{
  hypercubes:fact-table-for-hypercube($hypercube, $archives, {})
};

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*,
  $options as object?) as array
{
  hypercubes:fact-table-for-hypercube-and-facts(
    $hypercube,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
Utility function to convert a sequence of facts to a fact table
 : given a hypercube. It is not recommended to use this function directly.
 : Use hypercubes:fact-table-for-hypercube instead.

 :
 :
 : @param $hypercube a hypercube.
 : @param $facts a sequence of facts that were retrieved with the hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube-and-facts(
  $hypercube as object,
  $facts as object*) as array
{
  let $dimensions := distinct-values((
      $facts:CONCEPT,
      $facts:ENTITY,
      $facts:PERIOD,
      $facts:UNIT,
      keys($hypercube.Aspects)))
  return [
    [ $dimensions, "Value" ],
    for $fact in $facts
    return [
      $dimensions ! (let $member := $fact.Aspects.($$)
                     return if(exists($member))
                            then $member
                            else null),
      $fact.Value
    ]
  ]
};

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

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

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

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*,
  $options as object?) as object*
{
  hypercubes:populate-networks-with-retrieved-facts(
    $networks,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
A utility function that populates networks with facts against concepts that belong
 : to them. It is not recommended to use this function directly.
 : Use hypercubes:populate-networks-with-retrieved-facts.

 :
 : @param $networks networks.
 : @param $facts a sequence of facts.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-retrieved-facts(
  $networks as object*,
  $facts as object*) as object*
{
  let $fact-table-by-concepts := {|
    for $f in $facts
    group by $concept := $f.Aspects."xbrl:Concept"
    return { $concept : [ $f ] }
  |}
  return
    copy $result := $networks
    modify
      for $concept in descendant-objects(values($result.Trees))[exists($$.Name)]
      let $name := $concept.Name
      return insert json { "Facts" : $fact-table-by-concepts.$name } into $concept
    return $result
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing only the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited.

 :
 : @return dimensionless hypercube instantiation.
 :)
declare function hypercubes:dimensionless-hypercube() as object
{
  hypercubes:dimensionless-hypercube(())
};

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

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

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?) as object
{
  {
    "Name" : "xbrl:UserDefinedHypercube",
    "Label" : "User-defined Hypercube",
    "Aspects" : {|
      for $dimension-name as string in distinct-values((
          "xbrl:Concept",
          "xbrl:Entity",
          "xbrl:Period",
          "xbrl:Unit",
          keys($dimensions)
      ))
      let $dimension-spec as object? := $dimensions.$dimension-name
      let $dimension-type as string? := $dimension-spec.Type
      let $dimension-domain as array? := $dimension-spec.Domain
      let $dimension-default as atomic? := $dimension-spec.Default
      let $domain-name := $dimension-name || "Domain"
      return {
        $dimension-name : {|
          (: Name :)
          {
            "Name" : $dimension-name
          },

          (: Label :)
          switch($dimension-name)
          case "xbrl:Concept" return {
            "Label" : "XBRL Concept Dimension"
          }
          case "xbrl:Entity" return {
            "Label" : "XBRL Entity Dimension"
          }
          case "xbrl:Period" return {
            "Label" : "XBRL Period Dimension"
          }
          case "xbrl:Unit" return {
            "Label" : "XBRL Unit Dimension"
          }
          default return (),

          (: Default value :)
          switch(true)
          case exists($dimension-default) return {
            "Default" :
              hypercubes:check-type-and-return($dimension-default,
                                               ($dimension-type, "string")[1])
          }
          case $dimension-name eq "xbrl:Unit" return {
            "Default" : "xbrl:NonNumeric"
          }
          default return (),

          (: Explicit vs. Typed :)
          {
            "Kind" : if(exists($dimension-type))
                     then "TypedDimension"
                     else "ExplicitDimension"
          },

          (: Type :)
          { "Type": $dimension-type }[exists($dimension-type)],

          (: Domain :)
          if (empty($dimension-type))
          then
          {
            "Domains" : {
              $domain-name : {
                "Name" : $domain-name,
                "Label" :
                  switch($dimension-name)
                  case "xbrl:Concept" return "XBRL Concept Domain"
                  case "xbrl:Entity" return "XBRL Entity Domain"
                  case "xbrl:Period" return "XBRL Period Domain"
                  case "xbrl:Unit" return "XBRL Unit Domain"
                  default return $dimension-name || " Domain",
                "Members" : {|
                  for $dimension-member in distinct-values($dimension-domain[])
                  return {
                    $dimension-member: {
                      Name: hypercubes:check-type-and-return($dimension-member, "string")
                    }
                  }
                |}
              }
            }
          }[exists($dimension-domain)]
          else {
            "DomainRestriction" : {
              "Name" : $domain-name,
              "Label" : $dimension-name || " Domain",
              "Enumeration" : [ hypercubes:check-type-and-return($dimension-domain[], $dimension-type) ]
            }
          }[exists($dimension-domain)]

        |}
      }
    |}
  }
};

declare %private function hypercubes:check-type-and-return(
    $dimension-members as atomic*,
    $dimension-type as string) as atomic*
{
  for $dimension-member as atomic in $dimension-members
  return
  if (not $dimension-member instance of null
      and
      local-name-from-QName(schema:schema-type($dimension-member)) ne
      local-name-from-QName(xs:QName($dimension-type)))
  then error(
        xs:QName("hypercubes:invalid-type"),
        schema:schema-type($dimension-member) ||
          ": type doesn't match expected type (" || $dimension-type || ")")
  else $dimension-member
};

declare function hypercubes:modify-hypercube(
    $hypercube as object,
    $options as object?) as object
{
    let $options :=
    {|
        for $dimension in keys(($hypercube.Aspects, $options))
        let $hypercube-metadata := $hypercube.Aspects.$dimension
        let $new-metadata := $options.$dimension
        return {
            $dimension : if(exists($new-metadata))
            then
                $new-metadata
            else {|
                { Type : $hypercube.Aspects.$dimension.Type }[$hypercube-metadata.Kind eq "TypedDimension"],
                let $hypercube-domain-names := keys($hypercube-metadata.Domains)
                let $hypercube-members :=
                  (
                     descendant-objects(($hypercube-domain-names ! values($hypercube-metadata.Domains.$$.Members))).Name,
                     jn:flatten($hypercube-metadata.DomainRestriction.Enumeration)
                  )
                where exists($hypercube-members)
                return { Domain : [ distinct-values($hypercube-members) ] },
                let $hypercube-default := $hypercube-metadata.Default
                where exists($hypercube-default)
                return { Default : $hypercube-default }
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($options)
};

declare function hypercubes:merge($hypercubes as object*) as object
{
    hypercubes:user-defined-hypercube({|
        for $aspect in keys($hypercubes.Aspects)
        let $default := distinct-values($hypercubes.Aspects.$aspect.Default)
        let $domain := distinct-values((descendant-objects($hypercubes.Aspects.$aspect.Domains).Name))
        return {
            $aspect : {|
                { Default: $default}[exists($default)],
                { Domain: [ distinct-values(($domain, $default)) ]}[exists($domain)]
            |}
        }
    |})
};

jsoniq version "1.0";
(:
 : Copyright 2012-2013 28msec Inc.
 :)

(:~
 :
This module provides functionality for manipulating hypercubes.

 :
 :
A hypercube provides a multi-dimensional structure to organize facts.

 :
 :
The dimensions of a hypercube correspond to XBRL aspects (concepts, entities, periods,
 : units, further XBRL dimensions). An XBRL hypercube only is made of XBRL dimensions, however
 : including the other aspects as well is very useful in the context of NOLAP, so that 28.io
 : does so.

 :
 :
28.io introduces the notion of default hypercube. A default hypercube is implicitly added
 : to each component, and only contains the concept, entity, period and unit aspects.
 : In other words, the default hypercube
 : of a component contains all these facts that do not have any XBRL dimensions.

 :
 :
With this module, you can retrieve all hypercubes contained in a component. You can
 : retrieve all facts contained in a hypercube (default dimension values are processed automatically),
 : either in raw form, or organized as a (2D) fact table. You can also populate a network (for example,
 : a presentation network) with the facts contained in a hypercube.

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

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

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

(:~
 : Joker for all archives.
 :)
declare variable $hypercubes:ALL_ARCHIVES as boolean := true;

(:~
 :
Retrieves all hypercubes in the supplied components.

 :
 : @param $components a sequence of components.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components($components as object*) as object*
{
  $components.Hypercubes ! values($$)
};

(:~
 :
Retrieves all hypercubes in the supplied components and
 : with the given names.

 :
 : @param $components a sequence of components.
 : @param $names a sequence of names.
 :
 : @return all hypercubes.
 :)
declare function hypercubes:hypercubes-for-components(
    $components as object*,
    $names as string*
) as object*
{
  $names ! $components.Hypercubes.($$)
};

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

 :
Default values will be populated automatically.

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

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

 :
Default values will be populated automatically.

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

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

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

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:facts-for-hypercube(
    $hypercube as object,
    $archives as item*,
    $options as object?
)
as item* (: object* :)
{
    if (exists($options.facts-for-archives-and-concepts))
    then
      let $options as object :=
        facts:merge-objects(
          { Hypercube: $hypercube },
          $options,
          true (: parameter has higher priority than option :)
        )
      let $facts-for-archives-and-concepts := $options.facts-for-archives-and-concepts
      let $concepts :=
          keys($hypercube.Aspects.$facts:CONCEPT.Domains."xbrl:ConceptDomain".Members)
      let $concepts := if (exists($concepts))
                   then $concepts
                   else $facts:ALL_OF_THEM
      return
        $facts-for-archives-and-concepts(
        $archives, $concepts, $options)
    else
      let $options as object :=
        facts:merge-objects(
          {
            Hypercube: $hypercube,
            Filter: { Archive: [ archives:aid($archives) ] }
          },
          $options,
          true (: parameter has higher priority than option :)
        )
      return facts:facts-for($options)
};

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs or $hypercubes:ALL_ARCHIVES for no filtering.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*) as array
{
  hypercubes:fact-table-for-hypercube($hypercube, $archives, {})
};

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

 :
 :
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube(
  $hypercube as object,
  $archives as item*,
  $options as object?) as array
{
  hypercubes:fact-table-for-hypercube-and-facts(
    $hypercube,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
Utility function to convert a sequence of facts to a fact table
 : given a hypercube. It is not recommended to use this function directly.
 : Use hypercubes:fact-table-for-hypercube instead.

 :
 :
 : @param $hypercube a hypercube.
 : @param $facts a sequence of facts that were retrieved with the hypercube.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:fact-table-for-hypercube-and-facts(
  $hypercube as object,
  $facts as object*) as array
{
  let $dimensions := distinct-values((
      $facts:CONCEPT,
      $facts:ENTITY,
      $facts:PERIOD,
      $facts:UNIT,
      keys($hypercube.Aspects)))
  return [
    [ $dimensions, "Value" ],
    for $fact in $facts
    return [
      $dimensions ! (let $member := $fact.Aspects.($$)
                     return if(exists($member))
                            then $member
                            else null),
      $fact.Value
    ]
  ]
};

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

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

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

 :
 : @param $networks networks.
 : @param $hypercube a hypercube.
 : @param $archives a sequence of archives or their AIDs.
 : @param $options standard fact retrieving options.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-facts(
  $networks as object*,
  $hypercube as object,
  $archives as item*,
  $options as object?) as object*
{
  hypercubes:populate-networks-with-retrieved-facts(
    $networks,
    hypercubes:facts-for-hypercube($hypercube, $archives, $options)
  )
};

(:~
 :
 :
A utility function that populates networks with facts against concepts that belong
 : to them. It is not recommended to use this function directly.
 : Use hypercubes:populate-networks-with-retrieved-facts.

 :
 : @param $networks networks.
 : @param $facts a sequence of facts.
 :
 : @return a sequence of facts with populated dimension values.
 :)
declare function hypercubes:populate-networks-with-retrieved-facts(
  $networks as object*,
  $facts as object*) as object*
{
  let $fact-table-by-concepts := {|
    for $f in $facts
    group by $concept := $f.Aspects."xbrl:Concept"
    return { $concept : [ $f ] }
  |}
  return
    copy $result := $networks
    modify
      for $concept in descendant-objects(values($result.Trees))[exists($$.Name)]
      let $name := $concept.Name
      return insert json { "Facts" : $fact-table-by-concepts.$name } into $concept
    return $result
};

(:~
 :
Returns an instantiation of a dimensionless Hypercube containing only the basic
 :    characteristics (xbrl:Concept, xbrl:Period, xbrl:Entity, and xbrl:Unit).
 :    For each of those included aspects the value space is not limited.

 :
 : @return dimensionless hypercube instantiation.
 :)
declare function hypercubes:dimensionless-hypercube() as object
{
  hypercubes:dimensionless-hypercube(())
};

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

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

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

 :
 : @return the hypercube instantiation.
 : @param $dimensions: an object with pairs of (dimension name, object with dimension information). These
 : dimension information subobjects
 : may have two (optional) fields: Default (a string) and Domain (an array of strings).
 :
 : @error hypercubes:invalid-type if a member of a typed dimension does not match the expected type
 : @error hypercubes:invalid-type if a member of an explicit dimension is not of type string
 :)
declare function hypercubes:user-defined-hypercube($dimensions as object?) as object
{
  {
    "Name" : "xbrl:UserDefinedHypercube",
    "Label" : "User-defined Hypercube",
    "Aspects" : {|
      for $dimension-name as string in distinct-values((
          "xbrl:Concept",
          "xbrl:Entity",
          "xbrl:Period",
          "xbrl:Unit",
          keys($dimensions)
      ))
      let $dimension-spec as object? := $dimensions.$dimension-name
      let $dimension-type as string? := $dimension-spec.Type
      let $dimension-domain as array? := $dimension-spec.Domain
      let $dimension-default as atomic? := $dimension-spec.Default
      let $domain-name := $dimension-name || "Domain"
      return {
        $dimension-name : {|
          (: Name :)
          {
            "Name" : $dimension-name
          },

          (: Label :)
          switch($dimension-name)
          case "xbrl:Concept" return {
            "Label" : "XBRL Concept Dimension"
          }
          case "xbrl:Entity" return {
            "Label" : "XBRL Entity Dimension"
          }
          case "xbrl:Period" return {
            "Label" : "XBRL Period Dimension"
          }
          case "xbrl:Unit" return {
            "Label" : "XBRL Unit Dimension"
          }
          default return (),

          (: Default value :)
          switch(true)
          case exists($dimension-default) return {
            "Default" :
              hypercubes:check-type-and-return($dimension-default,
                                               ($dimension-type, "string")[1])
          }
          case $dimension-name eq "xbrl:Unit" return {
            "Default" : "xbrl:NonNumeric"
          }
          default return (),

          (: Explicit vs. Typed :)
          {
            "Kind" : if(exists($dimension-type))
                     then "TypedDimension"
                     else "ExplicitDimension"
          },

          (: Type :)
          { "Type": $dimension-type }[exists($dimension-type)],

          (: Domain :)
          if (empty($dimension-type))
          then
          {
            "Domains" : {
              $domain-name : {
                "Name" : $domain-name,
                "Label" :
                  switch($dimension-name)
                  case "xbrl:Concept" return "XBRL Concept Domain"
                  case "xbrl:Entity" return "XBRL Entity Domain"
                  case "xbrl:Period" return "XBRL Period Domain"
                  case "xbrl:Unit" return "XBRL Unit Domain"
                  default return $dimension-name || " Domain",
                "Members" : {|
                  for $dimension-member in $dimension-domain[]
                  return {
                    $dimension-member: {
                      Name: hypercubes:check-type-and-return($dimension-member, "string")
                    }
                  }
                |}
              }
            }
          }[exists($dimension-domain)]
          else {
            "DomainRestriction" : {
              "Name" : $domain-name,
              "Label" : $dimension-name || " Domain",
              "Enumeration" : [ hypercubes:check-type-and-return($dimension-domain[], $dimension-type) ]
            }
          }[exists($dimension-domain)]

        |}
      }
    |}
  }
};

declare %private function hypercubes:check-type-and-return(
    $dimension-members as atomic*,
    $dimension-type as string) as atomic*
{
  for $dimension-member as atomic in $dimension-members
  return
  if (not $dimension-member instance of null
      and
      local-name-from-QName(schema:schema-type($dimension-member)) ne
      local-name-from-QName(xs:QName($dimension-type)))
  then error(
        xs:QName("hypercubes:invalid-type"),
        schema:schema-type($dimension-member) ||
          ": type doesn't match expected type (" || $dimension-type || ")")
  else $dimension-member
};

declare function hypercubes:modify-hypercube(
    $hypercube as object,
    $options as object?) as object
{
    let $options :=
    {|
        for $dimension in keys(($hypercube.Aspects, $options))
        let $hypercube-metadata := $hypercube.Aspects.$dimension
        let $new-metadata := $options.$dimension
        return {
            $dimension : if(exists($new-metadata))
            then
                $new-metadata
            else {|
                { Type : $hypercube.Aspects.$dimension.Type }[$hypercube-metadata.Kind eq "TypedDimension"],
                let $hypercube-domain :=
                  (
                     descendant-objects(values($hypercube-metadata.Domains)).Name,
                     jn:flatten($hypercube-metadata.DomainRestriction.Enumeration)
                  )
                where exists($hypercube-domain)
                return { Domain : [ $hypercube-domain ] },
                let $hypercube-default := $hypercube-metadata.Default
                where exists($hypercube-default)
                return { Default : $hypercube-default }
            |}
        }
    |}
    return hypercubes:user-defined-hypercube($options)
};

declare function hypercubes:merge($hypercubes as object*) as object
{
    hypercubes:user-defined-hypercube({|
        for $aspect in keys($hypercubes.Aspects)
        let $default := distinct-values($hypercubes.Aspects.$aspect.Default)
        let $domain := distinct-values((descendant-objects($hypercubes.Aspects.$aspect.Domains).Name))
        return {
            $aspect : {|
                { Default: $default}[exists($default)],
                { Domain: [ distinct-values(($domain, $default)) ]}[exists($domain)]
            |}
        }
    |})
};


:green_heart: Fix warning.

:green_heart: Fix warning.

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

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

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

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

declare option rest:response "first-item";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    for $hypercube in $hypercubes[$$ = keys($component.Hypercubes)]
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ components:model-structure($component, $hypercube) ],
        TableName : $hypercube,
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        Hypercube: $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
let $result := { ModelStructures: [ if($count) then () else $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("component")
       default return api:serialize($result, $comment, $serializers, $format, "components")

import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";

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

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

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

declare option rest:response "first-item";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let $model-structures :=
    for $component in $components
    let $archive   := archives:archives($component.Archive)
    let $entity    := entities:entities($archive.Entity)
    for $hypercube in $hypercubes[$$ = keys($component.Hypercubes)]
    return if($profile-name eq "sec")
    then {
        CIK : entities:eid($entity),
        EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
        ModelStructure : [ components:model-structure($component, $hypercube) ],
        TableName : $hypercube,
        Label : $component.Label,
        AccessionNumber : $component.Archive,
        FormType : $archive.Profiles.SEC.FormType,
        FiscalPeriod : $archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
        FiscalYear : $archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
        AcceptanceDatetime : filings:acceptance-dateTimes($archive),
        NetworkIdentifier: $component.Role,
        Disclosure : $component.Profiles.SEC.Disclosure
    }
    else {
        Archive : $component.Archive,
        Role : $component.Role,
        Hypercube: $hypercube,
        ModelStructure : [ components:model-structure($component, $hypercube) ]
    }
let $result := { ModelStructures: [ if($count) then () else $model-structures ] }
let $comment := {
    NumComponents : count($components),
    TotalNumComponents: session:num-components()
}
let $serializers := {
    to-xml : local:to-xml#1,
    to-csv : local:to-csv#1
}
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $components-not-found return api:not-found("component")
       default return api:serialize($result, $comment, $serializers, $format, "components")