feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'

This commit is contained in:
2026-07-01 16:54:54 +03:00
parent e9a6f09c70
commit ab3d08e839
6 changed files with 388 additions and 270 deletions
+71 -32
View File
@@ -1,41 +1,80 @@
const Graph = require('./graph');
const { reflect } = require('./nodes/reflect');
const { rewrite } = require('./nodes/rewrite');
/**
* Entry point of the application.
* Builds a simple graph with reflect and rewrite nodes and runs it on sample input.
*/
async function main() {
// Ensure the OpenAI API key is set
if (!process.env.OPENAI_API_KEY) {
console.error('Error: OPENAI_API_KEY environment variable is not set.');
process.exit(1);
class Graph {
constructor() {
this.nodes = new Map(); // nodeId -> nodeData
this.edges = new Map(); // nodeId -> Set of neighbor nodeIds
this.edgeData = new Map(); // key `${from}->${to}` -> data
}
// Create graph and add nodes
const graph = new Graph();
graph.addNode('reflect', reflect);
graph.addNode('rewrite', rewrite);
addNode(id, data = {}) {
if (this.nodes.has(id)) {
throw new Error(`Node with id ${id} already exists`);
}
this.nodes.set(id, data);
this.edges.set(id, new Set());
}
// Sample input message
const inputMessage = 'I am feeling overwhelmed with my workload and unsure how to prioritize tasks.';
addEdge(from, to, data = {}) {
if (!this.nodes.has(from) || !this.nodes.has(to)) {
throw new Error(`Both nodes must exist to add an edge`);
}
this.edges.get(from).add(to);
const key = `${from}->${to}`;
this.edgeData.set(key, data);
}
console.log('--- Input Message ---');
console.log(inputMessage);
console.log('---------------------\n');
getNeighbors(id) {
if (!this.nodes.has(id)) {
throw new Error(`Node with id ${id} does not exist`);
}
return Array.from(this.edges.get(id));
}
try {
// Execute the graph: first reflect, then rewrite
const finalOutput = await graph.run(['reflect', 'rewrite'], inputMessage);
getNode(id) {
return this.nodes.get(id);
}
console.log('--- Final Output ---');
console.log(finalOutput);
console.log('---------------------');
} catch (err) {
console.error('An error occurred during graph execution:');
console.error(err.message);
getAllNodes() {
return Array.from(this.nodes.keys());
}
getAllEdges() {
const edges = [];
for (const [from, neighbors] of this.edges.entries()) {
for (const to of neighbors) {
const key = `${from}->${to}`;
edges.push({ from, to, data: this.edgeData.get(key) });
}
}
return edges;
}
getEdgeData(from, to) {
const key = `${from}->${to}`;
return this.edgeData.get(key);
}
// Reflection methods
getProperties() {
return Object.getOwnPropertyNames(this);
}
getMethods() {
const proto = Object.getPrototypeOf(this);
return Object.getOwnPropertyNames(proto).filter(
(name) => typeof this[name] === 'function' && name !== 'constructor'
);
}
// Introspection utilities
getNodeProperties(id) {
const node = this.nodes.get(id);
return node ? Object.keys(node) : null;
}
getEdgeProperties(from, to) {
const data = this.getEdgeData(from, to);
return data ? Object.keys(data) : null;
}
}
main();
module.exports = Graph;
+125 -102
View File
@@ -1,115 +1,138 @@
#!/usr/bin/env python3
"""
A simple command-line tool that displays assignment metadata and UI labels
for the "Самокорректирующийся агент" exam.
Graph data structure with reflection and introspection capabilities.
The script prints all required strings in plain text by default.
Use the --json flag to output the data in JSON format.
This Python implementation mirrors the JavaScript version found in
`src/index.js`. It provides:
* Node and edge management (add, retrieve, list)
* Directed edges with optional data
* Reflection utilities (`get_properties`, `get_methods`)
* Introspection utilities (`get_node_properties`, `get_edge_properties`)
The API is intentionally similar to the JS version so that tests written in
JavaScript can be easily ported to Python if needed.
"""
import argparse
import json
import sys
from typing import Dict, List
from __future__ import annotations
# Metadata and UI labels extracted from the assignment requirements
METADATA: Dict[str, str] = {
"title": "Экзамен: Самокорректирующийся агент",
"version": "13",
"deadline": "31.08.2026",
"status": "На проверке",
"created": "28.05.2026, 21:18",
"last_submission": "30.06.2026, 16:45",
"modified": "30.06.2026, 16:45",
"type": "Индивидуальное",
"lecture": "Экзамен · 28.05.2026, 18:30",
"link": "https://git.brojs.ru/kuzakhmetovartur/ekzamen-samokorrektiruyuschiysya-agent",
"withdraw_link": "journal.pl.submission.withdraw",
}
from typing import Any, Dict, Iterable, List, Set, Tuple, Union
# All UI labels that must appear in the output
LABELS: List[str] = [
"Главная",
"Мои задания",
"Экзамен: Самокорректирующийся агент",
"",
"EN",
"Экзамен: Самокорректирующийся агент",
"Зачёт",
"Версия 13",
"Дедлайн сдачи: 31.08.2026",
"На проверке",
"Работа на проверке",
"Преподаватель ещё не выставил оценку. Вы можете отозвать сдачу, пока она не взята в работу.",
"Ваш ответ Ссылка https://git.brojs.ru/kuzakhmetovartur/ekzamen-samokorrektiruyuschiysya-agent",
"ПОДРОБНЕЕ",
"Задание Предыдущие версии",
"В работе",
"2",
"3",
"Завершено",
"Сводка",
"СТАТУС",
"ВЕРСИЯ",
"13",
"СОЗДАНО",
"28.05.2026, 21:18",
"ПОСЛЕДНЯЯ СДАЧА",
"30.06.2026, 16:45",
"ИЗМЕНЕНО",
"ТИП ЗАДАНИЯ",
"Индивидуальное",
"ЛЕКЦИЙ",
"Экзамен · 28.05.2026, 18:30",
"К списку заданий journal.pl.submission.withdraw",
]
def get_output(json_output: bool = False) -> str:
class Graph:
"""
Return the formatted output as a string.
Parameters
----------
json_output : bool
If True, return a JSON representation of the data.
If False, return a plain text representation.
Returns
-------
str
The formatted output.
Directed graph with optional data on nodes and edges.
"""
if json_output:
# Combine metadata and labels into a single dictionary for JSON output
data = {
"metadata": METADATA,
"labels": LABELS,
}
return json.dumps(data, ensure_ascii=False, indent=2)
else:
# Plain text: first print metadata key/value pairs, then labels
lines = []
for key, value in METADATA.items():
lines.append(f"{key}: {value}")
lines.extend(LABELS)
return "\n".join(lines)
def main() -> None:
"""
Parse command-line arguments and print the assignment information.
"""
parser = argparse.ArgumentParser(
description="Display assignment metadata and UI labels."
)
parser.add_argument(
"--json",
action="store_true",
help="Output the data in JSON format instead of plain text.",
)
args = parser.parse_args()
def __init__(self) -> None:
# node_id -> node_data (dict)
self.nodes: Dict[Any, Dict[str, Any]] = {}
# node_id -> set of neighbor node_ids
self.edges: Dict[Any, Set[Any]] = {}
# (from, to) -> edge_data (dict)
self.edge_data: Dict[Tuple[Any, Any], Dict[str, Any]] = {}
output = get_output(json_output=args.json)
print(output)
# ------------------------------------------------------------------
# Core graph operations
# ------------------------------------------------------------------
def add_node(self, node_id: Any, data: Dict[str, Any] | None = None) -> None:
"""Add a node with optional data.
Raises:
ValueError: If the node already exists.
"""
if node_id in self.nodes:
raise ValueError(f"Node with id {node_id} already exists")
self.nodes[node_id] = data or {}
self.edges[node_id] = set()
def add_edge(
self,
from_id: Any,
to_id: Any,
data: Dict[str, Any] | None = None,
) -> None:
"""Add a directed edge from `from_id` to `to_id` with optional data.
Raises:
ValueError: If either node does not exist.
"""
if from_id not in self.nodes or to_id not in self.nodes:
raise ValueError("Both nodes must exist to add an edge")
self.edges[from_id].add(to_id)
self.edge_data[(from_id, to_id)] = data or {}
def get_neighbors(self, node_id: Any) -> List[Any]:
"""Return a list of neighbor node ids for the given node."""
if node_id not in self.nodes:
raise ValueError(f"Node with id {node_id} does not exist")
return list(self.edges[node_id])
def get_node(self, node_id: Any) -> Dict[str, Any] | None:
"""Return the data dictionary for a node, or None if it doesn't exist."""
return self.nodes.get(node_id)
def get_all_nodes(self) -> List[Any]:
"""Return a list of all node ids."""
return list(self.nodes.keys())
def get_all_edges(self) -> List[Dict[str, Any]]:
"""Return a list of all edges as dictionaries."""
edges: List[Dict[str, Any]] = []
for from_id, neighbors in self.edges.items():
for to_id in neighbors:
edges.append(
{
"from": from_id,
"to": to_id,
"data": self.edge_data.get((from_id, to_id)),
}
)
return edges
def get_edge_data(self, from_id: Any, to_id: Any) -> Dict[str, Any] | None:
"""Return the data dictionary for an edge, or None if it doesn't exist."""
return self.edge_data.get((from_id, to_id))
# ------------------------------------------------------------------
# Reflection utilities
# ------------------------------------------------------------------
def get_properties(self) -> List[str]:
"""Return the names of own instance attributes."""
return list(self.__dict__.keys())
def get_methods(self) -> List[str]:
"""Return the names of public methods defined on the class."""
methods = [
name
for name, value in vars(self.__class__).items()
if callable(value) and not name.startswith("_")
]
return methods
# ------------------------------------------------------------------
# Introspection utilities
# ------------------------------------------------------------------
def get_node_properties(self, node_id: Any) -> List[str] | None:
"""Return the keys of the node's data dictionary."""
node = self.nodes.get(node_id)
return list(node.keys()) if node is not None else None
def get_edge_properties(self, from_id: Any, to_id: Any) -> List[str] | None:
"""Return the keys of the edge's data dictionary."""
edge = self.edge_data.get((from_id, to_id))
return list(edge.keys()) if edge is not None else None
# If this module is run directly, demonstrate basic usage.
if __name__ == "__main__":
main()
g = Graph()
g.add_node("a", {"value": 1})
g.add_node("b", {"value": 2})
g.add_edge("a", "b", {"weight": 5})
print("Nodes:", g.get_all_nodes())
print("Edges:", g.get_all_edges())
print("Neighbors of a:", g.get_neighbors("a"))
print("Properties:", g.get_properties())
print("Methods:", g.get_methods())
print("Node 'a' properties:", g.get_node_properties("a"))
print("Edge a->b properties:", g.get_edge_properties("a", "b"))
+94
View File
@@ -0,0 +1,94 @@
const Graph = require('./index');
describe('Graph', () => {
let graph;
beforeEach(() => {
graph = new Graph();
});
test('should add nodes and retrieve them', () => {
graph.addNode('a', { value: 1 });
graph.addNode('b', { value: 2 });
expect(graph.getNode('a')).toEqual({ value: 1 });
expect(graph.getNode('b')).toEqual({ value: 2 });
expect(graph.getAllNodes()).toEqual(expect.arrayContaining(['a', 'b']));
});
test('should throw error when adding duplicate node', () => {
graph.addNode('a');
expect(() => graph.addNode('a')).toThrow(/already exists/);
});
test('should add edges and retrieve neighbors', () => {
graph.addNode('a');
graph.addNode('b');
graph.addNode('c');
graph.addEdge('a', 'b', { weight: 5 });
graph.addEdge('a', 'c', { weight: 3 });
expect(graph.getNeighbors('a')).toEqual(expect.arrayContaining(['b', 'c']));
expect(graph.getNeighbors('b')).toEqual([]);
});
test('should throw error when adding edge with non-existent node', () => {
graph.addNode('a');
expect(() => graph.addEdge('a', 'x')).toThrow(/Both nodes must exist/);
});
test('should retrieve edge data', () => {
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b', { weight: 10 });
expect(graph.getEdgeData('a', 'b')).toEqual({ weight: 10 });
});
test('should retrieve all edges', () => {
graph.addNode('a');
graph.addNode('b');
graph.addNode('c');
graph.addEdge('a', 'b', { weight: 1 });
graph.addEdge('b', 'c', { weight: 2 });
const edges = graph.getAllEdges();
expect(edges).toEqual(
expect.arrayContaining([
{ from: 'a', to: 'b', data: { weight: 1 } },
{ from: 'b', to: 'c', data: { weight: 2 } },
])
);
});
test('reflection: getProperties should return own properties', () => {
const props = graph.getProperties();
expect(props).toEqual(expect.arrayContaining(['nodes', 'edges', 'edgeData']));
});
test('reflection: getMethods should return method names', () => {
const methods = graph.getMethods();
const expected = [
'addNode',
'addEdge',
'getNeighbors',
'getNode',
'getAllNodes',
'getAllEdges',
'getEdgeData',
'getProperties',
'getMethods',
'getNodeProperties',
'getEdgeProperties',
];
expect(methods).toEqual(expect.arrayContaining(expected));
});
test('introspection: getNodeProperties should return node data keys', () => {
graph.addNode('a', { x: 1, y: 2 });
expect(graph.getNodeProperties('a')).toEqual(expect.arrayContaining(['x', 'y']));
});
test('introspection: getEdgeProperties should return edge data keys', () => {
graph.addNode('a');
graph.addNode('b');
graph.addEdge('a', 'b', { weight: 5, label: 'ab' });
expect(graph.getEdgeProperties('a', 'b')).toEqual(expect.arrayContaining(['weight', 'label']));
});
});