creacortex 0.2.0__tar.gz → 0.3.0__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.
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0
4
+
5
+ Segment-aware runs: `app or game × platform`.
6
+
7
+ ### Breaking
8
+
9
+ - `calibration_commit` / `calibrationCommit` now require `app_or_game` and `platform`
10
+ (`appOrGame` / `platform` in Node). Calibration is fitted **per pair**, and the tags apply to
11
+ the whole uploaded file.
12
+
13
+ ### Added
14
+
15
+ - `create_run` / `createRun` and `analyze` accept optional `app_or_game` + `platform`
16
+ (`appOrGame` / `platform`). Pass **both or neither** — a half pair is rejected with `422`.
17
+ Unknown values are created automatically; spelling is normalised, so `" applovin "` resolves
18
+ to the existing `"AppLovin"` instead of creating a duplicate.
19
+ - `get_segments()` / `getSegments()` — known tags and per-pair sample sizes, so you can see what
20
+ a forecast would be based on before spending credits. Platforms ship with a shared base list;
21
+ apps and games are private to your account.
22
+ - `Segments` / `SegmentPair` models. A pair's `state` is `ok` (>= `min_size`), `thin`
23
+ (provisional) or `empty` — **no forecast is issued** for an empty pair. Python exposes
24
+ `SegmentPair.forecastable`.
25
+ - `Run` and `RunListItem` carry `app_or_game` / `platform` (`appOrGame` / `platform`), `None`
26
+ when the run is untagged.
27
+
28
+ ### Notes
29
+
30
+ - Tags are optional. An untagged run behaves exactly as before: the forecast is compared against
31
+ all videos in the project.
32
+ - Excel exports now import as-is — semicolon separators, a leading BOM and decimal commas
33
+ (`3,55`) are detected automatically.
34
+
35
+ ## 0.2.0
36
+
37
+ Calibration endpoints, typed errors, `analyze()` one-call helper.
38
+
39
+ ## 0.1.0
40
+
41
+ Initial release.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: creacortex
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python client for the Creacortex video-creative attention-analysis API
5
5
  Project-URL: Homepage, https://creacortex.ai
6
6
  Author: Creacortex
@@ -40,7 +40,8 @@ from creacortex import Client
40
40
 
41
41
  with Client("ace_your_api_key") as cx:
42
42
  # one call: upload + start + wait for completion
43
- run = cx.analyze("ad.mp4", with_audio=True, creative_id="summer-promo")
43
+ run = cx.analyze("ad.mp4", with_audio=True, creative_id="summer-promo",
44
+ app_or_game="Raid Shadow Legends", platform="AppLovin")
44
45
 
45
46
  csv = cx.get_csv(run.run_id) # per-second analysis (bytes)
46
47
  payload = cx.get_export(run.run_id) # Gen-AI insights (dict)
@@ -81,17 +82,57 @@ print(cx.get_account().balance) # credits left
81
82
 
82
83
  `upload_video` / `analyze` accept a file path, raw `bytes`, or a binary file object.
83
84
 
85
+ ## Segments: app or game × platform
86
+
87
+ A forecast is only meaningful against comparable creatives, so runs can be tagged with an
88
+ **app or game** and a **platform**. When both are set, the prediction and the percentiles are
89
+ computed **only from creatives of that same pair**.
90
+
91
+ ```python
92
+ run = cx.create_run(video_id, app_or_game="Raid Shadow Legends", platform="AppLovin")
93
+ run.app_or_game # "Raid Shadow Legends" — canonical spelling as stored
94
+ ```
95
+
96
+ Tags are optional. Leave them out and the run stays untagged: the forecast is compared against
97
+ all videos in the project, exactly as before. Pass **both or neither** — one alone is rejected.
98
+
99
+ Unknown values are created automatically, and spelling is normalised: `" applovin "` resolves to
100
+ the existing `"AppLovin"` instead of creating a duplicate. Platforms come with a shared base list
101
+ (AppLovin, Unity Ads, Mintegral, ironSource, Liftoff, Moloco, Meta Ads, Google Ads, TikTok Ads,
102
+ Pangle, Chartboost, Digital Turbine); apps and games are private to your account.
103
+
104
+ Check what a forecast would be based on before you spend credits:
105
+
106
+ ```python
107
+ segs = cx.get_segments()
108
+ segs.apps_or_games # your apps
109
+ segs.platforms # base list + your own
110
+ segs.min_size # 50 — below this a forecast is flagged as provisional
111
+
112
+ for pair in segs.pairs:
113
+ print(pair.app_or_game, pair.platform, pair.size, pair.state, pair.forecastable)
114
+ ```
115
+
116
+ Pair states: **`ok`** (>= `min_size`) — normal forecast; **`thin`** (1..`min_size`-1) — returned
117
+ but provisional; **`empty`** (no data) — **no forecast is issued** for that run. Upload calibration
118
+ data for the pair to unlock it.
119
+
84
120
  ## Calibration
85
121
 
86
122
  Improve prediction accuracy (and unlock all metrics) by calibrating on your real performance data.
87
- Any CSV works — map your columns to our fields:
123
+ Any CSV works — map your columns to our fields. Calibration is fitted **per segment**, so
124
+ `app_or_game` and `platform` are required on commit: they tag the whole file.
125
+
126
+ Exports from Excel are handled as-is — semicolon separators, a leading BOM and decimal commas
127
+ (`3,55`) are all detected automatically.
88
128
 
89
129
  ```python
90
130
  csv = open("performance.csv", "rb").read()
91
131
  a = cx.calibration_analyze(csv) # detected columns + guessed mapping
92
132
  mapping = {**a["guess"], "creative_id": "Ad name"} # adjust if needed
93
133
  cx.calibration_preview(csv, mapping, min_impressions=50000) # cost + impression split
94
- cx.calibration_commit(csv, mapping, process_unmatched=True) # charge + fit; returns summary
134
+ cx.calibration_commit(csv, mapping, process_unmatched=True, # charge + fit; returns summary
135
+ app_or_game="Raid Shadow Legends", platform="AppLovin")
95
136
 
96
137
  cx.get_account().calibrated # True once fitted
97
138
  ```
@@ -1,99 +1,140 @@
1
- # creacortex
2
-
3
- Python client for the **Creacortex** video-creative attention-analysis API.
4
-
5
- Upload a video creative, run it through the pipeline, and pull back per-second
6
- attention data, a Gen-AI optimization payload, and PDF reports.
7
-
8
- ## Install
9
-
10
- ```bash
11
- pip install creacortex
12
- ```
13
-
14
- ## Quick start
15
-
16
- ```python
17
- from creacortex import Client
18
-
19
- with Client("ace_your_api_key") as cx:
20
- # one call: upload + start + wait for completion
21
- run = cx.analyze("ad.mp4", with_audio=True, creative_id="summer-promo")
22
-
23
- csv = cx.get_csv(run.run_id) # per-second analysis (bytes)
24
- payload = cx.get_export(run.run_id) # Gen-AI insights (dict)
25
- pdf = cx.get_report(run.run_id, "diagnose") # PDF report (bytes)
26
- ```
27
-
28
- Get an API key from **Usage & billing → API keys** in the dashboard. It is sent
29
- as the `X-API-Key` header.
30
-
31
- ## Async
32
-
33
- ```python
34
- import asyncio
35
- from creacortex import AsyncClient
36
-
37
- async def main():
38
- async with AsyncClient("ace_your_api_key") as cx:
39
- run = await cx.analyze("ad.mp4")
40
- print(await cx.get_export(run.run_id))
41
-
42
- asyncio.run(main())
43
- ```
44
-
45
- ## Step by step
46
-
47
- `analyze()` is a convenience over the raw endpoints, which you can also call directly:
48
-
49
- ```python
50
- video_id = cx.upload_video("ad.mp4")
51
- run = cx.create_run(video_id, with_audio=True, audience="men 20-45, mobile gamers")
52
- run = cx.wait(run.run_id) # poll until done (or RunFailedError / WaitTimeout)
53
-
54
- for item in cx.list_runs():
55
- print(item.run_id, item.status, item.ready)
56
-
57
- print(cx.get_account().balance) # credits left
58
- ```
59
-
60
- `upload_video` / `analyze` accept a file path, raw `bytes`, or a binary file object.
61
-
62
- ## Calibration
63
-
64
- Improve prediction accuracy (and unlock all metrics) by calibrating on your real performance data.
65
- Any CSV works — map your columns to our fields:
66
-
67
- ```python
68
- csv = open("performance.csv", "rb").read()
69
- a = cx.calibration_analyze(csv) # detected columns + guessed mapping
70
- mapping = {**a["guess"], "creative_id": "Ad name"} # adjust if needed
71
- cx.calibration_preview(csv, mapping, min_impressions=50000) # cost + impression split
72
- cx.calibration_commit(csv, mapping, process_unmatched=True) # charge + fit; returns summary
73
-
74
- cx.get_account().calibrated # True once fitted
75
- ```
76
-
77
- ## Errors
78
-
79
- | Exception | When |
80
- |---|---|
81
- | `AuthError` | invalid/missing API key (401) |
82
- | `InsufficientCreditsError` | not enough credits (402) |
83
- | `RateLimitError` | too many requests (429) |
84
- | `NotFoundError` | unknown run (404) |
85
- | `RunFailedError` | the run finished with `status="failed"` |
86
- | `WaitTimeout` | `wait()` exceeded its timeout |
87
- | `ApiError` | any other HTTP error (has `.status_code`) |
88
-
89
- All inherit from `CreacortexError`.
90
-
91
- ## Configuration
92
-
93
- ```python
94
- Client("ace_...", base_url="https://api.creacortex.ai", timeout=60.0)
95
- ```
96
-
97
- ## License
98
-
99
- MIT
1
+ # creacortex
2
+
3
+ Python client for the **Creacortex** video-creative attention-analysis API.
4
+
5
+ Upload a video creative, run it through the pipeline, and pull back per-second
6
+ attention data, a Gen-AI optimization payload, and PDF reports.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install creacortex
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ from creacortex import Client
18
+
19
+ with Client("ace_your_api_key") as cx:
20
+ # one call: upload + start + wait for completion
21
+ run = cx.analyze("ad.mp4", with_audio=True, creative_id="summer-promo",
22
+ app_or_game="Raid Shadow Legends", platform="AppLovin")
23
+
24
+ csv = cx.get_csv(run.run_id) # per-second analysis (bytes)
25
+ payload = cx.get_export(run.run_id) # Gen-AI insights (dict)
26
+ pdf = cx.get_report(run.run_id, "diagnose") # PDF report (bytes)
27
+ ```
28
+
29
+ Get an API key from **Usage & billing → API keys** in the dashboard. It is sent
30
+ as the `X-API-Key` header.
31
+
32
+ ## Async
33
+
34
+ ```python
35
+ import asyncio
36
+ from creacortex import AsyncClient
37
+
38
+ async def main():
39
+ async with AsyncClient("ace_your_api_key") as cx:
40
+ run = await cx.analyze("ad.mp4")
41
+ print(await cx.get_export(run.run_id))
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ## Step by step
47
+
48
+ `analyze()` is a convenience over the raw endpoints, which you can also call directly:
49
+
50
+ ```python
51
+ video_id = cx.upload_video("ad.mp4")
52
+ run = cx.create_run(video_id, with_audio=True, audience="men 20-45, mobile gamers")
53
+ run = cx.wait(run.run_id) # poll until done (or RunFailedError / WaitTimeout)
54
+
55
+ for item in cx.list_runs():
56
+ print(item.run_id, item.status, item.ready)
57
+
58
+ print(cx.get_account().balance) # credits left
59
+ ```
60
+
61
+ `upload_video` / `analyze` accept a file path, raw `bytes`, or a binary file object.
62
+
63
+ ## Segments: app or game × platform
64
+
65
+ A forecast is only meaningful against comparable creatives, so runs can be tagged with an
66
+ **app or game** and a **platform**. When both are set, the prediction and the percentiles are
67
+ computed **only from creatives of that same pair**.
68
+
69
+ ```python
70
+ run = cx.create_run(video_id, app_or_game="Raid Shadow Legends", platform="AppLovin")
71
+ run.app_or_game # "Raid Shadow Legends" — canonical spelling as stored
72
+ ```
73
+
74
+ Tags are optional. Leave them out and the run stays untagged: the forecast is compared against
75
+ all videos in the project, exactly as before. Pass **both or neither** — one alone is rejected.
76
+
77
+ Unknown values are created automatically, and spelling is normalised: `" applovin "` resolves to
78
+ the existing `"AppLovin"` instead of creating a duplicate. Platforms come with a shared base list
79
+ (AppLovin, Unity Ads, Mintegral, ironSource, Liftoff, Moloco, Meta Ads, Google Ads, TikTok Ads,
80
+ Pangle, Chartboost, Digital Turbine); apps and games are private to your account.
81
+
82
+ Check what a forecast would be based on before you spend credits:
83
+
84
+ ```python
85
+ segs = cx.get_segments()
86
+ segs.apps_or_games # your apps
87
+ segs.platforms # base list + your own
88
+ segs.min_size # 50 — below this a forecast is flagged as provisional
89
+
90
+ for pair in segs.pairs:
91
+ print(pair.app_or_game, pair.platform, pair.size, pair.state, pair.forecastable)
92
+ ```
93
+
94
+ Pair states: **`ok`** (>= `min_size`) — normal forecast; **`thin`** (1..`min_size`-1) — returned
95
+ but provisional; **`empty`** (no data) — **no forecast is issued** for that run. Upload calibration
96
+ data for the pair to unlock it.
97
+
98
+ ## Calibration
99
+
100
+ Improve prediction accuracy (and unlock all metrics) by calibrating on your real performance data.
101
+ Any CSV works — map your columns to our fields. Calibration is fitted **per segment**, so
102
+ `app_or_game` and `platform` are required on commit: they tag the whole file.
103
+
104
+ Exports from Excel are handled as-is — semicolon separators, a leading BOM and decimal commas
105
+ (`3,55`) are all detected automatically.
106
+
107
+ ```python
108
+ csv = open("performance.csv", "rb").read()
109
+ a = cx.calibration_analyze(csv) # detected columns + guessed mapping
110
+ mapping = {**a["guess"], "creative_id": "Ad name"} # adjust if needed
111
+ cx.calibration_preview(csv, mapping, min_impressions=50000) # cost + impression split
112
+ cx.calibration_commit(csv, mapping, process_unmatched=True, # charge + fit; returns summary
113
+ app_or_game="Raid Shadow Legends", platform="AppLovin")
114
+
115
+ cx.get_account().calibrated # True once fitted
116
+ ```
117
+
118
+ ## Errors
119
+
120
+ | Exception | When |
121
+ |---|---|
122
+ | `AuthError` | invalid/missing API key (401) |
123
+ | `InsufficientCreditsError` | not enough credits (402) |
124
+ | `RateLimitError` | too many requests (429) |
125
+ | `NotFoundError` | unknown run (404) |
126
+ | `RunFailedError` | the run finished with `status="failed"` |
127
+ | `WaitTimeout` | `wait()` exceeded its timeout |
128
+ | `ApiError` | any other HTTP error (has `.status_code`) |
129
+
130
+ All inherit from `CreacortexError`.
131
+
132
+ ## Configuration
133
+
134
+ ```python
135
+ Client("ace_...", base_url="https://api.creacortex.ai", timeout=60.0)
136
+ ```
137
+
138
+ ## License
139
+
140
+ MIT
@@ -1,44 +1,44 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "creacortex"
7
- version = "0.2.0"
8
- description = "Python client for the Creacortex video-creative attention-analysis API"
9
- readme = "README.md"
10
- requires-python = ">=3.9"
11
- license = { text = "MIT" }
12
- authors = [{ name = "Creacortex" }]
13
- keywords = ["creacortex", "video", "advertising", "attention", "analytics", "api"]
14
- classifiers = [
15
- "Programming Language :: Python :: 3",
16
- "License :: OSI Approved :: MIT License",
17
- "Operating System :: OS Independent",
18
- "Intended Audience :: Developers",
19
- ]
20
- dependencies = ["httpx>=0.24"]
21
-
22
- [project.optional-dependencies]
23
- dev = ["pytest>=7", "pytest-asyncio>=0.23", "ruff>=0.4", "mypy>=1.8"]
24
-
25
- [project.urls]
26
- Homepage = "https://creacortex.ai"
27
-
28
- [tool.hatch.build.targets.wheel]
29
- packages = ["src/creacortex"]
30
-
31
- [tool.ruff]
32
- line-length = 100
33
- src = ["src", "tests"]
34
-
35
- [tool.ruff.lint]
36
- select = ["E", "F", "I", "UP", "B"]
37
-
38
- [tool.mypy]
39
- strict = true
40
- files = ["src/creacortex"]
41
-
42
- [tool.pytest.ini_options]
43
- asyncio_mode = "auto"
44
- testpaths = ["tests"]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "creacortex"
7
+ version = "0.3.0"
8
+ description = "Python client for the Creacortex video-creative attention-analysis API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Creacortex" }]
13
+ keywords = ["creacortex", "video", "advertising", "attention", "analytics", "api"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Intended Audience :: Developers",
19
+ ]
20
+ dependencies = ["httpx>=0.24"]
21
+
22
+ [project.optional-dependencies]
23
+ dev = ["pytest>=7", "pytest-asyncio>=0.23", "ruff>=0.4", "mypy>=1.8"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://creacortex.ai"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/creacortex"]
30
+
31
+ [tool.ruff]
32
+ line-length = 100
33
+ src = ["src", "tests"]
34
+
35
+ [tool.ruff.lint]
36
+ select = ["E", "F", "I", "UP", "B"]
37
+
38
+ [tool.mypy]
39
+ strict = true
40
+ files = ["src/creacortex"]
41
+
42
+ [tool.pytest.ini_options]
43
+ asyncio_mode = "auto"
44
+ testpaths = ["tests"]
@@ -1,39 +1,41 @@
1
- """Creacortex — Python client for the video-creative attention-analysis API.
2
-
3
- from creacortex import Client
4
-
5
- with Client("ace_...") as cx:
6
- run = cx.analyze("ad.mp4", with_audio=True) # upload + run + wait
7
- cx.get_report(run.run_id, "diagnose") # PDF bytes
8
- """
9
- from .client import DEFAULT_BASE_URL, AsyncClient, Client
10
- from .errors import (
11
- ApiError,
12
- AuthError,
13
- CreacortexError,
14
- InsufficientCreditsError,
15
- NotFoundError,
16
- RateLimitError,
17
- RunFailedError,
18
- WaitTimeout,
19
- )
20
- from .models import Account, Run, RunListItem
21
-
22
- __version__ = "0.2.0"
23
-
24
- __all__ = [
25
- "DEFAULT_BASE_URL",
26
- "Client",
27
- "AsyncClient",
28
- "Run",
29
- "RunListItem",
30
- "Account",
31
- "CreacortexError",
32
- "ApiError",
33
- "AuthError",
34
- "InsufficientCreditsError",
35
- "RateLimitError",
36
- "NotFoundError",
37
- "RunFailedError",
38
- "WaitTimeout",
39
- ]
1
+ """Creacortex — Python client for the video-creative attention-analysis API.
2
+
3
+ from creacortex import Client
4
+
5
+ with Client("ace_...") as cx:
6
+ run = cx.analyze("ad.mp4", with_audio=True) # upload + run + wait
7
+ cx.get_report(run.run_id, "diagnose") # PDF bytes
8
+ """
9
+ from .client import DEFAULT_BASE_URL, AsyncClient, Client
10
+ from .errors import (
11
+ ApiError,
12
+ AuthError,
13
+ CreacortexError,
14
+ InsufficientCreditsError,
15
+ NotFoundError,
16
+ RateLimitError,
17
+ RunFailedError,
18
+ WaitTimeout,
19
+ )
20
+ from .models import Account, Run, RunListItem, SegmentPair, Segments
21
+
22
+ __version__ = "0.2.0"
23
+
24
+ __all__ = [
25
+ "DEFAULT_BASE_URL",
26
+ "Client",
27
+ "AsyncClient",
28
+ "Run",
29
+ "RunListItem",
30
+ "SegmentPair",
31
+ "Segments",
32
+ "Account",
33
+ "CreacortexError",
34
+ "ApiError",
35
+ "AuthError",
36
+ "InsufficientCreditsError",
37
+ "RateLimitError",
38
+ "NotFoundError",
39
+ "RunFailedError",
40
+ "WaitTimeout",
41
+ ]