From b459f560e921f5881d4777e56dcc0165c7de65dd Mon Sep 17 00:00:00 2001 From: agent-7e3bbc424e07835b Date: Mon, 20 Apr 2026 16:20:13 +0200 Subject: [PATCH] build(agent): new-agents-2#7e3bbc iteration --- .gitignore | 21 +++ AGENTS.md | 19 +++ README.md | 23 ++- pyproject.toml | 14 ++ .../__init__.py | 5 + src/idea74_polyport_studio_interactive/dsl.py | 133 ++++++++++++++++++ test.sh | 14 ++ tests/test_parser.py | 30 ++++ 8 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 pyproject.toml create mode 100644 src/idea74_polyport_studio_interactive/__init__.py create mode 100644 src/idea74_polyport_studio_interactive/dsl.py create mode 100644 test.sh create mode 100644 tests/test_parser.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..aa7663a --- /dev/null +++ b/AGENTS.md @@ -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 diff --git a/README.md b/README.md index 05448b3..c85ca64 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ -# idea74-polyport-studio-interactive +# PolyPort Studio: Interactive, Verifiable Algebraic Portfolio Design (MVP) -Source logic for Idea #74 \ No newline at end of file +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. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e72299a --- /dev/null +++ b/pyproject.toml @@ -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"} diff --git a/src/idea74_polyport_studio_interactive/__init__.py b/src/idea74_polyport_studio_interactive/__init__.py new file mode 100644 index 0000000..4346d82 --- /dev/null +++ b/src/idea74_polyport_studio_interactive/__init__.py @@ -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"] diff --git a/src/idea74_polyport_studio_interactive/dsl.py b/src/idea74_polyport_studio_interactive/dsl.py new file mode 100644 index 0000000..ce4daed --- /dev/null +++ b/src/idea74_polyport_studio_interactive/dsl.py @@ -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"] diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..5ba0a04 --- /dev/null +++ b/test.sh @@ -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." diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..fc7508c --- /dev/null +++ b/tests/test_parser.py @@ -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