pam-python 0.2.12__tar.gz → 0.2.13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. {pam_python-0.2.12/pam_python.egg-info → pam_python-0.2.13}/PKG-INFO +1 -1
  2. {pam_python-0.2.12 → pam_python-0.2.13}/pam/__init__.py +1 -1
  3. {pam_python-0.2.12 → pam_python-0.2.13}/pam/cli.py +1 -0
  4. pam_python-0.2.13/pam/templates/init/REPORTS-th.md +744 -0
  5. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/REPORTS.md +3 -2
  6. {pam_python-0.2.12 → pam_python-0.2.13/pam_python.egg-info}/PKG-INFO +1 -1
  7. {pam_python-0.2.12 → pam_python-0.2.13}/pam_python.egg-info/SOURCES.txt +1 -0
  8. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_cli_templates.py +8 -0
  9. {pam_python-0.2.12 → pam_python-0.2.13}/LICENSE.txt +0 -0
  10. {pam_python-0.2.12 → pam_python-0.2.13}/README.md +0 -0
  11. {pam_python-0.2.12 → pam_python-0.2.13}/pam/api.py +0 -0
  12. {pam_python-0.2.12 → pam_python-0.2.13}/pam/datetime_utils.py +0 -0
  13. {pam_python-0.2.12 → pam_python-0.2.13}/pam/interface_task_manager.py +0 -0
  14. {pam_python-0.2.12 → pam_python-0.2.13}/pam/logger.py +0 -0
  15. {pam_python-0.2.12 → pam_python-0.2.13}/pam/models/__init__.py +0 -0
  16. {pam_python-0.2.12 → pam_python-0.2.13}/pam/models/data_input_v2.py +0 -0
  17. {pam_python-0.2.12 → pam_python-0.2.13}/pam/models/request_command.py +0 -0
  18. {pam_python-0.2.12 → pam_python-0.2.13}/pam/models/sqlite_download.py +0 -0
  19. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/__init__.py +0 -0
  20. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/area.py +0 -0
  21. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/axis.py +0 -0
  22. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/bar.py +0 -0
  23. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/box_plot.py +0 -0
  24. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/bubble.py +0 -0
  25. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/candlestick.py +0 -0
  26. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/collection.py +0 -0
  27. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/definition.py +0 -0
  28. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/donut.py +0 -0
  29. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/errors.py +0 -0
  30. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/fields.py +0 -0
  31. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/heatmap.py +0 -0
  32. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/line.py +0 -0
  33. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/manager.py +0 -0
  34. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/metric.py +0 -0
  35. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/pie.py +0 -0
  36. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/polar_area.py +0 -0
  37. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/radar.py +0 -0
  38. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/radial_bar.py +0 -0
  39. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/range_area.py +0 -0
  40. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/range_bar.py +0 -0
  41. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/range_transform.py +0 -0
  42. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/record.py +0 -0
  43. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/report.py +0 -0
  44. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/scatter.py +0 -0
  45. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/schema.py +0 -0
  46. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/store.py +0 -0
  47. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/table.py +0 -0
  48. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/temporal.py +0 -0
  49. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/transport.py +0 -0
  50. {pam_python-0.2.12 → pam_python-0.2.13}/pam/reports/treemap.py +0 -0
  51. {pam_python-0.2.12 → pam_python-0.2.13}/pam/request_file_format.py +0 -0
  52. {pam_python-0.2.12 → pam_python-0.2.13}/pam/result_batch_uploader.py +0 -0
  53. {pam_python-0.2.12 → pam_python-0.2.13}/pam/server.py +0 -0
  54. {pam_python-0.2.12 → pam_python-0.2.13}/pam/service.py +0 -0
  55. {pam_python-0.2.12 → pam_python-0.2.13}/pam/sqlite.py +0 -0
  56. {pam_python-0.2.12 → pam_python-0.2.13}/pam/task_manager.py +0 -0
  57. {pam_python-0.2.12 → pam_python-0.2.13}/pam/temp_file_utils.py +0 -0
  58. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/buildcmd/pamb +0 -0
  59. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/buildcmd/pamb-base.sh +0 -0
  60. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/AGENT.md +0 -0
  61. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/dockerignore.tmpl +0 -0
  62. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/gitignore.tmpl +0 -0
  63. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/main.tmpl +0 -0
  64. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/pylintrc.tmpl +0 -0
  65. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/run_unit_test.bat +0 -0
  66. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/run_unit_test.ps1 +0 -0
  67. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/init/run_unit_test.sh +0 -0
  68. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/project/pip/Dockerfile +0 -0
  69. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/project/pip/requirements.txt +0 -0
  70. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/project/uv/Dockerfile +0 -0
  71. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/project/uv/pyproject.toml +0 -0
  72. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/project/uv/python-version +0 -0
  73. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/common/service.yaml +0 -0
  74. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/pandas/functions.tmpl +0 -0
  75. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/pandas/service.test.tmpl +0 -0
  76. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/pandas/service_class.tmpl +0 -0
  77. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/polars/functions.tmpl +0 -0
  78. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/polars/service.test.tmpl +0 -0
  79. {pam_python-0.2.12 → pam_python-0.2.13}/pam/templates/service/polars/service_class.tmpl +0 -0
  80. {pam_python-0.2.12 → pam_python-0.2.13}/pam/tester_task.py +0 -0
  81. {pam_python-0.2.12 → pam_python-0.2.13}/pam/utils.py +0 -0
  82. {pam_python-0.2.12 → pam_python-0.2.13}/pam_python.egg-info/dependency_links.txt +0 -0
  83. {pam_python-0.2.12 → pam_python-0.2.13}/pam_python.egg-info/entry_points.txt +0 -0
  84. {pam_python-0.2.12 → pam_python-0.2.13}/pam_python.egg-info/requires.txt +0 -0
  85. {pam_python-0.2.12 → pam_python-0.2.13}/pam_python.egg-info/top_level.txt +0 -0
  86. {pam_python-0.2.12 → pam_python-0.2.13}/pyproject.toml +0 -0
  87. {pam_python-0.2.12 → pam_python-0.2.13}/setup.cfg +0 -0
  88. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_api.py +0 -0
  89. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_data_error_v2.py +0 -0
  90. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_data_input_v2.py +0 -0
  91. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_data_timeout_v2.py +0 -0
  92. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_datetime_utils.py +0 -0
  93. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_range_transform.py +0 -0
  94. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_report_store.py +0 -0
  95. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_request_data_v2.py +0 -0
  96. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_result_batch_uploader.py +0 -0
  97. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_service_dataframe_output.py +0 -0
  98. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_sqlite_download_v2.py +0 -0
  99. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_temp_file_utils.py +0 -0
  100. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_temporal_mode.py +0 -0
  101. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_area_report.py +0 -0
  102. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_bar_report.py +0 -0
  103. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_box_plot_report.py +0 -0
  104. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_bubble_report.py +0 -0
  105. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_candlestick_report.py +0 -0
  106. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_donut_report.py +0 -0
  107. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_heatmap_report.py +0 -0
  108. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_line_report.py +0 -0
  109. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_metric_report.py +0 -0
  110. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_pie_report.py +0 -0
  111. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_polar_area_report.py +0 -0
  112. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_radar_report.py +0 -0
  113. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_radial_bar_report.py +0 -0
  114. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_range_area_report.py +0 -0
  115. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_range_bar_report.py +0 -0
  116. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_scatter_report.py +0 -0
  117. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_table_report.py +0 -0
  118. {pam_python-0.2.12 → pam_python-0.2.13}/tests/test_typed_treemap_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.2.12
