insighta-sdk 0.1.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,41 @@
1
+ """Insighta Cloud SDK - API client and data models."""
2
+
3
+ from .client import InsightaClient
4
+ from .models import (
5
+ Credentials,
6
+ UploadConfig,
7
+ OrderGroup,
8
+ CashDeposit,
9
+ Trade,
10
+ Holding,
11
+ Deposit,
12
+ RateEntry,
13
+ Dirs,
14
+ )
15
+ from .utils import (
16
+ load_order_groups,
17
+ load_cash_deposits,
18
+ merge_and_sort_groups,
19
+ fetch_ticker_info,
20
+ load_rate_file,
21
+ lookup_rate,
22
+ )
23
+
24
+ __all__ = [
25
+ "InsightaClient",
26
+ "Credentials",
27
+ "UploadConfig",
28
+ "OrderGroup",
29
+ "CashDeposit",
30
+ "Trade",
31
+ "Holding",
32
+ "Deposit",
33
+ "RateEntry",
34
+ "Dirs",
35
+ "load_order_groups",
36
+ "load_cash_deposits",
37
+ "merge_and_sort_groups",
38
+ "fetch_ticker_info",
39
+ "load_rate_file",
40
+ "lookup_rate",
41
+ ]
insighta_sdk/client.py ADDED
@@ -0,0 +1,144 @@
1
+ """Insighta OpenAPI client."""
2
+
3
+ import json as _json
4
+ import logging
5
+ import os
6
+
7
+ import requests
8
+
9
+ from .models import Credentials, OrderGroup, UploadConfig
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+
14
+ class InsightaClient:
15
+ """Insighta OpenAPI client."""
16
+
17
+ def __init__(self, credentials: Credentials, output_dir: str = "output"):
18
+ self.endpoint = credentials.endpoint
19
+ self.output_dir = output_dir
20
+ self.headers = {
21
+ "Authorization": f"Bearer {credentials.api_key}",
22
+ "Content-Type": "application/json",
23
+ }
24
+
25
+ def _request(self, method: str, path: str, **kwargs) -> requests.Response:
26
+ url = f"{self.endpoint}{path}"
27
+ payload = kwargs.get("json")
28
+ if payload is not None:
29
+ self._last_payload = payload
30
+ payload_str = _json.dumps(payload, indent=2, ensure_ascii=False)
31
+ log.debug("%s %s\n%s", method, url, payload_str)
32
+ log_path = os.path.join(self.output_dir, "request_payload.log")
33
+ with open(log_path, "a", encoding="utf-8") as lf:
34
+ lf.write(f"=== {method} {url} ===\n{payload_str}\n\n")
35
+ else:
36
+ log.debug("%s %s", method, url)
37
+ resp = requests.request(method, url, headers=self.headers, timeout=30, **kwargs)
38
+ log.debug("Response %s: %s", resp.status_code, resp.text)
39
+ resp.raise_for_status()
40
+ return resp
41
+
42
+ def create_portfolio(self, config: UploadConfig) -> str:
43
+ """POST /portfolios → portfolio_id 반환."""
44
+ items = [
45
+ {
46
+ "ticker": str(item["ticker"]),
47
+ "type": str(item.get("type", "stock")),
48
+ "quantity": float(item.get("quantity", 0)),
49
+ "ratio": float(item.get("ratio", 0)),
50
+ "price": float(item.get("price", 0)),
51
+ "sector": str(item.get("sector", "N/A")),
52
+ "industry": str(item.get("industry", "N/A")),
53
+ }
54
+ for item in config.items
55
+ ]
56
+ body = {
57
+ "name": config.name,
58
+ "description": config.description,
59
+ "type": config.portfolio_type,
60
+ "currency": config.currency,
61
+ "budget": float(config.budget),
62
+ "target_return": config.target_return,
63
+ "start_date": config.start_date,
64
+ "target_date": config.target_date,
65
+ "items": items,
66
+ }
67
+ if config.settings:
68
+ body["settings"] = config.settings
69
+ resp = self._request("POST", "/portfolios", json=body)
70
+ return resp.json()["portfolio_id"]
71
+
72
+ def get_portfolios(self) -> list[dict]:
73
+ """GET /portfolios → return caller's own portfolios."""
74
+ resp = self._request("GET", "/portfolios")
75
+ return resp.json()
76
+
77
+ def search_portfolios(
78
+ self,
79
+ search: str | None = None,
80
+ country: str | None = None,
81
+ sort_by: str | None = None,
82
+ last_item: str | None = None,
83
+ ) -> dict:
84
+ """GET /portfolios with search params."""
85
+ params = {k: v for k, v in {
86
+ "search": search, "country": country,
87
+ "sort_by": sort_by, "last_item": last_item,
88
+ }.items() if v is not None}
89
+ resp = self._request("GET", "/portfolios", params=params)
90
+ return resp.json()
91
+
92
+ def delete_portfolio(self, portfolio_id: str) -> None:
93
+ """DELETE /portfolios/{portfolio_id}."""
94
+ self._request("DELETE", f"/portfolios/{portfolio_id}")
95
+
96
+ def get_nav_history(self, portfolio_id: str) -> dict:
97
+ """GET /portfolios/{portfolio_id}/nav-history."""
98
+ resp = self._request("GET", f"/portfolios/{portfolio_id}/nav-history")
99
+ return resp.json()
100
+
101
+ def get_metrics_history(
102
+ self,
103
+ portfolio_id: str,
104
+ metrics: str = "twr",
105
+ from_t: int | None = None,
106
+ to_t: int | None = None,
107
+ ) -> dict:
108
+ """GET /portfolios/{portfolio_id}/metrics-history."""
109
+ params: dict = {"metrics": metrics}
110
+ if from_t is not None:
111
+ params["from_t"] = str(from_t)
112
+ if to_t is not None:
113
+ params["to_t"] = str(to_t)
114
+ resp = self._request(
115
+ "GET", f"/portfolios/{portfolio_id}/metrics-history",
116
+ params=params)
117
+ return resp.json()
118
+
119
+ def send_order(self, portfolio_id: str, order_group: OrderGroup, portfolio_currency: str) -> dict:
120
+ """POST /orders → 주문 그룹 하나 전송."""
121
+ body = {
122
+ "portfolio_id": portfolio_id,
123
+ "currency": portfolio_currency,
124
+ "payment_currency": order_group.currency,
125
+ "items": order_group.items,
126
+ }
127
+ if order_group.memo:
128
+ body["memo"] = order_group.memo
129
+ if order_group.exchange_rate:
130
+ body["custom_exchange_rate"] = order_group.exchange_rate
131
+ body["is_custom_exchange_rate"] = True
132
+ if order_group.cash_deposits:
133
+ body["cash_deposits"] = [
134
+ {k: v for k, v in {
135
+ "type": d.type,
136
+ "amount": d.amount,
137
+ "currency": d.currency,
138
+ "ticker": d.ticker,
139
+ "timestamp": d.timestamp,
140
+ }.items() if v is not None}
141
+ for d in order_group.cash_deposits
142
+ ]
143
+ resp = self._request("POST", "/orders", json=body)
144
+ return resp.json() if resp.text else {}
insighta_sdk/models.py ADDED
@@ -0,0 +1,212 @@
1
+ """Data models for Insighta SDK."""
2
+
3
+ import csv
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, timezone, timedelta
7
+ from decimal import Decimal
8
+
9
+ import yaml
10
+
11
+ WORKSPACES_DIR = "workspaces"
12
+ JST = timezone(timedelta(hours=9))
13
+
14
+
15
+ @dataclass
16
+ class Credentials:
17
+ api_key: str
18
+ endpoint: str
19
+
20
+ @property
21
+ def masked_key(self) -> str:
22
+ if len(self.api_key) <= 8:
23
+ return "****"
24
+ return self.api_key[:4] + "****" + self.api_key[-4:]
25
+
26
+ @classmethod
27
+ def from_file(cls, path: str) -> "Credentials":
28
+ with open(path, "r", encoding="utf-8") as f:
29
+ data = yaml.safe_load(f)
30
+ return cls(api_key=data["api_key"], endpoint=data["endpoint"].rstrip("/"))
31
+
32
+
33
+ @dataclass
34
+ class UploadConfig:
35
+ name: str
36
+ description: str
37
+ portfolio_type: str
38
+ currency: str
39
+ budget: Decimal
40
+ balance: Decimal
41
+ order_file: str
42
+ target_return: float = 0.0
43
+ start_date: str = ""
44
+ target_date: str = ""
45
+ items: list = field(default_factory=list)
46
+ cash_deposits_file: str | None = None
47
+ memo_file: str | None = None
48
+ settings: dict | None = None
49
+
50
+ @classmethod
51
+ def from_file(cls, path: str) -> "UploadConfig":
52
+ with open(path, "r", encoding="utf-8") as f:
53
+ data = yaml.safe_load(f)
54
+ p = data["portfolio"]
55
+ files = data.get("files", {})
56
+ return cls(
57
+ name=p["name"],
58
+ description=p.get("description", ""),
59
+ portfolio_type=p["type"],
60
+ currency=p["currency"],
61
+ budget=Decimal(str(p["budget"])),
62
+ balance=Decimal(str(p["budget"])),
63
+ target_return=float(p.get("target_return", 0)),
64
+ start_date=p.get("start_date", ""),
65
+ target_date=p.get("target_date", ""),
66
+ items=p.get("items", []),
67
+ order_file=files["order"],
68
+ cash_deposits_file=files.get("cash_deposits"),
69
+ memo_file=files.get("memo"),
70
+ settings=p.get("settings"),
71
+ )
72
+
73
+
74
+ @dataclass
75
+ class CashDeposit:
76
+ type: str # budget | dividend
77
+ amount: float
78
+ currency: str | None = None
79
+ ticker: str | None = None
80
+ timestamp: int | None = None
81
+
82
+
83
+ @dataclass
84
+ class OrderGroup:
85
+ group_id: str
86
+ currency: str
87
+ items: list = field(default_factory=list)
88
+ cash_deposits: list[CashDeposit] = field(default_factory=list)
89
+ exchange_rate: float | None = None
90
+ memo: str = ""
91
+
92
+
93
+ @dataclass
94
+ class Trade:
95
+ dt: str # ISO 8601 JST
96
+ ticker: str
97
+ qty: int
98
+ acct: str
99
+ price: Decimal
100
+ avg: Decimal
101
+ cur: str # 決済通貨 (JPY or USD)
102
+ base: str = "USD" # 銘柄の基準通貨
103
+
104
+
105
+ @dataclass
106
+ class Holding:
107
+ ticker: str
108
+ acct: str
109
+ qty: int
110
+ cost: Decimal = Decimal("0")
111
+ price: Decimal = Decimal("0")
112
+ pnl: Decimal = Decimal("0")
113
+
114
+
115
+ @dataclass
116
+ class Deposit:
117
+ dt: str # ISO 8601 JST or raw datetime
118
+ amount: Decimal
119
+ cur: str
120
+ type: str = "budget" # budget | dividend
121
+ ticker: str = ""
122
+ rate: Decimal | None = None
123
+
124
+
125
+ @dataclass
126
+ class RateEntry:
127
+ start: str
128
+ end: str
129
+ pair: str
130
+ rate: Decimal
131
+
132
+
133
+ @dataclass
134
+ class Dirs:
135
+ """作業ディレクトリ設定。--work オプションで切り替え可能。"""
136
+ work: str = ""
137
+
138
+ @classmethod
139
+ def from_work(cls, work: str = "") -> "Dirs":
140
+ return cls(work=work)
141
+
142
+ @property
143
+ def _base(self) -> str:
144
+ return os.path.join(WORKSPACES_DIR, self.work) if self.work else ""
145
+
146
+ @property
147
+ def input(self) -> str:
148
+ return os.path.join(self._base, "input") if self._base else "input"
149
+
150
+ @property
151
+ def output(self) -> str:
152
+ return os.path.join(self._base, "output") if self._base else "output"
153
+
154
+ @property
155
+ def history(self) -> str:
156
+ return os.path.join(self.input, "history")
157
+
158
+ @property
159
+ def summary(self) -> str:
160
+ return os.path.join(self.input, "summary")
161
+
162
+ @property
163
+ def seed(self) -> str:
164
+ return os.path.join(self.input, "seed")
165
+
166
+ @property
167
+ def deposit(self) -> str:
168
+ return os.path.join(self.input, "deposit")
169
+
170
+ @property
171
+ def exchange(self) -> str:
172
+ return os.path.join(self.input, "currency_exchange")
173
+
174
+ @property
175
+ def manual(self) -> str:
176
+ return os.path.join(self.input, "manual")
177
+
178
+ @property
179
+ def rate_csv(self) -> str:
180
+ return os.path.join(self.input, "rate.csv")
181
+
182
+ @property
183
+ def ratio_csv(self) -> str:
184
+ return os.path.join(self.input, "ratio.csv")
185
+
186
+ @property
187
+ def history_csv(self) -> str:
188
+ return os.path.join(self.output, "history.csv")
189
+
190
+ @property
191
+ def order_csv(self) -> str:
192
+ return os.path.join(self.output, "order.csv")
193
+
194
+ @property
195
+ def upload_yaml(self) -> str:
196
+ return os.path.join(self.output, "upload.yaml")
197
+
198
+ @property
199
+ def memo_csv(self) -> str:
200
+ return os.path.join(self.output, "memo.csv")
201
+
202
+ @property
203
+ def cash_deposits_csv(self) -> str:
204
+ return os.path.join(self.output, "cash_deposits.csv")
205
+
206
+ @property
207
+ def request_payload_log(self) -> str:
208
+ return os.path.join(self.output, "request_payload.log")
209
+
210
+ def ensure_output(self):
211
+ """output ディレクトリを作成する。"""
212
+ os.makedirs(self.output, exist_ok=True)
insighta_sdk/utils.py ADDED
@@ -0,0 +1,148 @@
1
+ """Utility functions for Insighta SDK."""
2
+
3
+ import csv
4
+ from collections import OrderedDict
5
+ from datetime import datetime, timezone
6
+ from decimal import Decimal
7
+
8
+ import requests
9
+
10
+ from .models import CashDeposit, OrderGroup, RateEntry
11
+
12
+
13
+ def _parse_timestamp(val: str) -> int | None:
14
+ if not val:
15
+ return None
16
+ try:
17
+ return int(val)
18
+ except ValueError:
19
+ pass
20
+ for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"):
21
+ try:
22
+ return int(datetime.strptime(val, fmt).replace(tzinfo=timezone.utc).timestamp() * 1000)
23
+ except ValueError:
24
+ continue
25
+ return None
26
+
27
+
28
+ def load_order_groups(filepath: str) -> list[OrderGroup]:
29
+ """order.csv를 읽어서 group_dt별로 묶어 반환."""
30
+ groups: OrderedDict[str, OrderGroup] = OrderedDict()
31
+ with open(filepath, "r", encoding="utf-8") as f:
32
+ for row in csv.DictReader(f):
33
+ gdt = row["group_dt"]
34
+ rate_val = row.get("rate", "").strip() if row.get("rate") else ""
35
+ if gdt not in groups:
36
+ groups[gdt] = OrderGroup(
37
+ group_id=gdt,
38
+ currency=row.get("settle_currency", row["currency"]),
39
+ exchange_rate=float(rate_val) if rate_val else None,
40
+ )
41
+ groups[gdt].items.append({
42
+ "id": row["ticker"],
43
+ "ticker": row["ticker"],
44
+ "quantity": float(row["quantity"]),
45
+ "price": float(row["price"]),
46
+ "currency": row["currency"],
47
+ "price_type": row["price_type"],
48
+ "timestamp": _parse_timestamp(row.get("timestamp", "")),
49
+ })
50
+ return list(groups.values())
51
+
52
+
53
+ def load_cash_deposits(filepath: str) -> dict[str, list[CashDeposit]]:
54
+ """cash_deposits.csv를 읽어서 group_dt별로 묶어 반환."""
55
+ groups: dict[str, list[CashDeposit]] = {}
56
+ with open(filepath, "r", encoding="utf-8") as f:
57
+ for row in csv.DictReader(f):
58
+ gdt = row["group_dt"]
59
+ groups.setdefault(gdt, []).append(CashDeposit(
60
+ type=row["type"],
61
+ amount=float(row["amount"]),
62
+ currency=row.get("currency") or None,
63
+ ticker=row.get("ticker") or None,
64
+ timestamp=_parse_timestamp(row.get("timestamp", "")),
65
+ ))
66
+ return groups
67
+
68
+
69
+ def merge_and_sort_groups(
70
+ orders: list[OrderGroup],
71
+ deposits_by_gdt: dict[str, list[CashDeposit]],
72
+ memos: dict[str, str],
73
+ ) -> list[OrderGroup]:
74
+ """order + deposit을 group_dt 기준으로 머지하고 시간순 정렬."""
75
+ existing_gdts = {g.group_id for g in orders}
76
+ for gdt, deps in deposits_by_gdt.items():
77
+ if gdt not in existing_gdts:
78
+ cur = deps[0].currency or "USD"
79
+ orders.append(OrderGroup(group_id=gdt, currency=cur))
80
+ deps_map = dict(deposits_by_gdt)
81
+ for g in orders:
82
+ if g.group_id in deps_map:
83
+ g.cash_deposits = deps_map[g.group_id]
84
+
85
+ def _sort_key(g: OrderGroup):
86
+ ts = _parse_timestamp(g.group_id)
87
+ return ts if ts is not None else float("inf")
88
+ orders.sort(key=_sort_key)
89
+ for i, g in enumerate(orders, 1):
90
+ g.group_id = str(i)
91
+ for g in orders:
92
+ if g.group_id in memos:
93
+ g.memo = memos[g.group_id]
94
+ return orders
95
+
96
+
97
+ def fetch_ticker_info(tickers: list[str]) -> dict[str, dict]:
98
+ """Insighta /tickers/info API로 sector/industry/type 조회."""
99
+ if not tickers:
100
+ return {}
101
+ resp = requests.get(
102
+ "https://api.insighta.cloud/tickers/info",
103
+ params={"tickers": ",".join(tickers), "conditions": "sector,industry,type"},
104
+ timeout=10,
105
+ )
106
+ resp.raise_for_status()
107
+ return resp.json()
108
+
109
+
110
+ def load_rate_file(filepath: str) -> list[RateEntry]:
111
+ """為替レートCSVを読み込む。
112
+
113
+ CSV format:
114
+ from,to,pair,rate
115
+ 2024/01/01,2024/12/31,USD/JPY,155.50
116
+ """
117
+ entries: list[RateEntry] = []
118
+ with open(filepath, "r", encoding="utf-8") as f:
119
+ for row in csv.DictReader(f):
120
+ entries.append(RateEntry(
121
+ start=row["from"].strip(),
122
+ end=row["to"].strip(),
123
+ pair=row["pair"].strip(),
124
+ rate=Decimal(row["rate"].strip()),
125
+ ))
126
+ return entries
127
+
128
+
129
+ def _normalize_dt(val: str) -> str:
130
+ return val if " " in val else f"{val} 00:00"
131
+
132
+
133
+ def _normalize_dt_end(val: str) -> str:
134
+ return val if " " in val else f"{val} 23:59"
135
+
136
+
137
+ def lookup_rate(entries: list[RateEntry], dt: str, cur: str, base: str) -> Decimal | None:
138
+ """決済通貨と基準通貨が異なる場合のみ該当期間のレートを返す。"""
139
+ if cur == base:
140
+ return None
141
+ trade_dt = dt[:16].replace("-", "/").replace("T", " ") if dt else ""
142
+ pair = f"{base}/{cur}"
143
+ for e in entries:
144
+ start = _normalize_dt(e.start)
145
+ end = _normalize_dt_end(e.end)
146
+ if e.pair == pair and start <= trade_dt <= end:
147
+ return e.rate
148
+ return None
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: insighta-sdk
3
+ Version: 0.1.0
4
+ Summary: Insighta Cloud SDK - API client and data models
5
+ Project-URL: Homepage, https://insighta.cloud
6
+ Author: insighta cloud Inc.
7
+ License-Expression: CC-BY-NC-4.0
8
+ License-File: LICENSE
9
+ Keywords: api-client,insighta,investment,portfolio,sdk
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: Other/Proprietary License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: requests>=2.28.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # insighta-sdk
25
+
26
+ Insighta Cloud SDK — API client and data models for portfolio management.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install insighta-sdk
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from insighta_sdk import Credentials, InsightaClient
38
+
39
+ creds = Credentials.from_file("credentials.yaml")
40
+ client = InsightaClient(creds)
41
+
42
+ # List portfolios
43
+ portfolios = client.get_portfolios()
44
+
45
+ # Create a portfolio
46
+ from insighta_sdk import UploadConfig
47
+ config = UploadConfig.from_file("upload.yaml")
48
+ portfolio_id = client.create_portfolio(config)
49
+ ```
50
+
51
+ ## Features
52
+
53
+ - **API Client** — Full coverage of Insighta OpenAPI (portfolios, orders, metrics)
54
+ - **Data Models** — `Trade`, `Holding`, `Deposit`, `OrderGroup`, `CashDeposit`, `RateEntry`
55
+ - **Utilities** — Rate lookup, order grouping, deposit merging
56
+ - **Workspace Management** — `Dirs` class for consistent file path resolution
57
+
58
+ ## API Reference
59
+
60
+ ### Client
61
+
62
+ | Method | Description |
63
+ |--------|-------------|
64
+ | `create_portfolio(config)` | Create a new portfolio |
65
+ | `get_portfolios()` | List own portfolios |
66
+ | `search_portfolios(...)` | Search public portfolios |
67
+ | `delete_portfolio(id)` | Delete a portfolio |
68
+ | `send_order(portfolio_id, group, currency)` | Submit an order group |
69
+ | `get_nav_history(id)` | Get NAV history |
70
+ | `get_metrics_history(id, ...)` | Get metrics (TWR, etc.) |
71
+
72
+ ### Utilities
73
+
74
+ | Function | Description |
75
+ |----------|-------------|
76
+ | `load_order_groups(path)` | Parse order.csv into OrderGroup list |
77
+ | `load_cash_deposits(path)` | Parse cash_deposits.csv |
78
+ | `merge_and_sort_groups(orders, deposits, memos)` | Merge and sequence order groups |
79
+ | `load_rate_file(path)` | Load exchange rate CSV |
80
+ | `lookup_rate(entries, dt, cur, base)` | Find applicable rate for a trade |
81
+ | `fetch_ticker_info(tickers)` | Query ticker metadata from API |
82
+
83
+ ## API Endpoints
84
+
85
+ | Environment | Base URL |
86
+ |-------------|----------|
87
+ | Production | `https://openapi.insighta.cloud` |
88
+ | Development | `https://dev.openapi.insighta.cloud` |
89
+
90
+ OpenAPI spec: [`insighta-app/openapi-docs/`](https://github.com/insighta-cloud/insighta/tree/main/insighta-app/openapi-docs)
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ pip install -e .
96
+ pytest
97
+ ```
98
+
99
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
100
+
101
+ ## License
102
+
103
+ CC-BY-NC-4.0 — See [LICENSE](LICENSE)
@@ -0,0 +1,8 @@
1
+ insighta_sdk/__init__.py,sha256=O-Mb_GJmDk0gyP6gFsM6S0_c1kS5AkPCJluYdwjMsy0,733
2
+ insighta_sdk/client.py,sha256=juCH5LDHkdUYGh-KBuX1nRPnsFwB9o5OuiHoO11loos,5410
3
+ insighta_sdk/models.py,sha256=7zLmqyDiJROf2pqrXFK3cWXUYBxlnFIajImcjeBBIfo,5308
4
+ insighta_sdk/utils.py,sha256=QaQU1K962Yo3vCbXf4A1Rx9_0q12X_1AXaXtL85jJe0,5015
5
+ insighta_sdk-0.1.0.dist-info/METADATA,sha256=HuulJU140OjxAaDztFmesd-51rEXtt08gGK73B2bL7g,3142
6
+ insighta_sdk-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ insighta_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=WPu_gaVv4rw7ucpTiIFHcubHh0VS_s5PI6RQJAz98l0,712
8
+ insighta_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,20 @@
1
+ Creative Commons Attribution-NonCommercial 4.0 International
2
+
3
+ Copyright (c) 2026 Insighta Cloud Inc.
4
+
5
+ You are free to:
6
+
7
+ Share — copy and redistribute the material in any medium or format
8
+ Adapt — remix, transform, and build upon the material
9
+
10
+ Under the following terms:
11
+
12
+ Attribution — You must give appropriate credit, provide a link to the
13
+ license, and indicate if changes were made.
14
+
15
+ NonCommercial — You may not use the material for commercial purposes.
16
+
17
+ No additional restrictions — You may not apply legal terms or technological
18
+ measures that legally restrict others from doing anything the license permits.
19
+
20
+ Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode