build(agent): new-agents-2#7e3bbc iteration

This commit is contained in:
agent-7e3bbc424e07835b 2026-04-20 16:20:13 +02:00
parent 5ab4009b6b
commit b459f560e9
8 changed files with 257 additions and 2 deletions

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
node_modules/
.npmrc
.env
.env.*
__tests__/
coverage/
.nyc_output/
dist/
build/
.cache/
*.log
.DS_Store
tmp/
.tmp/
__pycache__/
*.pyc
.venv/
venv/
*.egg-info/
.pytest_cache/
READY_TO_PUBLISH

19
AGENTS.md Normal file
View File

@ -0,0 +1,19 @@
# AGENTS
Architecture overview for the PolyPort Studio MVP.
- Language: Python (src/idea74_polyport_studio_interactive)
- Core: DSL parser and canonical bridge (LocalProblem) with a minimal skeleton for future adapters.
- Visualization: Placeholder scaffolding (3D frontend to be wired later).
- Persistence/Verifiability: Basic AuditLog placeholder to outline intent; signing and delta structures to be expanded.
- Testing: Pytest-based tests under tests/.
How to contribute:
- Run tests with `bash test.sh`.
- Use `pytest -q` for focused test runs.
- Docs and architecture decisions live in README.md and AGENTS.md.
Code organization:
- src/idea74_polyport_studio_interactive/
- tests/
- README.md

View File

@ -1,3 +1,22 @@
# idea74-polyport-studio-interactive # PolyPort Studio: Interactive, Verifiable Algebraic Portfolio Design (MVP)
Source logic for Idea #74 This repository implements a production-oriented MVP for a graphical, algebraic DSL-driven portfolio design tool.
- Language: Python (Core). See `pyproject.toml` for packaging and project metadata.
- Core functionality: A minimal DSL parser that converts a simple domain-specific language into a canonical LocalProblem-like representation.
- Extensibility: Designed for adapters (Graph-of-Contracts) to map real-world data feeds into the canonical form.
- Testing: Pytest-based tests; an executable `test.sh` orchestrates tests and packaging checks.
How to run locally:
- Install dependencies (if any) with your Python environment.
- Run tests: `bash test.sh`.
Project structure:
- src/idea74_polyport_studio_interactive/ # Python package with the DSL core
- tests/ # Pytest test-suite
- AGENTS.md # Architecture and contribution guide
- README.md # This file
- pyproject.toml # Packaging metadata
- test.sh # Test runner (also builds)
Note: This is an MVP that focuses on correctness, testability, and a clean path toward a production-grade, offline-first architecture.

14
pyproject.toml Normal file
View File

@ -0,0 +1,14 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "idea74-polyport-studio-interactive"
version = "0.1.0"
description = "Interactive, verifiable algebraic portfolio design DSL core (MVP)"
readme = "README.md"
requires-python = ">=3.9"
[tool.setuptools]
packages = ["idea74_polyport_studio_interactive"]
package-dir = {"idea74_polyport_studio_interactive" = "src/idea74_polyport_studio_interactive"}

View File

@ -0,0 +1,5 @@
"""Idea74 PolyPort Studio Interactive - Python package init"""
from .dsl import parse_dsl # Public entry point for parsing DSL strings
__all__ = ["parse_dsl"]

View File

