Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
26 changes: 17 additions & 9 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda",
"python-envs.pythonProjects": [
{
"path": ".",
"envManager": "ms-python.python:conda",
"packageManager": "ms-python.python:conda"
}
]
"python.analysis.extraPaths": [
"apps/src",
"../../SAGE/src"
],
"python.autoComplete.extraPaths": [
"apps/src",
"../../SAGE/src"
],
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda",
"python-envs.pythonProjects": [
{
"path": ".",
"envManager": "ms-python.python:conda",
"packageManager": "ms-python.python:conda"
}
]
}
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ python examples/run_video_intelligence.py

## 📁 Repository Structure

```
```text
sage-examples/
├── examples/ # 🎯 Production application examples
│ ├── run_video_intelligence.py
Expand Down Expand Up @@ -81,7 +81,21 @@ Complete, runnable applications demonstrating real-world use cases:
| 🎫 **Ticket Triage** | Stateful customer support ticket classification, urgency scoring, and routing | `examples/run_ticket_triage.py` |
| 🌐 **Ticket Triage API** | FastAPI service for triage ingestion, queue queries, and ticket lookup | `examples/run_ticket_triage_api.py` |

See `examples/README.md` for details.
The repository also includes the full 53-91 domain app suite across research, education, healthcare,
enterprise operations, public service, sustainability, and infrastructure monitoring.

Representative newer entry points include:

- `examples/run_patent_competition_monitor.py`
- `examples/run_campus_aid_gap_alert.py`
- `examples/run_lab_turnaround_alert.py`
- `examples/run_policy_search_helper.py`
- `examples/run_community_hotspot_drift.py`
- `examples/run_budget_variance_alert.py`
- `examples/run_carbon_collection.py`
- `examples/run_data_center_watch.py`

See `examples/README.md` for the complete runnable script index.

## 📦 Installation

Expand All @@ -107,7 +121,7 @@ pip install -e .[dev]

SAGE uses a strict 6-layer architecture with unidirectional dependencies:

```
```text
┌─────────────────────────────────────────────┐
│ L6: Interface │ CLI, Web UI, Tools
├─────────────────────────────────────────────┤
Expand Down Expand Up @@ -180,15 +194,16 @@ See `docs/DEVELOPMENT.md` for complete development guide.
- **Development Guide**: `docs/DEVELOPMENT.md` - Contributing
- **SAGE Tutorials**: [SAGE/tutorials](https://github.com/intellistream/SAGE/tree/main/tutorials) -
Learn SAGE
- **SAGE Docs**: https://intellistream.github.io/SAGE
- **SAGE Docs**: [intellistream.github.io/SAGE](https://intellistream.github.io/SAGE)

## 🤝 Contributing

We welcome contributions! Please see:

1. **Development Guide**: `docs/DEVELOPMENT.md`
1. **Code of Conduct**: Follow respectful collaboration
1. **Issue Tracker**: https://github.com/intellistream/sage-examples/issues
1. **Issue Tracker**:
[github.com/intellistream/sage-examples/issues](https://github.com/intellistream/sage-examples/issues)

### Adding Examples

Expand All @@ -201,9 +216,10 @@ We welcome contributions! Please see:

## 🔗 Related Repositories

- **SAGE Main**: https://github.com/intellistream/SAGE
- **SAGE Benchmark**: https://github.com/intellistream/sage-benchmark
- **PyPI Packages**: https://pypi.org/search/?q=isage
- **SAGE Main**: [github.com/intellistream/SAGE](https://github.com/intellistream/SAGE)
- **SAGE Benchmark**:
[github.com/intellistream/sage-benchmark](https://github.com/intellistream/sage-benchmark)
- **PyPI Packages**: [pypi.org/search/?q=isage](https://pypi.org/search/?q=isage)

## 📄 License

Expand All @@ -221,4 +237,4 @@ If you find this project helpful, please consider giving it a ⭐️!

______________________________________________________________________

**Made with ❤️ by the IntelliStream Team**
Made with ❤️ by the IntelliStream Team.
27 changes: 27 additions & 0 deletions apps/src/sage/apps/_batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

from typing import Any

from sage.foundation import BatchFunction


class ListBatchSource(BatchFunction):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._batch_items: list[Any] | None = None
self._batch_index = 0

def load_items(self) -> list[Any]:
raise NotImplementedError

def execute(self) -> Any | None:
if self._batch_items is None:
self._batch_items = list(self.load_items())
self._batch_index = 0

if self._batch_index >= len(self._batch_items):
return None

item = self._batch_items[self._batch_index]
self._batch_index += 1
return item
18 changes: 18 additions & 0 deletions apps/src/sage/apps/academic_metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Academic Metadata

学术文献元数据抽取应用。

## 功能

- 从目录、单文件或 CSV 读取文献文本
- 提取标题、作者、年份、DOI、邮箱与摘要
- 标准化作者名称
- 输出 JSON 结果

## 用法

```bash
python examples/run_academic_metadata.py \
--input-path sample_papers \
--output metadata.json
```
5 changes: 5 additions & 0 deletions apps/src/sage/apps/academic_metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Academic metadata extraction application."""

from .pipeline import run_academic_metadata_pipeline

__all__ = ["run_academic_metadata_pipeline"]
137 changes: 137 additions & 0 deletions apps/src/sage/apps/academic_metadata/operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Operators for academic metadata extraction."""

from __future__ import annotations

import csv
import json
import re
from pathlib import Path
from typing import Any

from sage.apps._batch import ListBatchSource
from sage.foundation import CustomLogger, MapFunction, SinkFunction


class PdfSource(ListBatchSource):
def __init__(self, input_path: str, **kwargs):
super().__init__(**kwargs)
self.input_path = Path(input_path)
self._logger = CustomLogger("PdfSource")

def load_items(self) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = []
if self.input_path.is_dir():
for file_path in sorted(self.input_path.iterdir()):
if file_path.is_file() and file_path.suffix.lower() in {".txt", ".md"}:
items.append(
{
"source_file": str(file_path),
"text": file_path.read_text(encoding="utf-8"),
}
)
elif self.input_path.suffix.lower() == ".csv":
with self.input_path.open("r", encoding="utf-8", newline="") as handle:
for row in csv.DictReader(handle):
items.append(
{"source_file": row.get("source_file", ""), "text": row.get("text", "")}
)
else:
items.append(
{
"source_file": str(self.input_path),
"text": self.input_path.read_text(encoding="utf-8"),
}
)
self.logger.info(f"Loaded {len(items)} academic documents")
return items


class TextExtractor(MapFunction):
def execute(self, item: dict[str, Any]) -> dict[str, Any]:
result = dict(item)
result["text"] = str(item.get("text", ""))
return result


class MetadataExtractor(MapFunction):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._logger = CustomLogger("MetadataExtractor")

def execute(self, item: dict[str, Any]) -> dict[str, Any]:
text = item.get("text", "")
lines = [line.strip() for line in text.splitlines() if line.strip()]
title = lines[0] if lines else ""
doi_match = re.search(r"10\.\d{4,9}/[-._;()/:A-Z0-9]+", text, flags=re.IGNORECASE)
year_match = re.search(r"\b(19|20)\d{2}\b", text)
email_matches = re.findall(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
author_line = ""
for line in lines[1:6]:
if "," in line or " and " in line.lower() or len(line.split()) <= 12:
author_line = line
break
authors = [part.strip() for part in re.split(r",| and |;", author_line) if part.strip()]
result = dict(item)
result.update(
{
"title": title,
"authors": authors,
"year": year_match.group(0) if year_match else "",
"doi": doi_match.group(0) if doi_match else "",
"emails": email_matches,
"abstract": self._extract_abstract(text),
}
)
return result

def _extract_abstract(self, text: str) -> str:
lowered = text.lower()
marker = lowered.find("abstract")
if marker == -1:
return ""

snippet = text[marker:]
lines = [line.strip() for line in snippet.splitlines()]
abstract_lines: list[str] = []
started = False
for line in lines:
if not started:
if line.lower().startswith("abstract"):
remainder = line.split(":", 1)[1].strip() if ":" in line else ""
if remainder:
abstract_lines.append(remainder)
started = True
continue

lowered_line = line.lower()
if not line or lowered_line.startswith("keywords"):
break
abstract_lines.append(line)

return " ".join(abstract_lines).strip()[:1200]


class AuthorNormalizer(MapFunction):
def execute(self, item: dict[str, Any]) -> dict[str, Any]:
normalized = []
for author in item.get("authors", []):
cleaned = " ".join(author.replace("*", " ").split())
if cleaned:
normalized.append(cleaned.title())
item["authors"] = normalized
item["has_complete_metadata"] = bool(item.get("title") and item.get("authors"))
return item


class MetadataSink(SinkFunction):
def __init__(self, output_file: str, **kwargs):
super().__init__(**kwargs)
self.output_file = output_file
self.items: list[dict[str, Any]] = []

def execute(self, item: dict[str, Any]) -> None:
self.items.append(item)

def teardown(self, context: Any) -> None:
with open(self.output_file, "w", encoding="utf-8") as handle:
json.dump(self.items, handle, ensure_ascii=False, indent=2)
28 changes: 28 additions & 0 deletions apps/src/sage/apps/academic_metadata/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Academic metadata pipeline."""

from __future__ import annotations

from sage.foundation import CustomLogger
from sage.runtime import LocalEnvironment

from .operators import AuthorNormalizer, MetadataExtractor, MetadataSink, PdfSource, TextExtractor


def run_academic_metadata_pipeline(
input_path: str,
output_file: str,
verbose: bool = False,
) -> None:
logger = CustomLogger("AcademicMetadataPipeline")
if verbose:
logger.info(f"Starting academic metadata extraction: {input_path}")

env = LocalEnvironment("academic_metadata")
(
env.from_batch(PdfSource, input_path=input_path)
.map(TextExtractor)
.map(MetadataExtractor)
.map(AuthorNormalizer)
.sink(MetadataSink, output_file=output_file)
)
env.submit(autostop=True)
5 changes: 5 additions & 0 deletions apps/src/sage/apps/api_log_analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# API Log Analytics

读取 API 日志,解析状态码和时延并识别异常请求。

输入:CSV 或 JSON API 日志。 输出:包含指标提取和异常判断的 JSON 文件。
5 changes: 5 additions & 0 deletions apps/src/sage/apps/api_log_analytics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""API log analytics application."""

from .pipeline import run_api_log_analytics_pipeline

__all__ = ["run_api_log_analytics_pipeline"]
Loading
Loading