SPB Git

Repository Public

Commit

feat: v1.0 features — sample datasets, docs site, Metal 2M-point renderer

- sysuse command with bundled samples (sales: 200-firm x 5-year panel;
  mtcars) shipped as ZQEngine resources, generated by
  scripts/make_samples.R
- metrika-cli docs: static documentation site rendered from the shared
  command registry (docs/index.html, 38 commands, light/dark); CLI
  restructured with a default 'run' subcommand
- Metal point-sprite renderer (CLAUDE.md §7 pane 4): packed 12-byte
  vertices, unified-memory buffer written once, scroll-pan /
  pinch-zoom / double-click-reset touching only a 24-byte uniform;
  takes over scatter plots past 100k points (METRIKA_METAL_THRESHOLD
  override for tests); verified rendering 2,000,000 points in-app
- METRIKA_AUTORUN debug hook for headless app driving
- MARKETING_VERSION 1.0.0
- 116 kit tests + 4 UI tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simon-pierre boucher committed · Aug 5, 2026 parent a184466
18 changed files +1,728 −6 Browse files
modified Metrika.xcodeproj/project.pbxproj +8 −0
@@ -7,11 +7,13 @@
7 objects = { 7 objects = {
8 8
9 /* Begin PBXBuildFile section */ 9 /* Begin PBXBuildFile section */
10+ 0B840EF0C831CE88C2C10C02 /* PointRenderer.metal in Sources */ = {isa = PBXBuildFile; fileRef = E080A0AFB04F825193F03420 /* PointRenderer.metal */; };
10 0BD0F927FC614CF19EF3D59D /* DoFileEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */; }; 11 0BD0F927FC614CF19EF3D59D /* DoFileEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */; };
11 0F7000A49C9879593F09A0D3 /* MetrikaKit in Frameworks */ = {isa = PBXBuildFile; productRef = 498C4115F45BADDF16191A57 /* MetrikaKit */; }; 12 0F7000A49C9879593F09A0D3 /* MetrikaKit in Frameworks */ = {isa = PBXBuildFile; productRef = 498C4115F45BADDF16191A57 /* MetrikaKit */; };
12 3084AE2A328ED6746D01F46E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0404FDEAF8A57414EFB3C649 /* Assets.xcassets */; }; 13 3084AE2A328ED6746D01F46E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0404FDEAF8A57414EFB3C649 /* Assets.xcassets */; };
13 7E936A23CCC90823D6B08BCC /* SessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453FB0F256E853D2C2F481C /* SessionModel.swift */; }; 14 7E936A23CCC90823D6B08BCC /* SessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453FB0F256E853D2C2F481C /* SessionModel.swift */; };
14 8B0CDB213D64E3EE87593808 /* ConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159695CF89D6BB25DF883F07 /* ConsoleView.swift */; }; 15 8B0CDB213D64E3EE87593808 /* ConsoleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159695CF89D6BB25DF883F07 /* ConsoleView.swift */; };
16+ 936699328DFFABBE4AB89A9D /* MetalScatterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05D9876A717AAC190A2750A /* MetalScatterView.swift */; };
15 9BE65709781719FAE0CA740A /* MetrikaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */; }; 17 9BE65709781719FAE0CA740A /* MetrikaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */; };
16 CB602D8A4EDDF5D0FBB0CFB7 /* ConsoleSmokeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */; }; 18 CB602D8A4EDDF5D0FBB0CFB7 /* ConsoleSmokeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */; };
17 D01BD59F090074D4C3FC392D /* ManualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C64C028218A21FE8E5D9D1 /* ManualView.swift */; }; 19 D01BD59F090074D4C3FC392D /* ManualView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C64C028218A21FE8E5D9D1 /* ManualView.swift */; };
@@ -43,6 +45,8 @@
43 A7C64C028218A21FE8E5D9D1 /* ManualView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualView.swift; sourceTree = "<group>"; }; 45 A7C64C028218A21FE8E5D9D1 /* ManualView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualView.swift; sourceTree = "<group>"; };
44 AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleSmokeTests.swift; sourceTree = "<group>"; }; 46 AB7ECE59F86249B3A25B17A4 /* ConsoleSmokeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleSmokeTests.swift; sourceTree = "<group>"; };
45 C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoFileEditorView.swift; sourceTree = "<group>"; }; 47 C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoFileEditorView.swift; sourceTree = "<group>"; };
48+ E05D9876A717AAC190A2750A /* MetalScatterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalScatterView.swift; sourceTree = "<group>"; };
49+ E080A0AFB04F825193F03420 /* PointRenderer.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = PointRenderer.metal; sourceTree = "<group>"; };
46 /* End PBXFileReference section */ 50 /* End PBXFileReference section */
47 51
48 /* Begin PBXFrameworksBuildPhase section */ 52 /* Begin PBXFrameworksBuildPhase section */
@@ -81,8 +85,10 @@
81 2B8E64F5B2DE7930640AEE29 /* DataBrowserView.swift */, 85 2B8E64F5B2DE7930640AEE29 /* DataBrowserView.swift */,
82 C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */, 86 C9BB0AC3BAA378743FD57FF7 /* DoFileEditorView.swift */,
83 A7C64C028218A21FE8E5D9D1 /* ManualView.swift */, 87 A7C64C028218A21FE8E5D9D1 /* ManualView.swift */,
88+ E05D9876A717AAC190A2750A /* MetalScatterView.swift */,
84 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */, 89 9C5393DC050C555E4FB69FC0 /* MetrikaApp.swift */,
85 4AD551AA707CD1853C3D6D21 /* PlotView.swift */, 90 4AD551AA707CD1853C3D6D21 /* PlotView.swift */,
91+ E080A0AFB04F825193F03420 /* PointRenderer.metal */,
86 0453FB0F256E853D2C2F481C /* SessionModel.swift */, 92 0453FB0F256E853D2C2F481C /* SessionModel.swift */,
87 ); 93 );
88 path = Sources; 94 path = Sources;
@@ -234,8 +240,10 @@
234 F0A49DC471FA794BE5948A37 /* DataBrowserView.swift in Sources */, 240 F0A49DC471FA794BE5948A37 /* DataBrowserView.swift in Sources */,
235 0BD0F927FC614CF19EF3D59D /* DoFileEditorView.swift in Sources */, 241 0BD0F927FC614CF19EF3D59D /* DoFileEditorView.swift in Sources */,
236 D01BD59F090074D4C3FC392D /* ManualView.swift in Sources */, 242 D01BD59F090074D4C3FC392D /* ManualView.swift in Sources */,
243+ 936699328DFFABBE4AB89A9D /* MetalScatterView.swift in Sources */,
237 9BE65709781719FAE0CA740A /* MetrikaApp.swift in Sources */, 244 9BE65709781719FAE0CA740A /* MetrikaApp.swift in Sources */,
238 DDCC2DC487AF9FAC7D6B8E3E /* PlotView.swift in Sources */, 245 DDCC2DC487AF9FAC7D6B8E3E /* PlotView.swift in Sources */,
246+ 0B840EF0C831CE88C2C10C02 /* PointRenderer.metal in Sources */,
239 7E936A23CCC90823D6B08BCC /* SessionModel.swift in Sources */, 247 7E936A23CCC90823D6B08BCC /* SessionModel.swift in Sources */,
240 ); 248 );
241 runOnlyForDeploymentPostprocessing = 0; 249 runOnlyForDeploymentPostprocessing = 0;
added Metrika/Sources/MetalScatterView.swift +232 −0
@@ -0,0 +1,232 @@
1+//
2+// MetalScatterView.swift
3+// Metrika
4+//
5+// Author: Simon-Pierre Boucher
6+// Contact: contact@spboucher.ai
7+// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.
8+//
9+
10+import MetalKit
11+import SwiftUI
12+import ZQGraphics
13+
14+/// Metal point-sprite renderer for scatter plots past the Swift Charts
15+/// threshold (CLAUDE.md §7 pane 4). Unified memory: the packed vertex
16+/// buffer is written once; pan (scroll) and zoom (pinch or scroll+⌥)
17+/// only touch a 24-byte uniform.
18+struct MetalScatterPane: View {
19+ let plot: ZQPlotSpec
20+
21+ var body: some View {
22+ VStack(spacing: 0) {
23+ MetalScatterView(plot: plot)
24+ Divider()
25+ HStack {
26+ Text("\(plot.series.reduce(0) { $0 + $1.x.count }) points — Metal renderer")
27+ Spacer()
28+ Text("scroll to pan · pinch or ⌥-scroll to zoom · double-click to reset")
29+ }
30+ .font(.caption)
31+ .foregroundStyle(.secondary)
32+ .padding(6)
33+ }
34+ }
35+}
36+
37+struct MetalScatterView: NSViewRepresentable {
38+ let plot: ZQPlotSpec
39+
40+ func makeCoordinator() -> Renderer { Renderer() }
41+
42+ func makeNSView(context: Context) -> ScatterMTKView {
43+ let view = ScatterMTKView()
44+ view.device = MTLCreateSystemDefaultDevice()
45+ view.delegate = context.coordinator
46+ view.renderer = context.coordinator
47+ view.enableSetNeedsDisplay = true
48+ view.isPaused = true
49+ view.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
50+ view.layer?.isOpaque = false
51+ context.coordinator.attach(view: view)
52+ context.coordinator.load(plot: plot)
53+ return view
54+ }
55+
56+ func updateNSView(_ view: ScatterMTKView, context: Context) {
57+ context.coordinator.load(plot: plot)
58+ view.needsDisplay = true
59+ }
60+
61+ /// MTKView subclass owning the pan/zoom gestures.
62+ final class ScatterMTKView: MTKView {
63+ weak var renderer: Renderer?
64+
65+ override func scrollWheel(with event: NSEvent) {
66+ if event.modifierFlags.contains(.option) {
67+ renderer?.zoom(by: 1 + event.scrollingDeltaY * 0.01)
68+ } else {
69+ renderer?.pan(
70+ deltaX: event.scrollingDeltaX,
71+ deltaY: event.scrollingDeltaY,
72+ viewSize: bounds.size
73+ )
74+ }
75+ needsDisplay = true
76+ }
77+
78+ override func magnify(with event: NSEvent) {
79+ renderer?.zoom(by: 1 + event.magnification)
80+ needsDisplay = true
81+ }
82+
83+ override func mouseDown(with event: NSEvent) {
84+ if event.clickCount == 2 {
85+ renderer?.resetViewport()
86+ needsDisplay = true
87+ }
88+ }
89+ }
90+
91+ /// Pipeline + vertex buffer + viewport state.
92+ @MainActor
93+ final class Renderer: NSObject, MTKViewDelegate {
94+ private struct Uniforms {
95+ var center: SIMD2<Float>
96+ var scale: SIMD2<Float>
97+ var pointSize: Float
98+ }
99+
100+ private var pipeline: MTLRenderPipelineState?
101+ private var commandQueue: MTLCommandQueue?
102+ private var vertexBuffer: MTLBuffer?
103+ private var pointCount = 0
104+ private var loadedPlotSignature = 0
105+
106+ // Data bounds and viewport state.
107+ private var dataCenter = SIMD2<Float>(0, 0)
108+ private var dataHalfSpan = SIMD2<Float>(1, 1)
109+ private var zoomLevel: Float = 1
110+ private var panOffset = SIMD2<Float>(0, 0)
111+ private var aspect: Float = 1
112+
113+ func attach(view: MTKView) {
114+ guard let device = view.device else { return }
115+ commandQueue = device.makeCommandQueue()
116+ guard let library = device.makeDefaultLibrary(),
117+ let vertexFunction = library.makeFunction(name: "scatterVertex"),
118+ let fragmentFunction = library.makeFunction(name: "scatterFragment")
119+ else { return }
120+
121+ let descriptor = MTLRenderPipelineDescriptor()
122+ descriptor.vertexFunction = vertexFunction
123+ descriptor.fragmentFunction = fragmentFunction
124+ descriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat
125+ descriptor.colorAttachments[0].isBlendingEnabled = true
126+ descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
127+ descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
128+ descriptor.colorAttachments[0].sourceAlphaBlendFactor = .one
129+ descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
130+ pipeline = try? device.makeRenderPipelineState(descriptor: descriptor)
131+ }
132+
133+ /// Rebuilds the vertex buffer when the plot actually changes.
134+ func load(plot: ZQPlotSpec) {
135+ let signature = plot.series.reduce(plot.series.count) {
136+ $0 &* 31 &+ $1.x.count
137+ }
138+ guard signature != loadedPlotSignature,
139+ let device = commandQueue?.device else { return }
140+ loadedPlotSignature = signature
141+
142+ let palette: [SIMD4<UInt8>] = [
143+ .init(79, 140, 255, 220), .init(255, 149, 0, 220),
144+ .init(52, 199, 89, 220), .init(175, 82, 222, 220),
145+ .init(255, 59, 48, 220), .init(90, 200, 250, 220),
146+ ]
147+
148+ var minX = Float.greatestFiniteMagnitude, maxX = -Float.greatestFiniteMagnitude
149+ var minY = Float.greatestFiniteMagnitude, maxY = -Float.greatestFiniteMagnitude
150+ var packed = [UInt8]()
151+ var count = 0
152+ for (seriesIndex, series) in plot.series.enumerated() {
153+ let color = palette[seriesIndex % palette.count]
154+ for i in 0..<series.x.count {
155+ let x = Float(series.x[i])
156+ let y = Float(series.y[i])
157+ guard x.isFinite, y.isFinite else { continue }
158+ minX = min(minX, x); maxX = max(maxX, x)
159+ minY = min(minY, y); maxY = max(maxY, y)
160+ withUnsafeBytes(of: x) { packed.append(contentsOf: $0) }
161+ withUnsafeBytes(of: y) { packed.append(contentsOf: $0) }
162+ packed.append(contentsOf: [color.x, color.y, color.z, color.w])
163+ count += 1
164+ }
165+ }
166+ guard count > 0 else { return }
167+ pointCount = count
168+ vertexBuffer = packed.withUnsafeBytes {
169+ device.makeBuffer(bytes: $0.baseAddress!, length: $0.count)
170+ }
171+
172+ dataCenter = SIMD2((minX + maxX) / 2, (minY + maxY) / 2)
173+ dataHalfSpan = SIMD2(
174+ max((maxX - minX) / 2, 1e-9) * 1.05,
175+ max((maxY - minY) / 2, 1e-9) * 1.05
176+ )
177+ resetViewport()
178+ }
179+
180+ func resetViewport() {
181+ zoomLevel = 1
182+ panOffset = .zero
183+ }
184+
185+ func zoom(by factor: Double) {
186+ zoomLevel = min(max(zoomLevel * Float(factor), 0.1), 10_000)
187+ }
188+
189+ func pan(deltaX: Double, deltaY: Double, viewSize: CGSize) {
190+ guard viewSize.width > 0, viewSize.height > 0 else { return }
191+ panOffset.x -= Float(deltaX) * 2 * dataHalfSpan.x
192+ / (zoomLevel * Float(viewSize.width))
193+ panOffset.y += Float(deltaY) * 2 * dataHalfSpan.y
194+ / (zoomLevel * Float(viewSize.height))
195+ }
196+
197+ nonisolated func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
198+
199+ nonisolated func draw(in view: MTKView) {
200+ MainActor.assumeIsolated {
201+ render(in: view)
202+ }
203+ }
204+
205+ private func render(in view: MTKView) {
206+ guard let pipeline,
207+ let vertexBuffer,
208+ pointCount > 0,
209+ let descriptor = view.currentRenderPassDescriptor,
210+ let drawable = view.currentDrawable,
211+ let commandBuffer = commandQueue?.makeCommandBuffer(),
212+ let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
213+ else { return }
214+
215+ var uniforms = Uniforms(
216+ center: dataCenter + panOffset,
217+ scale: SIMD2(
218+ zoomLevel / dataHalfSpan.x,
219+ zoomLevel / dataHalfSpan.y
220+ ),
221+ pointSize: Float(max(2, 6 - log10(Double(max(pointCount, 10)))))
222+ )
223+ encoder.setRenderPipelineState(pipeline)
224+ encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
225+ encoder.setVertexBytes(&uniforms, length: MemoryLayout<Uniforms>.size, index: 1)
226+ encoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: pointCount)
227+ encoder.endEncoding()
228+ commandBuffer.present(drawable)
229+ commandBuffer.commit()
230+ }
231+ }
232+}
modified Metrika/Sources/PlotView.swift +16 −1
@@ -16,8 +16,23 @@ import ZQGraphics
16 struct PlotView: View { 16 struct PlotView: View {
17 @Environment(SessionModel.self) private var model 17 @Environment(SessionModel.self) private var model
18 18
19+ /// Past this many points, Swift Charts degrades — the Metal renderer
20+ /// takes over (overridable for tests via METRIKA_METAL_THRESHOLD).
21+ private static let metalThreshold: Int = {
22+ if let text = ProcessInfo.processInfo.environment["METRIKA_METAL_THRESHOLD"],
23+ let value = Int(text) {
24+ return value
25+ }
26+ return 100_000
27+ }()
28+
19 var body: some View { 29 var body: some View {
20− if let plot = model.lastPlot { 30+ if let plot = model.lastPlot,
31+ plot.kind == .scatter,
32+ plot.series.reduce(0, { $0 + $1.x.count }) > Self.metalThreshold {
33+ MetalScatterPane(plot: plot)
34+ .accessibilityLabel("Metal scatter renderer")
35+ } else if let plot = model.lastPlot {
21 Chart { 36 Chart {
22 ForEach(Array(plot.series.enumerated()), id: \.offset) { _, series in 37 ForEach(Array(plot.series.enumerated()), id: \.offset) { _, series in
23 ForEach(series.x.indices, id: \.self) { index in 38 ForEach(series.x.indices, id: \.self) { index in
added Metrika/Sources/PointRenderer.metal +58 −0
@@ -0,0 +1,58 @@
1+//
2+// PointRenderer.metal
3+// Metrika
4+//
5+// Author: Simon-Pierre Boucher
6+// Contact: contact@spboucher.ai
7+// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.
8+//
9+// Instanced point-sprite renderer for large scatter plots (CLAUDE.md §7:
10+// custom MTKView renderer past the Swift Charts threshold). One packed
11+// 12-byte vertex per observation; the fragment stage rounds sprites.
12+//
13+
14+#include <metal_stdlib>
15+using namespace metal;
16+
17+struct PackedPoint {
18+ packed_float2 position; // data coordinates
19+ uchar4 color;
20+};
21+
22+struct Uniforms {
23+ float2 center; // data-space center of the viewport
24+ float2 scale; // data → clip scaling (zoom × aspect)
25+ float pointSize;
26+};
27+
28+struct VertexOut {
29+ float4 position [[position]];
30+ float pointSize [[point_size]];
31+ float4 color;
32+};
33+
34+vertex VertexOut scatterVertex(
35+ uint vertexID [[vertex_id]],
36+ const device PackedPoint *points [[buffer(0)]],
37+ constant Uniforms &uniforms [[buffer(1)]]
38+) {
39+ PackedPoint point = points[vertexID];
40+ float2 clip = (float2(point.position) - uniforms.center) * uniforms.scale;
41+
42+ VertexOut out;
43+ out.position = float4(clip, 0.0, 1.0);
44+ out.pointSize = uniforms.pointSize;
45+ out.color = float4(point.color) / 255.0;
46+ return out;
47+}
48+
49+fragment float4 scatterFragment(
50+ VertexOut in [[stage_in]],
51+ float2 pointCoord [[point_coord]]
52+) {
53+ float2 offset = pointCoord - 0.5;
54+ if (dot(offset, offset) > 0.25) {
55+ discard_fragment();
56+ }
57+ return in.color;
58+}
modified Metrika/Sources/SessionModel.swift +8 −0
@@ -66,6 +66,14 @@ final class SessionModel {
66 try? Data(csv.utf8).write(to: url) 66 try? Data(csv.utf8).write(to: url)
67 Task { await run("use \(url.path)") } 67 Task { await run("use \(url.path)") }
68 } 68 }
69+ // Headless driving: semicolon-separated commands run at startup.
70+ if let script = ProcessInfo.processInfo.environment["METRIKA_AUTORUN"] {
71+ Task {
72+ for command in script.split(separator: ";") {
73+ await run(command.trimmingCharacters(in: .whitespaces))
74+ }
75+ }
76+ }
69 #endif 77 #endif
70 } 78 }
71 79
modified MetrikaKit/Package.swift +1 −0
@@ -80,6 +80,7 @@ let package = Package(
80 "ZQParser", "ZQPlanner", "ZQData", "ZQStats", 80 "ZQParser", "ZQPlanner", "ZQData", "ZQStats",
81 "ZQGPU", "ZQGraphics", "ZQPlugins", 81 "ZQGPU", "ZQGraphics", "ZQPlugins",
82 ], 82 ],
83+ resources: [.copy("Resources/Samples")],
83 swiftSettings: strictConcurrency 84 swiftSettings: strictConcurrency
84 ), 85 ),
85 .executableTarget( 86 .executableTarget(
added MetrikaKit/Sources/MetrikaCLI/DocsCommand.swift +144 −0
@@ -0,0 +1,144 @@
1+//
2+// DocsCommand.swift
3+// Metrika
4+//
5+// Author: Simon-Pierre Boucher
6+// Contact: contact@spboucher.ai
7+// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.
8+//
9+
10+import ArgumentParser
11+import Foundation
12+import ZQEngine
13+
14+/// `metrika-cli docs --output docs/` — renders the shared command
15+/// reference (the same registry behind the app's Manual pane and the
16+/// console `help`) into a self-contained static site.
17+struct DocsCommand: ParsableCommand {
18+ static let configuration = CommandConfiguration(
19+ commandName: "docs",
20+ abstract: "Generate the static documentation site from the command reference."
21+ )
22+
23+ @Option(name: .shortAndLong, help: "Output directory.")
24+ var output: String = "docs"
25+
26+ func run() throws {
27+ let directory = URL(fileURLWithPath: output, isDirectory: true)
28+ try FileManager.default.createDirectory(
29+ at: directory, withIntermediateDirectories: true
30+ )
31+ let html = Self.render()
32+ try Data(html.utf8).write(to: directory.appendingPathComponent("index.html"))
33+ print("wrote \(output)/index.html (\(ZQCommandReference.all.count) commands)")
34+ }
35+
36+ static func escape(_ text: String) -> String {
37+ text.replacingOccurrences(of: "&", with: "&amp;")
38+ .replacingOccurrences(of: "<", with: "&lt;")
39+ .replacingOccurrences(of: ">", with: "&gt;")
40+ }
41+
42+ static func render() -> String {
43+ var nav = ""
44+ var body = ""
45+
46+ for category in ZQCommandReference.categories {
47+ let docs = ZQCommandReference.all.filter { $0.category == category }
48+ guard !docs.isEmpty else { continue }
49+ nav += "<h3>\(escape(category))</h3><ul>"
50+ body += "<h2 class=\"category\">\(escape(category))</h2>\n"
51+ for doc in docs {
52+ nav += "<li><a href=\"#\(doc.verb)\"><code>\(doc.verb)</code></a></li>"
53+ body += renderCommand(doc)
54+ }
55+ nav += "</ul>"
56+ }
57+
58+ return """
59+ <!DOCTYPE html>
60+ <html lang="en">
61+ <head>
62+ <meta charset="utf-8">
63+ <meta name="viewport" content="width=device-width, initial-scale=1">
64+ <title>Metrika — Command Reference</title>
65+ <style>
66+ :root { color-scheme: light dark;
67+ --accent: #4f8cff; --muted: #7a8199; --border: #d9dce6;
68+ --code-bg: rgba(120, 130, 180, 0.12); }
69+ @media (prefers-color-scheme: dark) {
70+ :root { --border: #2c3145; } }
71+ * { box-sizing: border-box; }
72+ body { margin: 0; font: 15px/1.55 -apple-system, "Helvetica Neue", sans-serif;
73+ display: flex; min-height: 100vh; }
74+ nav { width: 230px; flex-shrink: 0; padding: 24px 18px; overflow-y: auto;
75+ position: sticky; top: 0; height: 100vh; border-right: 1px solid var(--border); }
76+ nav h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
77+ color: var(--muted); margin: 18px 0 6px; }
78+ nav ul { list-style: none; margin: 0; padding: 0; }
79+ nav a { text-decoration: none; color: inherit; }
80+ nav a:hover code { color: var(--accent); }
81+ main { flex: 1; max-width: 760px; padding: 32px 40px 80px; }
82+ h1 { font-size: 28px; margin: 0 0 4px; }
83+ .subtitle { color: var(--muted); margin-bottom: 28px; }
84+ h2.category { font-size: 13px; text-transform: uppercase; letter-spacing: .08em;
85+ color: var(--muted); border-bottom: 1px solid var(--border);
86+ padding-bottom: 6px; margin-top: 44px; }
87+ article { margin: 26px 0; }
88+ article h2 { font-size: 20px; margin: 0 0 2px; font-family: ui-monospace, monospace; }
89+ .abbr { font-size: 12px; color: var(--muted); margin-left: 8px; }
90+ pre { background: var(--code-bg); border-radius: 8px; padding: 10px 14px;
91+ overflow-x: auto; font: 13px ui-monospace, "SF Mono", monospace; }
92+ code { font: 13px ui-monospace, "SF Mono", monospace; }
93+ table { border-collapse: collapse; margin: 8px 0; }
94+ td { padding: 3px 14px 3px 0; vertical-align: top; font-size: 14px; }
95+ td:first-child { font-family: ui-monospace, monospace; white-space: nowrap; }
96+ .notes { color: var(--muted); font-size: 14px; }
97+ footer { color: var(--muted); font-size: 12px; margin-top: 56px;
98+ border-top: 1px solid var(--border); padding-top: 12px; }
99+ </style>
100+ </head>
101+ <body>
102+ <nav>\(nav)</nav>
103+ <main>
104+ <h1>Metrika</h1>
105+ <p class="subtitle">Command reference — Stata-class syntax, GPU-accelerated by Apple Silicon.
106+ Every command follows one grammar:
107+ <code>command [varlist] [if] [in] [, options]</code></p>
108+ \(body)
109+ <footer>© 2026 Simon-Pierre Boucher. Generated from the in-app command
110+ reference by <code>metrika-cli docs</code>.</footer>
111+ </main>
112+ </body>
113+ </html>
114+ """
115+ }
116+
117+ static func renderCommand(_ doc: ZQCommandDoc) -> String {
118+ var html = "<article id=\"\(doc.verb)\">"
119+ html += "<h2>\(escape(doc.verb))"
120+ if let abbreviation = doc.abbreviation {
121+ html += "<span class=\"abbr\">abbreviation: \(escape(abbreviation))</span>"
122+ }
123+ html += "</h2>"
124+ html += "<p>\(escape(doc.summary))</p>"
125+ html += "<pre>\(escape(doc.syntax))</pre>"
126+ if !doc.options.isEmpty {
127+ html += "<table>"
128+ for option in doc.options {
129+ html += "<tr><td>\(escape(option.name))</td><td>\(escape(option.meaning))</td></tr>"
130+ }
131+ html += "</table>"
132+ }
133+ if !doc.examples.isEmpty {
134+ html += "<pre>"
135+ + doc.examples.map { ". " + escape($0) }.joined(separator: "\n")
136+ + "</pre>"
137+ }
138+ if let notes = doc.notes {
139+ html += "<p class=\"notes\">\(escape(notes))</p>"
140+ }
141+ html += "</article>"
142+ return html
143+ }
144+}
modified MetrikaKit/Sources/MetrikaCLI/MetrikaCLI.swift +13 −2
@@ -12,12 +12,23 @@ import Foundation
12 import ZQEngine 12 import ZQEngine
13 13
14 /// Companion command-line tool: runs ZQL commands or `.zyq` do-files 14 /// Companion command-line tool: runs ZQL commands or `.zyq` do-files
15−/// against a headless session. Interactive REPL when no input is given. 15+/// against a headless session (default subcommand), and generates the
16+/// documentation site (`docs`).
16 @main 17 @main
17 struct MetrikaCLI: AsyncParsableCommand { 18 struct MetrikaCLI: AsyncParsableCommand {
18 static let configuration = CommandConfiguration( 19 static let configuration = CommandConfiguration(
19 commandName: "metrika-cli", 20 commandName: "metrika-cli",
20− abstract: "Metrika statistical engine — command-line interface." 21+ abstract: "Metrika statistical engine — command-line interface.",
22+ subcommands: [RunCommand.self, DocsCommand.self],
23+ defaultSubcommand: RunCommand.self
24+ )
25+}
26+
27+/// Default behavior: execute commands, run a do-file, or start a REPL.
28+struct RunCommand: AsyncParsableCommand {
29+ static let configuration = CommandConfiguration(
30+ commandName: "run",
31+ abstract: "Execute ZQL commands or a do-file (default)."
21 ) 32 )
22 33
23 @Option( 34 @Option(
modified MetrikaKit/Sources/ZQEngine/CommandHelp.swift +7 −0
@@ -63,6 +63,13 @@ public enum ZQCommandReference {
63 examples: ["use sales.parquet", "use ~/data/panel.dta"], 63 examples: ["use sales.parquet", "use ~/data/panel.dta"],
64 notes: "Loading goes through DuckDB for columnar formats and a native reader for .dta. The sandboxed app reads user-selected files and its own container." 64 notes: "Loading goes through DuckDB for columnar formats and a native reader for .dta. The sandboxed app reads user-selected files and its own container."
65 ), 65 ),
66+ ZQCommandDoc(
67+ verb: "sysuse", category: "Data management",
68+ summary: "Load a sample dataset shipped with Metrika. Bare `sysuse` lists what is available.",
69+ syntax: "sysuse [name]",
70+ examples: ["sysuse sales", "sysuse mtcars"],
71+ notes: "sales: 200-firm × 5-year panel (revenue, price, region, employees). mtcars: the R classic, with a string model column."
72+ ),
66 ZQCommandDoc( 73 ZQCommandDoc(
67 verb: "save", category: "Data management", 74 verb: "save", category: "Data management",
68 summary: "Write the working dataset to disk (.parquet, .csv, or Stata .dta format 118).", 75 summary: "Write the working dataset to disk (.parquet, .csv, or Stata .dta format 118).",
added MetrikaKit/Sources/ZQEngine/Resources/Samples/mtcars.csv +33 −0
@@ -0,0 +1,33 @@
1+"model","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
2+"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4
3+"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4
4+"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
5+"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
6+"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
7+"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
8+"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
9+"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
10+"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
11+"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
12+"Merc 280C",17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
13+"Merc 450SE",16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
14+"Merc 450SL",17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15+"Merc 450SLC",15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
16+"Cadillac Fleetwood",10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
17+"Lincoln Continental",10.4,8,460,215,3,5.424,17.82,0,0,3,4
18+"Chrysler Imperial",14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
19+"Fiat 128",32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
20+"Honda Civic",30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
21+"Toyota Corolla",33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
22+"Toyota Corona",21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
23+"Dodge Challenger",15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
24+"AMC Javelin",15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
25+"Camaro Z28",13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
26+"Pontiac Firebird",19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27+"Fiat X1-9",27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
28+"Porsche 914-2",26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
29+"Lotus Europa",30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
30+"Ford Pantera L",15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
31+"Ferrari Dino",19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
32+"Maserati Bora",15,8,301,335,3.54,3.57,14.6,0,1,5,8
33+"Volvo 142E",21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
added MetrikaKit/Sources/ZQEngine/Resources/Samples/sales.csv +1001 −0
@@ -0,0 +1,1001 @@
1+firm_id,year,region,price,revenue,employees
2+1,2019,1,13.8188,26.4832,5
3+1,2020,1,17.7237,26.4094,7
4+1,2021,1,14.3484,30.6098,9
5+1,2022,1,20.1911,21.9176,7
6+1,2023,1,10.5081,65.2711,15
7+2,2019,1,8.4722,29.1197,7
8+2,2020,1,22.0202,16.0532,5
9+2,2021,1,18.2505,18.5946,6
10+2,2022,1,10.2143,33.0126,4
11+2,2023,1,23.8342,18.4339,6
12+3,2019,1,9.4742,28.8469,11
13+3,2020,1,15.8281,23.0886,5
14+3,2021,1,9.6621,69.3117,9
15+3,2022,1,19.0466,17.7664,6
16+3,2023,1,25.6289,18.0874,4
17+4,2019,2,17.1929,11.7742,5
18+4,2020,2,19.6243,15.1478,3
19+4,2021,2,17.7873,15.3628,13
20+4,2022,2,20.7242,10.8188,7
21+4,2023,2,6.7832,36.1605,7
22+5,2019,1,5.4886,97.4772,9
23+5,2020,1,15.6086,30.4995,9
24+5,2021,1,12.8387,36.1265,8
25+5,2022,1,19.7446,32.3737,7
26+5,2023,1,24.1304,17.6494,6
27+6,2019,3,12.0201,45.3476,7
28+6,2020,3,23.7536,21.1541,8
29+6,2021,3,23.7348,19.3619,5
30+6,2022,3,6.6427,67.9187,9
31+6,2023,3,25.6725,28.3471,5
32+7,2019,3,5.1948,35.6952,6
33+7,2020,3,14.5087,31.191,6
34+7,2021,3,7.5117,33.257,8
35+7,2022,3,23.7588,11.112,6
36+7,2023,3,8.2972,32.0576,7
37+8,2019,4,20.1045,20.4873,16
38+8,2020,4,20.7911,25.0039,11
39+8,2021,4,18.5145,44.2152,13
40+8,2022,4,19.7068,37.5996,8
41+8,2023,4,17.2698,74.2775,7
42+9,2019,4,24.2438,13.1857,6
43+9,2020,4,18.6541,17.3394,4
44+9,2021,4,17.346,27.1637,7
45+9,2022,4,16.2658,21.6728,7
46+9,2023,4,13.9039,32.1277,4
47+10,2019,4,23.569,15.226,6
48+10,2020,4,24.3319,13.4569,7
49+10,2021,4,15.8814,24.2056,5
50+10,2022,4,21.8208,16.6568,4
51+10,2023,4,13.5194,30.6397,7
52+11,2019,1,20.1874,13.9931,3
53+11,2020,1,19.0085,11.9489,10
54+11,2021,1,22.9635,7.9024,6
55+11,2022,1,6.3124,28.8013,4
56+11,2023,1,14.9045,15.0521,5
57+12,2019,3,5.6838,43.4331,8
58+12,2020,3,18.2387,16.394,5
59+12,2021,3,16.5559,21.7636,6
60+12,2022,3,23.4144,14.218,8
61+12,2023,3,26.1635,6.3023,9
62+13,2019,4,5.5091,49.0725,6
63+13,2020,4,12.4414,58.2992,9
64+13,2021,4,15.9237,32.4851,7
65+13,2022,4,9.7766,49.0527,8
66+13,2023,4,10.4376,56.2516,8
67+14,2019,2,13.1873,39.2138,10
68+14,2020,2,9.0762,37.7254,9
69+14,2021,2,24.1035,18.0675,7
70+14,2022,2,10.5768,56.5536,9
71+14,2023,2,22.1168,24.006,9
72+15,2019,2,17.8222,16.606,8
73+15,2020,2,21.9686,13.1297,6
74+15,2021,2,15.6616,18.2082,7
75+15,2022,2,15.8311,21.8368,6
76+15,2023,2,9.2348,42.116,7
77+16,2019,4,20.4876,18.7643,5
78+16,2020,4,19.8259,23.7656,15
79+16,2021,4,23.8775,19.6511,8
80+16,2022,4,19.9317,31.3511,7
81+16,2023,4,26.4656,12.5106,4
82+17,2019,3,18.6442,21.6793,7
83+17,2020,3,8.4674,55.3388,8
84+17,2021,3,18.6222,24.9631,8
85+17,2022,3,13.9804,23.4273,7
86+17,2023,3,24.3666,16.9619,7
87+18,2019,3,14.0281,58.2869,10
88+18,2020,3,10.4087,50.0897,12
89+18,2021,3,11.9779,59.3314,7
90+18,2022,3,11.6927,53.4208,8
91+18,2023,3,9.2506,94.9052,6
92+19,2019,1,20.3819,40.1318,9
93+19,2020,1,13.0506,59.2871,7
94+19,2021,1,20.3265,39.0047,8
95+19,2022,1,12.3828,63.2152,16
96+19,2023,1,23.9252,46.8862,15
97+20,2019,2,19.2034,17.4556,5
98+20,2020,2,15.3091,15.07,7
99+20,2021,2,8.9477,24.4905,7
100+20,2022,2,20.7484,14.7976,15
101+20,2023,2,13.1751,24.7756,4
102+21,2019,1,22.3879,25.8707,9
103+21,2020,1,15.1558,39.5224,11
104+21,2021,1,10.6058,55.6019,7
105+21,2022,1,10.331,80.8303,10
106+21,2023,1,25.8201,28.4423,7
107+22,2019,4,14.7043,74.488,9
108+22,2020,4,10.0538,93.7313,15
109+22,2021,4,19.1966,61.7593,10
110+22,2022,4,7.2223,92.9447,10
111+22,2023,4,15.4244,57.2499,10
112+23,2019,2,14.0132,38.2272,5
113+23,2020,2,19.7911,19.3886,3
114+23,2021,2,17.4176,27.8724,9
115+23,2022,2,8.6236,73.805,5
116+23,2023,2,11.5446,40.4094,3
117+24,2019,2,23.3233,15.6071,8
118+24,2020,2,8.4946,31.5549,5
119+24,2021,2,12.6422,43.2445,9
120+24,2022,2,11.2194,50.236,7
121+24,2023,2,20.317,35.0974,9
122+25,2019,2,22.3808,8.3779,8
123+25,2020,2,14.7784,15.7033,4
124+25,2021,2,6.1567,31.1982,7
125+25,2022,2,17.5573,16.2456,5
126+25,2023,2,6.9654,37.9968,4
127+26,2019,2,21.8867,16.2994,6
128+26,2020,2,7.3667,57.4986,6
129+26,2021,2,10.0478,50.7926,6
130+26,2022,2,23.3628,19.8791,5
131+26,2023,2,13.1193,55.9471,6
132+27,2019,2,21.2914,10.0827,8
133+27,2020,2,11.5517,33.8296,6
134+27,2021,2,9.7943,45.4652,5
135+27,2022,2,8.681,31.5088,8
136+27,2023,2,16.9693,24.482,5
137+28,2019,1,20.1401,12.7528,5
138+28,2020,1,20.0696,15.2402,8
139+28,2021,1,6.8877,43.6088,6
140+28,2022,1,19.9239,17.6806,7
141+28,2023,1,19.6463,28.9877,5
142+29,2019,1,19.0524,20.3629,6
143+29,2020,1,8.6024,34.5596,12
144+29,2021,1,17.365,22.1776,8
145+29,2022,1,9.3791,44.4841,11
146+29,2023,1,19.16,22.9806,5
147+30,2019,4,15.3176,50.524,6
148+30,2020,4,6.898,73.6949,8
149+30,2021,4,24.7212,28.4771,12
150+30,2022,4,25.8555,23.2191,6
151+30,2023,4,23.9406,23.6779,7
152+31,2019,4,13.5349,36.0615,10
153+31,2020,4,8.5953,47.8659,9
154+31,2021,4,19.5735,26.5424,7
155+31,2022,4,17.5768,44.321,7
156+31,2023,4,8.3341,63.0547,10
157+32,2019,1,15.3842,48.5857,23
158+32,2020,1,12.5495,47.917,11
159+32,2021,1,7.2682,102.2311,14
160+32,2022,1,21.7043,36.1054,12
161+32,2023,1,11.4662,108.2383,13
162+33,2019,4,16.1648,24.9216,10
163+33,2020,4,11.4613,45.477,8
164+33,2021,4,17.8664,32.4567,9
165+33,2022,4,24.6839,23.8671,10
166+33,2023,4,8.515,67.8256,14
167+34,2019,3,10.6412,39.3992,11
168+34,2020,3,19.174,35.9399,4
169+34,2021,3,22.9909,16.3234,6
170+34,2022,3,12.2928,39.9902,20
171+34,2023,3,10.4235,40.5021,10
172+35,2019,4,5.968,89.2419,4
173+35,2020,4,24.0375,16.6661,9
174+35,2021,4,20.4631,26.4378,7
175+35,2022,4,9.2094,70.1697,14
176+35,2023,4,16.7255,49.0901,6
177+36,2019,3,9.9866,43.0128,6
178+36,2020,3,10.0302,48.7513,14
179+36,2021,3,20.1901,16.7248,6
180+36,2022,3,18.3685,23.5421,6
181+36,2023,3,20.2568,23.8966,5
182+37,2019,3,5.6306,50.8153,4
183+37,2020,3,22.5915,13.165,4
184+37,2021,3,25.4237,10.8353,7
185+37,2022,3,6.7835,44.8282,5
186+37,2023,3,6.7823,37.3267,9
187+38,2019,2,21.6548,14.4552,7
188+38,2020,2,17.9643,28.4826,8
189+38,2021,2,12.0454,34.6841,6
190+38,2022,2,14.4002,39.2834,10
191+38,2023,2,15.2778,28.4058,15
192+39,2019,1,7.3776,45.6802,10
193+39,2020,1,8.0402,53.7305,12
194+39,2021,1,18.3892,21.5391,8
195+39,2022,1,8.5462,54.8142,9
196+39,2023,1,13.4406,44.2555,6
197+40,2019,4,19.4334,37.923,5
198+40,2020,4,24.128,18.2392,4
199+40,2021,4,17.7082,37.5264,5
200+40,2022,4,8.8995,76.9189,10
201+40,2023,4,13.447,81.454,10
202+41,2019,4,23.1874,30.0381,8
203+41,2020,4,11.9534,58.2538,8
204+41,2021,4,19.9954,38.0111,8
205+41,2022,4,22.087,24.6684,6
206+41,2023,4,9.5073,82.9634,6
207+42,2019,2,23.4402,13.4955,10
208+42,2020,2,13.8917,35.7257,7
209+42,2021,2,23.9304,16.7005,8
210+42,2022,2,9.8323,41.5618,6
211+42,2023,2,22.8915,21.0388,4
212+43,2019,2,5.454,78.556,13
213+43,2020,2,22.1021,24.6231,8
214+43,2021,2,25.2373,14.6737,8
215+43,2022,2,20.4527,19.561,10
216+43,2023,2,25.4335,19.1318,20
217+44,2019,1,20.8129,8.2823,6
218+44,2020,1,16.5158,15.0429,11
219+44,2021,1,7.3907,29.783,9
220+44,2022,1,7.7544,27.7163,5
221+44,2023,1,19.1189,10.3871,8
222+45,2019,4,18.921,32.8634,9
223+45,2020,4,10.1239,64.2378,12
224+45,2021,4,9.0342,56.9911,11
225+45,2022,4,12.5873,73.3843,8
226+45,2023,4,21.0964,36.5048,5
227+46,2019,1,24.5034,9.2363,8
228+46,2020,1,21.5453,13.3117,7
229+46,2021,1,10.3015,34.4094,5
230+46,2022,1,17.7011,13.1508,8
231+46,2023,1,9.0349,52.4779,6
232+47,2019,1,14.4475,23.439,15
233+47,2020,1,14.2458,29.1136,8
234+47,2021,1,22.9819,18.193,7
235+47,2022,1,18.3259,21.5988,13
236+47,2023,1,23.7578,21.3757,3
237+48,2019,1,23.5557,15.1709,8
238+48,2020,1,18.6671,22.9732,12
239+48,2021,1,21.8548,32.6409,8
240+48,2022,1,15.2088,34.0245,8
241+48,2023,1,18.4267,30.9107,11
242+49,2019,4,21.5134,14.8503,5
243+49,2020,4,22.8877,22.3818,6
244+49,2021,4,17.39,33.6805,6
245+49,2022,4,23.6462,16.8369,7
246+49,2023,4,25.1243,20.5659,11
247+50,2019,1,16.651,10.858,5
248+50,2020,1,7.3978,21.7986,5
249+50,2021,1,18.1196,12.7901,4
250+50,2022,1,15.5262,16.6642,3
251+50,2023,1,8.7964,28.8991,12
252+51,2019,1,22.2937,18.4591,9
253+51,2020,1,6.019,41.7161,7
254+51,2021,1,10.6975,35.1309,9
255+51,2022,1,16.0107,26.6171,8
256+51,2023,1,7.4445,57.111,6
257+52,2019,1,19.5206,14.9707,5
258+52,2020,1,19.6853,19.7203,6
259+52,2021,1,17.9342,23.9436,4
260+52,2022,1,11.7107,29.1194,4
261+52,2023,1,17.4512,20.3323,4
262+53,2019,2,11.246,59.2184,6
263+53,2020,2,12.2349,52.6123,8
264+53,2021,2,21.3804,22.0941,6
265+53,2022,2,13.345,48.5669,13
266+53,2023,2,26.2554,19.1015,7
267+54,2019,1,16.5002,20.9207,6
268+54,2020,1,15.7572,31.9234,8
269+54,2021,1,11.1673,34.2755,8
270+54,2022,1,22.7648,18.818,5
271+54,2023,1,20.5493,25.2465,13
272+55,2019,4,5.3363,98.6396,8
273+55,2020,4,24.5476,18.7599,12
274+55,2021,4,16.5205,51.6328,10
275+55,2022,4,10.2473,98.0067,11
276+55,2023,4,7.4201,84.6251,5
277+56,2019,3,16.6451,31.4279,7
278+56,2020,3,6.287,69.2501,8
279+56,2021,3,17.4,34.9615,5
280+56,2022,3,24.6096,27.488,11
281+56,2023,3,12.4508,80.3708,10
282+57,2019,3,20.1493,16.8119,7
283+57,2020,3,19.7343,15.2607,4
284+57,2021,3,20.1544,16.6808,5
285+57,2022,3,6.5085,63.5724,8
286+57,2023,3,12.5311,26.0515,5
287+58,2019,4,13.253,31.1957,7
288+58,2020,4,7.3985,44.0591,7
289+58,2021,4,11.6453,46.1704,11
290+58,2022,4,19.5252,25.1611,7
291+58,2023,4,11.9203,39.7328,6
292+59,2019,2,7.5871,30.7634,9
293+59,2020,2,5.6229,55.8274,5
294+59,2021,2,25.6447,10.8964,3
295+59,2022,2,12.4524,32.8172,7
296+59,2023,2,17.2239,37.2817,5
297+60,2019,2,19.4391,30.3346,6
298+60,2020,2,17.8817,31.0461,14
299+60,2021,2,13.3978,37.2755,10
300+60,2022,2,7.3424,51.323,11
301+60,2023,2,16.3985,35.5379,15
302+61,2019,1,18.9617,31.8612,6
303+61,2020,1,17.5619,37.2376,9
304+61,2021,1,23.2379,17.1737,14
305+61,2022,1,11.4086,42.2783,10
306+61,2023,1,19.0527,27.4505,7
307+62,2019,1,7.4459,33.0857,6
308+62,2020,1,15.843,13.512,4
309+62,2021,1,24.0318,12.4345,9
310+62,2022,1,25.0252,7.6888,9
311+62,2023,1,13.7501,26.0732,5
312+63,2019,3,20.2775,15.4599,12
313+63,2020,3,9.0707,36.9883,11
314+63,2021,3,20.5413,25.0972,4
315+63,2022,3,7.9302,42.6822,13
316+63,2023,3,23.7353,15.118,6
317+64,2019,1,23.5579,15.1071,14
318+64,2020,1,18.0757,29.062,4
319+64,2021,1,7.0514,75.1112,8
320+64,2022,1,26.1833,12.771,9
321+64,2023,1,12.9137,43.8833,7
322+65,2019,4,17.7362,17.6531,8
323+65,2020,4,22.0763,26.0053,5
324+65,2021,4,22.9688,20.6137,6
325+65,2022,4,23.7903,25.2365,5
326+65,2023,4,23.0056,18.7999,7
327+66,2019,4,15.5097,32.0628,7
328+66,2020,4,20.6703,28.2659,7
329+66,2021,4,24.8611,19.4386,9
330+66,2022,4,23.288,24.3606,6
331+66,2023,4,14.4466,51.3954,6
332+67,2019,3,17.7411,27.6427,8
333+67,2020,3,15.5607,36.9302,7
334+67,2021,3,6.3286,73.3364,11
335+67,2022,3,8.0174,61.7355,12
336+67,2023,3,10.8328,38.6415,18
337+68,2019,1,22.2581,10.4369,6
338+68,2020,1,25.2087,13.4873,13
339+68,2021,1,24.4331,17.567,7
340+68,2022,1,25.6525,8.7555,3
341+68,2023,1,12.2731,25.2594,4
342+69,2019,4,14.1278,18.8684,4
343+69,2020,4,22.4529,17.7283,4
344+69,2021,4,5.9198,50.6942,7
345+69,2022,4,25.0471,12.1224,13
346+69,2023,4,11.0748,40.7675,7
347+70,2019,1,21.8577,11.0033,6
348+70,2020,1,6.7221,49.077,5
349+70,2021,1,11.1831,33.9452,13
350+70,2022,1,7.133,44.4716,6
351+70,2023,1,15.1896,31.2791,10
352+71,2019,3,21.5794,18.4243,4
353+71,2020,3,11.2275,22.5454,6
354+71,2021,3,16.7944,27.198,4
355+71,2022,3,6.3993,61.8816,6
356+71,2023,3,25.0968,16.9554,11
357+72,2019,3,5.3621,42.0261,6
358+72,2020,3,7.2505,44.8128,9
359+72,2021,3,25.3821,12.4979,3
360+72,2022,3,9.3541,32.9704,6
361+72,2023,3,11.8472,54.4945,7
362+73,2019,1,19.7926,15.867,6
363+73,2020,1,18.9341,21.1625,11
364+73,2021,1,23.3254,23.7415,5
365+73,2022,1,8.5271,40.0303,10
366+73,2023,1,15.222,41.8308,9
367+74,2019,1,22.4641,11.2364,9
368+74,2020,1,8.7476,30.6515,11
369+74,2021,1,9.1204,22.9134,10
370+74,2022,1,25.5623,9.7274,11
371+74,2023,1,14.1473,28.448,8
372+75,2019,3,24.0789,14.7972,5
373+75,2020,3,20.6201,27.3527,11
374+75,2021,3,22.8963,19.5118,15
375+75,2022,3,24.5714,17.9612,9
376+75,2023,3,22.4468,21.5829,6
377+76,2019,1,10.3996,36.4523,8
378+76,2020,1,7.7224,55.0387,6
379+76,2021,1,21.1102,20.7814,8
380+76,2022,1,6.6247,59.7236,8
381+76,2023,1,7.6717,67.5219,3
382+77,2019,3,24.6634,7.9323,11
383+77,2020,3,15.328,17.2727,7
384+77,2021,3,18.1523,21.8578,7
385+77,2022,3,8.48,28.2438,5
386+77,2023,3,10.6957,32.4403,4
387+78,2019,4,7.6353,66.0731,12
388+78,2020,4,24.1258,30.9646,4
389+78,2021,4,19.3475,41.6469,12
390+78,2022,4,11.5867,57.4366,4
391+78,2023,4,13.7609,55.7488,9
392+79,2019,1,11.4569,25.8137,6
393+79,2020,1,17.2349,32.5075,6
394+79,2021,1,19.227,34.5306,7
395+79,2022,1,11.3959,89.556,10
396+79,2023,1,8.2272,73.7338,16
397+80,2019,2,11.1858,38.7953,8
398+80,2020,2,11.5533,35.6566,12
399+80,2021,2,19.0946,16.9234,9
400+80,2022,2,9.0887,46.8744,3
401+80,2023,2,12.5711,43.0239,8
402+81,2019,4,22.5985,16.5098,5
403+81,2020,4,16.79,28.482,10
404+81,2021,4,18.3888,23.0889,4
405+81,2022,4,14.725,28.7159,5
406+81,2023,4,14.3302,41.5614,6
407+82,2019,1,24.8652,13.3914,13
408+82,2020,1,23.5369,23.2579,8
409+82,2021,1,14.7017,40.1017,14
410+82,2022,1,15.8373,22.4824,12
411+82,2023,1,14.2202,40.334,12
412+83,2019,3,13.2993,46.7931,3
413+83,2020,3,21.4235,14.2066,7
414+83,2021,3,16.8723,41.5895,8
415+83,2022,3,16.7518,43.9622,12
416+83,2023,3,11.7893,62.1655,8
417+84,2019,3,18.0207,32.0997,10
418+84,2020,3,15.8551,35.6036,12
419+84,2021,3,21.5021,20.66,10
420+84,2022,3,22.6824,26.8654,6
421+84,2023,3,9.7822,67.6578,4
422+85,2019,4,16.6138,46.7029,6
423+85,2020,4,6.5564,71.3887,4
424+85,2021,4,24.7601,17.9062,4
425+85,2022,4,19.6231,36.6426,8
426+85,2023,4,12.1294,62.9141,8
427+86,2019,1,18.1839,20.3491,5
428+86,2020,1,12.9765,37.3367,9
429+86,2021,1,19.8998,19.4261,6
430+86,2022,1,24.386,14.7418,11
431+86,2023,1,18.2709,22.8055,5
432+87,2019,4,7.2764,81.826,11
433+87,2020,4,16.2417,40.7633,6
434+87,2021,4,7.3887,114.897,8
435+87,2022,4,11.3829,88.0823,5
436+87,2023,4,21.407,39.3243,7
437+88,2019,4,20.6402,38.0492,16
438+88,2020,4,12.2264,57.5365,7
439+88,2021,4,14.4066,53.953,5
440+88,2022,4,9.4746,64.417,7
441+88,2023,4,16.4231,59.846,13
442+89,2019,2,8.5381,47.424,10
443+89,2020,2,9.0041,42.8785,7
444+89,2021,2,21.5205,24.6228,19
445+89,2022,2,10.2595,40.5864,8
446+89,2023,2,11.5076,37.0441,10
447+90,2019,1,17.496,20.8696,4
448+90,2020,1,15.0678,24.3483,10
449+90,2021,1,12.544,30.8284,7
450+90,2022,1,10.5209,34.6935,7
451+90,2023,1,22.537,14.3061,12
452+91,2019,4,18.3604,26.0246,9
453+91,2020,4,21.6307,23.6426,12
454+91,2021,4,10.8569,55.9923,10
455+91,2022,4,7.5513,94.4222,6
456+91,2023,4,20.0724,32.8507,5
457+92,2019,1,8.1708,38.0751,8
458+92,2020,1,11.2366,31.8057,5
459+92,2021,1,5.8331,40.3623,8
460+92,2022,1,25.5668,8.7614,8
461+92,2023,1,26.4234,10.9403,4
462+93,2019,3,18.1237,42.6615,9
463+93,2020,3,14.3976,45.4623,7
464+93,2021,3,6.4224,107.0666,22
465+93,2022,3,15.3436,83.5639,15
466+93,2023,3,24.9085,30.174,9
467+94,2019,4,10.2265,45.278,13
468+94,2020,4,14.8053,56.0205,11
469+94,2021,4,11.1013,54.7658,7
470+94,2022,4,18.0019,25.1339,7
471+94,2023,4,9.4298,52.2677,6
472+95,2019,3,23.3506,19.9534,8
473+95,2020,3,20.4062,21.1477,6
474+95,2021,3,18.9513,18.9059,7
475+95,2022,3,24.4656,9.4518,10
476+95,2023,3,26.4957,8.738,10
477+96,2019,2,14.0326,35.1436,10
478+96,2020,2,11.3866,43.8979,9
479+96,2021,2,16.0151,41.52,17
480+96,2022,2,18.9547,48.1457,7
481+96,2023,2,20.4859,32.6007,4
482+97,2019,4,18.6533,19.2461,5
483+97,2020,4,23.2091,13.5723,4
484+97,2021,4,21.5158,15.9491,6
485+97,2022,4,13.7948,33.8443,7
486+97,2023,4,22.5418,16.2835,11
487+98,2019,4,21.0055,19.2752,6
488+98,2020,4,19.5679,14.9849,7
489+98,2021,4,20.3938,19.1346,9
490+98,2022,4,8.3414,33.7495,4
491+98,2023,4,23.6078,14.1014,9
492+99,2019,2,11.7934,19.7129,6
493+99,2020,2,21.022,11.2876,8
494+99,2021,2,16.3663,17.6393,6
495+99,2022,2,16.6815,19.1286,5
496+99,2023,2,12.8111,28.4097,9
497+100,2019,1,6.5443,44.1292,6
498+100,2020,1,17.9266,23.6334,9
499+100,2021,1,12.7866,25.9941,7
500+100,2022,1,10.4578,45.7536,5
501+100,2023,1,25.5083,15.2897,3
502+101,2019,4,21.3565,13.8505,6
503+101,2020,4,9.4734,34.9593,7
504+101,2021,4,17.4441,37.3167,6
505+101,2022,4,15.7222,33.6301,5
506+101,2023,4,20.762,22.5101,4
507+102,2019,3,9.5817,52.2891,8
508+102,2020,3,13.6455,30.2011,8
509+102,2021,3,20.2431,24.7267,13
510+102,2022,3,11.1537,59.5064,8
511+102,2023,3,16.1643,34.4965,14
512+103,2019,3,23.5567,13.3055,7
513+103,2020,3,17.5788,20.7921,6
514+103,2021,3,9.6841,58.9606,8
515+103,2022,3,26.043,16.3515,7
516+103,2023,3,10.8349,33.5438,7
517+104,2019,1,16.9383,10.1788,3
518+104,2020,1,10.3325,13.015,5
519+104,2021,1,15.317,14.5075,3
520+104,2022,1,25.5144,7.8196,5
521+104,2023,1,7.7578,25.6394,8
522+105,2019,1,14.1658,18.1043,2
523+105,2020,1,8.4708,24.9161,5
524+105,2021,1,23.1364,11.9772,6
525+105,2022,1,17.5384,13.0202,4
526+105,2023,1,9.6417,46.3364,4
527+106,2019,4,19.2942,36.5618,20
528+106,2020,4,12.1148,42.7339,13
529+106,2021,4,17.3801,65.2994,6
530+106,2022,4,14.9355,70.9669,10
531+106,2023,4,18.7781,30.7034,8
532+107,2019,2,19.8957,15.467,5
533+107,2020,2,19.2558,14.6017,8
534+107,2021,2,13.4154,37.0877,9
535+107,2022,2,8.0546,63.6268,5
536+107,2023,2,21.7392,18.0726,17
537+108,2019,1,8.3415,37.9985,4
538+108,2020,1,16.2979,25.1786,9
539+108,2021,1,13.7546,21.5529,7
540+108,2022,1,17.6305,20.8321,6
541+108,2023,1,14.9493,22.8048,6
542+109,2019,1,24.661,10.1442,13
543+109,2020,1,18.1329,20.4569,12
544+109,2021,1,10.7825,31.664,7
545+109,2022,1,20.5007,21.2353,11
546+109,2023,1,23.3058,14.2404,7
547+110,2019,3,20.2128,27.5967,14
548+110,2020,3,22.6751,33.0881,9
549+110,2021,3,22.179,38.791,18
550+110,2022,3,25.2456,20.366,5
551+110,2023,3,14.0656,48.2008,16
552+111,2019,4,17.5415,21.9937,7
553+111,2020,4,9.4369,41.0045,10
554+111,2021,4,17.7802,20.8391,13
555+111,2022,4,8.7179,62.5143,10
556+111,2023,4,17.8185,18.5746,15
557+112,2019,3,19.785,19.9574,11
558+112,2020,3,20.6553,24.5673,4
559+112,2021,3,10.8011,57.4985,7
560+112,2022,3,20.785,23.1286,13
561+112,2023,3,12.3375,67.7582,7
562+113,2019,1,12.7808,21.2401,8
563+113,2020,1,22.5534,9.5764,5
564+113,2021,1,22.0438,10.7072,8
565+113,2022,1,25.8163,8.8187,7
566+113,2023,1,21.0757,16.728,7
567+114,2019,4,18.5793,19.1358,9
568+114,2020,4,20.5615,21.6451,13
569+114,2021,4,12.1599,31.6034,6
570+114,2022,4,26.1512,16.1313,4
571+114,2023,4,20.3611,26.252,11
572+115,2019,1,17.1828,23.2065,4
573+115,2020,1,16.7805,19.0515,8
574+115,2021,1,11.8743,33.8173,10
575+115,2022,1,19.4488,12.5854,8
576+115,2023,1,19.8713,19.4491,15
577+116,2019,1,7.1383,50.8571,8
578+116,2020,1,13.3057,45.6311,13
579+116,2021,1,15.4448,23.5715,15
580+116,2022,1,10.6109,54.2955,8
581+116,2023,1,15.2423,32.624,10
582+117,2019,4,12.0654,49.5846,6
583+117,2020,4,16.8356,36.8939,13
584+117,2021,4,16.6777,41.3707,11
585+117,2022,4,7.3602,70.4167,4
586+117,2023,4,23.2923,29.0942,9
587+118,2019,1,12.5323,41.4082,11
588+118,2020,1,18.189,25.8596,12
589+118,2021,1,15.6856,31.3334,9
590+118,2022,1,10.2984,78.1112,6
591+118,2023,1,16.4288,28.2861,8
592+119,2019,1,13.7632,33.2103,18
593+119,2020,1,17.6966,31.4438,6
594+119,2021,1,7.0609,63.9712,6
595+119,2022,1,8.6021,75.7238,7
596+119,2023,1,14.9974,42.8621,3
597+120,2019,1,13.2627,58.297,6
598+120,2020,1,7.2968,97.3465,5
599+120,2021,1,22.3563,21.8127,16
600+120,2022,1,7.845,84.3395,15
601+120,2023,1,16.5857,55.4373,7
602+121,2019,2,21.6402,23.2099,9
603+121,2020,2,11.7227,24.0362,6
604+121,2021,2,14.3862,25.4407,8
605+121,2022,2,9.1155,57.4592,14
606+121,2023,2,16.6262,35.6942,3
607+122,2019,4,20.866,9.3432,4
608+122,2020,4,19.3108,8.7758,6
609+122,2021,4,20.5489,9.6329,2
610+122,2022,4,12.6835,28.6155,5
611+122,2023,4,11.1733,15.9013,7
612+123,2019,3,13.3296,26.5956,6
613+123,2020,3,17.4213,25.0398,7
614+123,2021,3,11.4695,34.7626,7
615+123,2022,3,14.167,30.8165,7
616+123,2023,3,13.8384,46.1922,9
617+124,2019,2,5.7041,37.0234,6
618+124,2020,2,8.3388,36.6884,3
619+124,2021,2,24.2021,11.0387,12
620+124,2022,2,21.8953,16.5392,5
621+124,2023,2,12.0551,28.9183,5
622+125,2019,1,19.6424,16.5306,6
623+125,2020,1,11.6323,27.777,8
624+125,2021,1,10.12,53.4615,7
625+125,2022,1,10.3405,48.3498,8
626+125,2023,1,25.8519,10.765,6
627+126,2019,3,11.2557,48.7813,9
628+126,2020,3,13.4264,37.0155,10
629+126,2021,3,25.1431,18.3529,12
630+126,2022,3,13.8334,35.6393,7
631+126,2023,3,15.1012,57.8348,10
632+127,2019,1,6.3379,29.7579,8
633+127,2020,1,23.2158,11.3488,7
634+127,2021,1,16.0729,28.7584,6
635+127,2022,1,17.9467,21.2021,10
636+127,2023,1,10.2227,25.9027,8
637+128,2019,1,9.9508,22.0466,2
638+128,2020,1,18.147,10.6699,6
639+128,2021,1,21.2292,10.268,6
640+128,2022,1,17.7493,23.6356,7
641+128,2023,1,6.632,44.0715,8
642+129,2019,3,21.1001,17.0258,9
643+129,2020,3,23.6421,22.2899,11
644+129,2021,3,10.8021,59.0901,5
645+129,2022,3,10.4699,44.0662,4
646+129,2023,3,7.564,68.6313,8
647+130,2019,2,7.8144,54.8645,7
648+130,2020,2,18.435,28.7225,11
649+130,2021,2,13.9968,45.1696,14
650+130,2022,2,19.6105,30.4114,8
651+130,2023,2,13.4378,44.639,4
652+131,2019,1,23.0085,21.01,12
653+131,2020,1,14.2961,23.6211,8
654+131,2021,1,14.5037,17.556,7
655+131,2022,1,24.3092,10.8911,9
656+131,2023,1,20.4262,16.7975,7
657+132,2019,2,8.4924,55.8312,20
658+132,2020,2,18.9459,17.8376,6
659+132,2021,2,8.768,38.7763,8
660+132,2022,2,12.5387,38.8208,5
661+132,2023,2,21.3592,26.3546,4
662+133,2019,4,6.1956,83.5927,11
663+133,2020,4,25.0544,18.8985,13
664+133,2021,4,10.7081,48.5196,11
665+133,2022,4,7.3986,119.2048,5
666+133,2023,4,17.7798,42.7937,9
667+134,2019,2,17.2723,25.5307,5
668+134,2020,2,16.4773,31.2628,9
669+134,2021,2,25.6921,13.22,3
670+134,2022,2,7.8996,58.6936,13
671+134,2023,2,12.6476,46.4825,17
672+135,2019,1,14.2582,17.9212,7
673+135,2020,1,20.0285,13.8057,12
674+135,2021,1,8.4348,38.5346,5
675+135,2022,1,15.1191,16.5641,6
676+135,2023,1,22.0362,18.4647,9
677+136,2019,4,20.2973,27.74,8
678+136,2020,4,16.0192,36.3594,5
679+136,2021,4,9.2987,58.9829,8
680+136,2022,4,12.3713,79.9009,7
681+136,2023,4,12.7741,55.3816,7
682+137,2019,2,19.5929,9.7444,7
683+137,2020,2,16.6058,11.096,10
684+137,2021,2,9.6483,17.8112,6
685+137,2022,2,12.2726,21.0378,6
686+137,2023,2,24.1645,8.4471,7
687+138,2019,4,18.0085,33.7972,8
688+138,2020,4,9.8146,53.0689,20
689+138,2021,4,17.3822,45.8399,9
690+138,2022,4,6.9072,91.4842,9
691+138,2023,4,22.2819,32.0537,16
692+139,2019,1,8.589,45.0934,3
693+139,2020,1,22.298,21.2125,9
694+139,2021,1,13.015,39.2106,7
695+139,2022,1,14.8798,27.8796,6
696+139,2023,1,15.1986,34.2766,7
697+140,2019,2,24.2575,25.6412,27
698+140,2020,2,9.6411,106.0409,12
699+140,2021,2,14.0867,44.1822,9
700+140,2022,2,13.1447,67.8952,6
701+140,2023,2,13.8272,55.0177,23
702+141,2019,4,17.157,21.9251,5
703+141,2020,4,6.6111,62.2076,9
704+141,2021,4,17.837,26.8103,11
705+141,2022,4,19.1909,31.6905,10
706+141,2023,4,17.4792,40.21,6
707+142,2019,1,20.3377,16.9369,5
708+142,2020,1,9.0011,38.5469,5
709+142,2021,1,8.0194,49.8737,9
710+142,2022,1,17.8862,20.7945,8
711+142,2023,1,24.4399,8.8737,7
712+143,2019,3,23.3948,29.5583,5
713+143,2020,3,17.2081,37.3813,5
714+143,2021,3,9.9884,77.2443,14
715+143,2022,3,8.1582,88.7393,9
716+143,2023,3,26.0508,23.2629,9
717+144,2019,3,6.0604,78.9316,12
718+144,2020,3,21.5333,19.0197,10
719+144,2021,3,23.6872,24.4969,6
720+144,2022,3,8.2626,88.7392,5
721+144,2023,3,20.4554,41.8587,4
722+145,2019,1,8.3876,33.6834,5
723+145,2020,1,16.8109,20.3516,8
724+145,2021,1,13.4161,35.6274,8
725+145,2022,1,10.76,32.6872,5
726+145,2023,1,16.5824,28.4128,13
727+146,2019,1,20.8477,14.6849,9
728+146,2020,1,9.0121,32.3311,19
729+146,2021,1,9.8401,37.2056,10
730+146,2022,1,11.3592,33.7181,7
731+146,2023,1,21.9407,16.029,11
732+147,2019,1,17.9331,17.6181,9
733+147,2020,1,8.8045,45.4022,9
734+147,2021,1,17.9898,31.1383,13
735+147,2022,1,15.8874,24.5479,14
736+147,2023,1,11.1042,30.7907,8
737+148,2019,1,16.6143,12.6225,6
738+148,2020,1,11.7615,23.2233,8
739+148,2021,1,19.1173,14.0679,5
740+148,2022,1,9.8074,22.7848,12
741+148,2023,1,23.7231,11.7476,9
742+149,2019,4,5.0923,95.047,6
743+149,2020,4,9.1922,81.4536,8
744+149,2021,4,11.3287,67.7191,9
745+149,2022,4,15.1028,41.7178,14
746+149,2023,4,11.8284,97.3957,13
747+150,2019,1,20.6219,17.604,12
748+150,2020,1,14.5565,21.7018,13
749+150,2021,1,10.738,40.3274,8
750+150,2022,1,9.4694,40.8107,7
751+150,2023,1,14.2665,20.8947,6
752+151,2019,1,18.6609,19.5491,7
753+151,2020,1,15.8767,16.8895,13
754+151,2021,1,7.3783,37.6058,17
755+151,2022,1,23.6621,14.9125,12
756+151,2023,1,25.5711,16.2627,10
757+152,2019,4,8.5964,55.2207,19
758+152,2020,4,7.9933,73.9973,8
759+152,2021,4,11.9811,71.2892,5
760+152,2022,4,19.5894,34.9085,3
761+152,2023,4,8.8975,53.7339,7
762+153,2019,3,24.4265,16.3882,6
763+153,2020,3,16.139,28.9225,6
764+153,2021,3,10.6345,47.6038,6
765+153,2022,3,21.3008,19.4694,6
766+153,2023,3,13.7009,44.9428,4
767+154,2019,1,7.9102,38.4471,12
768+154,2020,1,10.3745,33.1936,6
769+154,2021,1,10.8057,32.9823,7
770+154,2022,1,17.0081,36.2498,5
771+154,2023,1,23.1314,18.4619,3
772+155,2019,2,19.1993,19.1254,10
773+155,2020,2,24.5342,20.5041,12
774+155,2021,2,7.0795,93.3208,7
775+155,2022,2,16.6908,37.9063,17
776+155,2023,2,9.6699,62.5666,11
777+156,2019,4,10.4016,32.6535,3
778+156,2020,4,7.0432,54.4686,11
779+156,2021,4,21.3951,17.5711,16
780+156,2022,4,19.5523,23.3129,5
781+156,2023,4,13.1959,37.3068,5
782+157,2019,4,17.8839,24.7735,7
783+157,2020,4,9.1251,44.5961,8
784+157,2021,4,10.5802,40.1963,6
785+157,2022,4,24.7794,24.867,7
786+157,2023,4,9.6823,66.1214,6
787+158,2019,4,5.5177,104.698,12
788+158,2020,4,6.4085,103.4955,11
789+158,2021,4,17.6914,50.2833,10
790+158,2022,4,21.2521,56.945,10
791+158,2023,4,17.6525,49.5179,9
792+159,2019,1,9.5213,24.5363,8
793+159,2020,1,20.7381,15.0378,12
794+159,2021,1,21.0615,17.0428,11
795+159,2022,1,12.7907,29.3194,4
796+159,2023,1,26.1396,10.8546,10
797+160,2019,3,23.9153,15.995,4
798+160,2020,3,23.6174,19.5928,6
799+160,2021,3,25.5465,13.4146,10
800+160,2022,3,10.2829,58.9481,7
801+160,2023,3,16.4219,20.9642,10
802+161,2019,2,22.4507,14.3835,7
803+161,2020,2,23.1817,21.1218,8
804+161,2021,2,10.8238,37.5395,3
805+161,2022,2,13.9791,44.6922,8
806+161,2023,2,18.8689,23.1965,9
807+162,2019,2,12.2144,49.127,14
808+162,2020,2,16.7014,27.353,22
809+162,2021,2,10.8437,46.4488,8
810+162,2022,2,20.3336,27.1677,6
811+162,2023,2,11.4524,38.6064,9
812+163,2019,1,15.0715,18.9326,4
813+163,2020,1,21.8371,12.5543,7
814+163,2021,1,18.0928,21.8919,6
815+163,2022,1,19.3383,13.3424,9
816+163,2023,1,21.3268,19.4454,6
817+164,2019,2,16.752,21.9143,8
818+164,2020,2,5.7381,47.1439,13
819+164,2021,2,21.0879,21.927,7
820+164,2022,2,14.0476,33.3084,6
821+164,2023,2,7.7844,60.9635,10
822+165,2019,3,17.9551,32.1276,4
823+165,2020,3,8.5584,46.6657,7
824+165,2021,3,17.4431,18.754,3
825+165,2022,3,12.1076,36.7165,6
826+165,2023,3,17.6349,27.8064,17
827+166,2019,2,11.0756,26.3579,12
828+166,2020,2,24.5619,7.7738,11
829+166,2021,2,20.9266,12.4788,11
830+166,2022,2,15.1516,20.6202,9
831+166,2023,2,24.8574,11.5498,4
832+167,2019,1,15.7186,19.6586,12
833+167,2020,1,19.7218,16.0048,8
834+167,2021,1,14.8238,37.3891,5
835+167,2022,1,17.8113,21.6009,12
836+167,2023,1,26.4112,9.9517,8
837+168,2019,2,17.2584,30.1185,10
838+168,2020,2,10.737,62.2373,15
839+168,2021,2,10.6572,95.0707,11
840+168,2022,2,23.4696,31.7027,5
841+168,2023,2,23.4173,22.9176,9
842+169,2019,1,22.4486,17.8826,6
843+169,2020,1,23.4324,15.1913,15
844+169,2021,1,10.3887,36.9382,17
845+169,2022,1,11.1062,47.2751,5
846+169,2023,1,24.4417,31.2778,8
847+170,2019,3,18.3178,25.0787,6
848+170,2020,3,19.8455,13.5014,10
849+170,2021,3,13.6373,31.3819,6
850+170,2022,3,23.0627,17.7872,7
851+170,2023,3,8.2349,56.0195,7
852+171,2019,4,5.796,77.2976,4
853+171,2020,4,11.7582,66.3972,6
854+171,2021,4,10.1927,44.6179,6
855+171,2022,4,24.0623,26.8725,5
856+171,2023,4,13.5895,46.7825,7
857+172,2019,2,5.128,44.0484,4
858+172,2020,2,16.894,27.9061,11
859+172,2021,2,17.7169,34.0714,7
860+172,2022,2,15.7061,29.2684,7
861+172,2023,2,26.2504,15.8539,9
862+173,2019,2,13.8204,24.9788,6
863+173,2020,2,10.0712,39.1159,9
864+173,2021,2,20.309,14.482,12
865+173,2022,2,12.0214,37.2277,6
866+173,2023,2,22.992,23.5358,5
867+174,2019,1,6.0816,75.0252,19
868+174,2020,1,24.4441,16.0544,8
869+174,2021,1,8.6726,53.7227,10
870+174,2022,1,20.7392,17.8278,13
871+174,2023,1,24.0258,12.9749,9
872+175,2019,2,7.4523,60.3768,5
873+175,2020,2,14.3777,29.5974,6
874+175,2021,2,7.4999,59.9765,5
875+175,2022,2,13.6036,45.9314,6
876+175,2023,2,24.7781,25.998,17
877+176,2019,2,14.5989,46.2644,7
878+176,2020,2,12.3392,53.3481,7
879+176,2021,2,10.1026,50.5675,9
880+176,2022,2,18.9635,38.095,5
881+176,2023,2,8.163,57.5857,7
882+177,2019,3,18.1088,22.8679,8
883+177,2020,3,13.0025,32.1245,5
884+177,2021,3,20.7506,25.3423,5
885+177,2022,3,17.9399,36.3474,3
886+177,2023,3,13.3431,58.2491,4
887+178,2019,4,18.4073,25.4907,8
888+178,2020,4,12.8278,33.289,6
889+178,2021,4,24.9307,13.6035,8
890+178,2022,4,19.5856,22.2464,2
891+178,2023,4,8.6954,55.7938,5
892+179,2019,4,9.3301,53.5654,10
893+179,2020,4,13.6731,29.1487,5
894+179,2021,4,14.0055,27.2148,12
895+179,2022,4,10.9643,30.0508,6
896+179,2023,4,12.2043,32.9338,7
897+180,2019,4,22.3181,12.0567,6
898+180,2020,4,8.1216,35.3416,7
899+180,2021,4,18.3566,21.3461,7
900+180,2022,4,17.4897,19.2098,4
901+180,2023,4,26.437,12.0462,7
902+181,2019,4,18.4774,25.6966,5
903+181,2020,4,15.7165,32.2318,12
904+181,2021,4,16.5831,37.2739,14
905+181,2022,4,7.5238,59.8429,10
906+181,2023,4,19.2166,31.7211,5
907+182,2019,1,9.9788,81.0787,7
908+182,2020,1,14.6799,39.1507,4
909+182,2021,1,8.5261,53.9023,14
910+182,2022,1,14.8074,40.8958,8
911+182,2023,1,17.272,41.9881,11
912+183,2019,2,6.1625,31.92,5
913+183,2020,2,25.1799,7.9973,4
914+183,2021,2,23.5433,7.3873,4
915+183,2022,2,8.6801,27.6411,6
916+183,2023,2,25.1514,12.2199,5
917+184,2019,1,8.796,37.6609,8
918+184,2020,1,21.53,17.1787,6
919+184,2021,1,17.7635,22.0696,7
920+184,2022,1,20.6473,21.0688,15
921+184,2023,1,14.9429,42.505,8
922+185,2019,4,15.6601,41.4365,9
923+185,2020,4,9.4996,44.7744,9
924+185,2021,4,12.6644,54.2642,9
925+185,2022,4,17.8552,34.203,12
926+185,2023,4,18.1365,39.0706,10
927+186,2019,3,20.015,17.2019,4
928+186,2020,3,11.2017,48.9223,10
929+186,2021,3,25.6174,14.5726,6
930+186,2022,3,21.3823,22.9055,9
931+186,2023,3,20.0942,21.7527,6
932+187,2019,1,18.0913,23.9829,4
933+187,2020,1,15.0189,18.601,5
934+187,2021,1,15.8798,20.7015,5
935+187,2022,1,16.7442,17.5319,6
936+187,2023,1,14.5831,20.6955,6
937+188,2019,2,6.3737,35.904,4
938+188,2020,2,17.7719,21.5719,8
939+188,2021,2,9.5635,42.89,8
940+188,2022,2,21.5068,13.8241,6
941+188,2023,2,26.0504,10.9876,3
942+189,2019,4,23.4298,34.3331,6
943+189,2020,4,10.4034,96.8613,11
944+189,2021,4,7.4963,85.0713,8
945+189,2022,4,20.5408,37.5218,13
946+189,2023,4,12.0881,93.1643,7
947+190,2019,2,14.9988,26.6161,7
948+190,2020,2,24.5276,11.1471,4
949+190,2021,2,11.3591,41.1784,11
950+190,2022,2,14.8978,16.5933,7
951+190,2023,2,14.4625,37.6344,7
952+191,2019,3,23.9142,24.982,5
953+191,2020,3,23.7334,26.8057,11
954+191,2021,3,10.0448,74.927,5
955+191,2022,3,14.5782,47.2089,28
956+191,2023,3,24.2215,26.6729,11
957+192,2019,3,20.9926,13.4189,4
958+192,2020,3,9.3002,29.0714,6
959+192,2021,3,22.3659,18.2463,7
960+192,2022,3,10.0444,32.4654,7
961+192,2023,3,9.3207,58.0666,7
962+193,2019,4,17.3245,53.2245,9
963+193,2020,4,14.3782,51.2605,7
964+193,2021,4,13.6344,61.9347,10
965+193,2022,4,22.3399,46.1584,9
966+193,2023,4,9.2597,91.665,7
967+194,2019,1,8.8861,47.6828,7
968+194,2020,1,8.0868,54.0103,8
969+194,2021,1,8.2545,64.0067,7
970+194,2022,1,18.6194,23.1999,6
971+194,2023,1,21.375,21.9023,12
972+195,2019,1,7.7762,35.3601,8
973+195,2020,1,10.7776,25.1827,9
974+195,2021,1,22.7077,9.7305,9
975+195,2022,1,25.6171,11.1467,12
976+195,2023,1,24.6433,14.7998,10
977+196,2019,4,5.0163,83.1748,5
978+196,2020,4,7.6746,82.6295,5
979+196,2021,4,18.1385,43.524,8
980+196,2022,4,7.0012,118.9301,12
981+196,2023,4,25.0894,19.8567,11
982+197,2019,4,5.2815,91.3089,11
983+197,2020,4,23.4844,17.7966,6
984+197,2021,4,21.4466,25.6135,5
985+197,2022,4,22.0334,14.8712,4
986+197,2023,4,9.3772,74.044,12
987+198,2019,4,23.2035,17.2201,14
988+198,2020,4,22.3043,17.0998,5
989+198,2021,4,10.7436,39.5193,5
990+198,2022,4,14.5335,35.3105,17
991+198,2023,4,10.4838,42.6604,5
992+199,2019,4,6.8068,51.9515,5
993+199,2020,4,18.3557,19.3203,5
994+199,2021,4,9.6095,51.1629,4
995+199,2022,4,9.2797,47.6852,6
996+199,2023,4,16.9762,27.8802,15
997+200,2019,4,20.5422,19.0303,6
998+200,2020,4,24.963,19.9101,4
999+200,2021,4,9.2598,47.0182,9
1000+200,2022,4,19.1063,39.1627,3
1001+200,2023,4,20.913,40.4527,5
modified MetrikaKit/Sources/ZQEngine/Session.swift +27 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
modified MetrikaKit/Sources/ZQParser/KnownVerbs.swift +2 −1
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
modified MetrikaKit/Tests/MetrikaKitTests/EngineTests.swift +17 −1
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
modified Tests/UITests/ConsoleSmokeTests.swift +33 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added docs/index.html +78 −0
Large diff (79 lines) is not loaded by default to keep the page fast.Load diff
modified project.yml +1 −1
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added scripts/make_samples.R +49 −0
Large diff (50 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme