41
|
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
|
44
|
23 const version = "0.3b"
|
41
|
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>
|
43
|
67 <td colspan=4 style="font-family: serif; font-size: 8pt; padding-left: 30px;">{{.Biko}}</td>
|
41
|
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 > > ></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
|
43
|
145 s.Ymd = strings.Join([]string{s.Ymd[2:4], s.Ymd[4:6], s.Ymd[6:8]}, ".")
|
41
|
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 }
|
44
|
200 if dbdata[i].IraiYmd != dbdata[j].IraiYmd {
|
|
201 return dbdata[i].IraiYmd > dbdata[j].IraiYmd
|
|
202 }
|
41
|
203 return false
|
|
204 })
|
|
205
|
44
|
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
|
41
|
218 prevhash := make(map[string]PrevSinsei)
|
|
219 hhscnt := make(map[string]int)
|
44
|
220 for _, ss := range dbdata2 {
|
41
|
221 ss.Humanize()
|
42
|
222 switch hhscnt[ss.Hhsno] {
|
|
223 case 0:
|
44
|
224 lastdata = append(lastdata, ss)
|
42
|
225 case 1:
|
41
|
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 }
|
43
|
235 hhscnt[ss.Hhsno]++;
|
41
|
236 }
|
44
|
237 print_debug_log(fmt.Sprintf("lastdata: n=%d", len(lastdata))) //
|
41
|
238
|
|
239 doctorhash := make(map[string]Doctor)
|
|
240 hpcnt := make(map[string]int)
|
|
241 var hpcntsum int
|
44
|
242 for _, ss := range lastdata {
|
41
|
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 {
|
42
|
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
|
41
|
273 })
|
|
274
|
|
275 irai := struct {
|
|
276 Ymd string
|
|
277 NHhs int
|
44
|
278 //NSinsei int
|
41
|
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),
|
44
|
286 //NSinsei: len(dbdata),
|
41
|
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)
|
42
|
343 if ss.IraiYmd == date {
|
|
344 hhshash[ss.Hhsno] = true
|
41
|
345 }
|
42
|
346 }
|
|
347
|
|
348 for _, record := range records {
|
|
349 ss := csv2sinsei(record)
|
|
350 if _, ok := hhshash[ss.Hhsno]; ok {
|
|
351 sinsei = append(sinsei, ss)
|
|
352 }
|
41
|
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
|