3
+ Version: 0.2.13
4
4
  Summary: Pam Python Library
5
5
  Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
6
6
  Project-URL: Homepage, https://github.com/heart/pam-python
@@ -1,3 +1,3 @@
1
1
  """PAM Data Plugin framework."""
2
2
 
3
- __version__ = "0.2.12"
3
+ __version__ = "0.2.13"
@@ -21,6 +21,7 @@ COMMON_PROJECT_TEMPLATES = {
21
21
  "buildcmd/pamb-base.sh": "pamb-base.sh",
22
22
  "init/pylintrc.tmpl": ".pylintrc",
23
23
  "init/REPORTS.md": "REPORTS.md",
24
+ "init/REPORTS-th.md": "REPORTS-th.md",
24
25
  "init/AGENT.md": "AGENT.md",
25
26
  "init/gitignore.tmpl": ".gitignore",
26
27
  "init/dockerignore.tmpl": ".dockerignore",
@@ -0,0 +1,744 @@
1
+ # Managed Reports สำหรับนักพัฒนา Data Plugin
2
+
3
+ คู่มือนี้อธิบายวิธีสร้าง แก้ไข และเผยแพร่ Managed Report ด้วย public Python API
4
+ ของ PAM นักพัฒนาไม่ต้องสร้างตาราง SQLite, renderer payload หรือ report JSON เอง
5
+ ชื่อ class, method, parameter และค่าคงที่ในตัวอย่างคงเป็นภาษาอังกฤษตาม API จริง
6
+
7
+ Framework รองรับ Report ต่อไปนี้
8
+
9
+ | กลุ่ม | Report | Public builder |
10
+ |---|---|---|
11
+ | PAM native | Table | `store.table(...)` |
12
+ | PAM native | Metric | `store.metric(...)` |
13
+ | Apex axis | Line | `store.line_chart(...)` |
14
+ | Apex axis | Area | `store.area_chart(...)` |
15
+ | Apex axis | Bar/Column | `store.bar_chart(...)` |
16
+ | Apex non-axis | Pie | `store.pie_chart(...)` |
17
+ | Apex non-axis | Donut | `store.donut_chart(...)` |
18
+ | Apex non-axis | Radial Bar | `store.radial_bar_chart(...)` |
19
+ | Apex axis | Scatter | `store.scatter_chart(...)` |
20
+ | Apex axis | Bubble | `store.bubble_chart(...)` |
21
+ | Apex matrix | Heatmap | `store.heatmap(...)` |
22
+ | Apex financial | Candlestick | `store.candlestick_chart(...)` |
23
+ | Apex statistical | Box Plot | `store.box_plot(...)` |
24
+ | Apex non-axis | Radar | `store.radar_chart(...)` |
25
+ | Apex non-axis | Polar Area | `store.polar_area_chart(...)` |
26
+ | Apex range | Range Bar | `store.range_bar_chart(...)` |
27
+ | Apex range | Range Area | `store.range_area_chart(...)` |
28
+ | Apex hierarchy | Treemap | `store.treemap(...)` |
29
+
30
+ ไม่มี raw report builder หรือช่องทางสร้าง custom report ด้วย JSON หากลูกค้าต้องการ
31
+ Report ชนิดใหม่ ต้องใช้ Framework รุ่นที่มี typed builder สำหรับ Report นั้น
32
+
33
+ ---
34
+
35
+ # 1. หลักการสำคัญ
36
+
37
+ ## นักพัฒนาเลือกชนิด Report อย่างชัดเจน
38
+
39
+ Framework ไม่เดาชนิด Report จากข้อมูล นักพัฒนาต้องเลือกตาม requirement ที่ตกลง
40
+ กับลูกค้า โดยเรียก builder ที่ตรงกับความหมายของข้อมูล
41
+
42
+ ```python
43
+ store.table(...)
44
+ store.metric(...)
45
+ store.line_chart(...)
46
+ store.area_chart(...)
47
+ store.bar_chart(...)
48
+ store.pie_chart(...)
49
+ store.donut_chart(...)
50
+ store.radial_bar_chart(...)
51
+ store.scatter_chart(...)
52
+ store.bubble_chart(...)
53
+ store.heatmap(...)
54
+ store.candlestick_chart(...)
55
+ store.box_plot(...)
56
+ store.radar_chart(...)
57
+ store.polar_area_chart(...)
58
+ store.range_bar_chart(...)
59
+ store.range_area_chart(...)
60
+ store.treemap(...)
61
+ ```
62
+
63
+ ## นักพัฒนาไม่ระบุ renderer หรือ schema identifier
64
+
65
+ ห้ามกำหนดค่าทางเทคนิคเอง เช่น
66
+
67
+ ```python
68
+ renderer="table"
69
+ schema="customer/report-a@1"
70
+ ```
71
+
72
+ Typed builder เป็นเจ้าของ renderer และ contract version รายละเอียดเหล่านี้เป็น
73
+ สัญญาภายในระหว่าง Framework กับ CMS
74
+
75
+ ## ประกาศ Field เพียงครั้งเดียว
76
+
77
+ ชื่อแสดงผล ชนิดข้อมูล หน่วย และ nullability อยู่ใน Report definition ไม่ต้องส่งซ้ำ
78
+ ในทุก record
79
+
80
+ ```python
81
+ Measure(
82
+ "amount",
83
+ label="ยอดขาย",
84
+ value_type="number",
85
+ unit="THB",
86
+ )
87
+
88
+ {"amount": 1250.50}
89
+ ```
90
+
91
+ Typed writer จะแปลง declaration และข้อมูลเป็น managed definition, `dimensions`
92
+ และ `measures` นักพัฒนาห้ามสร้าง `definition_json`, `dimensions_json`,
93
+ `measures_json`, ตาราง SQLite หรือ renderer payload เอง
94
+
95
+ ---
96
+
97
+ # 2. พฤติกรรม Chart ที่รองรับ
98
+
99
+ - Column คือ Bar ที่ใช้ `orientation="vertical"`
100
+ - Horizontal Bar คือ Bar ที่ใช้ `orientation="horizontal"`
101
+ - Funnel ในรุ่นนี้เป็นตัวเลือกการแสดงผลของ Bar ไม่ใช่ top-level report type
102
+ - Mixed Chart ต้องมี public contract แยกก่อน Framework จึงจะรองรับ
103
+ - Straight, Smooth และ Stepline เป็นตัวเลือกการแสดงผลของ Line
104
+ - Pie และ Donut เป็นคนละ Apex type แต่ใช้โครงสร้าง slice แบบเดียวกัน
105
+
106
+ ห้ามประกอบ ApexCharts type อื่นผ่าน raw JSON
107
+
108
+ ---
109
+
110
+ # 3. วงจรชีวิตของ Report Store
111
+
112
+ สร้าง Store ใหม่เฉพาะเมื่อผู้ใช้สั่ง fresh restart อย่างชัดเจน กรณีทั่วไปให้เปิด
113
+ Store เดิม หาก remote object ยังไม่มี Framework จะสร้าง Store แรกให้อัตโนมัติ
114
+
115
+ ```python
116
+ fresh = self.request.get_runtime_bool("fresh", default=False)
117
+
118
+ if fresh:
119
+ store = self.reports.create(
120
+ "reports.sqlite",
121
+ tracker_name="Customer Report Group",
122
+ )
123
+ else:
124
+ store = self.reports.open(
125
+ "reports.sqlite",
126
+ tracker_name="Customer Report Group",
127
+ )
128
+ ```
129
+
130
+ `tracker_name` คือชื่อ Analytics Tracker group ใน PAM CMS ต้องใช้ชื่อเดิมสำหรับ
131
+ ทุก Report ใน Store หากไม่ระบุ Framework จะใช้ชื่อ Service
132
+
133
+ ประกาศ Report หลังเปิด Store เพียงครั้งเดียว สะสมข้อมูลจาก input page ได้หลายหน้า
134
+ และเรียก `publish()` ครั้งเดียวหลังคำนวณข้อมูลที่ต้องการครบและสำเร็จ
135
+
136
+ ```python
137
+ def on_start(self):
138
+ self.report_store = self.reports.open(
139
+ "reports.sqlite",
140
+ tracker_name="Customer Report Group",
141
+ )
142
+ self._declare_reports()
143
+ self._request_data_v2(file_format=RequestFileFormat.PARQUET)
144
+
145
+
146
+ def on_data_input(self, req):
147
+ self._append_page(req.data_input_v2)
148
+ if not req.data_input_v2.run_complete:
149
+ self._request_data_v2(
150
+ page=req.data_input_v2.next,
151
+ file_format=RequestFileFormat.PARQUET,
152
+ )
153
+ return
154
+
155
+ self.report_store.publish()
156
+ self._exit()
157
+ ```
158
+
159
+ การ publish จะ upload SQLite หนึ่งครั้ง และเผยแพร่ pointer ของ Report key ที่มี
160
+ การเปลี่ยนแปลงในรอบนั้น Remote read เป็น eventually consistent; Plugin ไม่ต้องรอ
161
+ หรือ poll จนผู้ใช้อ่าน Report ได้
162
+
163
+ Task lifecycle จะปิด manager และลบ local working files ให้อัตโนมัติ หากต้องการ
164
+ ทิ้ง Store ที่ยังเปิดก่อนเวลา ให้ใช้
165
+
166
+ ```python
167
+ self.reports.discard_local("reports.sqlite")
168
+ ```
169
+
170
+ คำสั่งนี้ไม่ลบ remote object และจะทิ้ง local change ที่ยังไม่ได้ publish
171
+
172
+ ---
173
+
174
+ # 4. สัญญาร่วมของ Field
175
+
176
+ Import ที่ใช้บ่อย:
177
+
178
+ ```python
179
+ from pam.reports import (
180
+ Dimension,
181
+ Measure,
182
+ Sum,
183
+ TemporalMode,
184
+ TimeRangeTransform,
185
+ )
186
+ ```
187
+
188
+ ## Dimension
189
+
190
+ Dimension ใช้ระบุ จัดกลุ่ม เรียง หรือแสดงชื่อของ data point
191
+
192
+ ```python
193
+ Dimension("branch", label="สาขา", value_type="string")
194
+ ```
195
+
196
+ ## Measure
197
+
198
+ Measure คือค่าที่คำนวณแล้วและนำไปแสดงหรือ plot
199
+
200
+ ```python
201
+ Measure("amount", label="ยอดขาย", value_type="number", unit="THB")
202
+ ```
203
+
204
+ Field ทุกตัวต้องมี:
205
+
206
+ | Property | ความหมาย |
207
+ |---|---|
208
+ | `name` | key ที่คงที่และอ่านโดยระบบ แนะนำ `snake_case` |
209
+ | `label` | ชื่อที่ CMS แสดงต่อผู้ใช้ |
210
+ | `value_type` | ชนิดข้อมูลสำหรับ validation และ formatting |
211
+
212
+ Property เสริม:
213
+
214
+ | Property | Default | ความหมาย |
215
+ |---|---:|---|
216
+ | `required` | `True` | ทุก row/point ต้องมี field นี้หรือไม่ |
217
+ | `nullable` | `False` | ยอมให้ส่ง `None` อย่างชัดเจนหรือไม่ |
218
+ | `unit` | `None` | หน่วย เช่น `THB`, `people`, `orders`, `%` |
219
+ | `description` | `None` | คำอธิบายสำหรับนักพัฒนา |
220
+
221
+ ชนิดข้อมูลเริ่มต้น:
222
+
223
+ - `string`
224
+ - `integer`
225
+ - `number`
226
+ - `boolean`
227
+ - `date` — serialize เป็น `YYYY-MM-DD`
228
+ - `datetime` — เก็บเป็น epoch milliseconds; string input ภายใน PAM ต้องใช้
229
+ UTC format `YYYY-MM-DD HH:mm:ss`
230
+
231
+ ยังไม่รองรับ object และ array เป็นชนิด cell/point หากต้องใช้ข้อมูลซับซ้อนต้องเพิ่ม
232
+ Framework และ CMS contract อย่างชัดเจนก่อน
233
+
234
+ `label="สาขา"` อธิบาย field แต่ไม่ได้แปลค่า `BKK` เป็น `กรุงเทพฯ` หากต้องการทั้ง
235
+ code และชื่อแสดงผล ให้ประกาศคนละ field
236
+
237
+ ```python
238
+ Dimension("branch_code", label="รหัสสาขา", value_type="string")
239
+ Dimension("branch_name", label="สาขา", value_type="string")
240
+ ```
241
+
242
+ ---
243
+
244
+ # 5. Range, Partition และความหมายของข้อมูลข้ามเวลา
245
+
246
+ หนึ่ง Report มี granularity คงที่เพียงค่าเดียว:
247
+
248
+ - `DAY`
249
+ - `MONTH`
250
+ - `NONE`
251
+
252
+ ใช้ `NONE` เมื่อ Report ไม่มี date range ให้ผู้ใช้เลือก `DAY` และ `MONTH` เก็บข้อมูล
253
+ แยกตาม partition โดย partition key ต้องมาจาก business date ในข้อมูล ไม่ใช่เวลา
254
+ ที่ Plugin เริ่มทำงาน
255
+
256
+ - `DAY` ใช้ `YYYY-MM-DD`
257
+ - `MONTH` ใช้ `YYYY-MM`
258
+
259
+ การคำนวณ partition เดิมซ้ำต้องแทนที่ข้อมูลทั้ง partition:
260
+
261
+ ```python
262
+ table.delete_partition("2026-08")
263
+ table.append_rows(rows_from_page, partition_key="2026-08")
264
+ table.append_rows(more_rows, partition_key="2026-08")
265
+
266
+ # Publish หลังคำนวณครบและสำเร็จเท่านั้น
267
+ store.publish()
268
+ ```
269
+
270
+ ## เลือกความหมายให้ถูกก่อนกำหนด Temporal Mode
271
+
272
+ อย่าเลือกจากรูปทรง Chart เพราะ Pie, Bar หรือ Table อาจเก็บข้อมูลคนละความหมายได้
273
+ ให้พิจารณาว่าหนึ่ง partition หมายถึงอะไรในทางธุรกิจ
274
+
275
+ | ความหมายของหนึ่ง partition | Declaration | สิ่งที่ผู้ใช้ CMS เลือกได้ |
276
+ |---|---|---|
277
+ | ค่าของช่วงเวลานั้นซึ่งรวม เฉลี่ย หาค่าต่ำสุด/สูงสุดได้อย่างมีเหตุผล | `temporal_mode=TemporalMode.AGGREGATE` | Trend, Total, Average, Minimum, Maximum |
278
+ | สถานะทั้งชุด ณ เวลานั้น | `temporal_mode=TemporalMode.SNAPSHOT` | หนึ่งสถานะใน Chart เดิม หรือหลายสถานะเป็นแนวโน้ม |
279
+ | Business บังคับ bucket และ reducer ตายตัว | `range_transform=TimeRangeTransform(...)` | เลือกได้เฉพาะ bucket ที่ developer อนุญาต |
280
+ | ไม่ต้องตีความข้าม partition | ไม่ระบุทั้งคู่ | คงพฤติกรรมเดิมของ Report |
281
+
282
+ ### `AGGREGATE`: ค่าประจำช่วงเวลา
283
+
284
+ ใช้เมื่อแต่ละ partition เป็นค่าที่เกิดขึ้นในช่วงนั้นและการคำนวณทุกแบบยังมีเหตุผล
285
+ เช่น ยอดขายรายวัน จำนวนคำสั่งซื้อรายเดือน หรือค่าใช้จ่ายแคมเปญรายวัน
286
+
287
+ ```python
288
+ sales = store.line_chart(
289
+ key="daily-sales",
290
+ granularity="DAY",
291
+ values=[Measure("sales_amount", label="ยอดขาย", value_type="number", unit="THB")],
292
+ temporal_mode=TemporalMode.AGGREGATE,
293
+ )
294
+ ```
295
+
296
+ Plugin ไม่เลือก `TREND`, `SUM`, `MEAN`, `MIN` หรือ `MAX` นักการตลาดเลือกใน CMS
297
+ หนึ่งตัวเลือกจะใช้กับทุก Measure ใน Report และค่าเริ่มต้นคือ `TREND`
298
+
299
+ อย่าใช้ `AGGREGATE` เพียงเพราะ Measure เป็นตัวเลข เช่น จำนวนสมาชิกปัจจุบันในแต่ละ
300
+ วันเป็นประชากรชุดเดิม การบวกหลายวันจะนับคนเดิมซ้ำ
301
+
302
+ ### `SNAPSHOT`: สถานะทั้งชุด
303
+
304
+ ใช้เมื่อแต่ละ partition คือสถานะสมบูรณ์ ณ วันหรือเดือนนั้น เช่น สมาชิกปัจจุบันแยก
305
+ VIP/LOYAL/LOST, สินค้าคงคลังรายคลัง หรือการกระจาย tier ลูกค้า
306
+
307
+ ```python
308
+ members = store.pie_chart(
309
+ key="member-status",
310
+ granularity="DAY",
311
+ category=Dimension("status", label="สถานะ", value_type="string"),
312
+ value=Measure("members", label="สมาชิก", value_type="integer"),
313
+ temporal_mode=TemporalMode.SNAPSHOT,
314
+ )
315
+ ```
316
+
317
+ หนึ่ง partition แสดงด้วย Chart ที่ประกาศไว้ หลาย partition เรียงตามเวลาและ CMS
318
+ เลือก presentation ตาม temporal policy เช่น Pie หลายวันเปลี่ยนเป็น Stacked Area
319
+ category ที่หายจาก snapshot หนึ่งจะถูกแสดงเป็นศูนย์ หาก zero กับ missing มีความหมาย
320
+ ต่างกัน Plugin ต้องเขียน category ที่ตั้งใจให้เป็นศูนย์ให้ครบ
321
+
322
+ Snapshot หลายจุดแสดงว่าจำนวนรวมเปลี่ยน แต่พิสูจน์ transition รายบุคคลไม่ได้ เช่น
323
+ ไม่สามารถสรุปว่าใครย้ายจาก LOYAL ไป VIP ได้ Sankey ต้องใช้ source-to-target facts
324
+ ที่ประกาศอย่างชัดเจน
325
+
326
+ Temporal Mode รองรับ Table, Metric, Line, Area, Bar/Column, Pie, Donut,
327
+ Polar Area และ Radial Bar โดยมีกฎดังนี้
328
+
329
+ - Granularity ต้องเป็น `DAY` หรือ `MONTH`
330
+ - ต้องมี numeric Measure อย่างน้อยหนึ่งตัว
331
+ - ต้องใช้ `TemporalMode` enum ห้ามส่ง raw string เช่น `"SNAPSHOT"`
332
+ - `temporal_mode` และ `range_transform` ใช้พร้อมกันไม่ได้
333
+ - หนึ่ง Mode ใช้กับทั้ง Report ห้ามรวม Aggregate และ Snapshot Measure ใน Report เดียว
334
+ - partition เดิมต้องถูกลบและเขียนสถานะใหม่ทั้งชุดก่อน publish
335
+
336
+ ### Fixed `range_transform`
337
+
338
+ ใช้เมื่อ requirement บังคับ reducer และ allowed bucket ตายตัว เช่น KPI ต้องรวมด้วย
339
+ SUM เสมอ หรือ compliance report ต้องเลือกค่ารายวันล่าสุดเสมอ หากต้องการให้นักการตลาด
340
+ สลับ Trend/Total/Average/Minimum/Maximum ให้ใช้ `TemporalMode.AGGREGATE`
341
+
342
+ ```python
343
+ line = store.line_chart(
344
+ key="sales-trend",
345
+ granularity="DAY",
346
+ values=[Measure("sales_amount", label="ยอดขาย", value_type="number")],
347
+ range_transform=TimeRangeTransform(
348
+ default_bucket="MONTH",
349
+ allowed_buckets=["DAY", "WEEK", "MONTH", "YEAR"],
350
+ reducers={"sales_amount": Sum()},
351
+ ),
352
+ )
353
+ ```
354
+
355
+ Source granularity และ bucket ที่อนุญาต:
356
+
357
+ | Source | Allowed buckets |
358
+ |---|---|
359
+ | `DAY` | `DAY`, `WEEK`, `MONTH`, `YEAR`, `ALL` |
360
+ | `MONTH` | `MONTH`, `YEAR`, `ALL` |
361
+ | `NONE` | ไม่รองรับ range transform |
362
+
363
+ Output key:
364
+
365
+ - `DAY`: `YYYY-MM-DD`
366
+ - `WEEK`: ISO week `YYYY-Www` (จันทร์–อาทิตย์)
367
+ - `MONTH`: `YYYY-MM`
368
+ - `YEAR`: `YYYY`
369
+ - `ALL`: literal `ALL` ซึ่งหมายถึง range ที่ผู้ใช้เลือก ไม่ใช่ทุกวันใน Store
370
+
371
+ Reducer ที่รองรับ:
372
+
373
+ | Reducer | ความหมาย |
374
+ |---|---|
375
+ | `SUM` | บวก numeric value ที่มีอยู่ทั้งหมดใน bucket |
376
+ | `MEAN` | ค่าเฉลี่ยเลขคณิต โดยทุก source value มีน้ำหนักเท่ากัน |
377
+ | `MIN` | ค่าต่ำสุด |
378
+ | `MAX` | ค่าสูงสุด |
379
+ | `LAST` | ค่าจาก source partition ล่าสุดตาม canonical partition order |
380
+
381
+ `MEAN` ไม่ใช่ weighted average หากต้องการ `WEIGHTED_MEAN` หรือ `RATIO_OF_SUMS`
382
+ ต้องเพิ่ม contract ที่ระบุความสัมพันธ์ของ Measure ก่อน
383
+
384
+ เมื่อไม่ระบุทั้ง `range_transform` และ `temporal_mode` จะคงพฤติกรรมเดิม: Table
385
+ คืน record ที่เลือก, Metric คืน value group ต่อ partition, Line/Area เรียงตาม
386
+ partition และ `NONE` แสดง current snapshot โดยตรง
387
+
388
+ ---
389
+
390
+ # 6. Table Report
391
+
392
+ เหมาะกับรายละเอียด Ranking, audit output หรือข้อมูลหลาย column
393
+
394
+ ```python
395
+ table = store.table(
396
+ key="member-summary",
397
+ granularity="MONTH",
398
+ columns=[
399
+ Dimension("branch", label="สาขา", value_type="string"),
400
+ Measure("members", label="สมาชิก", value_type="integer", unit="คน"),
401
+ ],
402
+ )
403
+
404
+ table.append_rows(
405
+ [{"branch": "กรุงเทพฯ", "members": 1250}],
406
+ partition_key="2026-08",
407
+ )
408
+ ```
409
+
410
+ กฎ:
411
+
412
+ - ไม่รับ column ที่ไม่ได้ประกาศ
413
+ - Required column ต้องมีครบและค่าต้องตรงชนิด
414
+ - Row ใน Report เดียวกันห้ามมี generated identity ซ้ำ
415
+ - CMS ใช้ลำดับ column จาก definition ไม่ใช่ row แรก
416
+ - Partition เป็น metadata สำหรับ range/order ไม่ถูกเพิ่มเป็น Day/Month column พิเศษ
417
+ - `remove_rows` รับเฉพาะ Dimension identity; Measure ไม่ใช่ identity
418
+
419
+ ---
420
+
421
+ # 7. Metric Report
422
+
423
+ เหมาะกับ KPI card หรือ headline number ที่ไม่มี Dimension
424
+
425
+ ```python
426
+ metric = store.metric(
427
+ key="monthly-kpis",
428
+ granularity="MONTH",
429
+ metrics=[
430
+ Measure("revenue", label="รายได้", value_type="number", unit="THB"),
431
+ Measure("orders", label="คำสั่งซื้อ", value_type="integer"),
432
+ ],
433
+ )
434
+
435
+ metric.delete_partition("2026-08")
436
+ metric.set_values(
437
+ {"revenue": 125000.0, "orders": 320},
438
+ partition_key="2026-08",
439
+ )
440
+ ```
441
+
442
+ ต้องมี Measure อย่างน้อยหนึ่งตัว ไม่รองรับ Dimension และหนึ่ง partition มี value set
443
+ ได้ชุดเดียว `set_values` ซ้ำใน DAY/MONTH ต้องลบ partition เดิมก่อน ส่วน `NONE`
444
+ จะ replace current value atomically
445
+
446
+ ---
447
+
448
+ # 8. Line Chart Report
449
+
450
+ ใช้ partition เป็นแกนเวลา นักพัฒนาไม่ต้องประกาศ x ซ้ำ
451
+
452
+ ```python
453
+ line = store.line_chart(
454
+ key="daily-sales",
455
+ granularity="DAY",
456
+ values=[Measure("amount", label="ยอดขาย", value_type="number")],
457
+ series=Dimension("branch", label="สาขา", value_type="string"),
458
+ )
459
+
460
+ line.append_points(
461
+ [{"branch": "กรุงเทพฯ", "amount": 1250}],
462
+ partition_key="2026-08-24",
463
+ )
464
+ ```
465
+
466
+ ต้องมี numeric Measure อย่างน้อยหนึ่งตัว `series` ต้องเป็น Dimension และ identity
467
+ คือ partition + optional series CMS เรียงตาม canonical partition ไม่ใช่ insertion order
468
+
469
+ ---
470
+
471
+ # 9. Area Chart Report
472
+
473
+ ใช้ contract ของ Line และเพิ่ม `stacked` ซึ่งเป็น presentation metadata ไม่ใช่กฎ
474
+ การคำนวณ business value
475
+
476
+ ```python
477
+ area = store.area_chart(
478
+ key="member-trend",
479
+ granularity="DAY",
480
+ values=[Measure("members", label="สมาชิก", value_type="integer")],
481
+ series=Dimension("status", label="สถานะ", value_type="string"),
482
+ stacked=True,
483
+ )
484
+ ```
485
+
486
+ ---
487
+
488
+ # 10. Bar และ Column Chart Report
489
+
490
+ ```python
491
+ bars = store.bar_chart(
492
+ key="sales-by-channel",
493
+ granularity="MONTH",
494
+ category=Dimension("channel", label="ช่องทาง", value_type="string"),
495
+ values=[Measure("amount", label="ยอดขาย", value_type="number")],
496
+ series=Dimension("branch", label="สาขา", value_type="string"),
497
+ orientation="vertical",
498
+ stacked=False,
499
+ )
500
+ ```
501
+
502
+ ต้องมี category Dimension หนึ่งตัวและ numeric Measure อย่างน้อยหนึ่งตัว `series`
503
+ ต้องเป็น Dimension, `orientation` เป็น `vertical` หรือ `horizontal` และห้ามมี
504
+ category/series identity ซ้ำ Partition เป็น metadata ไม่ถูกเติมหน้าชื่อ category
505
+
506
+ ---
507
+
508
+ # 11. Pie และ Donut Chart Report
509
+
510
+ ใช้แสดงส่วนประกอบของ whole หนึ่งชุด ไม่เหมาะกับค่าติดลบ แนวโน้ม หรือ category มาก
511
+
512
+ ```python
513
+ pie = store.pie_chart(
514
+ key="members-by-status",
515
+ granularity="DAY",
516
+ category=Dimension("status", label="สถานะ", value_type="string"),
517
+ value=Measure("members", label="สมาชิก", value_type="integer"),
518
+ temporal_mode=TemporalMode.SNAPSHOT,
519
+ )
520
+
521
+ pie.append_slices(
522
+ [{"status": "VIP", "members": 100}],
523
+ partition_key="2026-08-24",
524
+ )
525
+ ```
526
+
527
+ ต้องมี category หนึ่งตัวและ numeric Measure หนึ่งตัว ค่า finite และไม่ติดลบ ห้ามมี
528
+ category ซ้ำ Framework ไม่รวม category เล็กเป็น `Other` เอง Donut ใช้กฎข้อมูล
529
+ เดียวกับ Pie แต่ยังเป็นคนละ public type
530
+
531
+ ---
532
+
533
+ # 12. Radial Bar Chart Report
534
+
535
+ ใช้กับ progress, completion หรือ score ที่มีขอบเขต
536
+
537
+ ```python
538
+ radial = store.radial_bar_chart(
539
+ key="completion",
540
+ granularity="MONTH",
541
+ metrics=[Measure("score", label="ความสำเร็จ", value_type="number")],
542
+ min_value=0,
543
+ max_value=100,
544
+ )
545
+ ```
546
+
547
+ ต้องมี numeric Measure อย่างน้อยหนึ่งตัว ทุกค่าต้องอยู่ระหว่าง `min_value` และ
548
+ `max_value` และหนึ่ง partition มี value set เดียว Framework เก็บ business value จริง;
549
+ CMS เป็นผู้ normalize เป็นช่วง 0–100 ของ Apex
550
+
551
+ ---
552
+
553
+ # 13. Scatter Chart Report
554
+
555
+ ```python
556
+ scatter = store.scatter_chart(
557
+ key="price-volume",
558
+ granularity="DAY",
559
+ x=Dimension("price", label="ราคา", value_type="number"),
560
+ y=Measure("volume", label="ปริมาณ", value_type="number"),
561
+ series=Dimension("product", label="สินค้า", value_type="string"),
562
+ )
563
+ ```
564
+
565
+ x และ y ต้องเป็น finite number, series เป็น Dimension Partition ใช้ filter source
566
+ facts แต่ไม่ใช่แกน Chart CMS รวม point จาก partition ที่เลือกไว้ใน Scatter เดียว
567
+ และไม่รองรับ `range_transform`
568
+
569
+ ---
570
+
571
+ # 14. Bubble Chart Report
572
+
573
+ ใช้กฎ x, y และ series แบบ Scatter และเพิ่ม numeric `size` ที่ finite และไม่ติดลบ
574
+ `size` ไม่ใช่ identity และ selected partitions จะรวม raw bubbles ใน Chart เดียว
575
+
576
+ ```python
577
+ bubble = store.bubble_chart(
578
+ key="market-bubbles",
579
+ granularity="DAY",
580
+ x=Dimension("x", label="X", value_type="number"),
581
+ y=Measure("y", label="Y", value_type="number"),
582
+ size=Measure("size", label="ขนาด", value_type="number"),
583
+ )
584
+ ```
585
+
586
+ ---
587
+
588
+ # 15. Heatmap Report
589
+
590
+ ต้องมี Dimension สองตัวและ numeric Measure หนึ่งตัว x/y coordinate ต้องไม่ซ้ำ
591
+ cell ที่ไม่มีข้อมูลจะยังคง missing ไม่เติมศูนย์ และแต่ละ partition แสดงเป็น grid
592
+ แยกกัน จึงไม่รองรับ `range_transform`
593
+
594
+ ```python
595
+ heatmap = store.heatmap(
596
+ key="activity",
597
+ granularity="DAY",
598
+ x=Dimension("hour", label="ชั่วโมง", value_type="integer"),
599
+ y=Dimension("weekday", label="วัน", value_type="string"),
600
+ value=Measure("events", label="เหตุการณ์", value_type="integer"),
601
+ )
602
+ ```
603
+
604
+ ---
605
+
606
+ # 16. Candlestick Chart Report
607
+
608
+ x ต้องเป็น ordered date/datetime Dimension และมี finite numeric Measure สี่ตัว:
609
+ open, high, low, close โดยต้องเป็นไปตาม `low <= open <= high` และ
610
+ `low <= close <= high` ห้าม x ซ้ำ Selected partitions รวมเป็น Candlestick series
611
+ ที่เรียงแล้ว และ Framework ไม่เดา price reducer
612
+
613
+ ---
614
+
615
+ # 17. Box Plot Report
616
+
617
+ ต้องมี category Dimension หนึ่งตัวและ numeric Measure ห้าตัว: minimum, q1, median,
618
+ q3, maximum โดยต้องเป็น `minimum <= q1 <= median <= q3 <= maximum` ห้าม category
619
+ ซ้ำ และแต่ละ partition แสดง Box Plot แยกกัน ไม่ merge distribution ข้าม partition
620
+
621
+ ---
622
+
623
+ # 18. Radar Chart Report
624
+
625
+ ต้องมี axis Dimension หนึ่งตัวและ numeric Measure หนึ่งตัว optional series จะแยก
626
+ polygon แต่ละ axis/series ต้องไม่ซ้ำ `axis_order` ใช้กำหนดลำดับและปฏิเสธ axis
627
+ นอก declaration แต่ละ partition แสดง Radar หนึ่งชุดและไม่รองรับ `range_transform`
628
+
629
+ ---
630
+
631
+ # 19. Polar Area Chart Report
632
+
633
+ ต้องมี category Dimension หนึ่งตัวและ numeric Measure ที่ไม่ติดลบหนึ่งตัว ห้าม
634
+ category ซ้ำ `NONE` คือ current snapshot หากใช้ fixed range transform สามารถใช้
635
+ `ALL` ได้เมื่อ developer ประกาศ reducer สำหรับ Measure นั้น
636
+
637
+ ---
638
+
639
+ # 20. Range Bar Chart Report
640
+
641
+ ต้องมี category, start และ end โดย start/end ต้องเป็นชนิด numeric หรือ datetime
642
+ ชนิดเดียวกัน และ `end >= start` identity ของ category/series/range ห้ามซ้ำ
643
+
644
+ ---
645
+
646
+ # 21. Range Area Chart Report
647
+
648
+ ต้องมี ordered x Dimension และ numeric Measure สองตัวคือ lower/upper โดย
649
+ `upper >= lower` แต่ละ x/series ต้องไม่ซ้ำ และ CMS เรียงตาม normalized x
650
+
651
+ ---
652
+
653
+ # 22. Treemap Report
654
+
655
+ ต้องมี label Dimension และ factual numeric `value` หนึ่งตัว optional `size` ต้อง
656
+ เป็น numeric Measure ที่ไม่ติดลบ หากไม่ระบุ `size` ค่า `value` จะควบคุมทั้ง geometry
657
+ และค่าที่แสดง optional `group` ต้องเป็น Dimension และ label/group identity ห้ามซ้ำ
658
+
659
+ ```python
660
+ tree = store.treemap(
661
+ key="sales-by-brand",
662
+ granularity="DAY",
663
+ label=Dimension("brand", label="แบรนด์", value_type="string"),
664
+ value=Measure("sales", label="ยอดขาย", value_type="number"),
665
+ )
666
+ ```
667
+
668
+ หาก `size` เป็น synthetic layout weight CMS ต้องไม่แสดงเสมือนเป็น factual value
669
+ `NONE` Treemap เป็น snapshot ส่วน partitioned Treemap ใช้ fixed `range_transform`
670
+ ตาม declaration ปัจจุบัน Treemap ยังไม่รองรับ temporal mode
671
+
672
+ ---
673
+
674
+ # 23. กฎร่วมสำหรับการแก้ไขข้อมูล
675
+
676
+ | Report | เขียนข้อมูล | ลบเฉพาะรายการ |
677
+ |---|---|---|
678
+ | Table | `append_rows` | `remove_rows` |
679
+ | Metric | `set_values` | `delete_partition` หรือ `clear` |
680
+ | Line / Area | `append_points` | `remove_points` |
681
+ | Bar/Column | `append_bars` | `remove_bars` |
682
+ | Pie / Donut | `append_slices` | `remove_slices` |
683
+ | Radial Bar | `set_values` | `delete_partition` หรือ `clear` |
684
+ | Scatter | `append_points` | `remove_points` |
685
+ | Bubble | `append_bubbles` | `remove_bubbles` |
686
+ | Heatmap | `append_cells` | `remove_cells` |
687
+ | Candlestick | `append_candles` | `remove_candles` |
688
+ | Box Plot | `append_distributions` | `remove_distributions` |
689
+ | Radar | `append_points` | `remove_points` |
690
+ | Polar Area | `append_sectors` | `remove_sectors` |
691
+ | Range Bar / Range Area | `append_ranges` | `remove_ranges` |
692
+ | Treemap | `append_nodes` | `remove_nodes` |
693
+
694
+ ทุก Report รองรับ `delete_partition(partition_key)` สำหรับ DAY/MONTH, write method
695
+ ที่รับ `partition_key` อย่างชัดเจน และ `clear()` เพื่อลบ local records ก่อน publish
696
+ ถ้าคำนวณไม่สำเร็จห้าม publish; remote report จะไม่เปลี่ยน
697
+
698
+ Identity ที่ส่งให้ `remove_*`:
699
+
700
+ | Report | Identity fields |
701
+ |---|---|
702
+ | Table | Dimension ทุกตัวที่ประกาศ |
703
+ | Line / Area | optional `series`; หากไม่มีใช้ object ว่าง |
704
+ | Bar/Column | `category` และ optional `series` |
705
+ | Pie / Donut / Polar Area | `category` |
706
+ | Scatter | `x`, `y`, optional `series` |
707
+ | Bubble | `x`, `y`, optional `series`; ไม่รวม `size` |
708
+ | Heatmap | `x`, `y` |
709
+ | Candlestick | `x` |
710
+ | Box Plot | `category` |
711
+ | Radar | `axis`, optional `series` |
712
+ | Range Bar | `category`, optional `series`, `start`, `end` |
713
+ | Range Area | `x`, optional `series` |
714
+ | Treemap | `label`, optional `group` |
715
+
716
+ Metric และ Radial Bar มี value set เดียวต่อ partition จึง refresh ด้วย
717
+ `delete_partition` แล้วตามด้วย `set_values` สำหรับ `NONE`, `set_values` จะแทนที่
718
+ current snapshot
719
+
720
+ ---
721
+
722
+ # 24. Validation และการจัดการความล้มเหลว
723
+
724
+ - Field ที่ไม่ได้ประกาศเป็น error และไม่ถูกละเลย
725
+ - Required field ที่หายเป็น error
726
+ - Boolean ไม่ถูกยอมรับเป็น integer หรือ number
727
+ - ปฏิเสธ `NaN`, positive infinity และ negative infinity
728
+ - Field name เป็น contract key ที่คงที่ เปลี่ยนได้เมื่อเปลี่ยน definition/version โดยตั้งใจ
729
+ - Report key เดิมห้ามเปลี่ยน report type, fields, granularity หรือ presentation options
730
+ - เมื่อคำนวณล้มเหลว ห้ามเรียก `store.publish()`
731
+ - ทุก write batch เป็น transaction; validation failure ต้องไม่ insert เพียงบางส่วน
732
+ - Error ต้องระบุ report key, field, expected type และตำแหน่งค่าที่ผิด โดยไม่ log
733
+ sensitive dataset ทั้งชุด
734
+
735
+ ---
736
+
737
+ # 25. Report Type ที่ยังไม่รองรับ
738
+
739
+ Type นอก compatibility allowlist ถือว่าไม่รองรับ ห้ามเลียนแบบด้วยการเลือก renderer
740
+ หรือสร้าง raw JSON เอง Visual variant เช่น Mixed หรือ Funnel ต้องมี public contract
741
+ ก่อน แม้ ApexCharts ระดับล่างจะวาดได้ก็ตาม
742
+
743
+ หากลูกค้าต้องการ Report ชนิดใหม่ ให้หยุดและขอ Framework release ที่เพิ่ม typed
744
+ builder นั้น ห้ามเพิ่ม unregistered type ภายใน Plugin เพียงตัวเดียว
@@ -591,8 +591,9 @@ table.append_rows([
591
591
  - Values must match their declared types.
592
592
  - Two rows in the same report cannot have the same generated identity.
593
593
  - CMS renders columns from the stored declaration, not from the first row.
594
- - For `DAY` and `MONTH`, CMS also shows the record partition as a leading Day or
595
- Month column. It never merges rows from different partitions implicitly.
594
+ - For `DAY` and `MONTH`, partition remains internal range and ordering metadata.
595
+ CMS does not add a special Day or Month column, and it never merges rows from
596
+ different partitions implicitly.
596
597
 
597
598
  ## Removing selected rows
598
599
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pam-python
3
- Version: 0.2.12
3
+ Version: 0.2.13
4
4
  Summary: Pam Python Library
5
5
  Author-email: Narongrit Kanhanoi <narongrit@pams.ai>
6
6
  Project-URL: Homepage, https://github.com/heart/pam-python
@@ -55,6 +55,7 @@ pam/reports/treemap.py
55
55
  pam/templates/buildcmd/pamb
56
56
  pam/templates/buildcmd/pamb-base.sh
57
57
  pam/templates/init/AGENT.md
58
+ pam/templates/init/REPORTS-th.md
58
59
  pam/templates/init/REPORTS.md
59
60
  pam/templates/init/dockerignore.tmpl
60
61
  pam/templates/init/gitignore.tmpl
@@ -43,7 +43,13 @@ class CLITemplateTest(unittest.TestCase):
43
43
  self.assertIn("pyarrow>=19.0.1", (root / "requirements.txt").read_text())
44
44
  self.assertFalse((root / "pyproject.toml").exists())
45
45
  self.assertTrue((root / "REPORTS.md").is_file())
46
+ self.assertTrue((root / "REPORTS-th.md").is_file())
46
47
  reports_guide = (root / "REPORTS.md").read_text()
48
+ thai_reports_guide = (root / "REPORTS-th.md").read_text()
49
+ self.assertIn("Managed Reports สำหรับนักพัฒนา Data Plugin", thai_reports_guide)
50
+ self.assertIn("TemporalMode.AGGREGATE", thai_reports_guide)
51
+ self.assertIn("TemporalMode.SNAPSHOT", thai_reports_guide)
52
+ self.assertIn("อย่าเลือกจากรูปทรง Chart", thai_reports_guide)
47
53
  self.assertIn("TemporalMode.AGGREGATE", reports_guide)
48
54
  self.assertIn("TemporalMode.SNAPSHOT", reports_guide)
49
55
  self.assertIn(
@@ -196,6 +202,8 @@ class CLITemplateTest(unittest.TestCase):
196
202
  (root / "pyproject.toml").read_text(),
197
203
  )
198
204
  self.assertTrue((root / "uv.lock").is_file())
205
+ self.assertTrue((root / "REPORTS.md").is_file())
206
+ self.assertTrue((root / "REPORTS-th.md").is_file())
199
207
  self.assertFalse((root / "requirements.txt").exists())
200
208
  self.assertFalse((root / "run_unit_test.sh").exists())
201
209
  self.assertIn(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes