comparison Perori/perori.go @ 0:aaaa401818a1 draft

first commit.
author pyon <pyon@macmini>
date Mon, 24 May 2021 21:32:58 +0900
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:aaaa401818a1
1 package main
2
3 import (
4 "flag"
5 "compress/gzip"
6 "encoding/csv"
7 "fmt"
8 "html/template"
9 "io/ioutil"
10 "log"
11 "os"
12 "strings"
13 "sort"
14 "time"
15
16 "golang.org/x/text/encoding/japanese"
17 "golang.org/x/text/transform"
18 )
19
20 var debug_log bool
21
22 // Constants
23 const version = "0.3b"
24 const default_dbfile = "ikenshoirai.db"
25 const default_csvfile = "ikenshoirai.csv"
26
27 const tpl = `
28 <!DOCTYPE html> <html>
29 <head>
30 <style type="text/css">
31 body { font-size: 9pt; margin-left: 0px;}
32 h2 { font-size: 11pt; margin-bottom: 1px; background-color: #ccccff; padding-left: 5px; }
33 h3 { font-size: 11pt; margin-bottom: 1px; background-color: #f0a8a8; padding-left: 10px; }
34 table, th, td { border: 0.3px #c0c0c0 solid; border-collapse:collapse; }
35 table { margin-bottom: 5px; margin-left: 15px; }
36 th { background-color: #ccffcc; }
37 hr { page-break-before: always; }
38 </style>
39 <title> - </title>
40 </head>
41 <body>
42
43 <h2> List <small>( Date = {{.Ymd}} / N = {{.NHhs}} / Dr = {{.NDr}} )</small> </h2>
44 {{range .Doctors}}
45 <h3>{{.Name}}<small> ..... {{.Hp}}:{{.Senmon}}</small></h3>
46 {{range .Clients}}
47 <table>
48 <tr>
49 <td width=140 style="background-color: #98f0f0; padding-left: 10px;">{{.Name}}</td>
50 <td width=120 align=center>{{.Kubun}} {{.Ymd}}</td>
51 <td width=480 style="padding-left: 10px;">
52
53 {{if .Prev.Ymd}}
54 {{if eq .DrId .Prev.DrId}}
55 {{str2cp932 "★ 継続 -"}} {{.Prev.Ymd}}
56 {{else}}
57 {{.Prev.Dr}} {{.Prev.Ymd}}
58 {{end}}
59 {{else}}
60 New !
61 {{end}}
62
63 </td>
64 <td width=80 align=center>{{.Hhsno}}</td>
65 </tr>
66 <tr>
67 <td colspan=4 style="font-family: serif; font-size: 8pt; padding-left: 30px;">{{.Biko}}</td>
68 </tr>
69
70 </table>
71 {{end}}
72 {{end}}
73
74 <hr />
75 {{$hpno := 0}}
76 <h2> N by Hp </h2>
77 <table>
78 <tr> <th> no </th> <th> hp </th> <th width=60> n </th> </tr>
79 {{range $hp, $n := .Hp}}
80 <tr>
81 {{$hpno = add1 $hpno}}
82 <td align=right style="padding-right: 5px;"> {{$hpno}} </td>
83 <td style="padding-left: 5px;"> {{$hp}} </td>
84 <td align=right style="padding-right: 5px;"> {{$n}} </td>
85 </tr>
86 {{end}}
87 <tr> <td></td> <td align=right> sum &gt &gt &gt</td> <td align=right style="padding-right: 5px;"> <b> {{.HpSum}} </b> </td> </tr>
88 </table>
89 </body>
90 </html>`
91
92
93 // Define Types
94 type PrevSinsei struct {
95 Biko string
96 DrId string
97 Dr string
98 IraiYmd string
99 Ymd string
100 Kubun string
101 }
102
103 type Sinsei struct {
104 Hhsno string
105 Name string
106 Biko string
107 DrId string
108 Dr string
109 DrKana string
110 Hp string
111 IraiYmd string
112 Ymd string
113 Kubun string
114 Senmon string
115 Prev PrevSinsei
116 }
117
118 func (s *Sinsei) SetPrev(prev PrevSinsei) {
119 s.Prev = prev
120 }
121
122 func (s Sinsei) String() string {
123 return strings.Join([]string{s.Hhsno, s.Name, s.Ymd, s.Kubun, s.Dr, s.Hp, s.Senmon, s.IraiYmd, s.Biko}, ",")
124 }
125
126 func (s *Sinsei) Humanize() {
127 var buf string
128
129 switch s.Kubun {
130 case "01":
131 buf = "新規"
132 case "02":
133 buf = "更新"
134 case "10":
135 buf = "支介"
136 case "05":
137 buf = "区変"
138 case "03":
139 buf = "転入"
140 case "09":
141 buf = "証交"
142 }
143 s.Kubun, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), buf)
144
145 s.Ymd = strings.Join([]string{s.Ymd[2:4], s.Ymd[4:6], s.Ymd[6:8]}, ".")
146 }
147
148 type Doctor struct {
149 Id string
150 Name string
151 Kana string
152 Hp string
153 Senmon string
154 Clients []Sinsei
155 }
156
157 func (d *Doctor) AddClient(sinsei Sinsei) {
158 d.Clients = append(d.Clients, sinsei)
159 }
160
161 func (d Doctor) String() string {
162 return d.Name
163 }
164
165 // Main
166 func main() {
167 var csvfile, dbfile, date string
168
169 today := time.Now().Format("20060102")
170
171 flag.StringVar(&csvfile, "c", default_csvfile, "csv file")
172 flag.StringVar(&dbfile, "b", default_dbfile, "db file")
173 flag.StringVar(&date, "r", today, "Ikensho Irai YMD")
174 flag.BoolVar(&debug_log, "d", false, "print debug-log (stderr)")
175 flag.Parse()
176
177 csvdata, hhshash, err := getdata_fromCSV(csvfile, date)
178 if err != nil {
179 log.Fatal(err)
180 }
181 print_debug_log(fmt.Sprintf("csvdata: n=%d", len(csvdata))) //
182 print_debug_log(fmt.Sprintf("hhshash: n=%d", len(hhshash))) //
183
184 dbdata, err := getdata_fromDB(dbfile, hhshash)
185 if err != nil {
186 log.Fatal(err)
187 }
188 print_debug_log(fmt.Sprintf("dbdata: n=%d", len(dbdata))) //
189
190 dbdata = append(dbdata, csvdata...)
191 print_debug_log(fmt.Sprintf("dbdata: n=%d", len(dbdata))) //
192
193 sort.Slice(dbdata, func(i, j int) bool {
194 if dbdata[i].Hhsno != dbdata[j].Hhsno {
195 return dbdata[i].Hhsno < dbdata[j].Hhsno
196 }
197 if dbdata[i].Ymd != dbdata[j].Ymd {
198 return dbdata[i].Ymd > dbdata[j].Ymd
199 }
200 if dbdata[i].IraiYmd != dbdata[j].IraiYmd {
201 return dbdata[i].IraiYmd > dbdata[j].IraiYmd
202 }
203 return false
204 })
205
206 var dbdata2 []Sinsei // delete same Ymd (for changing Dr.)
207 var lasthhsno, lastymd string
208 for _, ss := range dbdata {
209 if ss.Hhsno == lasthhsno && lastymd == ss.Ymd {
210 continue
211 }
212 dbdata2 = append(dbdata2, ss)
213 lasthhsno = ss.Hhsno
214 lastymd = ss.Ymd
215 }
216
217 var lastdata []Sinsei
218 prevhash := make(map[string]PrevSinsei)
219 hhscnt := make(map[string]int)
220 for _, ss := range dbdata2 {
221 ss.Humanize()
222 switch hhscnt[ss.Hhsno] {
223 case 0:
224 lastdata = append(lastdata, ss)
225 case 1:
226 prevhash[ss.Hhsno] = PrevSinsei{
227 Biko: ss.Biko,
228 DrId: ss.DrId,
229 Dr: ss.Dr + "(" + ss.Hp + ":" + ss.Senmon + ")",
230 IraiYmd: ss.IraiYmd,
231 Ymd: ss.Ymd,
232 Kubun: ss.Kubun,
233 }
234 }
235 hhscnt[ss.Hhsno]++;
236 }
237 print_debug_log(fmt.Sprintf("lastdata: n=%d", len(lastdata))) //
238
239 doctorhash := make(map[string]Doctor)
240 hpcnt := make(map[string]int)
241 var hpcntsum int
242 for _, ss := range lastdata {
243 ss.SetPrev(prevhash[ss.Hhsno])
244 if d, ok := doctorhash[ss.DrId]; !ok {
245 doctorhash[ss.DrId] = Doctor{
246 Id: ss.DrId,
247 Name: ss.Dr,
248 Kana: ss.DrKana,
249 Hp: ss.Hp,
250 Senmon: ss.Senmon,
251 Clients: []Sinsei{ss},
252 }
253 } else {
254 d.AddClient(ss)
255 doctorhash[ss.DrId] = d
256 }
257 hpcnt[ss.Hp]++
258 hpcntsum++
259 }
260
261 var doctors []Doctor
262 for _, dr := range doctorhash {
263 doctors = append(doctors, dr)
264 }
265 sort.Slice(doctors, func(i, j int) bool {
266 if doctors[i].Kana != doctors[j].Kana {
267 return doctors[i].Kana < doctors[j].Kana
268 }
269 if doctors[i].Id != doctors[j].Id {
270 return doctors[i].Id < doctors[j].Id
271 }
272 return false
273 })
274
275 irai := struct {
276 Ymd string
277 NHhs int
278 //NSinsei int
279 NDr int
280 Doctors []Doctor
281 Hp map[string]int
282 HpSum int
283 }{
284 Ymd: strings.Join([]string{date[0:4], date[4:6], date[6:8]}, "."),
285 NHhs: len(hhshash),
286 //NSinsei: len(dbdata),
287 NDr: len(doctors),
288 Doctors: doctors,
289 Hp: hpcnt,
290 HpSum: hpcntsum,
291 }
292
293 funcmap := template.FuncMap{
294 "shorten": shorten,
295 "str2cp932": str2cp932,
296 "add1": func(a int) int { return a + 1 },
297 }
298
299 t, err := template.New("webpage").Funcs(funcmap).Parse(tpl)
300 if err != nil {
301 log.Fatal(err)
302 }
303
304 err = t.Execute(os.Stdout, irai)
305 if err != nil {
306 log.Fatal(err)
307 }
308 }
309
310 // Utility functions
311 func csv2sinsei(record []string) Sinsei {
312 return Sinsei{
313 Hhsno: strings.TrimSpace(record[0]),
314 Name: strings.TrimSpace(record[1]),
315 Biko: strings.TrimSpace(record[2]),
316 DrId: strings.TrimSpace(record[3]),
317 Dr: strings.TrimSpace(record[4]),
318 DrKana: strings.TrimSpace(record[5]),
319 Hp: strings.TrimSpace(record[6]),
320 IraiYmd: strings.TrimSpace(record[7]),
321 Ymd: strings.TrimSpace(record[8]),
322 Kubun: strings.TrimSpace(record[9]),
323 Senmon: strings.TrimSpace(record[10]),
324 }
325 }
326
327 func getdata_fromCSV(file, date string) (sinsei []Sinsei, hhshash map[string]bool, err error) {
328 hhshash = make(map[string]bool)
329
330 data, err := ioutil.ReadFile(file)
331 if err != nil {
332 return sinsei, hhshash, err
333 }
334
335 r := csv.NewReader(strings.NewReader(string(data)))
336 records, err := r.ReadAll()
337 if err != nil {
338 return sinsei, hhshash, err
339 }
340
341 for _, record := range records {
342 ss := csv2sinsei(record)
343 if ss.IraiYmd == date {
344 hhshash[ss.Hhsno] = true
345 }
346 }
347
348 for _, record := range records {
349 ss := csv2sinsei(record)
350 if _, ok := hhshash[ss.Hhsno]; ok {
351 sinsei = append(sinsei, ss)
352 }
353 }
354
355 return sinsei, hhshash, nil
356 }
357
358 func getdata_fromDB(file string, hhshash map[string]bool) (sinsei []Sinsei, err error) {
359 f, err := os.Open(file)
360 if err != nil {
361 return sinsei, err
362 }
363 defer f.Close()
364
365 zr, err := gzip.NewReader(f)
366 if err != nil {
367 return sinsei, err
368 }
369
370 data, err := ioutil.ReadAll(zr)
371 if err != nil {
372 return sinsei, err
373 }
374
375 if err := zr.Close(); err != nil {
376 return sinsei, err
377 }
378
379 r := csv.NewReader(strings.NewReader(string(data)))
380 records, err := r.ReadAll()
381 if err != nil {
382 return sinsei, err
383 }
384
385 for _, record := range records {
386 hno := strings.TrimSpace(record[0])
387 if _, ok := hhshash[hno]; ok {
388 sinsei = append(sinsei, csv2sinsei(record))
389 }
390 }
391
392 return sinsei, nil
393 }
394
395 func shorten(msg string, length int) string {
396 if len(msg) > length {
397 msg = msg[0:length] + "..."
398 }
399 return msg
400 }
401
402 func str2cp932(s string) string {
403 s, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), s)
404 return s
405 }
406
407 func print_debug_log(msg string) {
408 if debug_log {
409 fmt.Fprintf(os.Stderr, "%s\n", msg)
410 }
411 }
412