Update conversion.jq

Update conversion.jq

jsoniq version "1.0";

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

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

declare %private function conversion:aspect-label(
    $aspect as string,
    $fact as object) as string
{
    ($fact.Labels.$aspect, $aspect)[1]
};

declare %private function conversion:aspect-value-or-label(
    $aspect as string,
    $fact as object) as atomic
{
    let $aspect-value as atomic := $fact.Aspects.($aspect)
    let $labels as object? := $fact.Labels
    return
        switch(true)
        case ($aspect-value instance of string and exists($labels.($aspect-value)))
          return $labels.($aspect-value)
        default return $aspect-value
};

declare function conversion:facts-to-csv(
    $facts as object*,
    $options as object?) as string?
{
    let $projection :=
        if($options.Caller eq "Report")
        then ("Value",
              "Decimals",
              "EntityRegistrantName",
              "ReportedConcept")
        else ("Value",
              "Type",
              "Decimals")
    let $use-labels as boolean := exists($facts.Labels)
    return if (exists($facts)) (: bug in csv:serialize :)
           then string-join(
                csv:serialize(
                    for $fact in $facts
                    return
                        {|
                            if($use-labels)
                            then
                                for $aspect as string in keys($fact.Aspects)[$$ ne "sec:Archive"]
                                let $aspect-label as string := conversion:aspect-label($aspect, $fact)
                                let $aspect-value as atomic := conversion:aspect-value-or-label($aspect, $fact)
                                return
                                    {
                                        $aspect-label : $aspect-value
                                    }
                            else
                                $fact.Aspects,
                            project($fact, $projection)
                        |},
                    { serialize-null-as : "" }
                )
            )
            else ()
};

declare function conversion:facts-to-xml(
    $facts as object*,
    $options as object?) as element()*
{
    for $fact in $facts
    let $aspects := $fact.Aspects
    let $use-labels as boolean := exists($facts.Labels)
    return
        <Fact>{
            <Aspects>{
                for $aspect in keys($aspects)
                return
                    <Aspect>
                        <Name>{
                            if($use-labels)
                            then attribute { "label" } { conversion:aspect-label($aspect, $fact) }
                            else (),
                            $aspect
                        }</Name>
                        <Value>{
                            if($use-labels)
                            then attribute { "label" } { conversion:aspect-value-or-label($aspect, $fact) }
                            else (),
                            $aspects.$aspect
                        }</Value>
                    </Aspect>
            }</Aspects>,
            if($options.Caller eq "Report")
            then (
                <Value>{$fact.Value}</Value>,
                <Type>{$fact.Type}</Type>,
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)],
                <EntityRegistrantName>{$fact.EntityRegistrantName}</EntityRegistrantName>,
                conversion:audittrail-to-xml($fact.AuditTrails)[exists($fact.AuditTrails)]
            ) else (
                <Type>{$fact.Type}</Type>,
                <Value>{$fact.Value}</Value>,
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)]
            )
        }</Fact>
};

declare function conversion:facts-to-odata($facts as object*) as node()*
{
    for $f in $facts
    group by facts:canonical-grouping-key($f, "xbrl:Concept")
    count $y
    return
       <entry xmlns='http://www.w3.org/2005/Atom'
        xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'
        xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices'
        xml:base='http://www.28.io/'>
        <id>{"http://28.io/odata/" || current-date() || "/" || $y}</id>
        <category term='RelationalFactTable' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme'/>
        <title>{$f.EntityRegistrantName}</title>
        <updated>{current-dateTime()}</updated>
        <author><name>28msec, Inc.</name></author>
        <content type='application/xml'>
            <m:properties>{
            <d:EntityRegistrantName>{$f.EntityRegistrantName}</d:EntityRegistrantName>,
            for $k in keys($f.Aspects)
            where $k ne "xbrl:Concept"
            return
                element { "d:" || substring-after($k, ":") } { distinct-values($f.Aspects.$k) },
            for $f in $f
            group by $concept := $f.Aspects."xbrl:Concept"
            return
                element {"d:" || substring-after($concept, ":")} { attribute { "m:type" } {
                    switch ($f.Concept.DataType)
                    case "xbrli:booleanItemType" return "Edm.Boolean"
                    case "xbrli:base64BinaryItemType" return "Edm.Binary"
                    case "xbrli:dateTimeItemType" return "Edm.DateTime"
                    case "xbrli:timeItemType" return "Edm.Time"
                    case "xbrli:stringItemType"
                    case "xbrli:anyURIItemType" return "Edm.String"
                    case "xbrli:decimalItemType"
                    case "xbrli:monetaryItemType" return "Edm.Decimal"
                    case "xbrli:floatItemType" return "Edm.Single"
                    case "xbrli:doubleItemType" return "Edm.Double"
                    case "xbrli:integerItemType"
                    case "xbrli:nonPositiveIntegerItemType"
                    case "xbrli:negativeIntegerItemType"
                    case "xbrli:nonNegativeIntegerItemType"
                    case "xbrli:unsignedLongItemType"
                    case "xbrli:unsignedIntItemType"
                    case "xbrli:unsignedShortItemType"
                    case "xbrli:positiveIntegerItemType" return "Edm.Integer"
                    case "xbrli:unsignedByteItemType" return "Edm.Byte"
                    case "xbrli:longItemType" return "Edm.Int64"
                    case "xbrli:intItemType" return "Edm.Int32"
                    case "xbrli:shortItemType" return "Edm.Int16"
                    case "xbrli:byteItemType" return "Edm.SByte"
                    default return "Edm.String"
                }, $f.Value }
            }</m:properties>
        </content>
        </entry>
};

declare function conversion:audittrail-to-xml($audit as item) as element()
{
    <AuditTrails>{
        for $a in flatten($audit)
        return (
            <Type>{$a.Type}</Type>,
            <Label>{$a.Label}</Label>,
            <Message>{$a.Message}</Message>,
            <Data>{
                if (exists($a.Data.OriginalConcept))
                then <OriginalConcept>{$a.Data.OriginalConcept}</OriginalConcept>
                else (),
                if (exists($a.Data.OutputConcept))
                then <OutputConcept>{$a.Data.OutputConcept}</OutputConcept>
                else (),
                if (exists($a.Data.AuditTrails))
                then conversion:audittrail-to-xml($a.Data.AuditTrails)
                else ()
            }</Data>
        )
    }</AuditTrails>
};

jsoniq version "1.0";

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

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

declare %private function conversion:aspect-label(
    $aspect as string,
    $fact as object) as string
{
    ($fact.Labels.$aspect, $aspect)[1]
};

declare %private function conversion:aspect-value-or-label(
    $aspect as string,
    $fact as object) as atomic
{
    let $aspect-value as atomic := $fact.Aspects.($aspect)
    let $labels as object? := $fact.Labels
    return
        switch(true)
        case ($aspect-value instance of string and exists($labels.($aspect-value)))
          return $labels.($aspect-value)
        default return $aspect-value
};

declare function conversion:facts-to-csv(
    $facts as object*,
    $options as object?) as string?
{
    let $projection :=
        if($options.Caller eq "Report")
        then ("Value",
              "Decimals",
              "EntityRegistrantName",
              "ReportedConcept")
        else ("Value",
              "Type",
              "Decimals")
    let $use-labels as boolean := exists($facts.Labels)
    return if (exists($facts)) (: bug in csv:serialize :)
           then string-join(
                csv:serialize(
                    for $fact in $facts
                    return
                        {|
                            if($use-labels)
                            then
                                for $aspect as string in keys($fact.Aspects)[$$ ne "sec:Archive"]
                                let $aspect-label as string := conversion:aspect-label($aspect, $fact)
                                let $aspect-value as atomic := conversion:aspect-value-or-label($aspect, $fact)
                                return
                                    {
                                        $aspect-label : $aspect-value
                                    }
                            else
                                $fact.Aspects,
                            project($fact, $projection)
                        |},
                    { serialize-null-as : "" }
                )
            )
            else ()
};

declare function conversion:facts-to-xml(
    $facts as object*,
    $options as object?) as element()*
{
    for $fact in $facts
    let $aspects := $fact.Aspects
    let $use-labels as boolean := exists($facts.Labels)
    return
        <Fact>{
            <Aspects>{
                for $aspect in keys($aspects)
                return
                    <Aspect>
                        <Name>{
                            if($use-labels)
                            then attribute { "label" } { conversion:aspect-label($aspect, $fact) }
                            else (),
                            $aspect
                        }</Name>
                        <Value>{
                            if($use-labels)
                            then attribute { "label" } { conversion:aspect-value-or-label($aspect, $fact) }
                            else (),
                            $aspects.$aspect
                        }</Value>
                    </Aspect>
            }</Aspects>,
            if($options.Caller eq "Report")
            then (
                <Value>{$fact.Value}</Value>,
                <Type>{$fact.Type}</Type>,
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)],
                <EntityRegistrantName>{$fact.EntityRegistrantName}</EntityRegistrantName>,
                conversion:audittrail-to-xml($fact.AuditTrails)[exists($fact.AuditTrails)]
            ) else (
                <Type>{$fact.Type}</Type>,
                <Value>{$fact.Value}</Value>,
                <Decimals>{$fact.Decimals}</Decimals>[exists($fact.Decimals)]
            )
        }</Fact>
};

declare function conversion:facts-to-odata($facts as object*) as node()*
{
    for $f in $facts
    group by facts:canonical-grouping-key($f, "xbrl:Concept")
    count $y
    return
       <entry xmlns='http://www.w3.org/2005/Atom'
        xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'
        xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices'
        xml:base='http://www.xbrlsite.com/2014/Demos/OData/SECXBRLinfo.svc/'>
        <id>{"http://secxbrl.info/odata/" || current-date() || "/" || $y}</id>
        <category term='SEC.FundamentalAccountingConcepts' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme'/>
        <link rel='edit' title='{$f.EntityRegistrantName}' href='{"UKFacts" || $y}'/>
        <title>{$f.EntityRegistrantName}</title>
        <updated>{current-dateTime()}</updated>
        <author><name>secxbrl.info</name></author>
        <content type='application/xml'>
            <m:properties>{
            <d:EntityRegistrantName>{$f.EntityRegistrantName}</d:EntityRegistrantName>,
            for $k in keys($f.Aspects)
            where $k ne "xbrl:Concept"
            return
                element { "d:" || substring-after($k, ":") } { distinct-values($f.Aspects.$k) },
            for $f in $f
            group by $concept := $f.Aspects."xbrl:Concept"
            return
                element {"d:" || substring-after($concept, ":")} { attribute { "m:type" } {
                    switch ($f.Concept.DataType)
                    case "xbrli:booleanItemType" return "Edm.Boolean"
                    case "xbrli:base64BinaryItemType" return "Edm.Binary"
                    case "xbrli:dateTimeItemType" return "Edm.DateTime"
                    case "xbrli:timeItemType" return "Edm.Time"
                    case "xbrli:stringItemType"
                    case "xbrli:anyURIItemType" return "Edm.String"
                    case "xbrli:decimalItemType"
                    case "xbrli:monetaryItemType" return "Edm.Decimal"
                    case "xbrli:floatItemType" return "Edm.Single"
                    case "xbrli:doubleItemType" return "Edm.Double"
                    case "xbrli:integerItemType"
                    case "xbrli:nonPositiveIntegerItemType"
                    case "xbrli:negativeIntegerItemType"
                    case "xbrli:nonNegativeIntegerItemType"
                    case "xbrli:unsignedLongItemType"
                    case "xbrli:unsignedIntItemType"
                    case "xbrli:unsignedShortItemType"
                    case "xbrli:positiveIntegerItemType" return "Edm.Integer"
                    case "xbrli:unsignedByteItemType" return "Edm.Byte"
                    case "xbrli:longItemType" return "Edm.Int64"
                    case "xbrli:intItemType" return "Edm.Int32"
                    case "xbrli:shortItemType" return "Edm.Int16"
                    case "xbrli:byteItemType" return "Edm.SByte"
                    default return "Edm.String"
                }, $f.Value }
            }</m:properties>
        </content>
        </entry>
};

declare function conversion:audittrail-to-xml($audit as item) as element()
{
    <AuditTrails>{
        for $a in flatten($audit)
        return (
            <Type>{$a.Type}</Type>,
            <Label>{$a.Label}</Label>,
            <Message>{$a.Message}</Message>,
            <Data>{
                if (exists($a.Data.OriginalConcept))
                then <OriginalConcept>{$a.Data.OriginalConcept}</OriginalConcept>
                else (),
                if (exists($a.Data.OutputConcept))
                then <OutputConcept>{$a.Data.OutputConcept}</OutputConcept>
                else (),
                if (exists($a.Data.AuditTrails))
                then conversion:audittrail-to-xml($a.Data.AuditTrails)
                else ()
            }</Data>
        )
    }</AuditTrails>
};


