featrix-modelcard 1.10__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.
- featrix_modelcard-1.10/MANIFEST.in +4 -0
- featrix_modelcard-1.10/PKG-INFO +116 -0
- featrix_modelcard-1.10/README.md +90 -0
- featrix_modelcard-1.10/featrix_modelcard/__init__.py +67 -0
- featrix_modelcard-1.10/featrix_modelcard/html_renderer.py +123 -0
- featrix_modelcard-1.10/featrix_modelcard/text_renderer.py +406 -0
- featrix_modelcard-1.10/featrix_modelcard.egg-info/PKG-INFO +116 -0
- featrix_modelcard-1.10/featrix_modelcard.egg-info/SOURCES.txt +10 -0
- featrix_modelcard-1.10/featrix_modelcard.egg-info/dependency_links.txt +1 -0
- featrix_modelcard-1.10/featrix_modelcard.egg-info/top_level.txt +1 -0
- featrix_modelcard-1.10/setup.cfg +4 -0
- featrix_modelcard-1.10/setup.py +30 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: featrix-modelcard
|
|
3
|
+
Version: 1.10
|
|
4
|
+
Summary: Render Featrix Sphere Model Card JSON to HTML or plain text
|
|
5
|
+
Home-page: https://github.com/featrix/model-card
|
|
6
|
+
Author: Featrix
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Requires-Python: >=3.7
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
# Featrix Model Card - Python Package
|
|
28
|
+
|
|
29
|
+
Python package for rendering Featrix Model Card JSON to HTML or plain text.
|
|
30
|
+
|
|
31
|
+
**HTML rendering** delegates to the canonical JavaScript renderer loaded from CDN,
|
|
32
|
+
ensuring the Python output always matches the JS version.
|
|
33
|
+
|
|
34
|
+
**Text rendering** is a standalone Python implementation for terminal/log output.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install featrix-modelcard
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### HTML Renderer
|
|
45
|
+
|
|
46
|
+
The HTML renderer generates a standalone page that loads `model-card.js` from the
|
|
47
|
+
Featrix CDN. The JavaScript renderer handles all layout, styling, and interactivity.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from featrix_modelcard import render_html, render_html_to_file
|
|
51
|
+
|
|
52
|
+
# Load model card JSON
|
|
53
|
+
import json
|
|
54
|
+
with open('model_card.json', 'r') as f:
|
|
55
|
+
model_card = json.load(f)
|
|
56
|
+
|
|
57
|
+
# Render to file
|
|
58
|
+
render_html_to_file(model_card, 'output.html')
|
|
59
|
+
|
|
60
|
+
# Render to string
|
|
61
|
+
html = render_html(model_card)
|
|
62
|
+
|
|
63
|
+
# With 3D sphere visualization
|
|
64
|
+
html = render_html(model_card, show_sphere=True)
|
|
65
|
+
|
|
66
|
+
# With explicit session ID for sphere
|
|
67
|
+
html = render_html(model_card, show_sphere=True, session_id='my-session-id')
|
|
68
|
+
|
|
69
|
+
# Override CDN URL (e.g. for local development)
|
|
70
|
+
html = render_html(model_card, cdn_url='http://localhost:8080/model-card.js')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Text Renderer
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from featrix_modelcard import render_brief_text, render_detailed_text, print_text
|
|
77
|
+
|
|
78
|
+
# Brief summary
|
|
79
|
+
print_text(model_card, detailed=False)
|
|
80
|
+
|
|
81
|
+
# Detailed output
|
|
82
|
+
print_text(model_card, detailed=True)
|
|
83
|
+
|
|
84
|
+
# Get as strings
|
|
85
|
+
brief = render_brief_text(model_card)
|
|
86
|
+
detailed = render_detailed_text(model_card)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API Reference
|
|
90
|
+
|
|
91
|
+
### HTML Functions
|
|
92
|
+
|
|
93
|
+
- `render_html(model_card_json, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
94
|
+
- `render_html_to_file(model_card_json, output_path, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
95
|
+
- `print_html(model_card_json, file=None, **kwargs)` -> `str`
|
|
96
|
+
|
|
97
|
+
### Text Functions
|
|
98
|
+
|
|
99
|
+
- `render_brief_text(model_card_json)` -> `str`
|
|
100
|
+
- `render_detailed_text(model_card_json)` -> `str`
|
|
101
|
+
- `render_text_to_file(model_card_json, output_path, detailed=True)` -> `str`
|
|
102
|
+
- `print_text(model_card_json, detailed=True, file=None)` -> `str`
|
|
103
|
+
|
|
104
|
+
## Architecture
|
|
105
|
+
|
|
106
|
+
The HTML renderer is a thin wrapper that embeds your model card JSON into a page
|
|
107
|
+
that loads the canonical `FeatrixModelCard` JavaScript renderer from the CDN.
|
|
108
|
+
This means:
|
|
109
|
+
|
|
110
|
+
- Python HTML output is always in sync with the JS version
|
|
111
|
+
- No rendering logic to maintain in Python
|
|
112
|
+
- All styling, interactivity, and features come from the JS renderer
|
|
113
|
+
- Zero external Python dependencies
|
|
114
|
+
|
|
115
|
+
The text renderer is a standalone Python implementation for use in terminals,
|
|
116
|
+
logs, and non-browser contexts.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Featrix Model Card - Python Package
|
|
2
|
+
|
|
3
|
+
Python package for rendering Featrix Model Card JSON to HTML or plain text.
|
|
4
|
+
|
|
5
|
+
**HTML rendering** delegates to the canonical JavaScript renderer loaded from CDN,
|
|
6
|
+
ensuring the Python output always matches the JS version.
|
|
7
|
+
|
|
8
|
+
**Text rendering** is a standalone Python implementation for terminal/log output.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install featrix-modelcard
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### HTML Renderer
|
|
19
|
+
|
|
20
|
+
The HTML renderer generates a standalone page that loads `model-card.js` from the
|
|
21
|
+
Featrix CDN. The JavaScript renderer handles all layout, styling, and interactivity.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from featrix_modelcard import render_html, render_html_to_file
|
|
25
|
+
|
|
26
|
+
# Load model card JSON
|
|
27
|
+
import json
|
|
28
|
+
with open('model_card.json', 'r') as f:
|
|
29
|
+
model_card = json.load(f)
|
|
30
|
+
|
|
31
|
+
# Render to file
|
|
32
|
+
render_html_to_file(model_card, 'output.html')
|
|
33
|
+
|
|
34
|
+
# Render to string
|
|
35
|
+
html = render_html(model_card)
|
|
36
|
+
|
|
37
|
+
# With 3D sphere visualization
|
|
38
|
+
html = render_html(model_card, show_sphere=True)
|
|
39
|
+
|
|
40
|
+
# With explicit session ID for sphere
|
|
41
|
+
html = render_html(model_card, show_sphere=True, session_id='my-session-id')
|
|
42
|
+
|
|
43
|
+
# Override CDN URL (e.g. for local development)
|
|
44
|
+
html = render_html(model_card, cdn_url='http://localhost:8080/model-card.js')
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Text Renderer
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from featrix_modelcard import render_brief_text, render_detailed_text, print_text
|
|
51
|
+
|
|
52
|
+
# Brief summary
|
|
53
|
+
print_text(model_card, detailed=False)
|
|
54
|
+
|
|
55
|
+
# Detailed output
|
|
56
|
+
print_text(model_card, detailed=True)
|
|
57
|
+
|
|
58
|
+
# Get as strings
|
|
59
|
+
brief = render_brief_text(model_card)
|
|
60
|
+
detailed = render_detailed_text(model_card)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
### HTML Functions
|
|
66
|
+
|
|
67
|
+
- `render_html(model_card_json, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
68
|
+
- `render_html_to_file(model_card_json, output_path, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
69
|
+
- `print_html(model_card_json, file=None, **kwargs)` -> `str`
|
|
70
|
+
|
|
71
|
+
### Text Functions
|
|
72
|
+
|
|
73
|
+
- `render_brief_text(model_card_json)` -> `str`
|
|
74
|
+
- `render_detailed_text(model_card_json)` -> `str`
|
|
75
|
+
- `render_text_to_file(model_card_json, output_path, detailed=True)` -> `str`
|
|
76
|
+
- `print_text(model_card_json, detailed=True, file=None)` -> `str`
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
The HTML renderer is a thin wrapper that embeds your model card JSON into a page
|
|
81
|
+
that loads the canonical `FeatrixModelCard` JavaScript renderer from the CDN.
|
|
82
|
+
This means:
|
|
83
|
+
|
|
84
|
+
- Python HTML output is always in sync with the JS version
|
|
85
|
+
- No rendering logic to maintain in Python
|
|
86
|
+
- All styling, interactivity, and features come from the JS renderer
|
|
87
|
+
- Zero external Python dependencies
|
|
88
|
+
|
|
89
|
+
The text renderer is a standalone Python implementation for use in terminals,
|
|
90
|
+
logs, and non-browser contexts.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Featrix Model Card - Python Package
|
|
3
|
+
|
|
4
|
+
Render Featrix Model Card JSON to HTML or plain text.
|
|
5
|
+
|
|
6
|
+
HTML rendering delegates to the canonical JavaScript renderer (loaded from CDN),
|
|
7
|
+
ensuring the Python output always matches the JS version.
|
|
8
|
+
|
|
9
|
+
Text rendering is a standalone Python implementation for terminal/log output.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .html_renderer import render_html, render_to_file as render_html_to_file
|
|
13
|
+
from .text_renderer import (
|
|
14
|
+
render_brief_text,
|
|
15
|
+
render_detailed_text,
|
|
16
|
+
render_to_file as render_text_to_file,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__version__ = "1.10"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def print_html(model_card_json, file=None, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
Render model card to HTML and print it.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
model_card_json: Model card JSON dictionary.
|
|
28
|
+
file: File-like object to print to (default: sys.stdout).
|
|
29
|
+
**kwargs: Passed to render_html (show_sphere, session_id, cdn_url).
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: The rendered HTML string.
|
|
33
|
+
"""
|
|
34
|
+
html = render_html(model_card_json, **kwargs)
|
|
35
|
+
print(html, file=file)
|
|
36
|
+
return html
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_text(model_card_json, detailed=True, file=None):
|
|
40
|
+
"""
|
|
41
|
+
Render model card to text and print it.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
model_card_json: Model card JSON dictionary.
|
|
45
|
+
detailed: If True, render detailed version; if False, render brief version.
|
|
46
|
+
file: File-like object to print to (default: sys.stdout).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: The rendered text string.
|
|
50
|
+
"""
|
|
51
|
+
if detailed:
|
|
52
|
+
text = render_detailed_text(model_card_json)
|
|
53
|
+
else:
|
|
54
|
+
text = render_brief_text(model_card_json)
|
|
55
|
+
print(text, file=file)
|
|
56
|
+
return text
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"render_html",
|
|
61
|
+
"render_html_to_file",
|
|
62
|
+
"render_brief_text",
|
|
63
|
+
"render_detailed_text",
|
|
64
|
+
"render_text_to_file",
|
|
65
|
+
"print_html",
|
|
66
|
+
"print_text",
|
|
67
|
+
]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTML renderer for Featrix Model Card JSON.
|
|
4
|
+
|
|
5
|
+
Generates a standalone HTML page that loads the canonical JavaScript renderer
|
|
6
|
+
from the CDN, ensuring the Python output always matches the JS version.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
CDN_BASE = "https://bits.featrix.com/js/featrix-modelcard"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_html(
|
|
18
|
+
model_card_json: Dict[str, Any],
|
|
19
|
+
*,
|
|
20
|
+
show_sphere: bool = False,
|
|
21
|
+
session_id: Optional[str] = None,
|
|
22
|
+
cdn_url: Optional[str] = None,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Render a model card JSON dict to a standalone HTML page.
|
|
26
|
+
|
|
27
|
+
Uses the canonical FeatrixModelCard JavaScript renderer loaded from CDN,
|
|
28
|
+
so the output is always in sync with the JS version.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
model_card_json: Model card JSON dictionary.
|
|
32
|
+
show_sphere: If True, enable the 3D sphere visualization thumbnail.
|
|
33
|
+
session_id: Explicit session ID for the sphere viewer. If not provided,
|
|
34
|
+
it will be resolved from the model card data.
|
|
35
|
+
cdn_url: Override the CDN URL for model-card.js.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str: Complete standalone HTML document.
|
|
39
|
+
"""
|
|
40
|
+
js_url = cdn_url or f"{CDN_BASE}/model-card.js"
|
|
41
|
+
model_name = (model_card_json.get("model_identification") or {}).get(
|
|
42
|
+
"name", "Model Card"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Build the options object for renderHTML
|
|
46
|
+
options_parts = []
|
|
47
|
+
if show_sphere:
|
|
48
|
+
options_parts.append("showSphere: true")
|
|
49
|
+
if session_id:
|
|
50
|
+
options_parts.append(f'sessionId: {json.dumps(session_id)}')
|
|
51
|
+
options_js = "{" + ", ".join(options_parts) + "}" if options_parts else "{}"
|
|
52
|
+
|
|
53
|
+
json_str = json.dumps(model_card_json, indent=2)
|
|
54
|
+
|
|
55
|
+
return f"""<!DOCTYPE html>
|
|
56
|
+
<html lang="en">
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="UTF-8">
|
|
59
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
60
|
+
<title>Model Card - {_escape_html(model_name)}</title>
|
|
61
|
+
<style>
|
|
62
|
+
body {{
|
|
63
|
+
margin: 0;
|
|
64
|
+
padding: 20px;
|
|
65
|
+
background: #fff;
|
|
66
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
67
|
+
}}
|
|
68
|
+
</style>
|
|
69
|
+
</head>
|
|
70
|
+
<body>
|
|
71
|
+
<div id="model-card-container"></div>
|
|
72
|
+
<script src="{_escape_html(js_url)}"></script>
|
|
73
|
+
<script>
|
|
74
|
+
var modelCardJson = {json_str};
|
|
75
|
+
var options = {options_js};
|
|
76
|
+
var container = document.getElementById('model-card-container');
|
|
77
|
+
container.innerHTML = FeatrixModelCard.renderHTML(modelCardJson, options);
|
|
78
|
+
FeatrixModelCard.attachEventListeners(container);
|
|
79
|
+
</script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def render_to_file(
|
|
85
|
+
model_card_json: Dict[str, Any],
|
|
86
|
+
output_path: str,
|
|
87
|
+
*,
|
|
88
|
+
show_sphere: bool = False,
|
|
89
|
+
session_id: Optional[str] = None,
|
|
90
|
+
cdn_url: Optional[str] = None,
|
|
91
|
+
) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Render model card JSON to an HTML file.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
model_card_json: Model card JSON dictionary.
|
|
97
|
+
output_path: Path to write the HTML file.
|
|
98
|
+
show_sphere: If True, enable the 3D sphere visualization.
|
|
99
|
+
session_id: Explicit session ID for the sphere viewer.
|
|
100
|
+
cdn_url: Override the CDN URL for model-card.js.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
str: The output_path that was written to.
|
|
104
|
+
"""
|
|
105
|
+
html = render_html(
|
|
106
|
+
model_card_json,
|
|
107
|
+
show_sphere=show_sphere,
|
|
108
|
+
session_id=session_id,
|
|
109
|
+
cdn_url=cdn_url,
|
|
110
|
+
)
|
|
111
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
112
|
+
f.write(html)
|
|
113
|
+
return output_path
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _escape_html(text: str) -> str:
|
|
117
|
+
"""Escape HTML special characters."""
|
|
118
|
+
return (
|
|
119
|
+
text.replace("&", "&")
|
|
120
|
+
.replace("<", "<")
|
|
121
|
+
.replace(">", ">")
|
|
122
|
+
.replace('"', """)
|
|
123
|
+
)
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Plain text renderer for Featrix Model Card JSON.
|
|
4
|
+
|
|
5
|
+
Provides both brief and detailed versions matching the current JSON schema
|
|
6
|
+
(model_identification, embedding_space, best_epochs, class_imbalance,
|
|
7
|
+
training_optimization, training_dataset, disk_usage).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def format_value(value: Any, precision: int = 4) -> str:
|
|
16
|
+
"""Format a value for display."""
|
|
17
|
+
if value is None:
|
|
18
|
+
return "N/A"
|
|
19
|
+
if isinstance(value, float):
|
|
20
|
+
return f"{value:.{precision}f}".rstrip("0").rstrip(".")
|
|
21
|
+
if isinstance(value, bool):
|
|
22
|
+
return str(value)
|
|
23
|
+
if isinstance(value, (list, dict)):
|
|
24
|
+
return json.dumps(value, indent=2)
|
|
25
|
+
return str(value)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_pct(value: Optional[float]) -> str:
|
|
29
|
+
"""Format a 0-1 float as a percentage."""
|
|
30
|
+
if value is None:
|
|
31
|
+
return "N/A"
|
|
32
|
+
return f"{value * 100:.1f}%"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def format_large_number(value) -> str:
|
|
36
|
+
"""Format large numbers (e.g. 264925317 -> 265.0M)."""
|
|
37
|
+
if value is None:
|
|
38
|
+
return "N/A"
|
|
39
|
+
if value >= 1_000_000_000:
|
|
40
|
+
return f"{value / 1_000_000_000:.1f}B"
|
|
41
|
+
if value >= 1_000_000:
|
|
42
|
+
return f"{value / 1_000_000:.1f}M"
|
|
43
|
+
if value >= 1_000:
|
|
44
|
+
return f"{value / 1_000:.1f}K"
|
|
45
|
+
return f"{value:,}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _map_model_type(mi: dict) -> str:
|
|
49
|
+
"""Map model_type + target_column_type to display string."""
|
|
50
|
+
model_type = mi.get("model_type", "")
|
|
51
|
+
target_type = (mi.get("target_column_type") or "").lower()
|
|
52
|
+
mt = model_type.lower()
|
|
53
|
+
|
|
54
|
+
if mt in ("embedding space", "es"):
|
|
55
|
+
return "Foundational Embedding Space"
|
|
56
|
+
if mt in ("single predictor", "sp"):
|
|
57
|
+
if target_type == "set":
|
|
58
|
+
return "Binary Classifier"
|
|
59
|
+
if target_type == "scalar":
|
|
60
|
+
return "Regression"
|
|
61
|
+
return "Single Predictor"
|
|
62
|
+
return model_type or "N/A"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_model_path(path: Optional[str]) -> tuple:
|
|
66
|
+
"""Extract session ID and job ID from best_model_path."""
|
|
67
|
+
if not path:
|
|
68
|
+
return None, None
|
|
69
|
+
parts = path.split("/")
|
|
70
|
+
session_id = None
|
|
71
|
+
job_id = None
|
|
72
|
+
for part in parts:
|
|
73
|
+
if part.startswith("predictor-"):
|
|
74
|
+
session_id = part[: len(part) - 37] if len(part) > 37 else part
|
|
75
|
+
if part.startswith("train_single_predictor_") or part.startswith("train_"):
|
|
76
|
+
job_id = part
|
|
77
|
+
return session_id, job_id
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Brief renderer
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def render_brief_text(data: Dict[str, Any]) -> str:
|
|
86
|
+
"""Render a compact one-screen summary of the model card."""
|
|
87
|
+
mi = data.get("model_identification", {})
|
|
88
|
+
be = data.get("best_epochs", {})
|
|
89
|
+
ci = data.get("class_imbalance", {})
|
|
90
|
+
es = data.get("embedding_space", {})
|
|
91
|
+
|
|
92
|
+
model_name = mi.get("name", "Model Card")
|
|
93
|
+
model_type = _map_model_type(mi)
|
|
94
|
+
status = (mi.get("status") or "N/A").upper()
|
|
95
|
+
if status == "DONE":
|
|
96
|
+
status = "READY"
|
|
97
|
+
|
|
98
|
+
lines = [
|
|
99
|
+
f"MODEL CARD: {model_name}",
|
|
100
|
+
"=" * 60,
|
|
101
|
+
"",
|
|
102
|
+
f"Target: {mi.get('target_column', 'N/A')}",
|
|
103
|
+
f"Type: {model_type}",
|
|
104
|
+
f"Status: {status}",
|
|
105
|
+
f"Trained: {mi.get('training_date', 'N/A')}",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
# Best metrics
|
|
109
|
+
roc_auc = _get_metric_value(be, "best_roc_auc", "auc")
|
|
110
|
+
pr_auc = _get_metric_value(be, "best_pr_auc", "pr_auc")
|
|
111
|
+
f1 = _get_metric_value(be, "best_roc_auc", "f1")
|
|
112
|
+
acc = _get_metric_value(be, "best_roc_auc", "accuracy")
|
|
113
|
+
|
|
114
|
+
lines.append("")
|
|
115
|
+
lines.append(
|
|
116
|
+
f"Accuracy: {format_pct(acc)} "
|
|
117
|
+
f"AUC: {format_pct(roc_auc)} "
|
|
118
|
+
f"PR-AUC: {format_pct(pr_auc)} "
|
|
119
|
+
f"F1: {format_pct(f1)}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Class imbalance summary
|
|
123
|
+
if ci.get("total_samples"):
|
|
124
|
+
lines.append(
|
|
125
|
+
f"Samples: {ci['total_samples']:,} "
|
|
126
|
+
f"Imbalance: {ci.get('imbalance_ratio', 'N/A')}:1"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Model stack summary
|
|
130
|
+
if es:
|
|
131
|
+
lines.append(
|
|
132
|
+
f"Foundation: {format_large_number(es.get('num_parameters'))} params, "
|
|
133
|
+
f"d_model={es.get('d_model', 'N/A')}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
lines.append("")
|
|
137
|
+
return "\n".join(lines)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Detailed renderer
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def render_detailed_text(data: Dict[str, Any]) -> str:
|
|
146
|
+
"""Render a full detailed text model card."""
|
|
147
|
+
sections = [
|
|
148
|
+
_render_model_identification(data),
|
|
149
|
+
_render_model_stack(data),
|
|
150
|
+
_render_best_epochs(data),
|
|
151
|
+
_render_training_optimization(data),
|
|
152
|
+
_render_training_dataset(data),
|
|
153
|
+
]
|
|
154
|
+
return "\n".join(s for s in sections if s)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _render_model_identification(data: dict) -> str:
|
|
158
|
+
mi = data.get("model_identification", {})
|
|
159
|
+
du = data.get("disk_usage", {})
|
|
160
|
+
es = data.get("embedding_space", {})
|
|
161
|
+
be = data.get("best_epochs", {})
|
|
162
|
+
ci = data.get("class_imbalance", {})
|
|
163
|
+
|
|
164
|
+
model_name = mi.get("name", "Model Card")
|
|
165
|
+
model_type = _map_model_type(mi)
|
|
166
|
+
status = (mi.get("status") or "N/A").upper()
|
|
167
|
+
if status == "DONE":
|
|
168
|
+
status = "READY"
|
|
169
|
+
|
|
170
|
+
session_id, job_id = _parse_model_path(du.get("best_model_path"))
|
|
171
|
+
model_id = session_id or (mi.get("session_id", "N/A")[:20] if mi.get("session_id") else "N/A")
|
|
172
|
+
|
|
173
|
+
framework = mi.get("framework", "N/A")
|
|
174
|
+
framework = re.sub(r"\s+unknown$", "", framework, flags=re.IGNORECASE).strip() or "N/A"
|
|
175
|
+
|
|
176
|
+
# Best metrics
|
|
177
|
+
roc_auc = _get_metric_value(be, "best_roc_auc", "auc")
|
|
178
|
+
pr_auc = _get_metric_value(be, "best_pr_auc", "pr_auc")
|
|
179
|
+
|
|
180
|
+
# PR-AUC lift
|
|
181
|
+
prevalence = None
|
|
182
|
+
if ci.get("minority_class_count") and ci.get("total_samples"):
|
|
183
|
+
prevalence = ci["minority_class_count"] / ci["total_samples"]
|
|
184
|
+
pr_auc_lift = (pr_auc / prevalence) if (pr_auc and prevalence) else None
|
|
185
|
+
|
|
186
|
+
lines = [
|
|
187
|
+
f"MODEL CARD: {model_name}",
|
|
188
|
+
"=" * 80,
|
|
189
|
+
"",
|
|
190
|
+
"MODEL IDENTIFICATION",
|
|
191
|
+
"-" * 60,
|
|
192
|
+
f" Target Column: {mi.get('target_column', 'N/A')}",
|
|
193
|
+
f" Model Type: {model_type}",
|
|
194
|
+
f" Best ROC-AUC: {format_pct(roc_auc)}",
|
|
195
|
+
f" Best PR-AUC: {format_pct(pr_auc)}"
|
|
196
|
+
+ (f" [{pr_auc_lift:.1f}x lift]" if pr_auc_lift else ""),
|
|
197
|
+
"",
|
|
198
|
+
f" Status: {status}",
|
|
199
|
+
f" Training Date: {mi.get('training_date', 'N/A')}",
|
|
200
|
+
f" Model ID: {model_id}",
|
|
201
|
+
f" Cluster: {(mi.get('compute_cluster') or 'N/A').upper()}",
|
|
202
|
+
f" Dims: {es.get('d_model', 'N/A')}",
|
|
203
|
+
f" Framework: {framework}",
|
|
204
|
+
"",
|
|
205
|
+
]
|
|
206
|
+
return "\n".join(lines)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _render_model_stack(data: dict) -> str:
|
|
210
|
+
es = data.get("embedding_space")
|
|
211
|
+
if not es:
|
|
212
|
+
return ""
|
|
213
|
+
|
|
214
|
+
sp = data.get("single_predictor") or data.get("predictor") or {}
|
|
215
|
+
ma = data.get("model_architecture") or {}
|
|
216
|
+
ms = (data.get("model_stack") or [{}])[0] if data.get("model_stack") else {}
|
|
217
|
+
ci = data.get("class_imbalance") or {}
|
|
218
|
+
|
|
219
|
+
sp_rows = ci.get("total_samples") or ms.get("rows") or sp.get("num_rows", 0)
|
|
220
|
+
sp_layers = ms.get("layers") or ma.get("predictor_layers") or sp.get("num_layers", 0)
|
|
221
|
+
sp_params = ms.get("parameters") or ma.get("predictor_parameters") or sp.get("num_parameters", 0)
|
|
222
|
+
|
|
223
|
+
lines = [
|
|
224
|
+
"MODEL STACK",
|
|
225
|
+
"-" * 60,
|
|
226
|
+
f" {'':18s} {'Labeled':>8s} {'Rows':>10s} {'Layers':>10s} {'Parameters':>12s}",
|
|
227
|
+
f" {'Predictor':18s} {'Yes':>8s} {sp_rows:>10,} {format_large_number(sp_layers):>10s} {format_large_number(sp_params):>12s}",
|
|
228
|
+
f" {'Foundation':18s} {'No':>8s} {es.get('num_rows', 0):>10,} {format_large_number(es.get('num_layers')):>10s} {format_large_number(es.get('num_parameters')):>12s}",
|
|
229
|
+
"",
|
|
230
|
+
]
|
|
231
|
+
return "\n".join(lines)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _render_best_epochs(data: dict) -> str:
|
|
235
|
+
be = data.get("best_epochs")
|
|
236
|
+
if not be:
|
|
237
|
+
return ""
|
|
238
|
+
|
|
239
|
+
lines = [
|
|
240
|
+
"MODEL DETAILS",
|
|
241
|
+
"-" * 60,
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
for label, key in [("Best ROC-AUC", "best_roc_auc"), ("Best PR-AUC", "best_pr_auc")]:
|
|
245
|
+
epoch_data = be.get(key)
|
|
246
|
+
if not epoch_data:
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
cdm = epoch_data.get("classification_display_metadata") or {}
|
|
250
|
+
epoch_num = epoch_data.get("epoch") or cdm.get("epoch", "N/A")
|
|
251
|
+
metrics = cdm.get("classification_metrics") or {}
|
|
252
|
+
|
|
253
|
+
lines.append(f"\n {label} -- Epoch {epoch_num}")
|
|
254
|
+
lines.append(f" {'~' * 40}")
|
|
255
|
+
|
|
256
|
+
# Metrics table (top 4)
|
|
257
|
+
for mkey in ["accuracy", "auc", "pr_auc", "f1"]:
|
|
258
|
+
m = metrics.get(mkey)
|
|
259
|
+
if not m:
|
|
260
|
+
continue
|
|
261
|
+
val = format_pct(m.get("value"))
|
|
262
|
+
lines.append(f" {mkey.upper().replace('_', ' '):12s} {val}")
|
|
263
|
+
|
|
264
|
+
# Confusion matrix
|
|
265
|
+
cm = cdm.get("confusion_matrix")
|
|
266
|
+
if cm:
|
|
267
|
+
tp, fn, fp, tn = cm.get("tp", 0), cm.get("fn", 0), cm.get("fp", 0), cm.get("tn", 0)
|
|
268
|
+
total_pos = tp + fn
|
|
269
|
+
total_neg = tn + fp
|
|
270
|
+
|
|
271
|
+
lines.append("")
|
|
272
|
+
lines.append(" Confusion Matrix:")
|
|
273
|
+
lines.append(f" Pred Pos Pred Neg")
|
|
274
|
+
lines.append(f" Actual Pos {tp:>5d} {fn:>5d}")
|
|
275
|
+
lines.append(f" Actual Neg {fp:>5d} {tn:>5d}")
|
|
276
|
+
|
|
277
|
+
# Derived metrics
|
|
278
|
+
hit_rate = tp / total_pos if total_pos > 0 else 0
|
|
279
|
+
miss_rate = fn / total_pos if total_pos > 0 else 0
|
|
280
|
+
specificity = tn / total_neg if total_neg > 0 else 0
|
|
281
|
+
fpr = fp / total_neg if total_neg > 0 else 0
|
|
282
|
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
|
|
283
|
+
|
|
284
|
+
lines.append("")
|
|
285
|
+
lines.append(f" Hit Rate (Recall): {hit_rate * 100:.1f}% TP/(TP+FN)")
|
|
286
|
+
lines.append(f" Miss Rate: {miss_rate * 100:.1f}% FN/(TP+FN)")
|
|
287
|
+
lines.append(f" Specificity (TNR): {specificity * 100:.1f}% TN/(TN+FP)")
|
|
288
|
+
lines.append(f" False Alarm (FPR): {fpr * 100:.1f}% FP/(TN+FP)")
|
|
289
|
+
lines.append(f" Precision (PPV): {precision * 100:.1f}% TP/(TP+FP)")
|
|
290
|
+
|
|
291
|
+
lines.append("")
|
|
292
|
+
|
|
293
|
+
return "\n".join(lines)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _render_training_optimization(data: dict) -> str:
|
|
297
|
+
to = data.get("training_optimization")
|
|
298
|
+
if not to:
|
|
299
|
+
return ""
|
|
300
|
+
|
|
301
|
+
lines = [
|
|
302
|
+
"TRAINING OPTIMIZATION",
|
|
303
|
+
"-" * 60,
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
if to.get("optimization_description"):
|
|
307
|
+
lines.append(f" Strategy: {to['optimization_description']}")
|
|
308
|
+
lines.append("")
|
|
309
|
+
|
|
310
|
+
lines.append(f" Loss Function: {to.get('loss_function', 'N/A')}")
|
|
311
|
+
lines.append(f" Optimization Priority: {(to.get('optimization_priority') or 'N/A').capitalize()}")
|
|
312
|
+
|
|
313
|
+
checkpoint = to.get("checkpoint_metric", "")
|
|
314
|
+
if checkpoint and checkpoint.lower() != "none":
|
|
315
|
+
lines.append(f" Checkpoint Metric: {checkpoint.upper().replace('_', '-')}")
|
|
316
|
+
else:
|
|
317
|
+
lines.append(f" Checkpoint Metric: Default")
|
|
318
|
+
|
|
319
|
+
if to.get("focal_gamma") is not None or to.get("focal_alpha") is not None:
|
|
320
|
+
lines.append(f" Focal Loss: gamma={to.get('focal_gamma', 'N/A')}, alpha={to.get('focal_alpha', 'N/A')}")
|
|
321
|
+
|
|
322
|
+
if to.get("class_weights"):
|
|
323
|
+
lines.append(f" Class Weights: [{', '.join(str(w) for w in to['class_weights'])}]")
|
|
324
|
+
|
|
325
|
+
cs = to.get("cost_sensitive")
|
|
326
|
+
if cs:
|
|
327
|
+
lines.append(f" Cost-Sensitive: FP={cs.get('cost_false_positive', 1.0)}, FN={cs.get('cost_false_negative', 1.0)}")
|
|
328
|
+
|
|
329
|
+
if to.get("adaptive_loss") is not None:
|
|
330
|
+
adj = f" ({to['gamma_adjustments']} adjustments)" if to.get("gamma_adjustments") else ""
|
|
331
|
+
lines.append(f" Adaptive Loss: {'Yes' if to['adaptive_loss'] else 'No'}{adj}")
|
|
332
|
+
|
|
333
|
+
if to.get("checkpoint_value") is not None:
|
|
334
|
+
lines.append(f" Best Checkpoint: {to['checkpoint_value'] * 100:.2f}% at epoch {to.get('checkpoint_epoch', 'N/A')}")
|
|
335
|
+
|
|
336
|
+
if to.get("positive_class") is not None:
|
|
337
|
+
lines.append(f" Positive Class: \"{to['positive_class']}\"")
|
|
338
|
+
|
|
339
|
+
lines.append("")
|
|
340
|
+
return "\n".join(lines)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _render_training_dataset(data: dict) -> str:
|
|
344
|
+
ci = data.get("class_imbalance") or {}
|
|
345
|
+
td = data.get("training_dataset") or {}
|
|
346
|
+
|
|
347
|
+
if not ci and not td:
|
|
348
|
+
return ""
|
|
349
|
+
|
|
350
|
+
lines = [
|
|
351
|
+
"TRAINING DATASET",
|
|
352
|
+
"-" * 60,
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
if ci.get("train_distribution") or ci.get("class_distribution"):
|
|
356
|
+
train0 = (ci.get("train_distribution") or {}).get("0", 0)
|
|
357
|
+
train1 = (ci.get("train_distribution") or {}).get("1", 0)
|
|
358
|
+
val0 = (ci.get("val_distribution") or {}).get("0", 0)
|
|
359
|
+
val1 = (ci.get("val_distribution") or {}).get("1", 0)
|
|
360
|
+
total_train = train0 + train1
|
|
361
|
+
total_val = val0 + val1
|
|
362
|
+
total = ci.get("total_samples") or td.get("train_rows") or (total_train + total_val)
|
|
363
|
+
|
|
364
|
+
minority = ci.get("minority_class", "1")
|
|
365
|
+
majority = ci.get("majority_class", "0")
|
|
366
|
+
|
|
367
|
+
lines.append(f" {'':12s} Class \"{minority}\" Class \"{majority}\" Total")
|
|
368
|
+
lines.append(f" {'Train':12s} {train1:>10,} {train0:>12,} {total_train:>8,}")
|
|
369
|
+
lines.append(f" {'Validation':12s} {val1:>10,} {val0:>12,} {total_val:>8,}")
|
|
370
|
+
lines.append(f" {'Total':12s} {ci.get('minority_class_count', train1 + val1):>10,} {ci.get('majority_class_count', train0 + val0):>12,} {total:>8,}")
|
|
371
|
+
lines.append("")
|
|
372
|
+
|
|
373
|
+
if ci.get("imbalance_ratio"):
|
|
374
|
+
minority_pct = (ci.get("minority_class_count", 0) / total * 100) if total else 0
|
|
375
|
+
lines.append(f" Imbalance ratio: {ci['imbalance_ratio']}:1 (minority is {minority_pct:.1f}% of data)")
|
|
376
|
+
elif td.get("train_rows"):
|
|
377
|
+
lines.append(f" Training rows: {td['train_rows']:,}")
|
|
378
|
+
|
|
379
|
+
lines.append("")
|
|
380
|
+
return "\n".join(lines)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
# Helpers
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _get_metric_value(best_epochs: dict, epoch_key: str, metric_key: str):
|
|
389
|
+
"""Extract a metric value from best_epochs nested structure."""
|
|
390
|
+
epoch = best_epochs.get(epoch_key) or {}
|
|
391
|
+
cdm = epoch.get("classification_display_metadata") or {}
|
|
392
|
+
metrics = cdm.get("classification_metrics") or {}
|
|
393
|
+
m = metrics.get(metric_key) or {}
|
|
394
|
+
return m.get("value")
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def render_to_file(
|
|
398
|
+
model_card_json: Dict[str, Any],
|
|
399
|
+
output_path: str,
|
|
400
|
+
detailed: bool = True,
|
|
401
|
+
) -> str:
|
|
402
|
+
"""Render model card JSON to text file."""
|
|
403
|
+
text = render_detailed_text(model_card_json) if detailed else render_brief_text(model_card_json)
|
|
404
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
405
|
+
f.write(text)
|
|
406
|
+
return output_path
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: featrix-modelcard
|
|
3
|
+
Version: 1.10
|
|
4
|
+
Summary: Render Featrix Sphere Model Card JSON to HTML or plain text
|
|
5
|
+
Home-page: https://github.com/featrix/model-card
|
|
6
|
+
Author: Featrix
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Requires-Python: >=3.7
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-python
|
|
25
|
+
Dynamic: summary
|
|
26
|
+
|
|
27
|
+
# Featrix Model Card - Python Package
|
|
28
|
+
|
|
29
|
+
Python package for rendering Featrix Model Card JSON to HTML or plain text.
|
|
30
|
+
|
|
31
|
+
**HTML rendering** delegates to the canonical JavaScript renderer loaded from CDN,
|
|
32
|
+
ensuring the Python output always matches the JS version.
|
|
33
|
+
|
|
34
|
+
**Text rendering** is a standalone Python implementation for terminal/log output.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install featrix-modelcard
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### HTML Renderer
|
|
45
|
+
|
|
46
|
+
The HTML renderer generates a standalone page that loads `model-card.js` from the
|
|
47
|
+
Featrix CDN. The JavaScript renderer handles all layout, styling, and interactivity.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from featrix_modelcard import render_html, render_html_to_file
|
|
51
|
+
|
|
52
|
+
# Load model card JSON
|
|
53
|
+
import json
|
|
54
|
+
with open('model_card.json', 'r') as f:
|
|
55
|
+
model_card = json.load(f)
|
|
56
|
+
|
|
57
|
+
# Render to file
|
|
58
|
+
render_html_to_file(model_card, 'output.html')
|
|
59
|
+
|
|
60
|
+
# Render to string
|
|
61
|
+
html = render_html(model_card)
|
|
62
|
+
|
|
63
|
+
# With 3D sphere visualization
|
|
64
|
+
html = render_html(model_card, show_sphere=True)
|
|
65
|
+
|
|
66
|
+
# With explicit session ID for sphere
|
|
67
|
+
html = render_html(model_card, show_sphere=True, session_id='my-session-id')
|
|
68
|
+
|
|
69
|
+
# Override CDN URL (e.g. for local development)
|
|
70
|
+
html = render_html(model_card, cdn_url='http://localhost:8080/model-card.js')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Text Renderer
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from featrix_modelcard import render_brief_text, render_detailed_text, print_text
|
|
77
|
+
|
|
78
|
+
# Brief summary
|
|
79
|
+
print_text(model_card, detailed=False)
|
|
80
|
+
|
|
81
|
+
# Detailed output
|
|
82
|
+
print_text(model_card, detailed=True)
|
|
83
|
+
|
|
84
|
+
# Get as strings
|
|
85
|
+
brief = render_brief_text(model_card)
|
|
86
|
+
detailed = render_detailed_text(model_card)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API Reference
|
|
90
|
+
|
|
91
|
+
### HTML Functions
|
|
92
|
+
|
|
93
|
+
- `render_html(model_card_json, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
94
|
+
- `render_html_to_file(model_card_json, output_path, *, show_sphere=False, session_id=None, cdn_url=None)` -> `str`
|
|
95
|
+
- `print_html(model_card_json, file=None, **kwargs)` -> `str`
|
|
96
|
+
|
|
97
|
+
### Text Functions
|
|
98
|
+
|
|
99
|
+
- `render_brief_text(model_card_json)` -> `str`
|
|
100
|
+
- `render_detailed_text(model_card_json)` -> `str`
|
|
101
|
+
- `render_text_to_file(model_card_json, output_path, detailed=True)` -> `str`
|
|
102
|
+
- `print_text(model_card_json, detailed=True, file=None)` -> `str`
|
|
103
|
+
|
|
104
|
+
## Architecture
|
|
105
|
+
|
|
106
|
+
The HTML renderer is a thin wrapper that embeds your model card JSON into a page
|
|
107
|
+
that loads the canonical `FeatrixModelCard` JavaScript renderer from the CDN.
|
|
108
|
+
This means:
|
|
109
|
+
|
|
110
|
+
- Python HTML output is always in sync with the JS version
|
|
111
|
+
- No rendering logic to maintain in Python
|
|
112
|
+
- All styling, interactivity, and features come from the JS renderer
|
|
113
|
+
- Zero external Python dependencies
|
|
114
|
+
|
|
115
|
+
The text renderer is a standalone Python implementation for use in terminals,
|
|
116
|
+
logs, and non-browser contexts.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
featrix_modelcard/__init__.py
|
|
5
|
+
featrix_modelcard/html_renderer.py
|
|
6
|
+
featrix_modelcard/text_renderer.py
|
|
7
|
+
featrix_modelcard.egg-info/PKG-INFO
|
|
8
|
+
featrix_modelcard.egg-info/SOURCES.txt
|
|
9
|
+
featrix_modelcard.egg-info/dependency_links.txt
|
|
10
|
+
featrix_modelcard.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
featrix_modelcard
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="featrix-modelcard",
|
|
8
|
+
version="1.10",
|
|
9
|
+
author="Featrix",
|
|
10
|
+
description="Render Featrix Sphere Model Card JSON to HTML or plain text",
|
|
11
|
+
long_description=long_description,
|
|
12
|
+
long_description_content_type="text/markdown",
|
|
13
|
+
url="https://github.com/featrix/model-card",
|
|
14
|
+
packages=find_packages(),
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.7",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
],
|
|
27
|
+
python_requires=">=3.7",
|
|
28
|
+
install_requires=[],
|
|
29
|
+
)
|
|
30
|
+
|