@ -0,0 +1,133 @@
"""Minimal DSL parser for PolyPort Studio MVP.
This module provides a small, well-defined DSL that enables declaration of
assets, objectives, and constraints. It produces a canonical JSON-like
dictionary structure that can be consumed by downstream adapters and visualizers.
"""
from __future__ import annotations
from typing import Any, Dict, List
def _parse_assets(section_lines: List[str]) -> List[str]:
# Expect a single line of comma-separated asset names, e.g.:
# ASSETS: AAPL, MSFT, GOOGL
if not section_lines:
return []
line = section_lines[0].strip()
if line.upper().startswith("ASSETS:"):
content = line.split(":", 1)[1].strip()
else:
content = line
if not content:
return []
# Split by comma and strip spaces
return [a.strip() for a in content.split(",") if a.strip()]
def _parse_objectives(section_lines: List[str]) -> List[str]:
# Expect a single line like: OBJECTIVES: maximize_return
if not section_lines:
return []
line = section_lines[0].strip()
if line.upper().startswith("OBJECTIVES:"):
content = line.split(":", 1)[1].strip()
else:
content = line
if not content:
return []
return [c.strip() for c in content.split(",") if c.strip()]
def _parse_constraints(section_lines: List[str]) -> Dict[str, Any]:
# Constraints in a simple key=value format, separated by semicolons or commas.
# Example: "budget=1.0; max_risk=0.05; liquidity=0.2"
if not section_lines:
return {}
line = section_lines[0].strip()
if line.upper().startswith("CONSTRAINTS:"):
content = line.split(":", 1)[1].strip()
else:
content = line
if not content:
return {}
items = [p.strip() for p in content.replace(";", ",").split(",") if p.strip()]
result: Dict[str, Any] = {}
for item in items:
if "=" in item:
k, v = item.split("=", 1)
k = k.strip()
v = v.strip()
# Try to parse as float/int, otherwise leave as string
if "." in v:
try:
val: Any = float(v)
except ValueError:
val = v
else:
try:
val = int(v)
except ValueError:
try:
val = float(v)
except ValueError:
val = v
result[k] = val
return result
def _split_into_sections(text: str) -> Dict[str, List[str]]:
# Very lightweight section splitter for three known sections.
sections: Dict[str, List[str]] = {"assets": [], "objectives": [], "constraints": []}
current: str | None = None
for raw_line in text.splitlines():
line = raw_line.strip()
if not line:
continue
upper = line.upper()
if upper.startswith("ASSETS:"):
current = "assets"
sections[current].append(line)
continue
if upper.startswith("OBJECTIVES:"):
current = "objectives"
sections[current].append(line)
continue
if upper.startswith("CONSTRAINTS:"):
current = "constraints"
sections[current].append(line)
continue
if current is None:
# Ignore stray lines until a section header is found
continue
sections[current].append(line)
return sections
def parse_dsl(text: str) -> Dict[str, Any]:
"""Parse a tiny DSL into a canonical LocalProblem-like dict.
The DSL supports three sections in a single textual block:
- ASSETS: comma-separated asset names
- OBJECTIVES: comma-separated objective names
- CONSTRAINTS: key=value pairs separated by semicolons or commas
Example DSL:
ASSETS: AAPL, MSFT, GOOGL
OBJECTIVES: maximize_return, minimize_risk
CONSTRAINTS: budget=1.0; max_risk=0.05
"""
sections = _split_into_sections(text)
assets = _parse_assets(sections.get("assets", []))
objectives = _parse_objectives(sections.get("objectives", []))
constraints = _parse_constraints(sections.get("constraints", []))
canonical: Dict[str, Any] = {
"LocalProblem": {
"Assets": assets,
"Objectives": objectives,
"Constraints": constraints,
},
"SharedVariables": {}, # placeholder for future delta/state sharing
"PlanDelta": {}, # placeholder for change-tracking deltas
}
return canonical
__all__ = ["parse_dsl"]

14
test.sh Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
# Test script for PolyPort Studio MVP
# - Runs pytest tests
# - Builds the Python package (PEP 517/518) to verify packaging metadata
echo "==> Running pytest tests..."
pytest -q
echo "==> Building the package to verify packaging metadata..."
python3 -m build
echo "All tests and build completed successfully."

30
tests/test_parser.py Normal file
View File

@ -0,0 +1,30 @@
import sys
import os
# Ensure the src package is on the Python path for tests
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SRC = os.path.join(ROOT, "src")
if SRC not in sys.path:
sys.path.insert(0, SRC)
from idea74_polyport_studio_interactive import parse_dsl
def test_parse_basic_dsl():
dsl = (
"ASSETS: AAPL, MSFT, GOOGL\n"
"OBJECTIVES: maximize_return\n"
"CONSTRAINTS: budget=1.0; max_risk=0.05; liquidity=0.2"
)
result = parse_dsl(dsl)
# Basic shape checks
assert "LocalProblem" in result
lp = result["LocalProblem"]
assert isinstance(lp, dict)
assert lp.get("Assets") == ["AAPL", "MSFT", "GOOGL"]
assert lp.get("Objectives") == ["maximize_return"]
constraints = lp.get("Constraints")
assert isinstance(constraints, dict)
# Check a couple of parsed values
assert constraints.get("budget") == 1.0
assert constraints.get("max_risk") == 0.05