Monolith-Split: Staged-Plan, Limited-Visibility के साथ Context: आपके Point-7 (Single-File-Monolith → 9-Modules) पर हम पूरी तरह सहमत हैं, conceptually। पर हमें genuinely-एक-critical-constraint है जो हमारा अपना Plan बनाते वक़्त हमने identify की। हमारा gen
यह approach established software‑engineering practice के साथ अच्छी तरह aligned है, और आपके constraint (limited visibility, high risk of missed import) के हिसाब से technically sound है। नीचे तीनों points पर structured answer:
---
1. Strangler‑Fig + Backward‑Compatible Facade (stock_agent.py) की validity
आप जो कर रहे हैं, उसका classical नाम है:
- Strangler Fig Pattern +
- Branch‑by‑Abstraction / Facade Refactoring (पुराने public API को बनाए रखते हुए अंदर नया implementation लाना).
Established practice के key principles, जो आपके plan से match करते हैं:
1. Existing entry‑point को “facade” बनाए रखना
- पुराना `stock_agent.py` user‑facing API बना रहता है।
- Internally, वह नए modules (`ta_core.py`, `symbol_utils.py`, etc.) को import करके वही पुराने function‑names expose करता है।
- इससे किसी भी “missed import update” का blast radius कम हो जाता है, क्योंकि external callers अभी भी `stock_agent` ही use कर रहे हैं; wiring अंदर बदलती है।
2. Incremental replace और parallel coexistence
- पुराने और नए implementations कुछ समय तक साथ‑साथ रहते हैं।
- आप per‑phase regression test (जैसे आपका “Bug‑6: original.equals(before)”) चला रहे हैं, ये बिल्कुल वही pattern है जो legacy systems modernize करते समय recommended है।
3. High‑risk monolith refactor को छोटे, reversible steps में तोड़ना
- Research और industrial case‑studies दोनों ही ये recommend करते हैं कि large legacy refactors में:
- Public surface area वही रहे
- Internals को धीरे‑धीरे, test‑guarded micro‑steps में बदला जाए
- हर step के बाद rollback का option रहे
आपकी constraint (हमारे पास पूरी `D:\SIA` visibility नहीं है, 90+ Python files हैं, कोई भी missed import app तोड़ सकता है) के context में यह pattern बिल्कुल उपयुक्त है, क्योंकि:
- External files को अभी भी सिर्फ `stock_agent` दिखता है।
- पूरा risk `stock_agent` के अंदर localized रहता है।
- हर phase deploy के बाद आप वही contract tests चला कर verify कर सकते हैं।
इसलिए: हाँ, established practice के हिसाब से आपका “Strangler‑Fig + Backward‑Compatible stock_agent facade” approach सही और prudent है, especially under limited visibility + high‑risk constraints।
---
2. Phase Ordering (Pure Functions पहले, Decision Engine आख़िर में)
Risk‑based ordering के हिसाब से आपकी proposed sequence sound है:
> Phase‑1: `ta_core.py` (pure technical / self‑contained)
> Phase‑2: `symbol_utils.py`
> Phase‑3: `patterns_breakout.py`
> Phase‑4: `nse_clients.py` / `kotak_client.py`
> Phase‑5: `risk_derivatives.py`, `ownership_nse.py`, `fundamentals.py`
> Phase‑6: `decision_engine.py` (सबसे आख़िर)
ये ordering तीन well‑known principles follow करती है:
1. Leaf‑like, pure, low‑coupling code पहले
- Pure technical / TA functions, जिनका I/O कम और side‑effects negligible हैं, उन्हें पहले move करना industry में standard है, क्योंकि:
- Behavior deterministically test हो सकता है,
- Failure होने पर impact कम रहता है,
- इन्हें consumer modules से आसानी से stub/mock किया जा सकता है।
2. I/O heavy और external‑API boundary बाद में
- `nse_clients.py`, `kotak_client.py` जैसे external systems से बात करने वाले हिस्सों को बाद के phases में ले जाना भी सही है:
- Network / API failures, rate‑limits, auth issues आदि का risk ज़्यादा होता है।
- इन्हें refactor करते समय अधिक staging और observability की ज़रूरत पड़ती है।
3. Orchestration / Decision Engine सबसे आख़िर
- `decision_engine.py` जैसा module आम तौर पर:
- सबसे ज़्यादा dependencies pull करता है,
- behaviorally सबसे complex होता है (business rules, cross‑module coordination, etc.),
- और errors होने पर पूरे app का core behavior बदल सकता है।
- इसे last में refactor करना practically हर serious legacy‑refactor playbook में recommended है, ताकि:
- नीचे के building blocks पहले stabilize हो जाएँ,
- उनके contracts clear हो जाएँ,
- और decision‑engine को refactor करते वक़्त आप already‑tested, stable modules पर depend कर रहे हों।
इसलिए: आपका Phase‑Ordering (pure / TA core → utils → patterns → external clients → risk/fundamentals → decision engine) genuinely correct और low‑risk prioritization है।
Minor refinements future में dependency‑graph देखकर हो सकते हैं (जैसे किसी module के बहुत कम consumers हों, तो उसे पहले खिसका देना), पर high‑level strategy बिल्कुल सही दिशा में है।
---
3. Phase‑0: Full Dependency Map — बिना automated static‑analysis tools के
आपकी सबसे बड़ी genuine constraint ये है:
- हमारे पास सिर्फ user‑provided files की visibility है,
- आप codebase‑wide static analysis tools (जैसे pyan, pydeps, आदि) नहीं चला सकते,
- फिर भी एक भी missed import app को silently तोड़ सकता है।
ऐसी स्थिति में established practice three‑layered approach suggest करती है:
3.1. Static Text‑Search Based Map (Minimal but Systematic)
भले ही “formal” static‑analysis tool न चले, practically आप almost‑equivalent result object‑level tools के बिना भी ले सकते हैं, अगर आप disciplined text search करें:
1. Import patterns list कीजिए
हर provided file में ये patterns text‑search से निकालिए:
- `import stock_agent`
- `from stock_agent import`
- `stock_agent.`
- कोई aliasing हो तो (जैसे `import stock_agent as sa`), तो `sa.` भी search करें।
2. Manual dependency index बनाइए
एक simple table / CSV बनाइए (ये आप खुद बना सकते हैं, या यहां snippet paste कर सकते हैं, हम logically refine कर सकते हैं):
| Caller File | Import Style | Symbols Used |
|----------------------|-----------------------------------|--------------------------------------|
| `scanner_intraday.py`| `from stock_agent import scan` | `scan`, `load_symbols` |
| `backtest.py` | `import stock_agent` | `stock_agent.scan`, `stock_agent.ta`|
इससे आप देखेंगे:
- कौन‑कौन से files `stock_agent` पर depend करते हैं,
- कौन‑कौन से functions उन्हें externally visible रखनी ज़रूरी हैं।
3. Functions को categories में group कीजिए (Phase mapping के लिए)
हर exported function को tag कीजिए:
- `TA_CORE` (pure calculations),
- `SYMBOL_UTILS`,
- `PATTERN_BREAKOUT`,
- `NSE_CLIENTS/KOTAK_CLIENT`,
- `RISK/FUNDAMENTALS`,
- `DECISION_ENGINE` (high‑level orchestrators).
फिर verify कीजिए कि phase‑order में आपने low‑level categories पहले रखीं हैं — जो आपने already किया है।
ये manual step boring है, पर once‑off है, और आपके constraint में practically यही “best static approximation” है।
---
3.2. Dynamic Call‑Logging Facade in stock_agent (Runtime Dependency Map)
Static search से आप “declared imports” पकड़ लेते हैं; पर कुछ चीज़ें missed हो सकती हैं (dynamic imports, reflection‑based calls, etc.). इससे बचने के लिए established practice एक और layer add करती है:
`stock_agent.py` में temporary instrumentation डालें:
1. हर public function पर logging wrapper
Example pattern (pseudo):
```python
import inspect
import logging
logger = logging.getLogger("stock_agent_calls")
def _log_caller(func):
def wrapper(args, *kwargs):
frame = inspect.currentframe().f_back
module = inspect.getmodule(frame)
filename = getattr(module, "__file__", "unknown")
logger.info("CALL %s from %s", func.__name__, filename)
return func(args, *kwargs)
return wrapper
```
और फिर हर existing public function के लिए:
```python
@_log_caller
def scan_intraday(...):
...
```
2. Typical workflows चलाकर log collect कीजिए
- सारे वो scripts / flows चलाइए जो user realistically use करता है: intraday scan, positional, backtest, report generation, etc.
- Log से आपको practically उपयोग में आने वाले functions की list मिलेगी:
- कौन से functions actual में call हो रहे हैं,
- किस file से call हो रहे हैं।
3. Static + Dynamic दोनों maps merge कीजिए
- Static search से theoretical surface area,
- Dynamic logging से practically used subset,
- दोनों मिलाकर आप confidently कह सकते हैं:
कौन से functions high‑risk हैं (heavily used) और किसका footprint छोटा है।
इस combined approach को कई large‑scale refactors में use किया जाता है, जहाँ full static analysis possible नहीं होता, पर log‑driven dependency‑discovery feasible है।
---
3.3. Per‑Phase Contract Tests (जैसे आपका “Bug‑6” Test)
आप पहले से जो कर रहे हैं—practically यही gold standard है:
- हर phase में:
1. `stock_agent` अब नए modules को internally import करता है,
2. Public API वही नाम रखता है,
3. आप established contract‑tests चलाते हैं:
- जैसे Bug‑6 वाला check: `original.equals(before)` type validation,
- Plus कोई deterministic TA outputs (same inputs → same indicators, same patterns, आदि).
- जब तक:
- Core regression tests green,
- और कोई नया discrepancy log‑analysis में नहीं दिखता,
तब तक अगले phase पर जाना सुरक्षित माना जाता है।
If you want to be extra defensively strict:
- Phase‑N के बाद कुछ दिनों तक केवल limited internal use रखें,
- Monitoring / logs देखिए,
- फिर ही Phase‑(N+1) शुरू कीजिए।
---
Summary
- Approach: Strangler‑Fig + `stock_agent.py` को backward‑compatible facade बनाए रखना — ये बिल्कुल established practice है और आपके limited‑visibility constraint के लिए सही है।
- Phase Ordering: Pure / self‑contained technical functions पहले; external clients और decision‑engine आख़िर में — ये risk‑minimizing और industry‑standard ordering है।
- Phase‑0 Dependency Map:
- Static text search (import patterns + function usage) से एक manual dependency index बनाइए।
- `stock_agent` पर temporary call‑logging instrumentation लगाकर वास्तव में उपयोग हो रहे APIs log कीजिए।
- Static + dynamic maps को merge करके phased refactor की scope और risk दोनों control कीजिए।
- हर phase के बाद आपके जैसे contract‑tests (e.g., “original.equals(before)”) चलाना बिल्कुल सही gating mechanism है।
इस strategy के साथ, even बिना full `D:\SIA` visibility के भी, आप practical और research‑aligned तरीके से monolith को safe, staged तरीके से modularize कर रहे हैं।
If you have any further queries, please connect with us on 022-6290-10141 (Timings : 09.00 AM to 05.00 PM) or you can email us on info@cniinfoxchange.com