:green_heart: Add $token variable.

:green_heart: Add $token variable.

jsoniq version "1.0";

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace conversion = "http://28.io/modules/xbrl/conversion";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace labels = "http://28.io/modules/xbrl/labels";

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

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

declare option rest:response "first-item";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $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 $fiscalPeriodType   as string* external;
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 $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $rollup             as string* external;
declare  %rest:case-insensitive                 variable $map                as string? external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $labels             as boolean external := true;
declare  %rest:case-insensitive                 variable $merge              as boolean external := true;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
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 $language           as string  external := "en-US";
declare  %rest:case-insensitive                 variable $count              as boolean external := false;
declare  %rest:case-insensitive                 variable $override           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 as object* := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic,
  (),
  (),
  (),
  ())

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 $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

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

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

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

let $selected-hypercube as string? := multiplexer:hypercube(
    $component,
    $hypercube)

let $hypercube-not-found as boolean := exists($hypercube) and empty($selected-hypercube)

let $cid as string? := components:cid($component)
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

let $filter-override as object? :=
    if($profile-name eq "sec")
    then fiscal-core:filter-override(
        $entities,
        $fiscalYear,
        $fiscalPeriod,
        $fiscalPeriodType,
        $aid)
    else components:filter-override($entities, $aid)

(: Fact resolution :)
let $facts :=
    switch(true)
    case (exists($rollup) and $profile-name eq "sec")
         return
             let $calc-network := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
             let $hc := hypercubes:hypercubes-for-components($component)[][$$.Name = $selected-hypercube]
             let $hc := hypercubes:modify-hypercube($hc, {
                 "sec:FiscalYear" : { Type: "integer", Default: null },
                 "sec:FiscalPeriod" : { Type: "string", Default: null },
                 "sec:FiscalPeriodType" : { Type: "string", Default: null }
             })
             let $options as object? := if(exists($rules)) then { Rules: [ $rules ] } else ()
             let $p := hypercubes:populate-networks-with-facts($calc-network, $hc, $archives, $options)
             let $map := concept-maps:concept-maps($map)
             let $concepts :=
                if (not $map instance of null)
                then
                    for $d in $rollup[]
                    return
                        descendant-objects($p)[$$.Name = ($map.Trees[])[$$.Name eq $d].To[].Name][1].To[].Name
                else
                    for $d in $rollup[]
                    return ($d, keys(descendant-objects($p)[$$.Name eq $d].To))
             return sec:facts-for-archives-and-concepts($archives, $concepts, { Hypercube: $hc })
    case exists($selected-hypercube)
          return components:facts(
              $component,
              $selected-hypercube,
              {|
                  { FilterOverride: $filter-override }[$override],
                  { Validate: $validate },
                  { Rules : [ $rules ] }[exists($rules)]
              |}
          )
    default return ()

let $facts :=
    if(not $labels)
    then $facts
    else
        (: if labels are requested by the labels=true parameter then also add labels for concepts :)
        let $concepts as object* := $component.Concepts[]
        for $fact in $facts
        let $labels := labels:labels-for-facts(
          $fact,
          $labels:STANDARD_LABEL_ROLE,
          $language,
          $concepts,
          $entities,
          ()
        )
        return
            {|
                trim($fact, "Labels"),
                { Labels : $labels }
            |}
let $facts := api:normalize-facts($facts)

let $result :=
  switch($profile-name)
  case "sec" return {
    CIK : entities:eid($entities),
    EntityRegistrantName : $entities.Profiles.SEC.CompanyName,
    TableName : $selected-hypercube,
    Label : $component.Label,
    AccessionNumber : $component.Archive,
    FormType : $archives.Profiles.SEC.FormType,
    FiscalPeriod : $archives.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
    FiscalYear : $archives.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
    AcceptanceDatetime : ($archives ! filings:acceptance-dateTimes($$)),
    NetworkIdentifier: $component.Role,
    Disclosure : $component.Profiles.SEC.Disclosure,
    FactTable : [ if($count) then () else $facts ]
  } default return {
    Archive : $component.Archive,
    Role: $component.Role,
    TableName : $selected-hypercube,
    Label : $component.Label,
    FactTable : [ if($count) then () else $facts ]
  }

let $comment := {
    NumFacts : count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}

let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return <FactTable entityRegistrantName="{$res.EntityRegistrantName}"
            cik="{$res.CIK}"
            tableName="{$res.TableName}"
            label="{$res.Label}"
            accessionNumber="{$res.AccessionNumber}"
            networkIdentifier="{$res.NetworkIdentifier}"
            formType="{$res.FormType}"
            fiscalPeriod="{$res.FiscalPeriod}"
            fiscalYear="{$res.FiscalYear}"
            acceptanceDatetime="{$res.AcceptanceDatetime}"
            disclosure="{$res.Disclosure}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
        default return <FactTable
            tableName="{$res.TableName}"
            label="{$res.Label}"
            archive="{$res.Archive}"
            role="{$res.Role}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        string-join(conversion:facts-to-csv($res.FactTable[], { Caller: "Component"}))
    }
}
let $results as item* :=
    api:serialize($result, $comment, $serializers, $format, "facttable-" || $cid)
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $component-not-found return api:not-found("component")
       case $hypercube-not-found return api:not-found("hypercube")
       default return api:check-and-return-results($token, $results, $format)

jsoniq version "1.0";

import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec = "http://28.io/modules/xbrl/profiles/sec/core";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace conversion = "http://28.io/modules/xbrl/conversion";
import module namespace networks = "http://28.io/modules/xbrl/networks";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace rules = "http://28.io/modules/xbrl/rules";
import module namespace labels = "http://28.io/modules/xbrl/labels";

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

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

declare option rest:response "first-item";

(: 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 $fiscalPeriodType   as string* external;
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 $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $rollup             as string* external;
declare  %rest:case-insensitive                 variable $map                as string? external;
declare  %rest:case-insensitive                 variable $validate           as boolean external := false;
declare  %rest:case-insensitive                 variable $labels             as boolean external := true;
declare  %rest:case-insensitive                 variable $merge              as boolean external := true;
declare  %rest:case-insensitive                 variable $additional-rules   as string? external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;
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 $language           as string  external := "en-US";
declare  %rest:case-insensitive                 variable $count              as boolean external := false;
declare  %rest:case-insensitive                 variable $override           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 as object* := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic,
  (),
  (),
  (),
  ())

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 $entities as object* :=
    ($entities[entities:eid($$) = $archives.Entity],
    let $not-found := $archives.Entity[not entities:eid($entities) = $$]
    where exists($not-found)
    return entities:entities($not-found))

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

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

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

let $selected-hypercube as string? := multiplexer:hypercube(
    $component,
    $hypercube)

let $hypercube-not-found as boolean := exists($hypercube) and empty($selected-hypercube)

let $cid as string? := components:cid($component)
let $rules as object* := if(exists($additional-rules)) then rules:rules($additional-rules) else ()

let $filter-override as object? :=
    if($profile-name eq "sec")
    then fiscal-core:filter-override(
        $entities,
        $fiscalYear,
        $fiscalPeriod,
        $fiscalPeriodType,
        $aid)
    else components:filter-override($entities, $aid)

(: Fact resolution :)
let $facts :=
    switch(true)
    case (exists($rollup) and $profile-name eq "sec")
         return
             let $calc-network := networks:networks-for-components-and-short-names($component, $networks:CALCULATION_NETWORK)
             let $hc := hypercubes:hypercubes-for-components($component)[][$$.Name = $selected-hypercube]
             let $hc := hypercubes:modify-hypercube($hc, {
                 "sec:FiscalYear" : { Type: "integer", Default: null },
                 "sec:FiscalPeriod" : { Type: "string", Default: null },
                 "sec:FiscalPeriodType" : { Type: "string", Default: null }
             })
             let $options as object? := if(exists($rules)) then { Rules: [ $rules ] } else ()
             let $p := hypercubes:populate-networks-with-facts($calc-network, $hc, $archives, $options)
             let $map := concept-maps:concept-maps($map)
             let $concepts :=
                if (not $map instance of null)
                then
                    for $d in $rollup[]
                    return
                        descendant-objects($p)[$$.Name = ($map.Trees[])[$$.Name eq $d].To[].Name][1].To[].Name
                else
                    for $d in $rollup[]
                    return ($d, keys(descendant-objects($p)[$$.Name eq $d].To))
             return sec:facts-for-archives-and-concepts($archives, $concepts, { Hypercube: $hc })
    case exists($selected-hypercube)
          return components:facts(
              $component,
              $selected-hypercube,
              {|
                  { FilterOverride: $filter-override }[$override],
                  { Validate: $validate },
                  { Rules : [ $rules ] }[exists($rules)]
              |}
          )
    default return ()

