Skip to content

Commit dd05f3e

Browse files
committed
Add Render-Report Command
Closes: #105
1 parent 4364d42 commit dd05f3e

File tree

8 files changed

+364
-25
lines changed

8 files changed

+364
-25
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ Usage:
3838
blazectl [command]
3939
4040
Available Commands:
41+
compact Compact a Database Column Family
4142
completion Generate the autocompletion script for the specified shell
4243
count-resources Counts all resources by type
43-
download Download FHIR resources in NDJSON format
44+
download Download resources in NDJSON format
45+
download-history Download history in NDJSON format
4446
evaluate-measure Evaluates a Measure
4547
help Help about any command
48+
render-report Renders a MeasureReport
4649
upload Upload transaction bundles
4750
4851
Flags:
@@ -180,11 +183,19 @@ Given a measure in YAML form, creates the required FHIR resources, evaluates tha
180183
You can run:
181184

182185
```sh
183-
blazectl evaluate-measure --server "http://localhost:8080/fhir" stratifier-condition-code.yml
186+
blazectl evaluate-measure --server "http://localhost:8080/fhir" query.yml
184187
```
185188

186189
More comprehensive documentation can be found in the [Blaze CQL Queries Documentation][9].
187190

191+
### Render Report
192+
193+
Renders a FHIR MeasureReport resource as simple, standalone HTML file. The idea is to combine two `blazectl` calls, first the `evaluate-measure` call and second the `render-report` call.
194+
195+
```sh
196+
blazectl evaluate-measure --server "http://localhost:8080/fhir" query.yml | blazectl render-report > query.html
197+
```
198+
188199
## GitHub Attestations
189200

190201
To ensure trust and security in the software supply chain, GitHub [attestations][11] are available for all `blazectl` binaries. To verify the attestations, please install the [GitHub CLI][10] tool and run:

cmd/evaluateMeasure.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io"
10+
"net/http"
11+
"net/url"
12+
"os"
13+
"os/signal"
14+
"time"
15+
916
"github.com/goccy/go-yaml"
1017
"github.com/google/uuid"
1118
"github.com/samply/blazectl/data"
1219
"github.com/samply/blazectl/fhir"
1320
"github.com/samply/blazectl/util"
1421
fm "github.com/samply/golang-fhir-models/fhir-models/fhir"
1522
"github.com/spf13/cobra"
16-
"io"
17-
"net/http"
18-
"net/url"
19-
"os"
20-
"os/signal"
21-
"time"
2223
)
2324

