:bug: Remove _id.

:bug: Remove _id.

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token          as string?  external;
declare  %rest:case-insensitive                 variable $profile-name   as string  external := $config:profile-name;
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 := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod   as string*  external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $aid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $eid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $label          as string*  external;
declare  %rest:case-insensitive                 variable $map            as string?  external;
declare  %rest:case-insensitive                 variable $report         as string?  external;
declare  %rest:case-insensitive %rest:distinct  variable $name           as string*  external;
declare  %rest:case-insensitive                 variable $onlyNames      as boolean? external := false;
declare  %rest:case-insensitive                 variable $onlyTextBlocks as boolean? external := false;

session:audit-call($token);

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

let $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 $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

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 $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels(archives:aid($archives), $label[1])
        else local:concepts-for-archives(archives:aid($archives), $name, $map, { OnlyNames: $onlyNames })
let $result :=
  let $all-aids := $concepts.Archive
  let $roles := $concepts.Role
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      if ($onlyNames)
      then distinct-values($concepts.Name)
      else
        for $concept in $concepts
        group by $archive := $concept.Archive,  $role := $concept.Role
        let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
        let $members as object* := $component.Concepts[]
        return
          if($profile-name eq "sec")
          then
                let $archive-object as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[entities:eid($$) = $archive-object.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive,
                    CIK : entities:eid($entity),
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                where not $onlyTextBlocks or $concept.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          role: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |})
                    },
                    trim($concept, ("_id", "Archive", "Role", "Name", "Labels")),
                    $metadata
                |}
         else
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                where not $onlyTextBlocks or $concept.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          role: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |}),
                      Facts: backend:url("facts", {|
                        {
                          token: $token,
                          "xbrl:Concept": $original-name,
                          aid: $archive,
                          format: $format,
                          profile-name: $profile-name
                        },
                        {
                          fiscalYear: "ALL",
                          fiscalPeriod: "ALL",
                          fiscalPeriodType: "ALL"
                        }[$profile-name eq "japan"]
                      |})
                    },
                    trim($concept, ("_id", "Archive", "Role", "Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml($res.ReportElements[], "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          string-join(csv:serialize($res.ReportElements[], { serialize-null-as : "" }))
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)

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

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

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";
import module namespace concepts = "http://28.io/modules/xbrl/concepts";
import module namespace reports = "http://28.io/modules/xbrl/reports";
import module namespace concept-maps = "http://28.io/modules/xbrl/concept-maps";
import module namespace config = "http://apps.28.io/config";

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

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

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

declare function local:concepts-for-archives(
    $aids as string*,
    $names as string*,
    $map as object?,
    $options as object?) as object*
{
    let $projection as object := if($options.OnlyNames eq true) then { Name: 1 } else {}
    let $concepts-computable-by-maps as object* :=
        switch(true)
            case not exists($map) return ()
            case not exists($names) return $map.Trees[]
            default return
                let $keys as string* := $map.Trees[].Name
                for $concept as string in $names[$$ = $keys]
                return ($map.Trees[])[$$.Name eq $concept]
    let $mapped-names as string* := $concepts-computable-by-maps.To[].Name
    let $concepts-not-computable-by-maps as string* := seq:value-except($names, $mapped-names)

    let $all-results as object* := mw:find($concepts:col,
        {
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-not-computed-by-maps as object* := mw:find($concepts:col,
        {
            "Name" : { "$in" : [ $concepts-not-computable-by-maps ] },
            "Archive": { "$in" : [ $aids ] }
        },
        $projection)
    let $results-computed-by-maps as object* :=
        let $all-results as object* := mw:find($concepts:col,
            {
                "Name" : { "$in" : [ $mapped-names ] },
                "Archive": { "$in" : [ $aids ] }
            },
            $projection)
        for $concept as object in $concepts-computable-by-maps
        for $result as object in
            for $candidate-concept in $concept.To[].Name
            let $facts := $all-results[$$.Name = $candidate-concept]
            where exists($facts)
            count $c
            where $c eq 1
            return $facts
        let $map-concept := (for $candidate in $concepts-computable-by-maps
                            where $result.Name = (keys($candidate.To), $candidate.To[].Name)
                            return $candidate)[1]
        return
            copy $n := $result
            modify (
                replace value of json $n.Name with $map-concept.Name,
                insert json  { Origin : $result.Name } into $n)
            return $n
    return if(empty($map) and empty($names))
           then $all-results
           else ($results-not-computed-by-maps, $results-computed-by-maps)
};

declare function local:concepts-for-archives-and-labels($aids as string*, $labels as string) as object*
{
   mw:run-cmd-deterministic(
      {
        "text" : "concepts",
        "filter" : { "Archive" : { "$in" : [ $aids ] } },
        "search" : $labels,
        "limit" : 100,
        "score" : { "$meta" : "textScore" },
        "sort" : { score: { "$meta" : "textScore" } }
      }).results[].obj
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token          as string?  external;
declare  %rest:case-insensitive                 variable $profile-name   as string  external := $config:profile-name;
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 := "LATEST";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod   as string*  external := "FY";
declare  %rest:case-insensitive %rest:distinct  variable $aid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $eid            as string*  external;
declare  %rest:case-insensitive %rest:distinct  variable $label          as string*  external;
declare  %rest:case-insensitive                 variable $map            as string?  external;
declare  %rest:case-insensitive                 variable $report         as string?  external;
declare  %rest:case-insensitive %rest:distinct  variable $name           as string*  external;
declare  %rest:case-insensitive                 variable $onlyNames      as boolean? external := false;
declare  %rest:case-insensitive                 variable $onlyTextBlocks as boolean? external := false;

session:audit-call($token);

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

let $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 $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $aid)

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 $map as object? :=
    if(exists($report))
    then reports:concept-map($report)
    else concept-maps:concept-maps($map)
let $concepts as object* :=
    if (exists($label))
        then local:concepts-for-archives-and-labels(archives:aid($archives), $label[1])
        else local:concepts-for-archives(archives:aid($archives), $name, $map, { OnlyNames: $onlyNames })
let $result :=
  let $all-aids := $concepts.Archive
  let $roles := $concepts.Role
  let $components := components:components-for-archives-and-roles($all-aids, $roles)
  return {
    ReportElements : [
      if ($onlyNames)
      then distinct-values($concepts.Name)
      else
        for $concept in $concepts
        group by $archive := $concept.Archive,  $role := $concept.Role
        let $component as object := $components[$$.Archive eq $archive and $$.Role eq $role]
        let $members as object* := $component.Concepts[]
        return
          if($profile-name eq "sec")
          then
                let $archive-object as object := $archives[$$._id eq $archive]
                let $entity as object := $entities[entities:eid($$) = $archive-object.Entity]
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    AccessionNumber : $archive,
                    CIK : entities:eid($entity),
                    EntityRegistrantName : $entity.Profiles.SEC.CompanyName,
                    FiscalYear : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
                    FiscalPeriod : $archive-object.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                where not $onlyTextBlocks or $concept.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          role: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |})
                    },
                    trim($concept, ("Archive", "Role", "Name", "Labels")),
                    $metadata
                |}
         else
                let $metadata := {
                    ComponentRole : $component.Role,
                    ComponentLabel : $component.Label,
                    Archive : $archive
                }
                for $concept in $concept
                let $original-name := ($concept.Origin, $concept.Name)[1]
                where not $onlyTextBlocks or $concept.IsTextBlock
                return {|
                    project($concept, ("Name", "Origin")),
                    {
                      Labels: backend:url("labels", {|
                        {
                          token: $token,
                          concept: $original-name,
                          aid: $archive,
                          role: $role,
                          format: $format,
                          profile-name: $profile-name
                        }
                      |}),
                      Facts: backend:url("facts", {|
                        {
                          token: $token,
                          "xbrl:Concept": $original-name,
                          aid: $archive,
                          format: $format,
                          profile-name: $profile-name
                        },
                        {
                          fiscalYear: "ALL",
                          fiscalPeriod: "ALL",
                          fiscalPeriodType: "ALL"
                        }[$profile-name eq "japan"]
                      |})
                    },
                    trim($concept, ("Archive", "Role", "Name", "Labels")),
                    $metadata
                |}
        ]
    }
let $comment := {
    NumConcepts: count($concepts),
    TotalNumConcepts: session:num-concepts()
}
let $serializers := {
    to-xml : function($res as object) as node() {
        <ReportElements>{
          if ($onlyNames)
          then
            <ReportElement>{
              for $c in $res.ReportElements[]
              return <Name>{$c}</Name>
            }</ReportElement>
          else api:json-to-xml($res.ReportElements[], "ReportElement")
        }</ReportElements>
    },
    to-csv : function($res as object) as string {
      if ($onlyNames)
      then
          string-join(("Name", $res.ReportElements[]), "
")
      else
          string-join(csv:serialize($res.ReportElements[], { serialize-null-as : "" }))
    }
}
let $results := api:serialize($result, $comment, $serializers, $format, "report-elements")
return api:check-and-return-results($token, $results, $format)


added two tests

added two tests

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

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

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

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

local:check({
    cocacola: local:test-facttable(468, {
        ticker:"ko"
    }),
    tickerconcept: local:test-facttable(1, {
        ticker:"ko",
        concept:"us-gaap:Assets"
    }),
    tickerfyfprole: local:test-facttable(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    tagconcept: local:test-facttable(30, {
        tag:"DOW30",
        concept:"us-gaap:Assets"
    }),
    tagfyfprole: local:test-facttable(30, {
        tag:"DOW30",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        concept:"us-gaap:Assets"
    }),
    testNoEntity4Sic: local:test-empty({
        concept:"fac:Assets",
        map:"FundamentalAccountingConcepts",
        format:"csv",
        sic:"5052",
        fiscalYear:"2010",
        fiscalPeriod:"Q3"
    }),
    quartersfact: local:test-facttable(1, {
        aid: "0000051143-13-000007",
        concept: "disc:AssetRetirementObligationsPolicy",
        fiscalPeriod: "Q3",
        fiscalYear: "2013",
        map: "Disclosures"
    }),
    quartersfact2: local:test-facttable(1, {
        aid: "0001193125-12-207154",
        concept: "disc:CashFlowOperatingCapitalTableTextBlock",
        fiscalPeriod: "Q1",
        fiscalYear: "2012",
        map: "Disclosures"
    })

})

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

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

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

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

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


jq: Add a few component filters [ci skip]

# Custom jq filters for log analysis.
# See doc/developer/log_analysis.md to get started.

# Remove fields that are almost never used while debugging.
# To be used in other filters.
def clean:
  del(.hostname) |
  del(.name) |
  del(.pid) |
  del(.v);

# Filter by log level:
#
#     yarn jq error path/to/logs*
#     yarn jq warn path/to/logs*
#
# To get strictly warnings (without errors):
#
#     yarn jq warn_strict path/to/logs*
#
def error_level: 50;
def is_error: .level >= error_level;
def error: clean | select(is_error);
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def warn: clean | select(is_warn);
def warn_strict: clean | select(is_warn_strict);
def info_level: 30;
def is_info: .level >= info_level;
def info: clean | select(is_info);

# Components:
def chokidar: select(.component == "Chokidar");
def LocalChange: select(.component == "local/change");
def LocalWatcher: select(.component == "LocalWatcher");

# Find conflicts:
#
#     yarn jq -c conflicts path/to/logs*
#
def is_conflict: .msg == "resolveConflictAsync";
def conflicts: clean | select(is_conflict) | {time,path};

# Non-issue filters (so we can ignore them when looking for real issues):
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Find main issues:
#
#     yarn jq issues path/to/logs*
#
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def issues: clean | select(is_issue);

# Filter by path (should handle moves, conflict renaming...):
#
#     yarn jq 'path("foo/bar")' path/to/logs*
#
def path(pattern): clean | select((.path,.oldpath,"") | strings | test(pattern));

# Include/exclude GUI stuff:
#
#    yarn jq gui path/to/logs*
#    yarn jq no_gui path/to/logs*
#
def is_gui: .component | test("GUI");
def gui: select(is_gui);
def no_gui: select(is_gui | not);

# Find the OS / app versions:
#
#     yarn jq client path/to/logs*
#
def client:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Filter by file extension:
#
#    yarn jq 'ext("png")' path/to/logs*
#
# You can use/define additional filters for common extensions:
#
#    yarn jq xls path/to/logs*
#
def is_ext(x): .path | test("\\." + x);
def ext(x): select(is_ext(x));
def no_ext(x): select(is_ext(x) | not);
def xls: ext("xls");
def no_xls: no_ext("xls");
def txt: ext("txt");
def no_txt: no_ext("txt");

# Get a global overview of another filter:
#
#    yarn -s jq -c 'select(...)|short' path/to/logs*
#
def short:
  {time,component,msg,path};

# FIXME: Find a way to make aggregation work, e.g.:
#
#     yarn jq 'issues|.msg' | yarn jq:frequencies
#
# We should be able to use --slurp in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );

# Custom jq filters for log analysis.
# See doc/developer/log_analysis.md to get started.

# Remove fields that are almost never used while debugging.
# To be used in other filters.
def clean:
  del(.hostname) |
  del(.name) |
  del(.pid) |
  del(.v);

# Filter by log level:
#
#     yarn jq error path/to/logs*
#     yarn jq warn path/to/logs*
#
# To get strictly warnings (without errors):
#
#     yarn jq warn_strict path/to/logs*
#
def error_level: 50;
def is_error: .level >= error_level;
def error: clean | select(is_error);
def warn_level: 40;
def is_warn: .level >= warn_level;
def is_warn_strict: .level == warn_level;
def warn: clean | select(is_warn);
def warn_strict: clean | select(is_warn_strict);
def info_level: 30;
def is_info: .level >= info_level;
def info: clean | select(is_info);

# Find conflicts:
#
#     yarn jq -c conflicts path/to/logs*
#
def is_conflict: .msg == "resolveConflictAsync";
def conflicts: clean | select(is_conflict) | {time,path};

# Non-issue filters (so we can ignore them when looking for real issues):
def is_net_error: .msg | test("net::");
def is_maintenance_page: .msg | test("Maintenance en cours");
def is_seq_already_synced: .msg == "Seq was already synced!";
def is_pending_changes: .msg | test("Prepend [0-9]+ pending change");
def is_non_issue:
  (
    is_net_error or is_maintenance_page or is_seq_already_synced or
    is_pending_changes
  );

# Find main issues:
#
#     yarn jq issues path/to/logs*
#
def is_issue: (is_warn or is_conflict) and (is_non_issue | not);
def issues: clean | select(is_issue);

# Filter by path (should handle moves, conflict renaming...):
#
#     yarn jq 'path("foo/bar")' path/to/logs*
#
def path(pattern): clean | select((.path,.oldpath,"") | strings | test(pattern));

# Include/exclude GUI stuff:
#
#    yarn jq gui path/to/logs*
#    yarn jq no_gui path/to/logs*
#
def is_gui: .component | test("GUI");
def gui: select(is_gui);
def no_gui: select(is_gui | not);

# Find the OS / app versions:
#
#     yarn jq client path/to/logs*
#
def client:
  select(.appVersion)
    |del(.name)
    |del(.level)
    |del(.v)
    ;

# Filter by file extension:
#
#    yarn jq 'ext("png")' path/to/logs*
#
# You can use/define additional filters for common extensions:
#
#    yarn jq xls path/to/logs*
#
def is_ext(x): .path | test("\\." + x);
def ext(x): select(is_ext(x));
def no_ext(x): select(is_ext(x) | not);
def xls: ext("xls");
def no_xls: no_ext("xls");
def txt: ext("txt");
def no_txt: no_ext("txt");

# Get a global overview of another filter:
#
#    yarn -s jq -c 'select(...)|short' path/to/logs*
#
def short:
  {time,component,msg,path};

# FIXME: Find a way to make aggregation work, e.g.:
#
#     yarn jq 'issues|.msg' | yarn jq:frequencies
#
# We should be able to use --slurp in another aggregation script since the lines
# amount should have been reduced by the initial fitering.
def frequencies:
  reduce .[] as $x ({};
    . as $counts | $counts + {($x): (1 + (($counts|getpath([$x])) // 0))}
  );


Update spherical_voronoi.jq

Update spherical_voronoi.jq

module "spherical_voronoi";

include "helpers";

import "point" as point;
import "circle" as circle;
import "rectangle" as rectangle;
import "polygon" as polygon;
import "sphere" as sphere;
import "fortune" as fortune;
import "voronoi_basin" as vb;

##
# Module for calculation of voronoi diagrams of sphere.
# Implementation of paper "Voronoi diagrams on the sphere" by Hyeon-Suk Na, Chung-Nim Lee
# & Otfried Cheong.
#
# @see https://www.sciencedirect.com/science/article/pii/S0925772102000779?via%3Dihub
# @author hosuaby

##
# Tests if supplied planar voronoi cell lays completely inside bounding box (no vertex laying on the
# box border).
# @input {cell} planar voronoi cell
# @param $box {rectangle} bounding box
# @output {boolean} true - if cell is completely inside bounding box, false if not.
def is_bound_cell($box):
    . as $cell
    | map(rectangle::is_inside($box))
    | all
;

##
# Removes all vertexes of voronoi cell that situates on the border of the bounding box. Returns
# cell with trigonometrically ordered remaining vertexes.
# @input {cell} planar voronoi cell
# @param $box {rectangle} bounding box
# @output {cell} planar voronoir cell without vertexes on border
def remove_vertexes_on_border($box):
    .[0] as $site
    | .[1:]
    | count(rectangle::is_inside($box)) as $nbInside
    | until(.[:$nbInside] | all(rectangle::is_inside($box)); rotate_left)
    | .[:$nbInside]
    | [ $site, .[] ]
;

##
# Merges two parts of the same spherical voronoi into one.
# Pre-condition: two supplied parts of cell must have the same site.
# @input {sphericalPoint[][2]} two parts of the same cell
# @output {sphericalPoint[]} a single spherical voronoi cell
def merge_cell:
    . as [ $cell1, $cell2 ]
    | $cell1[0] as $site

    | [ $cell1[1:][], $cell2[1:][] ]

    | [ $site, .[] ]
;

##
# Projects vertex of voronoi cell from plane to sphere. Uses formula for projection of circle as
# vertex of voronoi cell is a center of circle between three or more sites.
# @input {polarPoint} vertex in polar coordinates
# @param $site {polarPoint} cells site in polar coordinates
# @output {sphericalPoint} projection of cell vertex on shpere surface
def project_vertex($site):
    { center: ., radius: sphere::polar_distance_from($site) }
    | sphere::circle_to_sphere
    | .center
;

##
# Projects voronoi cell from plane to sphere surface.
# @input {cell} voronoi cell on the plane
# @output {sphericalPoint[]} voronoi cell on the sphere
def project_cell:
    map(sphere::cartesian_to_polar(1))
    | .[0] as $site
    | .[1:]
    | map(project_vertex($site))

    | [
          ( $site | sphere::polar_to_spherical ),
          .[]
      ]
;

##
# Projects cell from sphere surface on plane using stereographic projection of circles (each vertex
# of voronoi cell is a center of the circle between 3 or more sites).
# @input {sphericalPoint[]} voronoi cell on the sphere
# @output {cell} voronoi cell on the plane
def project_cell_to_plane:
    .[0] as $site
    | .[1:]
    | map({ center: ., radius: sphere::spherical_distance_from($site) })
    | map(sphere::circle_to_plane)
    | map(.center)
    | map(sphere::polar_to_cartesian(1))
    | [
          ( $site | sphere::spherical_to_cartesian ),
          .[]
      ]
;

##
# Computes polar region as imaginary voronoi cell with site in north pole.
# @input {sphericalPoint[]} non north pole sites
# @output {sphericalPoint[]} imaginary polar voronoi cell
def polar_cell:
    map(sphere::spherical_flip)
    | map(sphere::spherical_to_cartesian) as $sites

    | sphere::projection_limit
    | map(sphere::spherical_to_cartesian) as $box

    | $sites
    | vb::voronoi_region([0, 0]; $box)

    | [ [0, 0], .[] ]
    | project_cell

    | map(sphere::spherical_flip)
;

##
# Clips planar voronoi cell inside $polygon.
# Pre-condition: site of the voronoi cell must be inside polygon.
# @input {cell} planar voronoi cell
# @param $polygon {polygon} polygon
# @output {cell} voronoi cell clipped inside polygon.
def clip($polygon):
    if polygon::is_polygon_inside($polygon) then
        .
    else
        .[0] as $site
        | .[1:]
        | polygon::edges

        | map(
            if polygon::is_segment_inside($polygon) then
                .
            elif (map(polygon::is_inside($polygon)) | any) then
                # One of segments points in inside polygon
                . as $segment
                | polygon::intersections_segment_polygon($polygon)
                | .[0]

                | if ($segment[0] | polygon::is_inside($polygon)) then
                      [ $segment[0], . ]
                  elif ($segment[1] | polygon::is_inside($polygon)) then
                      [ ., $segment[1] ]
                  else
                      empty
                  end
            else
                # Segment totally outside polygon
                . as $segment
                | polygon::intersections_segment_polygon($polygon)
                | if length > 0 then
                      . as $intersections
                      | $segment
                      | map(. as $p | $intersections | min_by(point::distance_euclidean(.; $p)))
                  else
                      empty
                  end
            end
          )

        | map(.[])

        | sort_by(point::inclination($site))
        | collapse_by(point::inclination($site); .[0])
        | [$site, .[]]
    end
;

##
# Computes part of voronoi diagram that lays inside polar region.
# @input {sphericalPoint[]} non north pole sites
# @param $northPole {site[0:1]} optional site on the north pole
# @output {cell[]} voronoi diagram of polar region
def north_voronoi($northPole; $polarCell):
    . as $other_sites

    | if ($northPole | length) == 0 then
          map(sphere::spherical_flip)
          | map(sphere::spherical_to_cartesian)

          | . as $sites

          | $polarCell
          | map(sphere::spherical_flip)
          | project_cell_to_plane
          | .[1:] as $polygon

          | [ $sites[], $polygon[] ]
          | rectangle::box as $box

          | $sites
          | fortune::fortune($box)

          | map(clip($polygon))

          # First & last vertexes must be on polygon border
          | map(
                .[0] as $site
                | .[1:]

                | count(polygon::is_on_edge($polygon)) as $nbOnBorder
                | until(
                      .[:$nbOnBorder] | all(polygon::is_on_edge($polygon));
                      rotate_left
                  )

                | [ $site, .[$nbOnBorder-1], .[$nbOnBorder:][], .[0] ]
            )

          | map(project_cell)
          | map(map(sphere::spherical_flip))
      else
          $polarCell
      end
;

##
# Calculate two Rodrigue's rotation matrices (rotation & reverse rotation).
# @input {sphericalPoint} new north pole
# @output {matrix[2]} two Rodrigue's matrices
def rotation_matrices:
    . as $p
    | [ $p[0] - HALF_PI, HALF_PI ]
    | sphere::to_3d_cartesian as $axe
    | $p[1] as $angle
    | $axe
    | sphere::cross_product_matrix
    | [ sphere::rotation_matrix($angle), sphere::rotation_matrix(-$angle) ]
;

##
# Rotates a single site using Rodrigue's rotation matrix.
# @input {sphericalPoint} site in spherical coordinates
# @param $R {matrix} Rodrigue's rotation matrix
# @output {sphericalPoint} site translated using matrix
def rotate_site($R):
    sphere::to_3d_cartesian
    | sphere::rotate($R)
    | sphere::from_3d_cartesian
;

##
# Rotates cell around axe using Rodrigue's rotation matrix.
# @input {cell} voronoi cell on the sphere
# @param $R {matrix} Rodrigue's rotation matrix
# @output {cell} translated cell
def rotate_cell($R):
    map(rotate_site($R))
;

##
# Normalizes all sites and vertexes of spherical voronoi diagram.
# @input {cell[]} cells of spherical voronoi diagram
# @output {cell[]} cells of spherical voronoi diagram where all sites & vertexes are normalized
def normalize_voronoi:
    map(map(sphere::normalize))
;

##
# Tests if supplied voronoi diagram covers whole sphere surface (no holes in diagram).
# @input {cell[]} cells of spherical voronoi diagram
# @output {boolean} true - if cells cover whole sphere, false if not
def is_whole:
    def _triangles:
        .[0] as $site
        | .[1:] as $vertexes
        | [ $vertexes[], $vertexes[0] ]
        | bigrams
        | map([ $site, .[] ])
    ;

    map(_triangles)
    | flatten(1)
    | map(sphere::excess)
    | add
    | . - sphere::UNIT_SPHERE_AREA
    | abs
    | . < EPSILON
;

##
# Asserts that supplied voronoi diagram covers whole sphere surface. Throws error if it not the
# case.
# @input {cell[]} cells of spherical voronoi diagram
# @output {cell[]} the same voronoi diagram if it covers the whole sphere surface
def assert_whole:
    if is_whole then
        .
    else
        error("Supplied Voronoi diagram don't cover whole spherical surface !")
    end
;

##
# Returns a voronoi cell that covers whole sphere surface.
# @input {sphericalPoint} site in spherical coordinates
# @output {cell[1]} voronoi cell covering a whole sphere
def _whole_sphere:
    . as $site
    | rotation_matrices as [ $R, $IR ]

    | ( PI - 0.001 ) as $zenith

    | [
          [ 0, $zenith ],
          [ HALF_PI, $zenith ],
          [ PI, $zenith ],
          [ PI + HALF_PI, $zenith ]
      ]

    | map(rotate_site($IR))

    | [[ $site, .[] ]]
;

def _slice:
    def _midpoint:
        map(atan2(.[1]; .[0]))
        | [
              .[0],
              if_else(.[0] <= .[1]; .[1]; .[1] + TWO_PI)
          ]
        | add
        | . / 2
        | [ cos, sin ]
    ;

    .[1] as $site
    | bigrams
    | map(_midpoint)
    | map(sphere::cartesian_to_spherical)

    | [
          ( $site | sphere::cartesian_to_spherical ),
          [ 0, PI ],
          .[0],
          [ 0, 0 ],
          .[1]
      ]
;

##
# Computes voronoi diagram for set of collinear sites on sphere surface. The result diagram looks
# like "orange": the whole sphere is divided in "slices".
# Pre-condition: all supplied sites are collinear on sphere surface.
# @input {sphericalPoint[2:]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def _orange:
    . as $sites

    | .[:2]
    | map(sphere::to_3d_cartesian)
    | sphere::cross_product
    | sphere::from_3d_cartesian
    | rotation_matrices as [ $R, $IR ]

    | $sites
    | map(rotate_site($R))

    # Order by angle
    | map(sphere::spherical_to_cartesian)
    | sort_by(atan2(.[1]; .[0]))

    | [ .[], .[0], .[1] ]
    | trigrams
    | map(_slice)

    | map(rotate_cell($IR))
    | normalize_voronoi
    | assert_whole
;

##
# General algorithm for calculation of voronoi diagram on spherical surface.
# Pre-condition: there is at least 3 non-collinear sites among supplied sites.
# @input {sphericalPoint[3:]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def _spherical_voronoi:
    . as $sites

    | trigrams
    | map(select(sphere::are_collinear | not))
    | .[0]
    | sphere::centroid
    | rotation_matrices as [ $R, $IR ]

    | $sites
    | map(rotate_site($R))

    | partitioning_by(sphere::is_spherical_north_pole) as [ $northPole, $notNorthPole ]

    | $notNorthPole
    | polar_cell as $polarCell

    | sphere::projection_limit
    | map(sphere::spherical_to_cartesian) as $box

    | $notNorthPole
    | map(sphere::spherical_to_cartesian)
    | fortune::fortune($box)

    | partitioning_by(is_bound_cell($box)) as [ $closed, $open ]

    | $closed
    | map(project_cell) as $southPart

    | $open
    | map(remove_vertexes_on_border($box))
    | map(project_cell)
    | sort_by(.[0][1]) as $openCells

    | $open
    | map(.[0])
    | map(sphere::cartesian_to_spherical)
    | north_voronoi($northPole; $polarCell)
    | sort_by(.[0][1]) as $northPart

    | $openCells
    | zip($northPart)
    | map(merge_cell) as $northPart

    | [ $northPart[] ,$southPart[] ]

    | map(rotate_cell($IR))
    | normalize_voronoi
    | assert_whole
;

##
# Computes voronoi diagram on spherical surface.
# @input {sphericalPoint[]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def spherical_voronoi:
    if length == 0 then
        []
    elif length == 1 then
        .[0]
        | _whole_sphere
    elif length == 2 or ( trigrams | map(sphere::are_collinear) | all ) then
        _orange
    else
        _spherical_voronoi
    end
;

module "spherical_voronoi";

include "helpers";

import "point" as point;
import "circle" as circle;
import "rectangle" as rectangle;
import "polygon" as polygon;
import "sphere" as sphere;
import "fortune" as fortune;
import "voronoi_basin" as vb;

##
# Module for calculation of voronoi diagrams of sphere.
# Implementation of whitepaper "Voronoi diagrams on the sphere" by Hyeon-Suk Na, Chung-Nim Lee
# & Otfried Cheong.
#
# @see https://www.sciencedirect.com/science/article/pii/S0925772102000779?via%3Dihub
# @author hosuaby

##
# Tests if supplied planar voronoi cell lays completely inside bounding box (no vertex laying on the
# box border).
# @input {cell} planar voronoi cell
# @param $box {rectangle} bounding box
# @output {boolean} true - if cell is completely inside bounding box, false if not.
def is_bound_cell($box):
    . as $cell
    | map(rectangle::is_inside($box))
    | all
;

##
# Removes all vertexes of voronoi cell that situates on the border of the bounding box. Returns
# cell with trigonometrically ordered remaining vertexes.
# @input {cell} planar voronoi cell
# @param $box {rectangle} bounding box
# @output {cell} planar voronoir cell without vertexes on border
def remove_vertexes_on_border($box):
    .[0] as $site
    | .[1:]
    | count(rectangle::is_inside($box)) as $nbInside
    | until(.[:$nbInside] | all(rectangle::is_inside($box)); rotate_left)
    | .[:$nbInside]
    | [ $site, .[] ]
;

##
# Merges two parts of the same spherical voronoi into one.
# Pre-condition: two supplied parts of cell must have the same site.
# @input {sphericalPoint[][2]} two parts of the same cell
# @output {sphericalPoint[]} a single spherical voronoi cell
def merge_cell:
    . as [ $cell1, $cell2 ]
    | $cell1[0] as $site

    | [ $cell1[1:][], $cell2[1:][] ]

    | [ $site, .[] ]
;

##
# Projects vertex of voronoi cell from plane to sphere. Uses formula for projection of circle as
# vertex of voronoi cell is a center of circle between three or more sites.
# @input {polarPoint} vertex in polar coordinates
# @param $site {polarPoint} cells site in polar coordinates
# @output {sphericalPoint} projection of cell vertex on shpere surface
def project_vertex($site):
    { center: ., radius: sphere::polar_distance_from($site) }
    | sphere::circle_to_sphere
    | .center
;

##
# Projects voronoi cell from plane to sphere surface.
# @input {cell} voronoi cell on the plane
# @output {sphericalPoint[]} voronoi cell on the sphere
def project_cell:
    map(sphere::cartesian_to_polar(1))
    | .[0] as $site
    | .[1:]
    | map(project_vertex($site))

    | [
          ( $site | sphere::polar_to_spherical ),
          .[]
      ]
;

##
# Projects cell from sphere surface on plane using stereographic projection of circles (each vertex
# of voronoi cell is a center of the circle between 3 or more sites).
# @input {sphericalPoint[]} voronoi cell on the sphere
# @output {cell} voronoi cell on the plane
def project_cell_to_plane:
    .[0] as $site
    | .[1:]
    | map({ center: ., radius: sphere::spherical_distance_from($site) })
    | map(sphere::circle_to_plane)
    | map(.center)
    | map(sphere::polar_to_cartesian(1))
    | [
          ( $site | sphere::spherical_to_cartesian ),
          .[]
      ]
;

##
# Computes polar region as imaginary voronoi cell with site in north pole.
# @input {sphericalPoint[]} non north pole sites
# @output {sphericalPoint[]} imaginary polar voronoi cell
def polar_cell:
    map(sphere::spherical_flip)
    | map(sphere::spherical_to_cartesian) as $sites

    | sphere::projection_limit
    | map(sphere::spherical_to_cartesian) as $box

    | $sites
    | vb::voronoi_region([0, 0]; $box)

    | [ [0, 0], .[] ]
    | project_cell

    | map(sphere::spherical_flip)
;

##
# Clips planar voronoi cell inside $polygon.
# Pre-condition: site of the voronoi cell must be inside polygon.
# @input {cell} planar voronoi cell
# @param $polygon {polygon} polygon
# @output {cell} voronoi cell clipped inside polygon.
def clip($polygon):
    if polygon::is_polygon_inside($polygon) then
        .
    else
        .[0] as $site
        | .[1:]
        | polygon::edges

        | map(
            if polygon::is_segment_inside($polygon) then
                .
            elif (map(polygon::is_inside($polygon)) | any) then
                # One of segments points in inside polygon
                . as $segment
                | polygon::intersections_segment_polygon($polygon)
                | .[0]

                | if ($segment[0] | polygon::is_inside($polygon)) then
                      [ $segment[0], . ]
                  elif ($segment[1] | polygon::is_inside($polygon)) then
                      [ ., $segment[1] ]
                  else
                      empty
                  end
            else
                # Segment totally outside polygon
                . as $segment
                | polygon::intersections_segment_polygon($polygon)
                | if length > 0 then
                      . as $intersections
                      | $segment
                      | map(. as $p | $intersections | min_by(point::distance_euclidean(.; $p)))
                  else
                      empty
                  end
            end
          )

        | map(.[])

        | sort_by(point::inclination($site))
        | collapse_by(point::inclination($site); .[0])
        | [$site, .[]]
    end
;

##
# Computes part of voronoi diagram that lays inside polar region.
# @input {sphericalPoint[]} non north pole sites
# @param $northPole {site[0:1]} optional site on the north pole
# @output {cell[]} voronoi diagram of polar region
def north_voronoi($northPole; $polarCell):
    . as $other_sites

    | if ($northPole | length) == 0 then
          map(sphere::spherical_flip)
          | map(sphere::spherical_to_cartesian)

          | . as $sites

          | $polarCell
          | map(sphere::spherical_flip)
          | project_cell_to_plane
          | .[1:] as $polygon

          | [ $sites[], $polygon[] ]
          | rectangle::box as $box

          | $sites
          | fortune::fortune($box)

          | map(clip($polygon))

          # First & last vertexes must be on polygon border
          | map(
                .[0] as $site
                | .[1:]

                | count(polygon::is_on_edge($polygon)) as $nbOnBorder
                | until(
                      .[:$nbOnBorder] | all(polygon::is_on_edge($polygon));
                      rotate_left
                  )

                | [ $site, .[$nbOnBorder-1], .[$nbOnBorder:][], .[0] ]
            )

          | map(project_cell)
          | map(map(sphere::spherical_flip))
      else
          $polarCell
      end
;

##
# Calculate two Rodrigue's rotation matrices (rotation & reverse rotation).
# @input {sphericalPoint} new north pole
# @output {matrix[2]} two Rodrigue's matrices
def rotation_matrices:
    . as $p
    | [ $p[0] - HALF_PI, HALF_PI ]
    | sphere::to_3d_cartesian as $axe
    | $p[1] as $angle
    | $axe
    | sphere::cross_product_matrix
    | [ sphere::rotation_matrix($angle), sphere::rotation_matrix(-$angle) ]
;

##
# Rotates a single site using Rodrigue's rotation matrix.
# @input {sphericalPoint} site in spherical coordinates
# @param $R {matrix} Rodrigue's rotation matrix
# @output {sphericalPoint} site translated using matrix
def rotate_site($R):
    sphere::to_3d_cartesian
    | sphere::rotate($R)
    | sphere::from_3d_cartesian
;

##
# Rotates cell around axe using Rodrigue's rotation matrix.
# @input {cell} voronoi cell on the sphere
# @param $R {matrix} Rodrigue's rotation matrix
# @output {cell} translated cell
def rotate_cell($R):
    map(rotate_site($R))
;

##
# Normalizes all sites and vertexes of spherical voronoi diagram.
# @input {cell[]} cells of spherical voronoi diagram
# @output {cell[]} cells of spherical voronoi diagram where all sites & vertexes are normalized
def normalize_voronoi:
    map(map(sphere::normalize))
;

##
# Tests if supplied voronoi diagram covers whole sphere surface (no holes in diagram).
# @input {cell[]} cells of spherical voronoi diagram
# @output {boolean} true - if cells cover whole sphere, false if not
def is_whole:
    def _triangles:
        .[0] as $site
        | .[1:] as $vertexes
        | [ $vertexes[], $vertexes[0] ]
        | bigrams
        | map([ $site, .[] ])
    ;

    map(_triangles)
    | flatten(1)
    | map(sphere::excess)
    | add
    | . - sphere::UNIT_SPHERE_AREA
    | abs
    | . < EPSILON
;

##
# Asserts that supplied voronoi diagram covers whole sphere surface. Throws error if it not the
# case.
# @input {cell[]} cells of spherical voronoi diagram
# @output {cell[]} the same voronoi diagram if it covers the whole sphere surface
def assert_whole:
    if is_whole then
        .
    else
        error("Supplied Voronoi diagram don't cover whole spherical surface !")
    end
;

##
# Returns a voronoi cell that covers whole sphere surface.
# @input {sphericalPoint} site in spherical coordinates
# @output {cell[1]} voronoi cell covering a whole sphere
def _whole_sphere:
    . as $site
    | rotation_matrices as [ $R, $IR ]

    | ( PI - 0.001 ) as $zenith

    | [
          [ 0, $zenith ],
          [ HALF_PI, $zenith ],
          [ PI, $zenith ],
          [ PI + HALF_PI, $zenith ]
      ]

    | map(rotate_site($IR))

    | [[ $site, .[] ]]
;

def _slice:
    def _midpoint:
        map(atan2(.[1]; .[0]))
        | [
              .[0],
              if_else(.[0] <= .[1]; .[1]; .[1] + TWO_PI)
          ]
        | add
        | . / 2
        | [ cos, sin ]
    ;

    .[1] as $site
    | bigrams
    | map(_midpoint)
    | map(sphere::cartesian_to_spherical)

    | [
          ( $site | sphere::cartesian_to_spherical ),
          [ 0, PI ],
          .[0],
          [ 0, 0 ],
          .[1]
      ]
;

##
# Computes voronoi diagram for set of collinear sites on sphere surface. The result diagram looks
# like "orange": the whole sphere is divided in "slices".
# Pre-condition: all supplied sites are collinear on sphere surface.
# @input {sphericalPoint[2:]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def _orange:
    . as $sites

    | .[:2]
    | map(sphere::to_3d_cartesian)
    | sphere::cross_product
    | sphere::from_3d_cartesian
    | rotation_matrices as [ $R, $IR ]

    | $sites
    | map(rotate_site($R))

    # Order by angle
    | map(sphere::spherical_to_cartesian)
    | sort_by(atan2(.[1]; .[0]))

    | [ .[], .[0], .[1] ]
    | trigrams
    | map(_slice)

    | map(rotate_cell($IR))
    | normalize_voronoi
    | assert_whole
;

##
# General algorithm for calculation of voronoi diagram on spherical surface.
# Pre-condition: there is at least 3 non-collinear sites among supplied sites.
# @input {sphericalPoint[3:]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def _spherical_voronoi:
    . as $sites

    | trigrams
    | map(select(sphere::are_collinear | not))
    | .[0]
    | sphere::centroid
    | rotation_matrices as [ $R, $IR ]

    | $sites
    | map(rotate_site($R))

    | partitioning_by(sphere::is_spherical_north_pole) as [ $northPole, $notNorthPole ]

    | $notNorthPole
    | polar_cell as $polarCell

    | sphere::projection_limit
    | map(sphere::spherical_to_cartesian) as $box

    | $notNorthPole
    | map(sphere::spherical_to_cartesian)
    | fortune::fortune($box)

    | partitioning_by(is_bound_cell($box)) as [ $closed, $open ]

    | $closed
    | map(project_cell) as $southPart

    | $open
    | map(remove_vertexes_on_border($box))
    | map(project_cell)
    | sort_by(.[0][1]) as $openCells

    | $open
    | map(.[0])
    | map(sphere::cartesian_to_spherical)
    | north_voronoi($northPole; $polarCell)
    | sort_by(.[0][1]) as $northPart

    | $openCells
    | zip($northPart)
    | map(merge_cell) as $northPart

    | [ $northPart[] ,$southPart[] ]

    | map(rotate_cell($IR))
    | normalize_voronoi
    | assert_whole
;

##
# Computes voronoi diagram on spherical surface.
# @input {sphericalPoint[]} sites in spherical coordinates
# @output {cell[]} voronoi diagram
def spherical_voronoi:
    if length == 0 then
        []
    elif length == 1 then
        .[0]
        | _whole_sphere
    elif length == 2 or ( trigrams | map(sphere::are_collinear) | all ) then
        _orange
    else
        _spherical_voronoi
    end
;


:lipstick: Fix warning.

:lipstick: Fix warning.

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

declare variable $local:expected as object :=
    {
      "byconcept" : [ "1001000 - Statement - Consolidated Statement of Income", "1005000 - Statement - Consolidated Statement of Equity", "2404402 - Disclosure - Summarized Financial Data - Chevron U.S.A. Inc. (Details)", "2405402 - Disclosure - Summarized Financial Data - Chevron Transport Corporation (Details)", "2410402 - Disclosure - Operating Segments and Geographic Data (Details)", "2427402 - Disclosure - Earnings Per Share (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "1005000 - Statement - CONSOLIDATED STATEMENTS OF SHAREOWNERS' EQUITY", "1001000 - Statement - CONSOLIDATED STATEMENTS OF EARNINGS", "2418402 - Disclosure - EARNINGS PER SHARE (DETAILS)", "2447402 - Disclosure - QUARTERLY RESULTS (UNAUDITED) (DETAILS)", "1002000 - Statement - CONSOLIDATED STATEMENTS OF OPERATIONS", "1004000 - Statement - CONSOLIDATED STATEMENTS OF CHANGES IN EQUITY", "2436402 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Income Statements", "000100 - Statement - Consolidated Statement Of Income", "041101 - Disclosure - Earnings Per Share (Details)", "041702 - Disclosure - Disclosures About Segments And Related Information (Schedule Of Segments And Related Information) (Details)", "1003 - Statement - Consolidated Statements of Income", "1008 - Statement - Consolidated Statements of Changes in Equity", "1118 - Disclosure - Schedule of Quarterly Financial Information (Detail)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Stockholders' Equity", "1002000 - Statement - Consolidated Statements of Income", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements of Changes in Stockholders' Equity", "1006000 - Statement - Consolidated Statements of Cash Flows", "2429403 - Disclosure - Earnings Per Share (Details)", "2438402 - Disclosure - International Operations (Details)", "2440402 - Disclosure - Parent Company - Statements of Income (Details)", "2440404 - Disclosure - Parent Company - Statements of Cash Flows (Details2)", "1001000 - Statement - Consolidated Statements of Income", "1002000 - Statement - Consolidated Statements Of Earnings", "1003000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Stockholders' Equity", "1005000 - Statement - Consolidated Statements Of Cash Flows", "2415402 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Operations", "1004000 - Statement - Consolidated Statement of Cash Flows", "2404402 - Disclosure - Earnings Per Share (Details)", "2410403 - Disclosure - Equity (Changes in Noncontrolling Interests) (Details)", "2420404 - Disclosure - Segment Financial Data (Selected Quarterly Financial Data) (Details)", "2421402 - Disclosure - Selected Quarterly Financial Data - Unaudited (Details)", "1001000 - Statement - Consolidated Statements Of Income (Audited)", "1002000 - Statement - Consolidated Statements Of Comprehensive Income (Audited)", "2419402 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1002000 - Statement - Consolidated Statements of Earnings", "1004000 - Statement - Consolidated Statements of Equity", "2419402 - Disclosure - Selected Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Equity", "2420402 - Disclosure - Earnings Per Share - Calculations of Earnings Per Share (Detail)", "1001000 - Statement - Consolidated Statements Of Operations", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements Of Cash Flows", "2401402 - Disclosure - Summary Of Business Segment Data (Details)", "2405403 - Disclosure - Earnings Per Share (Schedule Of Weighted-Average Number Of Shares Outstanding Used To Compute Earnings Per Share) (Details)", "2424403 - Disclosure - Quarterly Financial Data (Schedule Of Quarterly Financial Data) (Details)", "1003000 - Statement - Consolidated Statements of Operations", "1003000 - Statement - Consolidated Statements of Operations", "1005000 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "2429403 - Disclosure - Quarterly Financial Data Quarterly Financial Data (Unaudited) (Details)", "2430405 - Disclosure - Schedule I Condensed Income Statement (Details)", "000100 - Statement - Consolidated Statements Of Income", "000400 - Statement - Consolidated Statement Of Changes In Stockholders' Equity", "040201 - Disclosure - Earnings Per Share (Details)", "0110 - Statement - Consolidated Statements of Earnings", "0120 - Statement - Consolidated Statements of Comprehensive Income", "0140 - Statement - Consolidated Statements of Changes in Shareholders' Equity", "0150 - Statement - Consolidated Statements of Cash Flows", "0625 - Disclosure - Business Segments (Details)", "0630 - Disclosure - Parent Company (Details)", "06302 - Disclosure - Parent Company (Details 2)", "100020 - Statement - Statement of Earnings", "100030 - Statement - Statement of Comprehensive Income", "100040 - Statement - Statement of Changes in Shareowners' Equity", "100060 - Statement - Statement of Cash Flows", "401500 - Disclosure - Shareowners' Equity (Details)", "402800 - Disclosure - Quarterly Information (unaudited) (Details)", "1003 - Statement - INCOME STATEMENTS", "1004 - Statement - COMPREHENSIVE INCOME STATEMENTS", "1008 - Statement - CASH FLOWS STATEMENTS", "1009 - Statement - STOCKHOLDERS' EQUITY STATEMENTS", "1057 - Disclosure - Basic and Diluted Earnings Per Share (Detail)", "1129 - Disclosure - Quarterly Information (Unaudited) (Detail)", "010020 - Statement - Consolidated Statements of Income", "010035 - Statement - Consolidated Statements of Comprehensive Income", "010060 - Statement - Consolidated Statements of Cash Flows", "010080 - Statement - Consolidated Statements of Shareholders' Equity", "042600 - Disclosure - Parent Company (Details)", "042620 - Disclosure - Parent Company (Details 2)", "042700 - Disclosure - Quarterly Financial Data (Unaudited) (Details)", "1001000 - Statement - Consolidated Statement of Income", "1002000 - Statement - Consolidated Statement of Comprehensive Income", "1004000 - Statement - Consolidated Statement of Cash Flows", "1005000 - Statement - Consolidated Statement of Shareholders Equity", "2414402 - Disclosure - Quarterly Results (Detail)", "1003000 - Statement - Consolidated Statements Of Operations", "1004000 - Statement - Consolidated Statements of Comprehensive Income", "1005000 - Statement - Consolidated Statements Of Cash Flows", "1006000 - Statement - Consolidated Statements Of Equity", "2421402 - Disclosure - Net Income per Share (Calculation Of Basic And Diluted Net Income Per Share) (Details)", "1001000 - Statement - Consolidated Statements Of Income", "1001000 - Statement - Consolidated Statements Of Income", "1002000 - Statement - Consolidated Statements of Comprehensive Income", "1004000 - Statement - Consolidated Statements of Cash Flows", "1005000 - Statement - Consolidated Statements of Shareholders' Equity", "2401404 - Disclosure - Summary of Significant Accounting Policies - Revisions (Details)", "000011 - Statement - Consolidated Statement of Income", "004010 - Disclosure - Significant Accounting Policies (Details)", "004170 - Disclosure - Quarterly Data (Unaudited) (Details)", "1001000 - Statement - CONSOLIDATED STATEMENTS OF INCOME", "2423404 - Disclosure - Equity - Details about AOCI Components Reclassified to Net Income (Details)", "2476402 - Disclosure - Quarterly Financial Summary (Detail)" ]
    };

declare %an:nondeterministic function local:test-acceptance-date($expected as string, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as string? := $request[2].Archives[].AcceptanceDatetime
    let $status as integer := $request[1]
    return test:assert-eq($expected, $actual, $status, test:url($endpoint, $params))
};

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

declare %an:sequential function local:test-components-result($expected as array, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Archives[].Components[].NetworkLabel ]
    let $status as integer := $request[1]
    return test:assert-eq-array($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-generic-components($expected as integer, $params as object) as item
{
    let $endpoint := "components"
    let $request := test:invoke($endpoint, $params)
    let $actual as integer := count($request[2].Components[])
    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-components(94, {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    cocacolaincome: local:test-components(1, {
        ticker:"ko",
        networkIdentifier:"http://www.thecocacolacompany.com/role/ConsolidatedStatementsOfIncome",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    byconcept: local:test-components-result($local:expected.byconcept, {
        tag:"DOW30",
        concept:"us-gaap:NetIncomeLoss",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
    }),
    byfyfp: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"Q1",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    byfyfplowercase: local:test-components(1, {
        ticker:"ko",
        fiscalYear:"2012",
        fiscalPeriod:"q2",
        networkIdentifier:"http://www.thecoca-colacompany.com/role/CondensedConsolidatedBalanceSheets"
    }),
    acceptance: local:test-acceptance-date("2014-07-30T15:44:58Z", {
        aid:"0000021344-14-000029"
    }),
    generic: local:test-generic-components(1, {
      profile-name: "generic",
      aid: "0000021344-14-000008",
      role: "http://www.thecocacolacompany.com/role/DocumentAndEntityInformationDocument"
    }),
    generic-aid: local:test-generic-components(95, {
      profile-name: "generic",
      aid: "0000021344-14-000008"
    }),
    example1: test:invoke-and-assert-deep-equal(
      "components",
      {aid:"0000021344-13-000039"},
      function($b as item*) as item* { [ trim($b.Archives[].Components[], ("FactTable", "Spreadsheet", "ModelStructure", "ValidationErrors")) ] },
      test:get-expected-result("secxbrl/components-expected1.jq"),
      { NoArrayOrder: true }
    ),
    example2: test:invoke-and-assert-deep-equal(
      "components",
      {
        ticker:"ko",
        fiscalYear: "LATEST",
        fiscalPeriod: "FY"
      },
      function($b as item*) as item* { [ trim($b.Archives[].Components[], ("FactTable", "Spreadsheet", "ModelStructure", "ValidationErrors")) ] },
      test:get-expected-result("secxbrl/components-expected2.jq"),
      { NoArrayOrder: true }
    )
})


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

declare variable $local:expected as object :=
    {
      "dow30" : [ "http://www.sec.gov/CIK 0000066740", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000012927", "http://www.sec.gov/CIK 0000018230", "http://www.sec.gov/CIK 0000093410", "http://www.sec.gov/CIK 0000858877", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000030554", "http://www.sec.gov/CIK 0000034088", "http://www.sec.gov/CIK 0000040545", "http://www.sec.gov/CIK 0000886982", "http://www.sec.gov/CIK 0000354950", "http://www.sec.gov/CIK 0000050863", "http://www.sec.gov/CIK 0000051143", "http://www.sec.gov/CIK 0000200406", "http://www.sec.gov/CIK 0000019617", "http://www.sec.gov/CIK 0000063908", "http://www.sec.gov/CIK 0000789019", "http://www.sec.gov/CIK 0000310158", "http://www.sec.gov/CIK 0000320187", "http://www.sec.gov/CIK 0000078003", "http://www.sec.gov/CIK 0000080424", "http://www.sec.gov/CIK 0000086312", "http://www.sec.gov/CIK 0000101829", "http://www.sec.gov/CIK 0000731766", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001403161", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001001039" ],
      "generic" : [ "http://www.sec.gov/CIK 0000004962" ],
      "cik" : [ "http://www.sec.gov/CIK 0000004962" ],
      "ticker" : [ "http://www.sec.gov/CIK 0000104169" ],
      "ticker2" : [ "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000104169" ],
      "sic" : [ "http://www.sec.gov/CIK 0001058307", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001304492", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "sic-dow30": [ "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000732712" ],
      "mixed" : [ "http://www.sec.gov/CIK 0001058307", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001304492", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "mixed-dow30": [ "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0000104169" ]
    };

declare %an:nondeterministic function local:test-example1() as item
{
  let $expected :=
  [ {
    "_id" : "http://www.sec.gov/CIK 0000021344",
    "EIDs" : [ "http://www.sec.gov/CIK 0000021344" ],
    "Archives" : "http://" || request:server-name() || ":" || request:server-port() ||
      "/v1/_queries/public/api/filings.jq?token=" || $config:test-token || "&eid=http%3A%2F%2Fwww.sec.gov%2FCIK%200000021344&format=&profile-name=sec&fiscalYear=ALL&fiscalPeriod=ALL",
    "Profiles" : {
      "SEC" : {
        "Name" : "SEC",
        "CompanyName" : "COCA COLA CO",
        "CompanyType" : "Corporation",
        "CIK" : "0000021344",
        "SIC" : "2080",
        "SICDescription" : "BEVERAGES",
        "SICGroup" : 9,
        "Taxonomy" : "ci",
        "Sector" : "Food/Kindred Products",
        "Tickers" : [ "ko" ],
        "Tags" : [ "FORTUNE100", "DOW30", "RUSSELL1000", "SP500" ],
        "IsTrust" : false
      }
    }
  } ]
  let $endpoint := "entities"
  let $params := {cik:"21344"}
  let $request := test:invoke($endpoint, $params)
  let $status as integer := $request[1]
  let $actual as array? := $request[2].Entities
  return test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-entities(
    $expected as array,
    $params as object) as item
{
    let $endpoint := "entities"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Entities[].EIDs[] ]
    let $status as integer := $request[1]
    return test:assert-eq-array($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
};

let $dow30 := test:is-dow30()
return local:check({
    dow30: local:test-entities($local:expected.dow30, {tag:"DOW30"}),
    generic: local:test-entities($local:expected.generic, {eid:"http://www.sec.gov/CIK 0000004962", profile-name:"generic"}),
    cik: local:test-entities($local:expected.cik, {cik:"4962"}),
    ticker: local:test-entities($local:expected.ticker, {ticker:"wmt"}),
    ticker2: local:test-entities($local:expected.ticker2, {ticker:["wmt","ko"]}),
    sic: local:test-entities(if($dow30) then $local:expected.sic-dow30 else $local:expected.sic, {sic:"4813"}),
    mixed: local:test-entities(if($dow30) then $local:expected.mixed-dow30 else $local:expected.mixed, {cik:"4962", sic:"4813", ticker:[ "wmt", "ko" ]}),
    example1: local:test-example1()
})

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

declare variable $local:expected as object :=
    {
      "dow30" : [ "http://www.sec.gov/CIK 0000066740", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000012927", "http://www.sec.gov/CIK 0000018230", "http://www.sec.gov/CIK 0000093410", "http://www.sec.gov/CIK 0000858877", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000030554", "http://www.sec.gov/CIK 0000034088", "http://www.sec.gov/CIK 0000040545", "http://www.sec.gov/CIK 0000886982", "http://www.sec.gov/CIK 0000354950", "http://www.sec.gov/CIK 0000050863", "http://www.sec.gov/CIK 0000051143", "http://www.sec.gov/CIK 0000200406", "http://www.sec.gov/CIK 0000019617", "http://www.sec.gov/CIK 0000063908", "http://www.sec.gov/CIK 0000789019", "http://www.sec.gov/CIK 0000310158", "http://www.sec.gov/CIK 0000320187", "http://www.sec.gov/CIK 0000078003", "http://www.sec.gov/CIK 0000080424", "http://www.sec.gov/CIK 0000086312", "http://www.sec.gov/CIK 0000101829", "http://www.sec.gov/CIK 0000731766", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001403161", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001001039" ],
      "generic" : [ "http://www.sec.gov/CIK 0000004962" ],
      "cik" : [ "http://www.sec.gov/CIK 0000004962" ],
      "ticker" : [ "http://www.sec.gov/CIK 0000104169" ],
      "ticker2" : [ "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000104169" ],
      "sic" : [ "http://www.sec.gov/CIK 0001058307", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001304492", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "sic-dow30": [ "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000732712" ],
      "mixed" : [ "http://www.sec.gov/CIK 0001058307", "http://www.sec.gov/CIK 0001023731", "http://www.sec.gov/CIK 0001089511", "http://www.sec.gov/CIK 0000104777", "http://www.sec.gov/CIK 0001047919", "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000879585", "http://www.sec.gov/CIK 0001122063", "http://www.sec.gov/CIK 0001104358", "http://www.sec.gov/CIK 0001205727", "http://www.sec.gov/CIK 0000018926", "http://www.sec.gov/CIK 0000716133", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0001161706", "http://www.sec.gov/CIK 0001304421", "http://www.sec.gov/CIK 0001086740", "http://www.sec.gov/CIK 0001352819", "http://www.sec.gov/CIK 0001084384", "http://www.sec.gov/CIK 0001101239", "http://www.sec.gov/CIK 0000766561", "http://www.sec.gov/CIK 0001062613", "http://www.sec.gov/CIK 0001504678", "http://www.sec.gov/CIK 0000020520", "http://www.sec.gov/CIK 0001071411", "http://www.sec.gov/CIK 0000075679", "http://www.sec.gov/CIK 0000808461", "http://www.sec.gov/CIK 0001061322", "http://www.sec.gov/CIK 0000746210", "http://www.sec.gov/CIK 0001384929", "http://www.sec.gov/CIK 0001315255", "http://www.sec.gov/CIK 0001006837", "http://www.sec.gov/CIK 0001005731", "http://www.sec.gov/CIK 0001041954", "http://www.sec.gov/CIK 0001292653", "http://www.sec.gov/CIK 0001160142", "http://www.sec.gov/CIK 0001084752", "http://www.sec.gov/CIK 0000794323", "http://www.sec.gov/CIK 0001130888", "http://www.sec.gov/CIK 0001520744", "http://www.sec.gov/CIK 0001005699", "http://www.sec.gov/CIK 0000732412", "http://www.sec.gov/CIK 0000071557", "http://www.sec.gov/CIK 0001328571", "http://www.sec.gov/CIK 0001288359", "http://www.sec.gov/CIK 0001372041", "http://www.sec.gov/CIK 0000090721", "http://www.sec.gov/CIK 0001558294", "http://www.sec.gov/CIK 0001304492", "http://www.sec.gov/CIK 0001405660", "http://www.sec.gov/CIK 0001458704", "http://www.sec.gov/CIK 0001037949", "http://www.sec.gov/CIK 0000068622", "http://www.sec.gov/CIK 0001341319", "http://www.sec.gov/CIK 0000354963", "http://www.sec.gov/CIK 0000101830", "http://www.sec.gov/CIK 0000943117", "http://www.sec.gov/CIK 0001560158", "http://www.sec.gov/CIK 0001051512", "http://www.sec.gov/CIK 0001101688", "http://www.sec.gov/CIK 0001143238", "http://www.sec.gov/CIK 0001499355", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0001270400", "http://www.sec.gov/CIK 0001322791", "http://www.sec.gov/CIK 0001353505", "http://www.sec.gov/CIK 0001272830", "http://www.sec.gov/CIK 0001562636", "http://www.sec.gov/CIK 0000104169", "http://www.sec.gov/CIK 0001585644", "http://www.sec.gov/CIK 0001282266", "http://www.sec.gov/CIK 0000919742", "http://www.sec.gov/CIK 0001111634", "http://www.sec.gov/CIK 0001448962", "http://www.sec.gov/CIK 0001502756", "http://www.sec.gov/CIK 0001608249", "http://www.sec.gov/CIK 0001457737", "http://www.sec.gov/CIK 0001373444", "http://www.sec.gov/CIK 0001057758" ],
      "mixed-dow30": [ "http://www.sec.gov/CIK 0000004962", "http://www.sec.gov/CIK 0000732717", "http://www.sec.gov/CIK 0000021344", "http://www.sec.gov/CIK 0000732712", "http://www.sec.gov/CIK 0000104169" ]
    };

declare %an:nondeterministic function local:test-example1() as item
{
  let $expected :=
  [ {
    "_id" : "http://www.sec.gov/CIK 0000021344",
    "EID" : "http://www.sec.gov/CIK 0000021344",
    "Archives" : "http://" || request:server-name() || ":" || request:server-port() ||
      "/v1/_queries/public/api/filings.jq?token=" || $config:test-token || "&eid=http%3A%2F%2Fwww.sec.gov%2FCIK%200000021344&format=&profile-name=sec&fiscalYear=ALL&fiscalPeriod=ALL",
    "Profiles" : {
      "SEC" : {
        "Name" : "SEC",
        "CompanyName" : "COCA COLA CO",
        "CompanyType" : "Corporation",
        "CIK" : "0000021344",
        "SIC" : "2080",
        "SICDescription" : "BEVERAGES",
        "SICGroup" : 9,
        "Taxonomy" : "ci",
        "Sector" : "Food/Kindred Products",
        "Tickers" : [ "ko" ],
        "Tags" : [ "FORTUNE100", "DOW30", "RUSSELL1000", "SP500" ],
        "IsTrust" : false
      }
    }
  } ]
  let $endpoint := "entities"
  let $params := {cik:"21344"}
  let $request := test:invoke($endpoint, $params)
  let $status as integer := $request[1]
  let $actual as array? := $request[2].Entities
  return test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $params))
};

declare %an:nondeterministic function local:test-entities(
    $expected as array,
    $params as object) as item
{
    let $endpoint := "entities"
    let $request := test:invoke($endpoint, $params)
    let $actual as array := [ $request[2].Entities[].EID ]
    let $status as integer := $request[1]
    return test:assert-eq-array($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
};

let $dow30 := test:is-dow30()
return local:check({
    dow30: local:test-entities($local:expected.dow30, {tag:"DOW30"}),
    generic: local:test-entities($local:expected.generic, {eid:"http://www.sec.gov/CIK 0000004962", profile-name:"generic"}),
    cik: local:test-entities($local:expected.cik, {cik:"4962"}),
    ticker: local:test-entities($local:expected.ticker, {ticker:"wmt"}),
    ticker2: local:test-entities($local:expected.ticker2, {ticker:["wmt","ko"]}),
    sic: local:test-entities(if($dow30) then $local:expected.sic-dow30 else $local:expected.sic, {sic:"4813"}),
    mixed: local:test-entities(if($dow30) then $local:expected.mixed-dow30 else $local:expected.mixed, {cik:"4962", sic:"4813", ticker:[ "wmt", "ko" ]}),
    example1: local:test-example1()
})