let $facts :=
    if(not $labels)
    then $facts
    else
        (: if labels are requested by the labels=true parameter then also add labels for concepts :)
        let $concepts as object* := $component.Concepts[]
        for $fact in $facts
        let $labels := labels:labels-for-facts(
          $fact,
          $labels:STANDARD_LABEL_ROLE,
          $language,
          $concepts,
          $entities,
          ()
        )
        return
            {|
                trim($fact, "Labels"),
                { Labels : $labels }
            |}
let $facts := api:normalize-facts($facts)

let $result :=
  switch($profile-name)
  case "sec" return {
    CIK : entities:eid($entities),
    EntityRegistrantName : $entities.Profiles.SEC.CompanyName,
    TableName : $selected-hypercube,
    Label : $component.Label,
    AccessionNumber : $component.Archive,
    FormType : $archives.Profiles.SEC.FormType,
    FiscalPeriod : $archives.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
    FiscalYear : $archives.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
    AcceptanceDatetime : ($archives ! filings:acceptance-dateTimes($$)),
    NetworkIdentifier: $component.Role,
    Disclosure : $component.Profiles.SEC.Disclosure,
    FactTable : [ if($count) then () else $facts ]
  } default return {
    Archive : $component.Archive,
    Role: $component.Role,
    TableName : $selected-hypercube,
    Label : $component.Label,
    FactTable : [ if($count) then () else $facts ]
  }

let $comment := {
    NumFacts : count($facts),
    TotalNumFacts: session:num-facts(),
    TotalNumArchives: session:num-archives(),
    TotalNumEntities: session:num-entities()
}

let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return <FactTable entityRegistrantName="{$res.EntityRegistrantName}"
            cik="{$res.CIK}"
            tableName="{$res.TableName}"
            label="{$res.Label}"
            accessionNumber="{$res.AccessionNumber}"
            networkIdentifier="{$res.NetworkIdentifier}"
            formType="{$res.FormType}"
            fiscalPeriod="{$res.FiscalPeriod}"
            fiscalYear="{$res.FiscalYear}"
            acceptanceDatetime="{$res.AcceptanceDatetime}"
            disclosure="{$res.Disclosure}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
        default return <FactTable
            tableName="{$res.TableName}"
            label="{$res.Label}"
            archive="{$res.Archive}"
            role="{$res.Role}"
            >{
            conversion:facts-to-xml($res.FactTable[], { Caller: "Component" })
        }</FactTable>
    },
    to-csv : function($res as object) as string {
        string-join(conversion:facts-to-csv($res.FactTable[], { Caller: "Component"}))
    }
}
let $results as item* :=
    api:serialize($result, $comment, $serializers, $format, "facttable-" || $cid)
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       case $component-not-found return api:not-found("component")
       case $hypercube-not-found return api:not-found("hypercube")
       default return api:check-and-return-results($token, $results, $format)


Update spreadsheet-for-component.jq

Update spreadsheet-for-component.jq

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

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

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

local:check({
    cocacola: local:test-spreadsheet(1, {
        ticker:"ko"
    }),
    tickerrole: local:test-spreadsheet(60, {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome"
    }),
    tickerconcept: local:test-spreadsheet(76, {
        ticker:"ko",
        fiscalYear:"2013",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-spreadsheet(36, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    generic: local:test-spreadsheet(70, {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role:"http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
    })
})

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

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

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

local:check({
    cocacola: local:test-spreadsheet(1, {
        ticker:"ko"
    }),
    tickerrole: local:test-spreadsheet(60, {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome"
    }),
    tickerconcept: local:test-spreadsheet(76, {
        ticker:"ko",
        fiscalYear:"2013",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-spreadsheet(70, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    generic: local:test-spreadsheet(70, {
        profile-name: "generic",
        aid: "0000021344-14-000008",
        role:"http://www.thecocacolacompany.com/role/ConsolidatedBalanceSheets"
    })
})