keihan-tracker 2.2.0__py3-none-any.whl

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.
@@ -0,0 +1,5 @@
1
+ from .keihan_train import KHTracker
2
+ from .keihan_train.tracker import TrainData, ActiveTrainData, LineLiteral
3
+ from .keihan_train.schemes import TrainType
4
+ from .bus import get_khbus_info
5
+ from .delay_tracker import get_yahoo_delay
@@ -0,0 +1 @@
1
+ from .tracker import get_khbus_info
@@ -0,0 +1,58 @@
1
+ from typing import List, Optional
2
+ from pydantic import BaseModel, model_validator
3
+
4
+
5
+ class Head(BaseModel):
6
+ errorcode: int
7
+ objid: str
8
+
9
+ class BusStatePrms(BaseModel):
10
+ order: int # 接近順位
11
+ lat: float # 緯度
12
+ lon: float # 経度
13
+ heading: int # 方位
14
+ guid: str # GUID
15
+ stop_id: int # 停留所ID?
16
+ platform_index: int # のりばインデックス?
17
+ route: str # 系統番号(例: [22])
18
+ destination: str # 行先
19
+ via: str # 経由
20
+ status: str # 状態(到着済など)
21
+ timetable: str # 時刻文字列(例: 17:30 到着予定)
22
+
23
+ @classmethod
24
+ def from_string(cls, raw: str):
25
+ parts = raw.split(":")
26
+ return cls(
27
+ order=int(parts[0]),
28
+ lat=float(parts[1]),
29
+ lon=float(parts[2]),
30
+ heading=int(parts[3]),
31
+ guid=parts[4],
32
+ stop_id=int(parts[5]),
33
+ platform_index=int(parts[6]),
34
+ route=parts[7],
35
+ destination=parts[8],
36
+ via=parts[9],
37
+ status=parts[10],
38
+ timetable=parts[11].replace("__________", ":"),
39
+ )
40
+
41
+ class BusState(BaseModel):
42
+ html: Optional[str] = None
43
+ html_sp: Optional[str] = None
44
+ busstateprms: BusStatePrms
45
+
46
+ @model_validator(mode="before")
47
+ def parse_prms(cls, values):
48
+ if isinstance(values, dict) and "busstateprms" in values and isinstance(values["busstateprms"], str):
49
+ values["busstateprms"] = BusStatePrms.from_string(values["busstateprms"])
50
+ return values
51
+
52
+ class Body(BaseModel):
53
+ datetimeStr: str
54
+ busstates: List[BusState]
55
+
56
+ class BusLocationResponse(BaseModel):
57
+ head: Head
58
+ body: Body
@@ -0,0 +1,13 @@
1
+ from httpx import AsyncClient
2
+ import urllib.parse
3
+ from keihan_tracker.bus.schemes import BusLocationResponse
4
+
5
+ UPDATE_URL = "https://busnavi.keihanbus.jp/pc/busstateupd"
6
+
7
+ async def get_khbus_info(stop_name:str, stop_num:int=1):
8
+ async with AsyncClient() as client:
9
+ dgmpl = f"{stop_name}:{stop_num}::"
10
+ result = await client.post(UPDATE_URL,data={"dgmpl":urllib.parse.quote(dgmpl), "sort4":"0"})
11
+ result.raise_for_status()
12
+ res = BusLocationResponse.model_validate_json(result.text)
13
+ return res
@@ -0,0 +1,153 @@
1
+ from pydantic import BaseModel, field_validator, BeforeValidator, Field
2
+ from typing import Optional, Union, Annotated, List, Any
3
+ from datetime import datetime
4
+ from zoneinfo import ZoneInfo
5
+ from bs4 import BeautifulSoup as bs
6
+ import asyncio
7
+ import httpx
8
+ import re
9
+ from urllib.parse import urljoin
10
+
11
+ JST = ZoneInfo("Asia/Tokyo")
12
+ BASE_URL = "https://transit.yahoo.co.jp/diainfo/area/"
13
+
14
+ class DelayLine(BaseModel):
15
+ LineName: str
16
+ status: str
17
+ detail: str
18
+ AnnouncedTime: datetime
19
+
20
+ def __str__(self):
21
+ return f"{self.LineName}: {self.status}\n{self.detail}\n{self.AnnouncedTime} 発表"
22
+
23
+ def force_list(v: Any) -> List[Any]:
24
+ if v is None: return []
25
+ if isinstance(v, list): return v
26
+ return [v]
27
+
28
+ AsList = BeforeValidator(force_list)
29
+
30
+ # --- モデル定義 (クラス名を変更して衝突を回避) ---
31
+
32
+ class LineData(BaseModel):
33
+ Name: str = ""
34
+ code: str = ""
35
+ corporationIndex: str = ""
36
+
37
+ class CommentData(BaseModel):
38
+ text: str = ""
39
+ status: str = ""
40
+
41
+ class PrefectureData(BaseModel):
42
+ Name: str = ""
43
+ code: str = ""
44
+
45
+ class StationData(BaseModel):
46
+ Name: str = ""
47
+ code: str = ""
48
+ Type: str = ""
49
+ Yomi: str = ""
50
+
51
+ class PointData(BaseModel):
52
+ # フィールド名はJSONに合わせて "Prefecture" だが、型は "PrefectureData" にする
53
+ Prefecture: PrefectureData = Field(default_factory=PrefectureData)
54
+ Station: StationData = Field(default_factory=StationData)
55
+
56
+ class SectionData(BaseModel):
57
+ Point: Annotated[List[PointData], AsList] = []
58
+
59
+ class CorporationData(BaseModel):
60
+ Name: str = ""
61
+ code: str = ""
62
+
63
+ class InformationData(BaseModel):
64
+ status: str = ""
65
+ provider: str = ""
66
+ Title: str = ""
67
+ Datetime: datetime
68
+
69
+ # ここでもフィールド名と型名を区別する
70
+ Line: LineData = Field(default_factory=LineData)
71
+
72
+ Section: Annotated[List[SectionData], AsList] = []
73
+ Comment: Annotated[List[CommentData], AsList] = []
74
+ Prefecture: Annotated[List[PrefectureData], AsList] = []
75
+
76
+ class ResultSetData(BaseModel):
77
+ apiVersion: str = ""
78
+ engineVersion: str = ""
79
+ Information: Annotated[List[InformationData], AsList] = []
80
+ Corporation: Annotated[List[CorporationData], AsList] = []
81
+
82
+ class ResponseModel(BaseModel):
83
+ ResultSet: ResultSetData
84
+
85
+
86
+ async def get_yahoo_delay(area:int=6) -> list[DelayLine]:
87
+ async with httpx.AsyncClient() as crowler:
88
+ html = await crowler.get(f"{BASE_URL}{area}")
89
+
90
+ soup = bs(html.content, "html.parser")
91
+
92
+ table = soup.select("div.elmTblLstLine.trouble")[0].find_all("table")
93
+ table = table[0] if len(table) != 0 else None
94
+ if not table:
95
+ return []
96
+
97
+ async def get_detail(url:str) -> tuple[str,str]:
98
+ res = await crowler.get(url,follow_redirects=True)
99
+ soup = bs(res.text, "html.parser")
100
+ info = soup.select("div#mdServiceStatus")[0]
101
+ title = info.select("dl > dt")[0].text
102
+ text = info.select("dl > dd > p")[0].text
103
+
104
+ return title, text
105
+
106
+ delays:list[DelayLine] = []
107
+ tasks:list = []
108
+ lines:list[tuple[str,str]] = []
109
+ for tr in table.find_all("tr")[1:]:
110
+ url = urljoin(BASE_URL, str(tr.find_all("td")[0].find_all("a")[0].get("href")))
111
+ line = tr.find_all("td")[0].text # ○○線
112
+ short_status = tr.find_all("td")[2].text # 17:00頃、宇都宮...
113
+ tasks.append(get_detail(url))
114
+ lines.append((line, short_status))
115
+
116
+ for i, line in zip(await asyncio.gather(*tasks), lines):
117
+ line, status = line
118
+ title, text = i
119
+
120
+ pattern = r"((?P<month>\d{1,2})月(?P<day>\d{1,2})日\s*(?P<hour>\d{1,2})時(?P<minute>\d{1,2})分掲載)"
121
+ m = re.search(pattern, text) or {}
122
+
123
+ dt = datetime(
124
+ year=datetime.now().year, # 年は別途補完
125
+ month=int(m["month"]),
126
+ day=int(m["day"]),
127
+ hour=int(m["hour"]),
128
+ minute=int(m["minute"]),
129
+ tzinfo=JST
130
+ )
131
+ delays.append(DelayLine(LineName=line, status=title, detail=text, AnnouncedTime=dt))
132
+ return delays
133
+
134
+ async def get_ekispert_delay(api_key:str, prefs:list[int]=[26,27,28]) -> list[DelayLine]:
135
+ async with httpx.AsyncClient() as web:
136
+ uri = f"http://api.ekispert.jp/v1/json/operationLine/service/rescuenow/information?key={api_key}"
137
+ uri += f"&prefectureCode={':'.join(map(str,prefs))}"
138
+ request = await web.get(uri)
139
+ request.raise_for_status()
140
+ res = ResponseModel.model_validate(request.json())
141
+
142
+ results:dict[str,DelayLine] = {}
143
+ for i in res.ResultSet.Information or []:
144
+ if i.Line.code in results:
145
+ if i.Datetime < (results[i.Line.code].AnnouncedTime or datetime.min):
146
+ continue
147
+ results[i.Line.code] = DelayLine(
148
+ LineName=i.Line.Name,
149
+ status=i.status,
150
+ detail=i.Comment[0].text,
151
+ AnnouncedTime=i.Datetime
152
+ )
153
+ return list(results.values())
@@ -0,0 +1 @@
1
+ from .tracker import KHTracker
@@ -0,0 +1,129 @@
1
+ # GEMINI VIBE CODING
2
+
3
+ from typing import Optional
4
+ from .schemes import LineLiteral
5
+
6
+ def calc_position(col: int, row: int) -> tuple[LineLiteral, int, Optional[int]]:
7
+ """座標から、停車中の駅番号、もしくは2駅の駅番号を返します。"""
8
+ line: LineLiteral
9
+
10
+ # --- 1. 路線判定 ---
11
+ if 1 <= row <= 131:
12
+ # Row 118以降かつcol 1,2は中之島線
13
+ if row >= 118 and (col == 1 or col == 2):
14
+ line = "中之島線"
15
+ else:
16
+ line = "京阪本線・鴨東線"
17
+ elif 132 <= row <= 153:
18
+ line = "宇治線"
19
+ elif 154 <= row <= 175:
20
+ line = "交野線"
21
+ else:
22
+ raise ValueError(f"Inputed row {row} is out of range.")
23
+
24
+ # colのバリデーション
25
+ if col <= 0 or col >= 6:
26
+ raise ValueError(f"Inputed col {col} is out of range.")
27
+
28
+
29
+ # --- 2. 駅IDと状態の計算 ---
30
+
31
+ # === A. 京阪本線・鴨東線・中之島線 (Row 1-131) ===
32
+ if row <= 131:
33
+ # A-1. 通常区間 (Row 1-117)
34
+ if row <= 117:
35
+ block_index = (row - 1) // 3
36
+ current_station = 42 - block_index
37
+ pos_in_block = (row - 1) % 3 # 0:Top, 1:Mid, 2:Bot
38
+
39
+ is_stopped = False
40
+
41
+ # 基本: Top(0)は停車
42
+ if pos_in_block == 0:
43
+ is_stopped = True
44
+
45
+ # 特例: 2行目(Mid)以降も停車扱いになる駅 (HTMLの big-area/special-area 等に基づく)
46
+ # 出町柳(42): Row 2も停車
47
+ if current_station == 42 and pos_in_block == 1: is_stopped = True
48
+ # 枚方市(21): Row 65(Mid)も停車
49
+ if current_station == 21 and pos_in_block == 1: is_stopped = True
50
+ # 京橋(4): Row 116(Mid)も停車
51
+ if current_station == 4 and pos_in_block == 1: is_stopped = True
52
+
53
+ if is_stopped:
54
+ return (line, current_station, None)
55
+ else:
56
+ # 移動中 (当駅 -> 次駅)
57
+ # Next station is current - 1
58
+ return (line, current_station, current_station - 1)
59
+
60
+ # A-2. 地下・中之島線区間 (Row 118-131)
61
+ # この区間はHTML上隙間なく special-area が続くため、すべて停車扱いとする
62
+ else:
63
+ # 共通: 天満橋(3) Row 118-120
64
+ if row <= 120: return (line, 3, None)
65
+
66
+ if line == "中之島線":
67
+ if row <= 123: return (line, 51, None) # なにわ橋
68
+ elif row <= 126: return (line, 52, None) # 大江橋
69
+ elif row <= 129: return (line, 53, None) # 渡辺橋
70
+ else: return (line, 54, None) # 中之島
71
+
72
+ else: # 京阪本線 大阪側
73
+ if row <= 123: return (line, 2, None) # 北浜
74
+ elif row <= 126: return (line, 1, None) # 淀屋橋
75
+ # Row 127以降は本線には無し
76
+ else: raise ValueError(f"Invalid row {row} for Main Line")
77
+
78
+ # === B. 宇治線 (Row 132-153) ===
79
+ elif row <= 153:
80
+ # 中書島(28) Row 132-134 (始発)
81
+ if row <= 134:
82
+ # HTML: 132(special), 133(big) -> 停車
83
+ pos = row - 132
84
+ if pos <= 1: return (line, 28, None)
85
+ else: return (line, 28, 71) # 発車
86
+
87
+ # 終点: 宇治(77)
88
+ if row == 153: return (line, 77, None)
89
+
90
+ # 通常駅 (KH71-KH76)
91
+ block_idx = (row - 135) // 3
92
+ current_station = 71 + block_idx
93
+ pos_in_block = (row - 135) % 3
94
+
95
+ # 基本: Top(0)のみ停車
96
+ if pos_in_block == 0:
97
+ return (line, current_station, None)
98
+ else:
99
+ return (line, current_station, current_station + 1)
100
+
101
+ # === C. 交野線 (Row 154-175) ===
102
+ elif row <= 175:
103
+ # 枚方市(21) Row 154-156 (始発)
104
+ if row <= 156:
105
+ # HTML: 154(special), 155(big) -> 停車
106
+ pos = row - 154
107
+ if pos <= 1: return (line, 21, None)
108
+ else: return (line, 21, 61) # 発車
109
+
110
+ # 終点: 私市(67)
111
+ if row == 175: return (line, 67, None)
112
+
113
+ # 通常駅 (KH61-KH66)
114
+ block_idx = (row - 157) // 3
115
+ current_station = 61 + block_idx
116
+ pos_in_block = (row - 157) % 3
117
+
118
+ # 基本: Top(0)のみ停車
119
+ if pos_in_block == 0:
120
+ return (line, current_station, None)
121
+ else:
122
+ return (line, current_station, current_station + 1)
123
+
124
+ return (line, 0, None)
125
+
126
+ if __name__ == "__main__":
127
+ col,row=map(int,input().split())
128
+ r = calc_position(col,row)
129
+ print(r)
@@ -0,0 +1,214 @@
1
+ """
2
+ 京阪電車リアルタイム列車位置情報APIのデータを、Pythonで安全かつ型安全に扱うためのPydanticモデル群。
3
+
4
+ 【特徴】
5
+ ・APIから取得したJSONをバリデート(型検証)することが主目的。
6
+ ・API仕様に忠実で、余計な変換やロジックを加えず「生に近い」データ構造を維持。
7
+ ※臨時列車判定は例外的に実装。
8
+
9
+ 【バリデートできるJSON APIと内容】
10
+ ------------------------------------------------------------
11
+
12
+ 1. 駅ごとの乗り換え情報
13
+ URL: https://www.keihan.co.jp/zaisen/transferGuideInfo.json
14
+ 内容: 各駅で接続している他路線(京阪・地下鉄・モノレール等)の情報。駅番号をキーに、接続路線名(多言語対応)などが格納されている。
15
+ 対応クラス: TransferGuideInfo > StationConnections > MultiLang_Lines
16
+
17
+
18
+ 2. 路線ごとの駅名データ
19
+ URL: https://www.keihan.co.jp/zaisen/select_station.json
20
+ 内容: 路線ごとに、駅番号・駅名(多言語対応)などをまとめたデータ。路線名をキーに、各駅の情報が格納されている。
21
+ 対応クラス: SelectStation > LineDetail > MultiLang
22
+
23
+
24
+ 3. 列車のリアルタイム走行位置
25
+ URL: https://www.keihan.co.jp/zaisen-up/trainPositionList.json
26
+ 内容: 現在運行中の列車の位置・種別・遅延情報など。各列車の現在地や行先、種別、遅延分数などが含まれる。
27
+ 対応クラス: trainPositionList > LocationObject > trainInfoObject
28
+
29
+
30
+ 4. 列車ごとのダイヤ(停車駅・時刻表)
31
+ URL: https://www.keihan.co.jp/zaisen-up/startTimeList.json
32
+ 内容: 各列車の停車駅・発車時刻などのダイヤ情報。列車ごとに、どの駅に何時何分に停車するか等が記載されている。
33
+ 対応クラス: startTimeList > TrainInfo > diaStationInfoObject
34
+ """
35
+
36
+ from pydantic import BaseModel, RootModel, Field, model_validator, field_validator
37
+ from typing import Optional, Literal
38
+ from enum import Enum
39
+ import datetime
40
+ from zoneinfo import ZoneInfo
41
+
42
+ JST = ZoneInfo("Asia/Tokyo")
43
+
44
+ # 列車種別をEnumで定義
45
+ class TrainType(str, Enum):
46
+ LOCAL = "普通"
47
+ SEMI_EXP = "区間急行"
48
+ SUB_EXP = "準急"
49
+ COMMUTER_SUB_EXP = "通勤準急"
50
+ EXPRESS = "急行"
51
+ COMMUTER_EXP = "通勤急行"
52
+ MIDNIGHT_EXP = "深夜急行"
53
+ RAPID_EXP = "快速急行"
54
+ COMMUTER_RAPID_EXP = "通勤快急"
55
+ LTD_EXP = "特急"
56
+ LINER = "ライナー"
57
+ RAPID_LTD_EXP = "快速特急 洛楽"
58
+ EXTRA_TRAIN = "臨時列車" # 臨時列車は臨時特急や臨時休校などとは別
59
+
60
+ LineLiteral = Literal["京阪本線・鴨東線","中之島線","交野線","宇治線"]
61
+
62
+ # 1. 言語別の路線名リストを表現するモデル
63
+ class MultiLang_Lines(BaseModel):
64
+ """
65
+ 各言語での路線名を保持するモデル。
66
+ """
67
+ ja: list[str] = Field(description="日本語での名称")
68
+ en: list[str] = Field(description="英語での名称")
69
+ cn: list[str] = Field(description="中国語(簡体字)での名称")
70
+ tw: list[str] = Field(description="中国語(繁体字)での名称")
71
+ kr: list[str] = Field(description="韓国語での名称")
72
+
73
+ # 1. 言語名称モデル
74
+ class MultiLang(BaseModel):
75
+ """
76
+ 各言語での名称を保持するモデル。
77
+ """
78
+ ja: str = Field(description="日本語での名称")
79
+ en: str = Field(description="英語での名称")
80
+ cn: str = Field(description="中国語(簡体字)での名称")
81
+ tw: str = Field(description="中国語(繁体字)での名称")
82
+ kr: str = Field(description="韓国語での名称")
83
+
84
+ # 2. 交通手段ごとの接続情報を表現するモデル
85
+ class StationConnections(BaseModel):
86
+ """
87
+ 駅で接続する交通手段(電車、地下鉄、モノレール)とその路線名を保持するモデル。
88
+ 各交通手段は存在しない場合があるため、Optionalとして定義します。
89
+ """
90
+ train: Optional[MultiLang_Lines] = None
91
+ subway: Optional[MultiLang_Lines] = None
92
+ monorail: Optional[MultiLang_Lines] = None
93
+
94
+ ### 3. JSONのパーサー
95
+ class TransferGuideInfo(RootModel[dict[str, StationConnections]]):
96
+ pass
97
+
98
+ # 1. 駅名データ
99
+ class LineDetail(BaseModel):
100
+ lineName: MultiLang
101
+ stations: dict[str, MultiLang]
102
+
103
+ ### 2. JSONのパーサー
104
+ class SelectStation(
105
+ RootModel[
106
+ dict[
107
+ LineLiteral,
108
+ LineDetail
109
+ ]
110
+ ]):
111
+ pass
112
+
113
+ # 1. trainInfoObjectsの要素を表現するモデル
114
+
115
+ class trainInfoObject(BaseModel):
116
+ """
117
+ 個々の電車の詳細情報を保持するモデル。
118
+ """
119
+ wdfBlockNo: int
120
+ carsOfTrain: int
121
+ delayMinutes: str
122
+ delayMinutesEn: str
123
+ delayMinutesKo: str
124
+ delayMinutesZhCn: str
125
+ delayMinutesZhTw: str
126
+ destStationCode: int
127
+ destStationNameEn: str
128
+ destStationNameJp: str
129
+ destStationNameKo: str
130
+ destStationNameZhCn:str
131
+ destStationNameZhTw:str
132
+ destStationNumber: int
133
+ lastPassStation: int
134
+ trainNumber: str
135
+ trainTypeEn: str
136
+ trainTypeIcon: str
137
+ trainTypeJp: TrainType
138
+ is_special: bool
139
+ trainTypeKo: str
140
+ trainTypeZhCn: str
141
+ trainTypeZhTw: str
142
+
143
+ @model_validator(mode="before")
144
+ @classmethod
145
+ def check_type_special(cls, d:dict):
146
+ if "臨時" in d["trainTypeJp"]:
147
+ d["is_special"] = True
148
+ d["trainTypeJp"] = d["trainTypeJp"].replace("臨時    ", "")
149
+ else:
150
+ d["is_special"] = False
151
+ return d
152
+
153
+ # 2. locationObjectsの要素を表現するモデル
154
+ class LocationObject(BaseModel):
155
+ """
156
+ 路線図上での電車の位置と基本情報を保持するモデル。
157
+ """
158
+ delay: str
159
+ delayEn: str
160
+ delayKo: str
161
+ delayZhCn: str
162
+ delayZhTw: str
163
+ locationCol: int
164
+ locationRow: int
165
+ trainDirection: int
166
+ trainIconTypeImageJp: str
167
+ trainInfoObjects: list[trainInfoObject]
168
+ trainTypeVisIconVis: str
169
+
170
+ ### 3. JSONのパーサー
171
+ class trainPositionList(BaseModel):
172
+ fileCreatedTime: datetime.datetime
173
+ fileVersion: str
174
+ linkNum: str
175
+ locationObjects: list[LocationObject]
176
+
177
+ @field_validator("fileCreatedTime", mode="before")
178
+ def validate_time(cls,value) -> datetime.datetime:
179
+ return datetime.datetime.strptime(value,"%Y%m%d%H%M%S").replace(tzinfo=JST)
180
+
181
+ # 1.
182
+ class diaStationInfoObject(BaseModel):
183
+ stationNumber: str = Field(description="駅ナンバリング2桁+ホーム番号1桁 ")
184
+ stationDepTime: str = Field(description="00:00の形式。深夜は25時などで表す。出発駅の場合は-(ハイフン)、不明な場合は99:99。")
185
+ stationNameJp: str
186
+ stationNameEn: str
187
+ stationNameZhTw:str
188
+ stationNameZhCn:str
189
+ stationNameKo: str
190
+
191
+ class TrainInfo(BaseModel):
192
+ wdfBlockNo:int
193
+ extTrain:bool
194
+ premiumCar:int = Field(description="プレミアムカーがあるかどうか",)
195
+ trainCar:str = Field(description="車両番号")
196
+ diaStationInfoObjects:list[diaStationInfoObject]
197
+
198
+ ### 3. JSONのパーサー
199
+ class startTimeList(BaseModel):
200
+ fileCreatedTime: datetime.datetime
201
+ fileVersion: str
202
+ TrainInfo: list[TrainInfo]
203
+
204
+ @field_validator("fileCreatedTime", mode="before")
205
+ def validate_time(cls,value) -> datetime.datetime:
206
+ return datetime.datetime.strptime(value,"%Y%m%d%H%M%S").replace(tzinfo=JST)
207
+
208
+ # FileList.xmlのモデル
209
+ class FileList(BaseModel):
210
+ time: datetime.datetime
211
+ traininfo: str
212
+ image_PC: str
213
+ image_SP: str
214
+ html_FP: str
@@ -0,0 +1,8 @@
1
+ # ./tracker.py next_stop_station関数の探索用
2
+ # 路線の方面別の駅リスト
3
+ KYOBASHI_TO_DEMACHIYANAGI=[i for i in range(4,42+1)]
4
+
5
+ UJI_UP = [77,76,75,74,73,72,71,28]
6
+ KATANO_UP = [67,66,65,64,63,62,61,21]
7
+ HONNSEN_UP = [1,2,3] +KYOBASHI_TO_DEMACHIYANAGI
8
+ NAKANOSHIMA_UP = [54,53,52,51,3] +KYOBASHI_TO_DEMACHIYANAGI