2425
var forceSync bool
@@ -69,6 +70,12 @@ func createMeasureGroup(g data.Group) (*fm.MeasureGroup, error) {
6970
},
7071
}
7172
}
73+
if g.Code != "" {
74+
group.Code = &fm.CodeableConcept{Text: &g.Code}
75+
}
76+
if g.Description != "" {
77+
group.Description = &g.Description
78+
}
7279
for i, population := range g.Population {
7380
p, err := createMeasureGroupPopulation(population)
7481
if err != nil {
@@ -103,22 +110,26 @@ func createMeasureGroupPopulation(population data.Population) (*fm.MeasureGroupP
103110
}, nil
104111
}
105112

106-
func createMeasureGroupStratifier(stratifier data.Stratifier) (*fm.MeasureGroupStratifier, error) {
107-
if stratifier.Code == "" {
113+
func createMeasureGroupStratifier(s data.Stratifier) (*fm.MeasureGroupStratifier, error) {
114+
if s.Code == "" {
108115
return nil, fmt.Errorf("missing code")
109116
}
110-
if stratifier.Expression == "" {
117+
if s.Expression == "" {
111118
return nil, fmt.Errorf("missing expression name")
112119
}
113-
return &fm.MeasureGroupStratifier{
120+
stratifier := fm.MeasureGroupStratifier{
114121
Code: &fm.CodeableConcept{
115-
Text: &stratifier.Code,
122+
Text: &s.Code,
116123
},
117124
Criteria: &fm.Expression{
118125
Language: "text/cql-identifier",
119-
Expression: &stratifier.Expression,
126+
Expression: &s.Expression,
120127
},
121-
}, nil
128+
}
129+
if s.Description != "" {
130+
stratifier.Description = &s.Description
131+
}
132+
return &stratifier, nil
122133
}
123134

124135
func createCoding(system string, code string) fm.Coding {

cmd/evalueMeasure_test.go

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7-
"github.com/samply/blazectl/data"
8-
"github.com/samply/blazectl/fhir"
9-
fm "github.com/samply/golang-fhir-models/fhir-models/fhir"
10-
"github.com/stretchr/testify/assert"
117
"net/http"
128
"net/http/httptest"
139
"net/url"
1410
"testing"
11+
12+
"github.com/samply/blazectl/data"
13+
"github.com/samply/blazectl/fhir"
14+
fm "github.com/samply/golang-fhir-models/fhir-models/fhir"
15+
"github.com/stretchr/testify/assert"
1516
)
1617

1718
func TestCreateMeasureResource(t *testing.T) {
@@ -106,6 +107,52 @@ func TestCreateMeasureResource(t *testing.T) {
106107
assert.Equal(t, "InInitialPopulation", *resource.Group[0].Population[0].Criteria.Expression)
107108
})
108109

110+
t.Run("with one group with code and one population", func(t *testing.T) {
111+
m := data.Measure{
112+
Group: []data.Group{
113+
{
114+
Code: "observation",
115+
Population: []data.Population{
116+
{
117+
Expression: "InInitialPopulation",
118+
},
119+
},
120+
},
121+
},
122+
}
123+
124+
resource, err := CreateMeasureResource(m, measureUrl, libraryUrl)
125+
if err != nil {
126+
t.Fatalf("error while generating the measure resource: %v", err)
127+
}
128+
129+
assert.Equal(t, 1, len(resource.Group))
130+
assert.Equal(t, "observation", *resource.Group[0].Code.Text)
131+
})
132+
133+
t.Run("with one group with description and one population", func(t *testing.T) {
134+
m := data.Measure{
135+
Group: []data.Group{
136+
{
137+
Description: "all the observations",
138+
Population: []data.Population{
139+
{
140+
Expression: "InInitialPopulation",
141+
},
142+
},
143+
},
144+
},
145+
}
146+
147+
resource, err := CreateMeasureResource(m, measureUrl, libraryUrl)
148+
if err != nil {
149+
t.Fatalf("error while generating the measure resource: %v", err)
150+
}
151+
152+
assert.Equal(t, 1, len(resource.Group))
153+
assert.Equal(t, "all the observations", *resource.Group[0].Description)
154+
})
155+
109156
t.Run("with one group and one population and one empty stratifier", func(t *testing.T) {
110157
m := data.Measure{
111158
Group: []data.Group{
@@ -185,6 +232,36 @@ func TestCreateMeasureResource(t *testing.T) {
185232
assert.Equal(t, "foo", *resource.Group[0].Stratifier[0].Code.Text)
186233
})
187234

235+
t.Run("with one group and one population and one stratifier with description", func(t *testing.T) {
236+
m := data.Measure{
237+
Group: []data.Group{
238+
{
239+
Population: []data.Population{
240+
{
241+
Expression: "InInitialPopulation",
242+
},
243+
},
244+
Stratifier: []data.Stratifier{
245+
{
246+
Code: "foo",
247+
Description: "the foo stratifier",
248+
Expression: "Foo",
249+
},
250+
},
251+
},
252+
},
253+
}
254+
255+
resource, err := CreateMeasureResource(m, measureUrl, libraryUrl)
256+
if err != nil {
257+
t.Fatalf("error while generating the measure resource: %v", err)
258+
}
259+
260+
assert.Equal(t, 1, len(resource.Group))
261+
assert.Equal(t, 1, len(resource.Group[0].Stratifier))
262+
assert.Equal(t, "the foo stratifier", *resource.Group[0].Stratifier[0].Description)
263+
})
264+
188265
t.Run("with one Condition group", func(t *testing.T) {
189266
m := data.Measure{
190267
Group: []data.Group{

cmd/renderReport.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2019 - 2025 The Samply Community
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
_ "embed"
19+
"encoding/json"
20+
"io"
21+
"os"
22+
23+
"html/template"
24+
25+
fm "github.com/samply/golang-fhir-models/fhir-models/fhir"
26+
"github.com/spf13/cobra"
27+
)
28+
29+
//go:embed report-template.gohtml
30+
var reportTemplate string
31+
32+
var renderReportCmd = &cobra.Command{
33+
Use: "render-report",
34+
Short: "Renders a MeasureReport",
35+
RunE: func(cmd *cobra.Command, args []string) error {
36+
data, err := io.ReadAll(os.Stdin)
37+
if err != nil {
38+
return err
39+
}
40+
41+
var report fm.MeasureReport
42+
if err := json.Unmarshal(data, &report); err != nil {
43+
return err
44+
}
45+
46+
funcMap := template.FuncMap{
47+
"inc": func(i int) int {
48+
return i + 1
49+
},
50+
"ratio": func(n int, d int) float32 {
51+
return float32(n*100) / float32(d)
52+
},
53+
"isNullString": func(s *string) bool {
54+
return s == nil || *s == "null"
55+
},
56+
}
57+
58+
tmpl := template.Must(template.New("report").Funcs(funcMap).Parse(reportTemplate))
59+
60+
if err := tmpl.Execute(os.Stdout, report); err != nil {
61+
return err
62+
}
63+
64+
return nil
65+
},
66+
}
67+
68+
func init() {
69+
rootCmd.AddCommand(renderReportCmd)
70+
}

0 commit comments

Comments
 (0)